Linux进程管理2
目录
一 PCB结构体
为什么要有结构体PCB
在windows中,我们双击桌面图标,启动一个软件的本质上,就是启动了一个进程。那么类似的,在linux下,运行一条命令,就是在系统层面创建了一个进程。操作系统只能对进程来进行调度。因此我们用操作系统管理的时候,也就是对进程进行管理。
有这样一种情况,我们可以同时运行成千上万条命令,同时加载多个程序,也就是说,在OS中可能存在大量的进程,那么OS怎么对这些进程来进行管理?
之前谈到的概念,先描述,再组织(管理的本质)
更正:struct_task->task_struct
我们写的一个个可执行程序以文件(内容+属性)的形式存储在磁盘上,当运行的时候,需要把他加载到内存中。由于进程很多,为了实现管理,把他们描述成一个个PCB结构体(不用存放内容,只用加载属性即可),以链表的数据结构形式对他们来实现管理。
struct PCB
{
struct PCB* next;//指向下一个PCB
struct PCB* prev;//指向上一个PCB
……//data 其他的属性数据与进程相关的
};
写成代码就是这样的一个意思。
因此,PCB存在的意义就是管理OS中存在的大量进程。
PCB是什么
PCB(process control block)进程控制块,是结构体的形式存储的。在不同的操作系统中,PCB不同,但是在Linux中叫做task_struct
task_struct中,有各种属性数据。
从知乎上截取的:
(1)标示符 : 描述本进程的唯一标识符,用来区别其他进程。
(2)状态 :任务状态,退出代码,退出信号等。
(3)优先级 :相对于其他进程的优先级。
(4)程序计数器:程序中即将被执行的下一条指令的地址。
(5)内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
(6)上下文数据:进程执行时处理器的寄存器中的数据。
(7) I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
(8) 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
还有一些其他信息
接下来会对这些先简单说明下,有个初步的概念,有个感性的认知。
标识符:相当于我们的身份证号码。用来单独唯一标识我们这些人。也是便于管理
状态:一个人平时可以在学习,在运动,在娱乐,对应进程就相当于这些状态
优先级:就是先做什么事情,再做什么事情。注意与权限差别:能否做某事
程序计数器:一个程序被执行,默认是顺序结构,自顶向下一句一句执行的。但是遇到一些比如循环语句,分支语句,就要进行跳转。是由PC指针(EPC)保存当前正在被执行的下一条指令的地址,从而保证指令自顶向下执行。
上下文数据:以各种队列的方式存储在寄存器中的。
io状态信息:云服务器啊,内存多大啊,硬盘空间有多少啊,带宽啊,这些。
记账信息:进程被处理器执行了多长时间。
二 如何创建一个进程?
1 用命令行创建一个进程
首先我们用vim编写一个程序,之后编写相关的makefile,运行之后就是一个进程了。
查看进程
我们可以通过ps命令来查看进程 如下
如果带上head -1的话可以带上对应的标头
pid->当前进程id ppid->父进程的id STAT->状态
也可以用/proc的方式来查看一个进程
ls /可以查看当前有什么文件目录
ls /proc 进入当前进程目录。每一个运行中的进程都会有一个对应的属性,来标识当前所在的工作路径,是一个二进制的文件,也是一个动态的目录,会根据进程的创建和销毁来改变。
ls /proc/pid(第三行对应的数字) 查看对应的进程 进入之后就可以查看这个进程的所有属性了
如果想要杀掉对应的进程只需要 kill-9就行了
有了对一个进程的初步认识——如何以命令行的方式创建进程以及杀死进程,一个进程到底是什么之后,我们从代码的层面来了解一下进程的创建。这就不得不谈到一个系统调用接口——fork了。
2 用代码来创建进程
fork——创建子进程的系统调用接口
① 基本使用
基本功能:创建一个子进程,有两个返回值:给父进程返回子进程的pid,给子进程返回0。
在介绍这个功能的时候,我们为了更显著的观察到这个现象,介绍两个获取pid和ppid的函数——getpid()和getppid(),他们的返回值类型都是pid_t,一个是返回该进程的pid,一个是返回该进程父进程的pid。头文件#include<unistd.h>
我们确实观察到了fork之后会有两个返回值,两个执行流,给父进程返回子进程的pid,给子进程返回0.
②其他问题
a 为什么有两个返回值
有两个返回值,必定是return执行了两次。也就是存在两个进程,分别执行了return语句。
我们先来了解下CPU如何执行对应的进程的。
一个个进程的代码和数据会被一个个task_struct结构体来管理,这些结构体会以链表的形式存储,形成一个运行队列,等待CPU的调度。
CPU执行代码的时候,会根据对应的调度算法,来确定执行的顺序。当CPU执行对应的进程的时候 ,会根据task_struct来找到对应的代码和数据,执行,之后将这个进程从运行队列中拿出来,再去执行其他的进程。操作系统和CPU运行某一个进程,本质是从task_struct形成的队列中挑选一个task_struct来执行他的代码。进程调度就变成了在task_struct的队列中选择一个进程的过程。
父子进程被创建出来,哪一个先运行?不一定
运行队列会因为各种原因变化,谁先运行是完全不可控的。是由操作系统的调度器来决定的。
我的代码在用户层当中,fork是对应的接口,是个系统调用接口,是个函数,用来创建子进程谁实现的?是操作系统实现的。
在操作系统中,fork内部就是实现创建子进程的逻辑。
那么在创建进程时,操作系统需要做什么?
本质就是系统多了个进程:
要新建一个task_struct结构体,创建了对应的空间,但是内部所有属性没有创建,信息如何填充?以父进程的task_struct为模板,父进程是什么值,子进程一般就是什么值。有些会继承,有些是私有的。创建对应的代码和数据。
创建一个子进程:要给子进程创建一个task_struct操作系统来管理对应的新进程,对应的结构体变量产生之后入到系统全局的维护进程列表中,操作系统实现管理。
关于return:当我们已经准备return了,我们的核心代码执行完了。也就是说,使用fork在return最终结果的时候,子进程已经被创建出来了。
创建子进程。不仅创建进程,还要让他在对应的队列里待着。通过父进程的手,准备return的时候,子进程已经被创建并且放在了对应的运行队列中,也就是说可以被调度。已经出现了对应父子进程执行的逻辑。这个return被父子对应执行了。
因此会被return两次,有两个返回值。
但是这不代表一个变量里面会记录两次。因为一个变量被第二次写入的时候,是覆盖式写入的。这一点之后说明。
b 为什么这样返回
为什么给子进程返回0,给父进程返回子进程的pid?
一个子进程永远都只有一个父进程,但是父进程有多个子进程,父:子=1:n
父进程为了更好地控制子进程,会标识子进程:fork之后,给父进程返回子进程的pid,以进行相关的管理。
子进程只有一个对应的父进程,只需要返回0就可以了。
c if和else会被同时执行?
因为fork之后有两个进程,是2个不同的执行流,父子进程的代码是共享的,根据不同的id来执行不同的语句。
我们对一个进程是什么,有哪些属性值,怎么被os管理以及如何创建进程(命令行,代码),以及fork的使用和基本问题的认识做了一些说明。下次来深入介绍进程状态。