浅谈 Linux进程回收、wait、waitpid函数

文章目录

  • 前言
  • 进程回收
  • [wait 函数](#wait 函数)
  • [进程回收 相关的宏函数介绍](#进程回收 相关的宏函数介绍)
  • [waitpid 函数](#waitpid 函数)
  • [waitpid 多进程回收 案例](#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:回收子进程,无需查明死亡原因

c 复制代码
#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:回收子进程,并查明进程结束原因

c 复制代码
#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 以非阻塞的方式回收进程

c 复制代码
#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回收多个子进程

c 复制代码
#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;
};

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

相关推荐
zz_nj1 小时前
工作的环境
linux·运维·服务器
极客先躯2 小时前
如何自动提取Git指定时间段的修改文件?Win/Linux双平台解决方案
linux·git·elasticsearch
suijishengchengde2 小时前
****LINUX时间同步配置*****
linux·运维
qiuqyue3 小时前
基于虹软Linux Pro SDK的多路RTSP流并发接入、解码与帧级处理实践
linux·运维·网络
切糕师学AI3 小时前
Linux 操作系统简介
linux
南烟斋..3 小时前
GDB调试核心指南
linux·服务器
爱跑马的程序员4 小时前
Linux 如何查看文件夹的大小(du、df、ls、find)
linux·运维·ubuntu
oMcLin6 小时前
如何在 Ubuntu 22.04 LTS 上部署并优化 Magento 电商平台,提升高并发请求的响应速度与稳定性?
linux·运维·ubuntu
Qinti_mm6 小时前
Linux io_uring:高性能异步I/O革命
linux·i/o·io_uring
优雅的38度6 小时前
linux环境下,使用docker安装apache kafka (docker-compose)
linux·架构