前言
在前面的文章中,我们已经知道了一点:管理是先描述再组织。
对于进程而言,也是如此的。
操作系统需要先描述进程,之后再进行组织。
进程的基本概念
操作系统书上对于进程下的定义是:程序的一个执行实例,正在执行的程序等。
但,我们换一种观点来看,从操作系统的角度来看,那么进程就是:担当分配系统资源(CPU时间、内存)的实体。
举个例子:
在我们写代码时,一个程序要运行起来,必须要经过编译链接阶段。之后就生成了一个可执行程序在磁盘当中。然后我们双击这个文件就可以运行起来这个程序了。双击运行的本质其实是程序从磁盘加载到内存当中。只有程序加载到内存当中了,CPU才可以逐行执行。当程序从磁盘加载到内存后,我们就将这个程序称为“进程”。
进程描述-PCB
系统当中可以同时运行一大批进程,我们可以使用ps -aux查看。
之前的文章说过了,操作系统也是一个软件,必须要运行起来。那么,操作系统也一就一定会是第一个加载到内存当中的进程。
然后,我们又知道操作系统是做管理工作的,其中有一项就是进程管理。那么,操作系统是如何管理进程的呢?
在前言部分我们已经说了,先描述再组织。
操作系统管理进程也是如此,操作系统作为管理者不需要直接和进程对话。当一个进程出现时,操作系统就要立马对其进行描述,然后再把描述的信息用一个数据结构组织起来。之后我们对进程的管理,其实就是对这个数据结构的管理。
在Linux当中,我们将保存进程信息的node称为进程控制块,我们可以将其理解为进程属性的结构体。它的英文名字是PCB(process control block)。
在Linux当中,会用双向链表将PCB组织起来。
这样,我们就只需要有头节点的地址,就可以访问到全部的PCB。
然后,我们对PCB的管理,也就是对这条双链表的增删查改。
task_struct -PCB
因为Linux的代码是用C编写的,那么PCB的表现形式一定是结构体来实现的。
- Linux当中描述进程的结构体叫做task_struct
- task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并包含进程的信息。
task_struct主要内容
task_struct当中的属性主要包括如下:
- 标识符:描述本进程的唯一标识符,用来区别该进程和别的进程
- 状态:任务状态,退出代码,退出信号等
- 优先级:相对于其他进程的优先级
- 程序计数器(PC):程序中即将执行的下一条命令的地址
- 内存指针:包含程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
- 上下文数据:进程执行时处理器的寄存器中的数据
- I/O状态信息:包括显示的I/O请求、分配给进程的io设备和被进程使用的文件列表。
- 记账信息:可能包括处理器时间综合,使用的时钟总和,时间限制,记帐号等。
查看进程
通过系统目录查看
在根目录下有一个名为proc的系统文件夹,这个文件夹中的文件都是目录。
上图,proc文件夹中有数字作为名字的目录,也有英文作为名字的目录。
这些数字就是一个进程的号码牌,唯一标识一个进程。以数字作为名字的目录当中记录了对应进程的各种信息。
我们若是想看进程号为1的进程信息,直接ls到这个目录即可。
其实,这个数字就是PID,也就是process的ID。
通过ps命令查看
我们可以用ps -aux查看进程。如下:
ps命令与grep命令搭配使用,就可以显示某一进程的信息,如下:
通过系统调用获取进程的pid和ppid
首先,我么你先介绍一下PPid:parent process ID 即父进程。
同样的,我们还可以用系统调用函数,getpid和getppid即可分别获取进程的pid和ppid。
通过系统调用创建进程 fork函数
fork是一个系统级别的函数,其在unistd.h的头文件中。
其作用是创建一个子进程。
加入了fork函数之后,打印结果如下,我们可以看出的确是创建了一个子进程。
运行结果是循环打印两行数据,第一行数据是该进程的PID和PPID,第二行是fork出来的子进程的PID和PPID。我们可以通过观察两行打印数据得出,第二行数据是子进程。
在代码中,我们的确创建了一个子进程,而父子进程的代码是共享的,数据是各自一份的,子进程会拷贝一份父进程的数据,然后从fork()后的代码开始独自运行。(写时拷贝)
在父进程创建子进程后,操作系统也会为子进程创造出属于他的PCB。
if-else的demo
我们用fork函数创造出来的子进程和父进程是使用同一份代码的,但是如果父子进程做的事都是一样的,那么就没有什么意义了。
如果,我们想要子进程和父进程做不同的事,那么我们应该怎么做呢?
先补充一个知识点:
fork函数的返回值:
- fork函数的返回值是pid_t类型的。
- 如果子进程返回成功,则在父进程中返回子进程的PID,在子进程中返回0
- 如果子进程创建失败,则在父进程中返回-1.
既然父子进程在fork函数中的函数返回值不同,那么我们就可以据此让父子进程执行不同的代码。
如下:
输出结果如下:
下面,我们详细的介绍一下fork的过程: