关于进程(PCB | 父进程 | 子进程 | fork深层探讨 |僵尸进程与孤儿进程)


一、进程与PCB

1. 进程的概念:

在一个程序运行之前,要先加载进内存中,称之为,进程。

2. 什么是PCB

当一个系统中有多个进程同时在运行时,操作系统需要对它们进行“管理”。在认识了OS管理的概念后,我们知道,管理的第一步要先描述。

因此,把描述进程的结构体叫做PCB(Process Control Block),进程控制块。

以某种数据结构存放着进程的信息,可以理解为进程属性的集合。

task_struct

任何一款操作系统统一将控制块称为PCB,但具体每一款操作系统的PCB的名字是不同的。

在Linux中,PCB的名字叫做task_struct。(下文主要以task_struct为主)

task_ struct内容分类

struct task_struct
{
标示符: 描述本进程的唯一标示符,用来区别其他进程。(pid、ppid)
状态: 任务状态,退出代码,退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。
内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
其他信息
}

4. 查看进程

进程的信息可以通过 /proc 系统文件夹查看。

/proc:会将当前系统当中的所有进程的信息,镜像到对应的某个硬盘目录当中。可以通过proc目录来查看当前系统中有多少个进程。
(图中数字编号皆为进程 )
在这里插入图片描述
如:要获取PID为10的进程信息,需要查看 /proc/10这个文件夹。

大多数进程信息也可以使用top和ps这些用户级工具来获取。
在这里插入图片描述

5. 进程概念的加深

管理进程,实际占用内存空间要比文件本身大:因为要管理,所以就要先描述,描述的话就要构建结构体,定义结构变量,所以会占用一定的额外空间。

因此我们可以得出进程的进一步概念:

进程 = 对应的文件 + 进程属性
进程:可执行程序与管理进程需要的数据结构的集合

二、父进程与子进程

1. 通过系统调用获取进程标示符

进程id(PID)
父进程id(PPID)

在这里插入图片描述
在运行该程序的时候发现,pid一直是变化的,而ppid一直是不变的。这是为什么呢?
在这里插入图片描述
查看父进程11834的信息,我们发现父进程其实就是bash。
在这里插入图片描述bash也是一个进程,但它绝对不能挂掉。

bash的运行原理:bash叫做命令行解释器,通过创建子进程,让子进程去完成对应的任务。因为进程是具有独立性的,即使子进程出现问题,也不会影响到bash。

bash的任务只需要接收任务,识别任务,创建子进程,让子进程去完成即可。

2. 进程的创建 - fork

认识fork:fork的返回值很特殊,有两个。
给父进程(这里不是bash,而是调用fork的那个进程)返回子进程的pid。
给子进程返回0。(下文会重点解释为什么有两个)

#include<unistd.h> //头文件
	pid_t fork()    
	
// Pid_t在Linux中是一个封装。
// 它其实是一个无符号整型或者是无符号常整型。

fork创建进程
在这里插入图片描述在这里插入图片描述
当前的进程是22027,在fork之后,创建了该进程的子进程22028。也就是说,当前的进程即为新创建的子进程的父进程。

3. fork的深层探讨(关键)

(1)如何理解进程创建?

首先,在前面的介绍我们了解到,

在计算机当中,进程指的是一个可执行程序的代码和数据。


操作系统为了管理该进程会创建描述该进程的数据结构task_struct。(理解成一个结构体定义出来的变量即可,描述的是一个进程的大部分的属性和信息)

那么创建进程 —> 系统里就会多了一个进程 —> 也就多了一个管理该进程的数据结构。

所以创建一个进程的意思就是,要在内存中创建一个全新的task_struct来描述该进程,同时,也需要给它申请出对应的代码,供新进程去执行。

总的来说,多一个进程,系统就要多一组管理进程的数据结构(系统创建的)+ 该进程对应的代码和数据(自己写的代码中的系统变量及调用的函数)

(2)如何深刻理解fork为什么会有两个返回值?

我们要清晰的知道,fork是函数。

那么我们可以大概的猜到函数内部的伪代码↓

Pid_t fork(){
	// 创建子进程的逻辑
	// 给子进程创建task_struct
	Struct task_struct * ts = malloc(sizeof(struct task_struct));
	ts.xx = father.xx;           
	…
	ts.status = running;
	ts.link = task_queue;//连接任务队列等
	// 以上语句是父进程执行,形成子进程
	//创建子进程的任务在这里就结束了
	
	return id;  

到return之前,子进程已经创建完毕。

且return是语句,父进程要执行,子进程也会执行, 所以会有两个返回值。

因此,父进程返回的就是对应的子进程的id,子进程返回的就是0。

函数的返回值是数据,所以是各自私有一份的,也就是有两个返回值。这两个返回值虽然名字一样,但对应的内存地址,一定是不一样的。

(3)fork父子进程执行顺序、代码和数据复制问题?

进程 = 代码 + 数据
在这里插入图片描述

父进程在创建子进程时遵守的规则是:代码是共享的(fork之后的代码,父子进程共享),数据是各自私有一份(写时拷贝)

代码是逻辑,一般是不可被修改的,而数据,是既可读又可写。

因此,通过数据私有,可以表现出进程的独立性。

父子进程fork完毕,谁先运行?不确定,这个由调度器去决定。

回看问题(2),为什么会有两个返回值就解释的更清晰了。
fork是函数,内部由多个语句构成,fork执行完毕,子进程已经被创建成功。
return是语句,父子都要执行,而return的是数据,也就意味着父子各自要私有一份。也就有两个返回值了。

三、进程的状态

进程状态查看命令:ps aux / ps axj

当linux中要终止一个进程时:
(1)前台进程:Ctrl + c (此时通过状态查看命令可以看到状态前有+号)
(2)后台进程:kill -9 进程id

  • R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列 里。
  • S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 (interruptiblesleep))。
  • D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的 进程通常会等待IO的结束。(当一个进程处于等待休眠的状态时,操作系统可能误杀一个进程,导致io失败,进而后续工作无法完成。这时将task_struct设置为D状态 [深度睡眠],保证进程无法被杀掉。)
  • T停止状态(stopped): 可以通过发送 SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可 以通过发送 SIGCONT 信号让进程继续运行。
  • X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

还有一种特殊的状态(僵尸),下面进行补充。

僵尸状态

什么叫做僵尸状态?

  • 当子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。
  • 也就是父进程在运行,子进程退出了,且父进程没有读取子进程的信息,子进程进入Z状态。
  • 僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

1.为什么要有僵尸的状态?

一个故事来描述原因:一个人死亡,警察过来做的第一件事情是封锁现场,采集信息(验尸,确认如何死亡)。然后才解除封锁,抬走尸体,通知家属。尽管这个人已经死了,但尸体还在。

同样,进程(人)死亡,父进程(警察)需要调用(操作系统的)系统调用,来检测进程运行完毕时的结果情况,这个情况包括

  1. 结果是否正常
  2. 是否异常
  3. 发生了什么异常

所以,[ 保持进程基本退出信息,方便父进程读取,获得退出原因 ] 是僵尸进程存在的目的。

2. 僵尸进程有什么特征?

一般来说,进程僵尸状态时,PCB(Linux中叫做test_struct)是会被保留的,进程的退出信息是放在进程控制块中的。

一个进程处于僵尸状态,当它被父进程读取之后(被检测之后),知道了它对应的退出原因后,那么就会这个进程就会从Z状态变为X状态。

处于X状态才是真正意义上的进程退出,就可以被释放掉了。

父进程是如何读取的呢?
调用wait/waitpid读信息。(在之后的学习中会进行详细讲解)
进程相关的退出的信息在哪?
会被放在task_struct(进程的PCB结构当中)中,在它的属性字段中,有一些字段是用来表明进程的退出信息的。

孤儿进程

如果父进程提前退出,子进程后退出,进入僵尸状态后,怎么办呢?

  • 把这种,父进程先退出,子进程后退出的情况称之为“孤儿进程”

事实上,如果一个进程没有父进程,但是当前进程退出,进入僵尸,该进程的资源(内存)就没有办法被回收,就会导致一个问题——内存泄漏。

操作系统考虑了这个问题,所以,孤儿进程是要被操作系统的1号进程进行领养的。

Linux中所有的进程都有父进程,只有1号没有。且只要有父进程就都会被回收。


评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值