【linux进程控制(二)】进程等待-->死亡的子进程是如何被父进程等待回收的?

🎬 个人主页:HABuo

📖 个人专栏:《C++系列》 《Linux系列》《数据结构》《C语言系列》《Python系列》《YOLO系列》

⛰️ 如果再也不能见到你,祝你早安,午安,晚安


目录

📚一、进程等待概念

📚二、wait/waitpid

[📖2.1 waitpid的参数status](#📖2.1 waitpid的参数status)

[📖2.2 waitpid的第三个参数option](#📖2.2 waitpid的第三个参数option)

📚三、总结


前言

上篇博客我们讲解了进程创建和终止,进程状态的时候我们留了一个伏笔,就是一个子进程进入了僵尸状态,谁回收它我们清楚了,但是怎么回收当时我们并没有说,那在这篇博客我们就来介绍如何回收的问题,即进程等待

本章重点:

本篇文章着重讲解进程等待的必要性 ,以及系统调用wait/waitpid的使用详情 ,并且重点讲解waitpid的三个参数的含义和不同的用法!


📚一、进程等待概念

进程等待就是父进程通过系统调用(如wait()、waitpid())来等待子进程的状态改变(终止或停止),并获取子进程的退出状态,从而释放子进程占用的系统资源

进程等待的必要性:

  • 若子进程退出,而父进程对它不管不顾此时会有僵尸进程问题,有内存泄漏风险
  • 当一个进程变成僵尸进程,那它就刀枪不入了,因为无法杀掉一个死去的进程
  • 父进程创建子进程是为了完成某任务,父进程需要知道它把任务完成得如何,所以等待子进程死亡是很有必要的!
  • 父进程需要等待子进程死亡后,回收它的代码和数据!

进程等待的主要目的:

  • 防止僵尸进程:子进程退出后,父进程如果不等待,子进程会成为僵尸进程,占用系统资源。

  • 获取子进程的退出状态:父进程可以知道子进程是如何退出的(正常退出、异常退出等)。

进程等待的系统调用:

  • wait():等待任意一个子进程结束,如果当前没有子进程退出,则阻塞等待。

  • waitpid():可以等待指定的子进程,也可以设置非阻塞模式。

📚二、wait/waitpid

因为waitpid比wait更灵活,且waitpid懂了wait自然也就懂了,因此本篇文章着重讲解waitpid函数

waitpid函数的详情请看下图:

话不多说,上代码来测试:

cpp 复制代码
int main()
{
    pid_t pids[3];
    // 创建3个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            printf("子进程 %d (PID=%d) 启动\n", i, getpid());
            sleep(i + 1);  // 每个子进程睡眠时间不同
            exit(100 + i);
        }
        pids[i] = pid;
    }
    // 父进程等待特定子进程
    for (int i = 0; i < 3; i++) {

        // 等待特定PID,阻塞模式
        pid_t ret = waitpid(pids[i], NULL, 0);
        printf("我等待成功一个子进程!它的id是: %d\n", ret);

    }
}

可以发现,当子进程执行它的代码时,父进程并不会进入if语句中,而是往下继续执行代码,但是父进程迟迟不打印"我等待成功一个子进程",这是因为父进程在阻塞等待子进程,如果子进程不退出,父进程就在这里等它死亡!

📖2.1 waitpid的参数status

如果父进程想要知道子进程的退出信息,也就是退出码和退出信号,就要用到这个输出型参数status

status参数可没有你想的这么简单,它的信息并不是按照整个整数来存储的,如果你学过位图的话,那么下面的内容会很好理解!

我们只研究status的低16比特位

低八位存储的是终止信号,次低八位存储的是退出状态

所以如果想要获取status中的这两个信息,我们需要使用下面的代码来解析:

cpp 复制代码
退出码:
(status >> 8) & 0xFF
退出信号:
status & 0x7F

我们用下面的代码验证一下:

cpp 复制代码
int main()
{
    pid_t pids[3];
    // 创建3个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            printf("子进程 %d (PID=%d) 启动\n", i, getpid());
            sleep(i + 1);  // 每个子进程睡眠时间不同
            exit(100 + i);
        }
        pids[i] = pid;
    }
    // 父进程等待特定子进程
    for (int i = 0; i < 3; i++) {
        int status;
        // 等待特定PID,阻塞模式
        pid_t ret = waitpid(pids[i], &status, 0);
        if (ret > 0)
        { 
            printf("回收子进程 PID=%d,退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
        }
    }
}

运行结果如下:

对于退出信号,我们正常退出即为0,如果我们使用kill -9杀死进程,那么这个信号也即为9了,当然此时退出码也就没有意义了!

总有人会觉得,上面的位操作麻烦,因此接口提供了宏替换如下:

cpp 复制代码
printf("子进程正常退出,退出码: %d\n", WEXITSTATUS(status));
printf("子进程被信号终止,信号: %d\n", WTERMSIG(status));

📖2.2 waitpid的第三个参数option

前面说到,option默认为0代表父进程阻塞等待子进程死亡,阻塞等待的意思就是父进程什么都不干,就在waitpid函数处停下等待子进程!那么假如父进程想要干一些自己的事情应该怎样做?

cpp 复制代码
使用宏定义: WNOHANG
waitpid(pid,&status,WNOHANG);

WNOHANG就是wait no hang,hang也就是悬挂(挂起来也就有停的意思),no hang也就是不停也即是非阻塞等待子进程死亡,若父进程执行到waitpid时,子进程还没退出,则函数返回0后接着运行下面的代码,若执行到waitpid后子进程已经退出则返回退出子进程的pid

然而如果非阻塞等待一次,这样肯定是不行的,因为你不知道子进程什么时候退出

又因此父进程执行非阻塞waitpid时,``只要子进程不返回父进程就执行下一步代码
因此使用非阻塞等待时往往会循环访问,这个过程叫轮询

cpp 复制代码
int main()
{
    pid_t pids[3];
    // 创建3个子进程
    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程
            printf("子进程 %d (PID=%d) 启动\n", i, getpid());
            sleep(i*3 + 2);  // 每个子进程睡眠时间不同
            exit(100 + i);
        }
        pids[i] = pid;
    }
    // 父进程等待特定子进程
    for (int i = 0; i < 3; i++) {
        int status;
        int count = 0;
        while (1)
        {
            pid_t ret = waitpid(pids[i], &status, WNOHANG);
            if (ret > 0)
            {
                printf("回收子进程 PID=%d,退出码: %d, 退出信号是: %d\n", ret, (status >> 8) & 0xFF, status & 0x7F);
                printf("-----------回收完成----------\n");
                break;
            }
            sleep(1);
            printf("我执行我的代码 %d 次\n", count++);
        }
    }
}

注:这里父进程可以执行任一任务,我使用printf打印只是为了明显的看到父进程是没有阻塞等待的!

所以我们学完waitpid之后回头再看wait那是相当ok,它无非就是把waitpid的第二个参数即获取子进程退出信息的参数拿过来作为参数!


📚三、总结

本篇博客我们主要了解了进程等待的相关知识点。

总结一下:

进程等待

  • 是什么:
cpp 复制代码
父进程通过系统调用(如wait()、waitpid())
来等待子进程的状态改变(终止或停止)
并获取子进程的退出状态,从而释放子进程占用的系统资源
  • 为什么:
cpp 复制代码
①防止僵尸进程,僵尸进程会占用系统资源
②获取子进程的退出状态
  • 怎么办:
cpp 复制代码
通过系统调用wait、waitpid。

waitpid返回值:
等待成功返回所等待进程的pid,
等待失败返回-1,
当在非阻塞模式即WNOHANG,可能返回0,这并不表示失败,而是表示没有子进程退出。

waitpid参数:
第一个表示要等待进程的pid,
第二个int* status
    低八位表示退出信号,次低八位表示退出码
    可以通过位移操作获取((status >> 8) & 0xFF、status & 0x7F)
    也可通过宏替换获取(WEXITSTATUS(status)、WTERMSIG(status))
第三个表示以什么方式进行等待,阻塞为0,非阻塞为WNOHANG。
相关推荐
Demo_xr2 小时前
【系统安装】ubuntu20.04启动盘制作,装到物理服务器上
ubuntu
客卿1232 小时前
力扣--数组 入门三题-485/283/27---刷题笔记+思路分析+C语言
c语言·笔记·leetcode
wheeldown2 小时前
【Linux网络基础】Linux 网络基础与 TCP 协议
linux·网络·tcp/ip
小龙报2 小时前
【算法通关指南:算法基础篇 】贪心专题之简单贪心:1.最大子段和 2.纪念品分组
c语言·数据结构·c++·算法·ios·贪心算法·动态规划
上海云盾商务经理杨杨2 小时前
2026服务器保卫战:构建攻不垮的业务连续性体系
运维·服务器
杜子不疼.3 小时前
进程控制(四):自主Shell命令行解释器
linux·c语言·人工智能
橘颂TA3 小时前
【Linux 网络】深入理解 UDP
linux·运维·服务器·网络·网络协议
乱蜂朝王10 小时前
Ubuntu 20.04安装CUDA 11.8
linux·运维·ubuntu
君义_noip10 小时前
信息学奥赛一本通 1661:有趣的数列 | 洛谷 P3200 [HNOI2009] 有趣的数列
c++·算法·组合数学·信息学奥赛·csp-s