回顾
之前我们已经学习了进程的状态和进程的退出如果你没有这些基础知识,应先去了解进程的相关基础知识。
这次我们主要来学习如何让进程等待子进程的退出。
为什么要等待子进程?
之前我们在学习进程的状态的时候,我们知道了进程有一种状态名叫 僵尸状态(Z状态)。
这种状态是因为 子进程已经运行完了,而父进程仍然在运行,子进程不能把它的退出码(返回值)给父进程,从而导致子进程处于僵尸状态,会一直占用系统资源。
如果僵尸进程长时间存在,那必然就会导致操作系统资源被占用、泄漏。所以我们有没有什么办法去释放掉该僵尸进程呢?
答案是有的! 使用操作系统提供的wait系统接口函数,可以检查等待并释放掉僵尸进程,并得它的退出码。
1.wait
因为wait是系统接口函数,所以我们是输入 man 2 wait,这个2指的是系统接口函数类。
它是被包含在头文件#include<sys/types.h> 和 #include<sys/wait.h>头文件中
我们直接来进行demo演示用法。
至于*status这个参数是什么,我们稍后再讲。
首先我们开两个窗口,一个窗口用于执行我们的代码程序,另一个代码来检测我们的进程状态
我们检测进程的状态使用 一个shell脚本指令
while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep; sleep 1; echo "########################################################";done
先来看看我们写的这个代码,使用fork创建子进程。
子进程运行5秒,然后退出。
父进程休息10s后开始等待,如果等待成功,就返回子进程的PID,并且休息10s方便我们观察进程的状态。
程序都开始运行,子父进程都处于S状态,为什么处于S状态看过之前的内容应该清楚。
子进程运行完毕后,父进程还没运行完,子进程进入僵尸状态。
父进程休眠10s后,开始等待子进程,并且成功等到子进程,从右边可以看到,子进程已经被释放了,父进程仍在运行!
这就是进程的等待!
2.waitpid
他与wait 不同的地方在于他有三个参数,
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
首先是多了一个pid的参数,这个参数是指定要等待的子进程的pid,不过如果你传的pid是-1的话,他就会默认等待任意一个子进程。注意是一个!
第二个参数是int* status,可以发现wait也有这个参数,那么这个参数是什么呢?它是一个输出型参数,用于返回子进程的退出状态。
这里的输出中status的结果不是1而是256呢?
这是因为这里的status并不只是子程序的返回值,它还存储着其他东西
status是int类型的,所以他占了4个字节,不过它存储进程退出状态需要前2个字节就够了(16个比特位)。
之前我们就讲了,进程的退出有三种情况。
一种是进程运行成功,且结果正确。
一种是进程运行成功,结果不正确。
一种是进程异常终止了。
如果进程因为异常终止,通常是给进程发送了某种信号迫使进程终止。所以,进程退出状态就不仅仅是只有退出码,还有它收到了什么信号。
进程如果正常终止,前八个比特位是全0,次八个比特位存储着它的退出码。
进程如果异常终止,仅使用前八个比特位,并且前七个比特位存储它的终止信号,第八个比特位为core dump信号。
所以这就是为什么这就知道了刚刚的进程的status是256(0001 0000 0000)。而如果我们想分别打印出退出码和终止信号,可以用位操作符
也可以使用宏来完成
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
再来看看收到信号终止的情况
1.程序直接收到信号终止
开另外一个窗口直接给子进程发送信号终止它
2.程序异常而收到信号终止
因为浮点异常终止
那么它的第三个参数options是什么呢?
它决定是否是阻塞等待,看我们之前的例子,如果一旦父进程开始等待,它就会停在那里,直到子进程退出,这就叫做阻塞等待。
非阻塞等待
如果在options参数传入WNOHANG,就是非阻塞等待,如果子进程没有完成,父进程不必停下等子进程,可以继续运行。如果子进程完毕,再释放子进程。