本篇文章,继续和大家来分享与Linux相关的知识。本篇文章主要涉及的知识内容是进程等待。

进程等待

进程等待,这里,我们主要了解三个部分。一 为什么要有进程等待,二 进程等待是什么,三 怎么进行进程等待。

是什么

什么是进程等待?进程等待就是通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能!

为什么

我们知道,子进程在完成自己的任务后,不会立马死掉,会进入僵尸状态。处于这个状态的进程,系统也杀不死它,如果父进程一直不来回收它,它会一直占用这内存空间,从而造成内存泄漏问题。这是需要进程等待的原因之一。其二,我们创建子进程,不就是让它帮我们完成某种任务吗?那我们怎么知道,子进程任务完成得怎么样?是不是需要有一个反馈?怎么反馈,通过进程等待。

怎么办

我们怎么进行进程等待,这里我们介绍两个函数,一、wait,二 、waitpid。

Linux-进程等待_进程控制

实践

回收一个进程

我们利用fork创建子进程,当子进程执行5秒后,我们用exit结束子进程(原因:我们并不希望子进程执行后面的代码),这里我们先不会回收子进程,让父进程一直运行,看看监视结果

Linux-进程等待_status_02

子进程退出后,会一直处于僵尸状态,等待父进程来回收它

Linux-进程等待_非阻塞轮询_03

wait

现在,我们将父进程的死循环改成10次。待父进程执行完它的代码后,需要用wait对子进程进行回收。那wait怎么使用呢?我们可以查查man手册,wait是等待任意一个子进程退出,然后,把它等到的子进程的pid作为返回值返回。我们可以根据它的返回值,判断进程是否等待成功。至于参数,我们这里先设成NULL。待会讲waitpid的时候,一起讲。

Linux-进程等待_wait函数_04

Linux-进程等待_进程控制_05

26559,不就是我们要等的子进程吗?

Linux-进程等待_非阻塞轮询_06

回收多个进程

回收一个进程我们可以直接wait,wait只能等待回收一个进程,那我们怎么回收多个进程?使用for循环就可以了。

Linux-进程等待_status_07

十个子进程一瞬间创建好了。大概五秒钟后,就都被退出回收了

Linux-进程等待_进程控制_08

刚刚我们是子进程先退出,父进程在运行。如果反过来呢?会是出现什么现象?

我们把子进程改成死循环

Linux-进程等待_进程等待_09

我们可以看到,父进程一直处于S状态也就是阻塞状态。这说明了什么?这说明了:如果子进程一直不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不会返回,默认叫阻塞状态。这还说明了一个问题,子进程是软件资源,进程阻塞的原因可以是硬件资源未就绪,也可以是软件资源未就绪。

Linux-进程等待_非阻塞轮询_10

waitpid
第一个参数pid

waitpid函数,同样也在man手册第二章。它的参数比wait的参数多两个,这也就意味着它的功能更多。它们两者是什么关系呢?wait的功能是waitpid功能的子集。waitpid的第一个参数pid,可以是具体的pid,指定等待某个子进程。也可以设置为-1,表示等待任意一个子进程。

Linux-进程等待_进程控制_11

我们简单演示一下,waitpid的第二个参数暂时设为NULL,第三个参数暂时设为0。

Linux-进程等待_进程等待_12

编译运行,我们可以发现当第一个参数设置为-1的时候,它的功能和wait是一样的

Linux-进程等待_进程控制_13

我们也可以指定等待我们创建的子进程

Linux-进程等待_进程控制_14

运行结果除了pid不同外,其他效果是一样的

Linux-进程等待_进程控制_15

第二个参数status

waitpid的第二个参数status,是一个输出型参数,它会保存子进程的退出结果。我们需要在外面定义一个status变量,把地址传给它。

为了便于测试,我们这里把子进程的退出码设置为1

Linux-进程等待_status_16

我们的进程退出码设置的是1,为什么打印的status值是256,这是为什么?

Linux-进程等待_进程控制_17

还记得进程退出的场景,有几种情况吗?是不是有三种

Linux-进程等待_status_18

这就意味着,我们不能把status当成一个整数来看,而是分成下面这样看,低七位用来存放信号信息,表示进程有没有出现异常,收到信号。core dump标志位,我们这里不讲,放到后面。次低8位,用来保存退出码,表示进程是否正常运行完毕。

Linux-进程等待_进程等待_19

Linux-进程等待_wait函数_20

所以,我们要查看status中的内容,需要分部分查看。怎么查看?查看信号部分,我们直接status按位与上0x7F即可,0x7F就是7个二进制1序列(111 1111)。而查看次低8位,我们需要先将status右移8位,再按位与上0x8F。

Linux-进程等待_非阻塞轮询_21

这样,我们就可以获取到子进程的运行结果了

Linux-进程等待_非阻塞轮询_22

我们增添一个除零错误,再看看

Linux-进程等待_进程控制_23

数字8,对应的不就是8号信号,浮点数错误吗?

Linux-进程等待_进程控制_24

这里,我们简单讲一下,进程等待的原理

父进程创建子进程完成某个任务,子进程在完成它的任务后,会除了自己PCB以外的资源释放掉。PCB为什么不释放?因为父进程还没来回收它,它的PCB中,保存了它的退出信息,也就是它的退出码以及它有没有到信号。当父进程来回收它时,它会把它PCB中的exitcode和sigcode一并写入,status这个变量里

Linux-进程等待_进程等待_25

通过查看Linux源码,我们是可以看到进程的PCB存在exit_code和exit_signal这两个变量用于保存进程退出信息的

Linux-进程等待_进程控制_26

其实,系统中已经提供了读取status的方法了

Linux-进程等待_status_27

正确读取status的方式,应该是这样

Linux-进程等待_status_28

Linux-进程等待_进程等待_29

刚刚我们举的例子等待子进程都成功,那什么时候,才会等待不成功呢?如果父进程等待的不是自己的进程就不会等待成功。

Linux-进程等待_非阻塞轮询_30

Linux-进程等待_非阻塞轮询_31

第三个参数option

waitpid的第三个参数,表示父进程的阻塞方式。我们刚刚默认填零,表示如果子进程未退出,父进程就在调用waitpid的位置阻塞住,直到子进程结束退出。这里还有一种参数是WNOHANG,叫非阻塞轮询。这种等待方式,如果父进程在调用waitpid的位置,没有等到退出的子进程,父进程不会因此进入阻塞状态,而是继续往后执行它的代码。

WNOHANG怎么去记呢?WNO是不要阻塞,HANG,我们访问一个网页的时候,经常会说,它夯住了。也就是这个网页卡住了。

等到子进程或者等待子进程失败都是结果,waitpid可以直接返回。可如果我们没等到进程怎么办?我们只是还没等到子进程,并不是出错。所以,waitpid返回值除了大于零 表示等待成功,小于零 表示等待失败外,还会等于零 表示还没有等到退出的子进程。

再讲个小故事,加深大家对非阻塞轮询的理解。小华是个学渣,老师说,明天要考C语言了。小华一听,神情立马变得紧张起来,开学一来,我什么也没学,怎么办?他的好兄弟,小张是个学霸,一定有很多的笔记。于是,小华,就来到小张的宿舍楼下,打电话给小张,说小张,你在干嘛呢?明天要考试了,我想借你的C语言笔记来看一下。小张说:可以,不过你需要等会,我现在在写题,一会就拿下去给你,说完小张就挂了。小华,等呀等呀!怎么还没看到小张。于是,又打电话过去。咦,怎么没人接电话...... 电话终于接通了,小张说再等等,说完他又把电话挂了。等呀等呀!又等了一会,还是没看到小张下来。小华又给小张打电话,这次小华吸去前两次的教训,说你别挂电话,我要时刻直到你在干嘛。不一会,小张终于下来了,小华如愿拿到笔记。没过几天,老师又说,要考操作系统。小华心想,操作系统可比C语言难多了,我得提前一点复习。上次看了小张的笔记,C语言考了80分。这次,看他的笔记,准没错。于是,小华,又到小张的宿舍楼下,给小张打电话,小张说等会。小华,有了上次的经验,知道小张不会太快下来。也不急着再次给他打电话,而是利用等待的时间复习操作系统。后面,也如愿等到小张,拿到了笔记。

通过小华两次拿笔记的事,我们也就理解什么是非阻塞轮询,非阻塞轮询就是父进程询问子进程,说你退出没。子进程说没好。父进程也不急,先去做自己的事,让后过一会再来询问。

Linux-进程等待_进程控制_32

我们简单来演示一下:

Linux-进程等待_进程控制_33

我们可以看到,这次父进程没有因为没有等到子进程,而阻塞住

Linux-进程等待_非阻塞轮询_34

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。