文章目录
- 前言
- 进程回收
- wait 函数
- 进程回收 相关的宏函数介绍
- waitpid 函数
前言
本文介绍 进程回收 的概念、相关宏函数、wait 函数 以及 waitpid 函数的使用方式。
进程回收
一个进程终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用 wait 或者 waitpid 获取这些信息,然后彻底清除掉这个进程,这就被称为 进程回收。
wait 函数
wait 函数: 回收子进程残留的资源, 阻塞回收任意一个子进程,如果有多个子进程,哪个子进程先结束就回收哪个。
pid_t wait(int *status)
或者
pid_t wait(NULL)
参数:(status 是传出参数) 保存进程的状态。传入 NULL 表示父进程不关心子进程结束原因 返回值:成功: 回收的进程的 pid失败: -1, errno函数作用1: 阻塞等待子进程退出(子进程不结束就死等,一直等到子进程结束后收尸)函数作用2: 回收子进程残留资源(清理子进程残留在内核的 pcb 资源)函数作用3: 得到子进程结束状态。(这个 status 的值还需搭配宏函数使用,才能得到进程退出的原因)
NULL 作为参数没啥好说的,我们重点说说 status 这个参数。
前面说了,status 是一个传出参数,所谓传出参数,也就是说 wait 函数会在函数内部为 status 赋值,status 里保留的就是进程状态。我们单看这个 status 的值是没有意义的,这个值需要作为参数传入到宏函数里面,宏函数会根据传进来的 status 参数分析出进程结束的原因,所以 status 参数是给宏函数使用的。
进程回收 相关的宏函数介绍
宏函数是分析进程结束原因的,常用的宏函数有以下两类,需要头文件 #include <sys/wait.h>:
-
WIFEXITED(status) 为真,表示子进程正常终止 --》调用 WEXITSTATUS(status) --》 得到 子进程 退出值。这个进程退出只也就是进程结束的原因。
-
WIFSIGNALED(status) 为真,表示子进程异常终止 --》调用 WTERMSIG(status) --》 得到 导致子进程异常终止的信号编号。
代码演示1:回收子进程,无需查明死亡原因
#include <stdio.h>
#include <unistd.h>/** 代码实现思路:* 创建子进程,让子进程sleep(10),* 父进程调用 wait 函数,获取 wait 的返回值,根据返回值判断是否回收成功**/int main() {int pid, wret;pid = fork();if(pid == 0) {printf("我是子进程 id: %d ,我正在 sleep...\n", getpid());sleep(10);printf("我是子进程 id: %d , 我先挂了\n", getpid());} else if(pid > 0) {printf("我是父进程, id: %d \n", getpid());wret = wait(NULL); // 传入 NULL 表示不关心子进程死亡原因,也可以传入 &status ,但不会被用到if(wret == -1) {perror("子进程回收失败");}printf("成功回收子进程 %d\n", wret);}return 0;
}
通过以上输出,可以看到 wait 是阻塞的,父进程一直等到 wait 返回后才输出"成功回收子进程 xxx".
代码演示2:回收子进程,并查明进程结束原因
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>/** 代码实现思路:* 创建子进程,让子进程sleep(12),* 父进程调用 wait(&status) ,获取 wait 的返回值,根据返回值判断是否回收成功,* 再根据 status + 宏函数 的返回值,得出子进程结束的原因**/int main() {int pid, wret, status;pid = fork();if(pid == 0) {printf("我是子进程 id: %d ,我正在 sleep...\n", getpid());sleep(12);printf("我是子进程 id: %d , 我先挂了\n", getpid());return 153;} else if(pid > 0) {printf("我是父进程, id: %d \n", getpid());wret = wait(&status);if(wret == -1) {perror("子进程回收失败");exit(-1);}// 结合 status 和 宏函数 查明进程结束原因if(WIFEXITED(status)) {printf("子进程正常结束,退出值:%d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子进程异常结束,退出值:%d\n", WTERMSIG(status));}printf("我是父进程 %d,我已成功回收子进程 %d,并查明了结束原因。\n", getpid(), wret);}return 0;
}
以上结果很容易看出:
第一次执行后,子进程正常结束,退出码是153,153也就是子进程的结束原因。
第二次执行后,子进程被 kill -9 杀死,返回的是信号 9,这个信号 9 就是子进程的结束原因。
还有一点要说明的是 return 153,经过测试,这个返回值默认是0。
返回值的范围只能指定 0 - 255。
waitpid 函数
上面分析过 wait 函数,缺点很明显:
- 父进程阻塞回收子进程。
- 只能随机回收一个进程。
那么 waitpid 就是增强版了,waitpid 函数能以非阻塞的方式回收指定的任一子进程。
需要注意,wait 或是 waitpid,一次都只能回收一个进程。
pid_t waitpid(pid_t pid, int *status, int options)
参数:pid:> 0: (传入进程 id) 指定回收某一个子进程-1:任意子进程,传入 -1 表示随便回收当前进程的一个子进程,谁先结束回收谁0:回收当前进程组中的所有子进程。status:(传出参数)被回收进程的状态,如果是 NULL,表示不关心子进程退出的原因。options:WNOHANG 指定回收方式为,非阻塞。如果使用 WNOHANG 选项并且没有已结束的子进程,waitpid() 将立即返回0,不会等待。如果有子进程已经终止,waitpid() 将返回该子进程的进程 ID。返回值:> 0 : 表示成功回收的子进程 pid0 : 函数调用时, 如果参3 是WNOHANG, 并且没有子进程结束,则返回0,回收不成功。-1: 失败。errno
代码演示:使用 waitpid 以非阻塞的方式回收进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>/** 所谓非阻塞,就是父进程调用 waitpid 后,继续执行后续语句,不必等到子进程结束。** 那么实现思路是这样的:* 创建子进程* 父进程调用 waitpid() ,获取 waitpid 的返回值,根据返回值判断是否回收成功,** 需要注意的是:fork 后,父子进程的执行顺序是随机的,父进程可能在子进程之前结束,所以要在父进程中sleep,等子进程先结束。*/int main() {int pid, wret;pid = fork();if(pid == 0) {printf("我是子进程 id: %d , 我先走一步\n", getpid());return 101;} else if(pid > 0) {// 防止父进程先于子进程结束导致回收失败,因为 waitpid 是非阻塞的sleep(2);// 该案例只是演示进程回收方式,不关系子进程结束原因,所以传入NULLwret = waitpid(-1, NULL, WNOHANG); printf("我是父进程, id: %d,我已经执行 waitpid() \n", getpid());printf("我是父进程,id: %d ,我非阻塞地继续执行后续语句...\n", getpid());if(wret == -1) {perror("子进程回收失败");exit(-1);} else if(wret == 0) { // 如果没在父进程中sleep,可能进入这个分支perror("回收失败,没有子进程结束。");exit(-1);}if(wret > 0) {printf("我是父进程 %d,我已成功回收子进程 %d \n", getpid(), wret);}}return 0;
}