当一个进程结束时,会产生一个终止状态字,然后内核发出一个SIGCHLD信号通知父进程,因为子进程的结束对于父进程是异步的,因而这个信号对于父进程也是异步的,父进程可以不响应,也可以调用wait或waitpid函数进行处理,默认情况是不响应。
下面我们来了解下wait和waitpid这两个函数:
要使用这两个函数需要引入sys/wait.h头文件和sys/types.h头文件
我们来看下wait函数的格式:
__pid_t wait (int *__stat_loc);
上面参数stat_loc是一个整形的指针,如果它不为空,子进程的终止状态字就被存放在该参数指定的内存位置,如果不关心终止状态字,传入一个空指针就可以了。
传统上,返回的这个状态字是一个整数,特定位表示特定的信息,如某些位表示退出状态(对于正常结束的进程),某些为表示终止原因(对于异常终止的进程),还有其他一些标记位,具体实现可能略有差异,系统屏蔽了实现的细节,可以调用wait头文件中的几个宏得到相关信息:
我们来看看有那几个宏:
![de26de565cb36eeebbd739799b2782ee.png](https://img-blog.csdnimg.cn/img_convert/de26de565cb36eeebbd739799b2782ee.png)
如果一个进程调用了wait或waitpid函数,可能会产生以下3种情况:
1、 如果所有子进程都还在运行,进程挂起;
2、 如果恰有子进程结束,它的终止状态字正等待父进程提取,立即得到该终止状态字并返回,其返回值为该子进程的进程号;
3、 如果该进程没有子进程,立即返回,返回值为-1;
可以看到,如果收到SIGCHLD信号后调用这两个函数,由于有子进程结束,因此,可以立即正确返回,否则,在程序某一点随机地调用,则进程很可能被挂起,这时,如果进程有多个子进程,就等待直至有一个子进程结束,然后返回该子进程的终止状态字,父进程可以根据返回的进程ID判断是哪个子进程结束了。
下面我们来看一个例子:
![7fc137c64338caba840445a36615ae64.png](https://img-blog.csdnimg.cn/img_convert/7fc137c64338caba840445a36615ae64.png)
如果某个进程有多个子进程,wait返回任一子进程的终止状态字,那么如果要等待某个特定的子进程结束(假设知道要等待的子进程的ID),该怎么办呢?
我们可以循环调用wait函数,然后将返回的进程ID 与感兴趣的那个进程ID比较,如果两者不等就将相关信息保存起来,在继续等待,直至等到所等待的那个进程结束,下一次类似操作时,又要先遍历一遍以前保存过的已结束的子进程信息,看其中是否有感兴趣的进程,在根据情况调用wait函数,显然,这样循环十分麻烦,幸好linux提供了另一个函数waitpid,使用这个函数不仅可以指定等待某个进程,而且还提供了其他一些参数选择,具有很大的灵活性。
我们来看下waitpid的调用格式:
__pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options);
我们用一个表来了解pid的值的含义
![2c5609d9c365de913a4aff02a94ea9ec.png](https://img-blog.csdnimg.cn/img_convert/2c5609d9c365de913a4aff02a94ea9ec.png)
waitpid函数返回子进程的进程ID,子进程的终止状态字存放在stat_loc指向的地址中。
参数options进一步控制waitpid的运行,它可以取零,也可以取一些常数,或是常数的组合,一个常用的常数是WNOHANG,使用这个常数,即使pid指定的子进程的终止状态字不能立即得到,waitpid也不会挂起,而是返回零。
可以看到wait其实是waitpid的一个特例,等价于waitpid(-1,*statloc,0)。
好了我们再来看一个例子:
![ebb7f52fbdf2d02c4e3dbe50338ce2a2.png](https://img-blog.csdnimg.cn/img_convert/ebb7f52fbdf2d02c4e3dbe50338ce2a2.png)
上面这段代码,进程创建了第一个子进程,第一个子进程又创建了一个子进程,然后第一个子进程结束(第20行),第2个子进程是第一个子进程的子进程,而不是原进程的子进程,因此第一个子进程结束后它就成为了zombie(僵尸进程),被init接管,而init的进程号为1,所以显示的父进程ID为1.