1. 进程等待必要性
1. 子进程退出,父进程如果不管不顾,就可能造成 ‘ 僵尸进程 ’ 的问题,进而造成内存泄漏。2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“ 杀人不眨眼 ” 的 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
2. 进程等待的方法
进程等待用于回收子进程的资源,避免子进程的PCB一直占用资源造成内存泄漏,并且可以获取子进程的退出信息,得知子进程任务的执行情况,进程等待主要通过两个系统调用接口 wait 和
waitpid
来完成。
wait接口
使用wait
接口,需要包含头文件<sys/types.h>
和<sys/wait.h>
,其接收一个int*
指针,该参数是一个输出型参数,用于返回子进程的相关推出信息,不关心则可以设置成为NULL。返回值是一个int
类型:返回值大于0:返回等待到的子进程的pid;返回值小于0:等待失败
例如,我们有这样一个test1.c文件,先通过fork
创建了一个子进程,子进程进入if
语句,进行三秒倒计时,然后退出,并且退出码为3。父进程则进行六秒倒计时,通过wait
函数进行等待,传入指针&status
,接收返回值ret
,最后输出status
和ret
的值。
我们可以输入 while true;do ps -ajx | head -1;ps -ajx | grep test1 | grep -v grep;echo "---------------------------------------------------------------";sleep 1;done; 来实时检测父进程和子进程的状态
首先,子进程的pid为853483,而wait的返回值就是子进程的pid。其次,status一开始被初始化为0,wait之后,status = 768,可知wait确实会修改传入的参数。
status
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。一旦子进程退出,操作系统就会将退出信息拷贝到status中,然后就可以将其带出来。如果传递NULL,表示不关心子进程的退出状态信息。 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
灰色部分:status是一个int类型,占32比特,但是前16比特是无效的,不填入任何内容
黄色部分:第8 - 15位,共8比特,用于表示wait到的子进程的退出码
绿色部分:第7位,core dump标志位本博客不关心该位置
蓝色部分:第0 - 6位,共7比特,用户表示wait到的子进程的退出信号
那么我们要从status
中提取出退出码
和退出信号
,就可以要对其进行位操作:
1. status
直接与01111111
进行按位与&
,就能得到退出信号
,01111111
的十六进制表示为0X7F
int signal = status & 0x7f;
2. status
右移8
位后,与11111111
进行按位与&
,就能得到退出码
,11111111
的十六进制表示为0XFF
int code = (status >> 8) & 0xff;
接着我们对test1.c文件进行修改,增加如下代码并再次运行。
Linux还给用户提供了两个宏函数,用于检测status:
WIFEXITED:检测进程是否正常退出,返回一个布尔值,如果进从正常退出,返回真
WEXITSTATUS:提取子进程的退出码,也就是第8 - 15位if(WIFSIGNALED(status))
waitpid接口
进程等待的另外一个接口是waitpid
接口,需要包含头文件<sys/types.h>
和<sys/wait.h>,一个进程是可以有多个子进程的,一个wait只能等待一个子进程,如果有多个子进程,那么wait函数等待第一个结束的子进程。而waitpid则是针对pid来对进程进行等待,当然也可以在第一个参数取 -1 就可以任意等待一个子进程。
参数:1. pid :pid=-1, 等待任一个子进程。与 wait 等效。pid>0, 等待其进程 ID 与 pid 相等的子进程。2. status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若 WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)3. options:WNOHANG: 若 pid 指定的子进程没有结束,则 waitpid() 函数返回 0 ,不予以等待。若正常结束,则返回该子进程的ID 。
返回值:1. 当正常返回的时候 waitpid 返回收集到的子进程的进程 ID ;2. 如果设置了选项 WNOHANG, 而调用中 waitpid 发现没有已退出的子进程可收集 , 则返回 0 ;3. 如果调用中出错 , 则返回 -1, 这时 errno 会被设置成相应的值以指示错误所在;
现在我们来尝试多进程的进程等待,我们有这样一个test2.c的文件,创建了五个子进程并进行五次进程等待。
我们再来看看第三个参数。第三个参数用于控制进程等待的模式:
0
:进行阻塞等待WNOHANG
:进行非阻塞等待
如果退出多个子进程需要使用多个wait。如果子进程不退出,父进程默认在wait的时候调用系统调用的时候,也就不返回,默认叫做阻塞状态。
父进程在wait
的时候,什么也不做,进入阻塞状态,直到wait
成功。而非阻塞等待不一样,进行非阻塞等待时,如果本次waitpid
没有等待到,那么父进程不会阻塞,waitpid
直接返回0
,表示本次等待没有等待到子进程。此时父进程就可以空出时间去完成别的任务,而不是傻乎乎地死等了。
例如,我们有一个test3.c文件如下
以上代码中先通过fork创建了一个子进程,子进程sleep五秒。父进程陷入一个while死循环,每次循环开始,都waitpid一次,以WNOHANG模式。由于该模式不会阻塞,只要当前子进程没有结束,那么waitpid直接返回,去执行后面的if语句。
如果当前返回值为0,说明当前子进程没有结束,那么父进程可以去做些别的事情,一秒后再回来检测子进程有没有结束。如果当前返回值> 0,说明子进程结束了,waitpid也成功了,此时返回值就是子进程的pid ,跳出循环。