Linux系统:详解进程等待wait与waitpid解决僵尸进程

news2025/4/20 15:45:27

本节重点

  • 理解进程等待的相关概念
  • 掌握系统调用wait与waitpid的使用方法
  • 输出型status参数的存储结构
  • 阻塞等待与非阻塞等待

一、概念

进程等待是操作系统中父进程与子进程协作的核心机制,指父进程通过特定方式等待子进程终止并回收其资源的过程。这一机制的主要目的是避免子进程成为僵尸进程,从而释放系统资源并维护进程表的完整性。

二、进程等待的方法

2.1 wait与waitpid

2.1.1 wait

wait 是一个用于进程控制的系统调用,主要用于父进程等待其任意一个子进程终止,并回收子进程的资源(如进程表项)。

函数原型:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

参数:

status :输出型参数,一个指向整数的指针,用于存储子进程的终止状态。如果不需要关心子进程的退出状态信息,可以传入NULL。 

返回值:

成功时返回终止的子进程的PID,失败时返回-1并设置errno。

代码示例:

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

int main()
{
    //创建子进程:
    pid_t id=fork();
    if(id==0)
    {
        //child
        int i=5;
        while(i)
        {
            printf("我是一个子进程,PID->%d,PPID->%d\n",getpid(),getppid());
            sleep(1);
            i--;
        }
        exit(10);
    }
    else if(id>0)
    {
        //parent
        int ret=0;
        printf("我是一个父进程,PID->%d\n",getpid());
        int _ret=wait(&ret);
        if(_ret>0)
        {
            printf("wait success, child PID->%d,exitcode->%d\n",_ret,ret);
        }
        else
        {
            printf("wait fail,error->%d\n",errno);
        }
    }
    else
    {
        //id<0
        printf("fork fail\n");
    }
    return 0;
}

 运行结果:

注意事项:

系统调用wait默认等待方式是阻塞等待,也就是说当父进程调用 wait 或waitpid 时,它会阻塞在调用wait的一段代码行,直到有一个子进程终止父进程解除阻塞状态后才会执行后续代码。

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

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("Child process: PID = %d\n", getpid());
        sleep(2); // 模拟子进程工作
        exit(0);
    } else if (pid > 0) {
        // 父进程
        int status;
        printf("Parent process: Waiting for child to terminate...\n");
        wait(&status); // 阻塞,直到子进程终止
        printf("Child process terminated with status: %d\n", WEXITSTATUS(status));
    } else {
        perror("fork failed");
    }
    return 0;
}

2.1.2 waitpid

waitpid 是 Unix/Linux 系统中用于等待子进程状态变化的系统调用,是 wait 的增强版本。它允许父进程更灵活地控制等待行为,可以指定等待特定的子进程或进程组,并支持阻塞和非阻塞模式。

函数原型:

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

参数解析:

 pid: 指定要等待的子进程

  • pid > 0:等待进程 ID 为 pid 的子进程。
  • pid == 0:等待与调用进程同组的任意子进程。
  • pid < -1:等待进程组 ID 为 |pid| 的任意子进程。
  • pid == -1:等待任意子进程(等效于 wait)。

status:指向一个整数用来存储子进程的退出状态

options:控制waitpid的行为,可以是以下标志的按位或:

  • 0:阻塞模式,等待子进程终止。
  • WNOHANG:非阻塞模式,如果没有子进程退出,立即返回。
  • WUNTRACED:报告已停止但未终止的子进程状态。
  • WCONTINUED:报告已继续运行的子进程状态。

返回值:

成功:

  • 返回被等待子进程的 PID。
  • 如果没有子进程满足条件且设置了 WNOHANG,返回 0

失败:

  • 返回 -1,并设置 errno 指示错误原因。

代码示例:

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

#define N 3

int main() {
    pid_t pids[N];
    int i;

    // 创建多个子进程
    for (i = 0; i < N; i++) {
        pids[i] = fork();
        if (pids[i] == 0) {
            // 子进程
            printf("Child %d: PID = %d\n", i, getpid());
            sleep(i + 1);
            exit(i);
        }
    }

    // 父进程等待所有子进程终止
    for (i = 0; i < N; i++) {
        int status;
        pid_t result = waitpid(pids[i], &status, 0);
        if (result > 0) {
            if (WIFEXITED(status)) {
                printf("Child %d exited with status: %d\n", i, WEXITSTATUS(status));
            }
        } else {
            perror("waitpid failed");
        }
    }

    return 0;
}

运行结果:

Child 0: PID = 12346
Child 1: PID = 12347
Child 2: PID = 12348
Child 0 exited with status: 0
Child 1 exited with status: 1
Child 2 exited with status: 2

 注意事项:

 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源获得子进程的退出信息。

如果调用wait/waitpid时,子进程存在且正常运行,则进程可能会阻塞。

如果不存在该子进程,则立即出错返回。

2.3 获取子进程的status

在Linux系统中,父进程通过wait/waitpid来等待和回收子进程的相关资源并通过status参数返回子进程的退出信息,该参数是一个输出型参数由操作系统填充。

如果传递NULL表示不关心子进程的退出信息。

2.3.1 status参数的存储结构

ststus 不能简单当作整型来看待,可以当作位图来看待,其中包含了子进程的退出码与终止信号,如下图:

其低 16 位用于存储子进程的退出状态信息。具体来说:

  • 低 7 位:表示导致子进程终止的信号编号(如果子进程被信号终止)。
  • 第 8 位:表示是否生成了核心转储文件(core dump)。
  • 高 8 位:表示子进程的退出码(如果子进程正常退出)。

注意:

当进程正常终止时,低7位和第8位都为0,高8位存储子进程的退出码,可以通过位运算(ststus>>8)&0×FF或通过系统提供的宏来获取。

当进程异常终止或被信号所终止时,退出状态无意义故高8位都为0,终止信号存储在低7位中可以通过位运算status&0×7F或通过宏来获取。

2.3.2 用于解析ststus的宏

以下宏定义在<sys/wait.h>文件中,用于解析返回后的status参数。

子进程是否正常退出:

WIFEXITED(status)
  •  如果子进程正常退出(例如调用了 exit( ) 或 return ),则返回非零值(真)。
  • 否则返回0(假)。

 获取子进程的退出码:

WEXITSTATUS(status)
  •  如果 WEXITSTATUS(status) 返回真,则可以使用此宏获取子进程的退出码。
  • 退出码是子进程调用 exit(code) 或 return code 时指定的值。

检查子进程是否被信号终止(异常退出):

WIFSIGNALED(status)

 如果子进程被信号终止返回非零,否则返回0。

获取导致子进程终止的信号编号:

WTERMSIG(status)

 如果WTERMSIG(ststus)返回真,则可以使用此宏获取导致子进程终止的信号编号。

2.4 阻塞等待与非阻塞等待


pid_t waitpid(pid_t pid, int *status, int options);

系统调用waitpid的第三个参数options用来设置阻塞等待与非阻塞等待,默认为0表示阻塞等待,当传递宏WNHANG时表示非阻塞等待。

阻塞等待:

当waitpid处于阻塞等待时进程会阻塞于调用waitpid的代码段处(停止执行),直到等待的进程终止,waitpid才会返回继续执行后续代码。

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
int main()
{
    pid_t id=fork();
    int status;
    if(id==0)
    {
        //child
        printf("我是一个子进程,我的PID是%d,父进程PID%d\n",getpid(),getppid());
        sleep(10);
        exit(0);
    }
    else if(id>0)
    {
        //parent
        pid_t ret=waitpid(id,&status,0);
        if(ret>0)
        {
            printf("wait success!\n");
        }
        else
        {
            printf("wait fail!\n");
        }
        printf("这是父进程的后续代码\n");
    }
    else
    {
        perror("fork fail!");
    }
    return 0;
}

运行结果:

 非阻塞等待:

当waitpid处于非阻塞状态时,进程不会停止执行自己的代码,但waitpid会不断查询相应进程的状态信息直到被等待进程终止,期间waitpid的返回值一直是0。

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

//函数指针类型:
typedef void (*func_t)();
#define NUM 5
func_t handers[NUM+1];

void DownLoad()
{
    printf("我是一个下载的任务\n");
}
void Flush()
{
    printf("我是一个刷新的任务\n");
}
void Log()
{
    printf("我是一个记录日志的任务\n");
} 

//注册
void registerhanders(func_t h[],func_t f)
{
    int i=0;
    for(;i<NUM;i++)
    {
        if(h[i]==NULL)
        {
            break;
        }
    }
    if(i==NUM)
    {
        return;
    }
    h[i]=f;
    h[i+1]=NULL;
}
int main()
{
    //注册函数:
    registerhanders(handers,DownLoad);
    registerhanders(handers,Flush);
    registerhanders(handers,Log);


    pid_t id=fork();
    int status;
    if(id==0)
    {
        //child
        printf("我是一个子进程,我的PID是%d,父进程PID为%d\n",getpid(),getppid());
        sleep(5);
    }
    else if(id>0)
    {
        //parent
        while(1)
        {
            int ret=waitpid(id,&status,WNOHANG);
            if(ret==0)
            {
                printf("轮询结束,子进程未退出!\n");
            }
            else if(ret>0)
            {
                printf("轮询结束,子进程退出!\n");
                break;
            }
            else
            {
                //ret<0
                perror("waitpid fail!\n");
                break;
            }
            sleep(1);
            //printf("这是父进程代码!\n");
            for(int i=0;i<NUM;i++)
            {
                if(handers[i]!=NULL)
                handers[i]();
            }
       }
    }
    else
    {
        //id<0
        //fork fail!
        perror("fork fail!");
    }
    return 0;
}

如上述所示就是一段非阻塞进程等待的代码,父进程除了一次次查询子进程的进程状态之外也在执行自己的三个函数任务(下载任务,刷新任务,记录日志的任务),当子进程退出后父进程会立即回收子进程资源,接着整段程序结束。

运行结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338823.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA clean git password

IntelliJ IDEA clean git password 清除git密码 方法一&#xff1a;&#xff08;这个要特别注意啊&#xff0c;恢复默认设置&#xff0c;你的插件什么要重新下载了&#xff09; File->Manage IDE Settings->Restore Default Settings以恢复IDEA的默认设置(可选); 清空…

【已更新完毕】2025泰迪杯数据挖掘竞赛C题数学建模思路代码文章教学:竞赛智能客服机器人构建

完整内容请看文末最后的推广群 基于大模型的竞赛智能客服机器人构建 摘要 随着国内学科和技能竞赛的增多&#xff0c;参赛者对竞赛相关信息的需求不断上升&#xff0c;但传统人工客服存在效率低、成本高、服务不稳定和用户体验差的问题。因此&#xff0c;设计一款智能客服机器…

ACI EP Learning Whitepaper 3. Disabling IP Data-plane Learning 功能

目录 1. 使用场景 1.1 未disable IP data-plane learning时 1.2 disable IP data-plane learning后 2. 一代Leaf注意事项 3. L2 未知单播注意事项 1. 使用场景 Windows网卡的动态负载均衡绑定模式等。或多个设备共享相同VIP并通过ARP/GARP/ND来宣告VIP切换时,这些外部设…

C++入门七式——模板初阶

目录 函数模板 函数模板概念 函数模板格式 函数模板的原理 函数模板的实例化 模板参数的匹配原则 类模板 类模板的定义格式 类模板的显式实例化 当面对下面的代码时&#xff0c;大家会不会有一种无力的感觉&#xff1f;明明这些代码差不多&#xff0c;只是因为类型不…

【教程】检查RDMA网卡状态和测试带宽 | 附测试脚本

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 检查硬件和驱动状态 测试RDMA通信 报错修复 对于交换机的配置&#xff0c;可以看这篇&#xff1a; 【教程】详解配置多台主机通过交换机实现互…

(二)Trae 配置C++ 编译

Trae配置c编译 零 CMake 编译C0.1 下载安装0.2 安装设置0.3 三种编译方式(见 下文 一 二 三)0.4 调试 (见 下文四) 一 使用MSVC方式编译1.1 安装编译环境1.2安装插件1.3 设置文件 二 使用GCC方式2.1 安装编译环境2.1.1下载:[MinGw](https://gcc-mcf.lhmouse.com/)2.1.2安装:(以…

日本公司如何实现B2B商城订货系统的自动化和个性化?

在日本构建具备前后台日文本地化、业务员代客下单、一客一价、智能拆单发货的B2B电商系统&#xff0c;需结合日本商业习惯与技术实现。以下是关键模块的落地方案&#xff1a; 一、系统架构设计 1. 前端本地化 语言与UI适配 采用全日语界面&#xff0c;包含敬语体系&#xff08…

用魔法打败魔法——获取软件安装路径

用魔法打败魔法——获取软件安装路径 &#x1f31f;嗨&#xff0c;我是LucianaiB&#xff01; &#x1f30d; 总有人间一两风&#xff0c;填我十万八千梦。 &#x1f680; 路漫漫其修远兮&#xff0c;吾将上下而求索。 目录 背景普通方法用魔法一句话 1.首先新建‘PC自动化应…

【排队论】Probabilistic Forecasts of Bike-Sharing Systems for Journey Planning

Probabilistic Forecasts of Bike-Sharing Systems forJourney Planning abstract 我们研究了对共享单车系统&#xff08;BSS&#xff09;车站未来自行车可用性进行预测的问题。这是相关的&#xff0c;以便提出建议&#xff0c;保证用户能够进行旅行的概率足够高。为此&#x…

高精度算法(加、减、乘、除、阶乘和)​

归纳编程学习的感悟&#xff0c; 记录奋斗路上的点滴&#xff0c; 希望能帮到一样刻苦的你&#xff01; 如有不足欢迎指正&#xff01; 共同学习交流&#xff01; &#x1f30e;欢迎各位→点赞 &#x1f44d; 收藏⭐ 留言​&#x1f4dd; 唯有主动付出&#xff0c;才有丰富的果…

实战设计模式之备忘录模式

概述 与解释器模式、迭代器模式一样&#xff0c;备忘录模式也是一种行为设计模式。备忘录模式允许我们保存一个对象的状态&#xff0c;并在稍后恢复到这个状态。该模式非常适合于需要回滚、撤销或历史记录等功能的应用场景。通过使用备忘录模式&#xff0c;开发者可以轻松添加诸…

吉尔吉斯斯坦工商会代表团赴齐河德瑞新能源汽车考察

德州齐河&#xff0c;2025年4月15日电 时中美贸易突变之际&#xff0c;乘国家一带一路之风。 展中国新能源之宏图&#xff0c;塑国贸体系之新方向。 今日上午&#xff0c;吉尔吉斯斯坦共和国工商会代表团一行三人受邀抵达济南&#xff0c;开启对德瑞新能源科技有限公司&…

无人机在农业中的应用与挑战!

一、无人机在农业中的作用 1. 提升作业效率与降低成本 无人机在喷洒农药、播种、施肥、吊运等环节显著提升效率。例如&#xff0c;湖北秭归县使用大疆T100无人机吊运脐橙&#xff0c;单次85公斤的运输任务仅需2分钟&#xff0c;而人工需1小时&#xff0c;综合成本降低250元…

QT网络拓扑图绘制实验

前言 在网络通讯中&#xff0c;我qt常用的是TCP或者UDP协议&#xff0c;就比方说TCP吧&#xff0c;一台服务器有时可能会和多台客户端相连接&#xff0c;我之前都是处理单链接情况&#xff0c;最近研究图结构的时候&#xff0c;突然就想到了这个问题。那么如何解决这个问题呢&…

支持中文对齐的命令行表格打印python库——tableprint

文章目录 快速入门 还在为表格中含有中文&#xff0c;命令行打印无法对齐而苦恼吗&#xff1f; 还在为冗长的数据添加代码而抓狂吗&#xff1f; tableprint来了&#xff01;&#xff01;&#xff01;&#xff0c;它完美的解决了上述两个问题&#xff0c;快来试试吧&#xff01;…

从《周游记3》演绎歌剧版《菊花台》,周杰伦婚礼曲目意大利文版惊喜亮相

今天&#xff08;4月19日&#xff09;22:00&#xff0c;由魔胴西西里咖啡冠名的户外实境互动综艺《周游记3》第四期即将播出。本期节目中&#xff0c;“J式之旅”发起人周杰伦和林暐恒、杜国璋、陈冠霖、陈冠廷&#xff0c;将继续意大利之旅&#xff0c;从那不勒斯的百年老店到…

生物化学笔记:医学免疫学原理23 免疫检查点分子与肿瘤免疫治疗(PD-1抑制剂黑色素瘤)

免疫检查点分子与肿瘤免疫治疗 免疫检查点分子与肿瘤免疫治疗-2

CasualLanguage Model和Seq2Seq模型的区别

**问题1&#xff1a;**Causal Language Modeling 和 Conditional Generation 、Sequence Classification 的区别是什么&#xff1f; 因果语言模型(Causal Language Model)&#xff1a; 预测给定文本序列中的下一个字符&#xff0c;一般用于文本生成、补全句子等&#xff0c;模型…

verilog float mult

module pipe_float_mul(input wire clk ,// 时钟信号input wire en ,// 使能信号input wire rst_n ,// 复位信号input wire round_cfg ,// 决…

微信小程序调用yolo目标检测模型

目录 后端 前端微信小程序 完整代码 后端 利用Flask&#xff0c;调用目标检测模型&#xff0c;后端代码如下。 # flask_yolo.py from flask import Flask, request, jsonify from ultralytics import YOLO from PIL import Imageapp Flask(__name__) model_path best.p…