浅谈 Linux进程回收、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 函数,缺点很明显:

  1. 父进程阻塞回收子进程。
  2. 只能随机回收一个进程。

那么 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 : 表示成功回收的子进程 pid

	0 : 函数调用时, 如果参3 是WNOHANG, 并且没有子进程结束,则返回0,回收不成功。

	-1: 失败。errno

代码演示1:使用 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);
                
                // 该案例只是演示进程回收方式,不关系子进程结束原因,所以传入NULL
                wret = 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;
}

在这里插入图片描述

waitpid 多进程回收 案例

前面已经把 waitpid 基本用法讲了,是基于单个子进程讲解的。
这里再把回收多进程的场景讲一下。

代码演示1:使用waitpid回收多个子进程

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

/*
 * 需求:使用 waitpid 回收指定的子进程
 *
 * 实现思路:
 *  先创建多个子进程 (循环创建)
 *      循环调用 waitpid(-1, NULL, WNOHANG) ,根据 waitpid() 返回值判断是否回收成功
 */

int main() {

        int pid, wret, i;
        for(i=0; i<3; i++) {
                pid = fork();
                if(pid == 0) {
                        printf("[诞生] 第 %d 个子进程 id: %d\n", i, getpid());
                        break;  // 这里让子进程跳出循环,防止子进程创建自己的子进程
                }
        }

        if(pid > 0) {
                // 防止父进程先于子进程结束导致回收失败,因为 waitpid 是非阻塞的
                sleep(2);
                while((wret = waitpid(-1, NULL, WNOHANG)) != -1) {
                        if (wret == 0){
                                perror("回收失败,没有子进程结束.");
                                exit(-1);
                        }
                        printf("子进程 %d 回收成功.\n", wret);
                }
				printf("所有子进程已回收完毕。\n");
        }
        return 0;
};

在这里插入图片描述

因为子进程结束的顺序是不同的,所以回收的顺序也是不同的。

  • 25
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值