Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?

进程等待(原理+接口调用)及进程状态 什么是进程等待 简单来说, 进程等待是进程的一种状态, 是父进程等待子进程退出时的一个过程 为什么要进程等待 简单来讲, 因为要避免产生僵尸进程. 进程在退出的时候, 会关闭所有的文件描述符, 释放在用户空间中分配的内存, 但是 PCB 仍会暂时保留, 里面还存放着进程的退出状态以及统计信息等. 当一个进程创建一个子进程时, 父进程往往需要读取子进程的运行结果, 如果子进程先于父进程退出, 父进程... 阅读详情

一、进程等待的概念

 进程等待通常是指:父进程通过wait()/waitpid()的方式,让父进程对子进程进行资源回收的等待过程!!

二、进程等待存在的意义

 进程等待通常是为了解决以下两种情况:

  1. 解决子进程僵尸所带来的内存泄漏问题,对僵尸子进程进行资源回收! 原因在于当子进程僵尸后,便“刀枪不入”了。即使是操作系统也没法对僵尸进程进行资源回收,进而导致内存泄漏问题。
  2. 让父进程获得子进程运行结果(代码运行正常结果正确、代码运行正常结果错误、代码异常)。父进程创建子进程,通常是希望子进程帮父进程执行某些任务。但子进程任务执行的如何,父进程需要得到反馈。此时父进程可以通过进程等待的方式来获取子进程的退出信息(退出码和退出信号)。

三、如何进行进程等待

下面依次介绍进程等待所需调用的接口:wait()/waitpid()。

3.1 wait()是实现进程等待

1、wait()原型

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

pid_t wait(int *status);
  1. 返回值:如果成功返回被等待进程的pid,否则返回-1.
  2. 参数:ststus为输出型参数,获取子进程的退出信息,由操作系统自动填充。(后续会单独详细介绍,这里我们暂且不关心该参数,设为NULL)
  3. wait()用于等待任意进程,而waitpid则可以等待任意进程!!

2. 验证wait()能回收僵尸子进程的空间

 下面这样一段代码:fork()创建子进程,让子进程运行约5秒后退出但父进程不退出。此时子进程变为僵尸进程,进程状态为Z。此时调用父进程调用wait()接口回收僵尸子进程的资源空间。

【源代码】:

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

void worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        //child
        worker();
        exit(0);  //子进程执行完worker()后直接退出,变成僵尸状态  
    }    
    else{
        //parent    
        sleep(10);    
        pid_t rid = wait(NULL);//对子进程进行回收                                                                                       
        if(rid == id)    
        {   
            printf("child process being recyceled sucess!, pid:%d, rid:%d\n", getpid(), rid);    
        }
        sleep(3);
    }
    return 0;
}

【运行结果】:
请添加图片描述

 我们观察左边监视脚本发现,子进程在执行5次代码后退出,进程状态变为Z。一段时间后,父进程调用wait()函数对子进程进行回收,子进程消失。即父进程通过wait()实现了对子进程的回收!!

tips:

  1. 父进程调用wait()后,如果子进程没有退出,父进程会在wait上发生进程阻塞。直到子进程僵尸,wait自动回收后,返回被回收的子进程pid。
  2. 对于多个进程来说,谁先被调度是未知的,由内核调度算法决定。但可以肯定的是,父进程一定是最后退出的!

3.2 waitpid()实现进程等待

1、系统调用接口waitpid()原型

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

pid_t waitpid(pid_t pid, int *status, int options);
  1. 参数pid: 如果pid=-1,等待任意进程,和wait效果一样。如果pid>0,等待进程ID和pid值相等的子进程!
  2. 参数status:子进程的退出信息。status为NULL,表示不关心子进程的退出状态信息,否则操作系统会将子进程的退出码和错误码相关信息写入该参数中。(后续具体介绍其实现机制)
  3. 参数options: 为0表示阻塞等待。options除了0外,还可以被设置为WNOHANG,此时表示父进程以非阻塞方式进行等待(非阻塞 + 轮询方案)。
  4. 返回值:当正常退出时,waitpid返回收集到的子进程ID;如果进程异常,返回-1,此时errno会被设置为对于的错误码;如果进程采用非阻塞轮询方案,即将options设置为WNOHANG,如果子进程waitpid收集到的子进程没有退出,此时返回0!!

四、获取子进程status实现机制

 在wait()/waitpid()中,均存在参数status,该参数是一个输出型参数,由操作系统自动填充。如果该参数被设为NULL,表示不关心子进程的退出信息;否则OS会通过status的值,来将子进程相关退出信息返回给父进程!!

status如何保存相关信息?

 status是int类型,32bit。这里我们仅研究低16位!!
 其中status的最低7位保存子进程的退出信号(exit signal);第8位表示的是core dump标志;9~16位表示的是进程的退出码(exit code)

在这里插入图片描述
status中保存信息验证

 下面我们来做实验:我们通过fork()创建出子进程,然后让子进程运行约3秒;此时父进程通过waitpid以阻塞方式对子进程进行等待回收。然后通过位运算对status进行处理,获取status中的子进程退出码和退出信号。

【源代码】:

#include <stdio.h>    
#include <unistd.h>       
#include <sys/types.h>    
#include <sys/wait.h>    
    
int main()    
{    
    int status = 0;    
    pid_t id = fork();    
    if(id == 0)    
    {    
        int cnt = 3;    
        while(cnt)    
        {    
            printf("I am child, cnt:%d\n", cnt--);    
            sleep(1);    
        }    
        exit(3);    
    }    
    else if(id > 0)    
    {    
        pid_t rid = waitpid(id, &status, 0);//以阻塞方式等待    
        printf("I am parent, pid:%d, rid:%d, status:%d, exit code:%d, signal:%d\n", getpid(), rid, status, (status>>8)&0xFF, status&0x7F);               
    }    
    return 0;    
}  

【运行结果】:
请添加图片描述
 我们发现status变量中确实保存着子进程的退出码和退出信号等相关信息。

在系统中提供了一些宏函数,用于直接获取进程的相关退出信息,具体如下:

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

父进程如何得知子进程的退出信息(底层执行流程)

 在子进程pcb中存在如下几个变量,分别用于保存进程的状态、退出码、退出信号:(Linux为例)

strucr task_struct{
	int exit_state;	//退出状态
	int exit_code;	//退出码
	int exit_signal;//退出信号
}

 当子进程退出时,操作系统会将子进程的退出码和退出信号保存到子进程PCB的exit_code变量和exit_signal变量中。
 而父进程通过waitpid/wait等待子进程时,OS会将子进程PCB中的退出码和退出信号通过组合放入status变量中,并将子进程的状态从Z改成S!!
 此时,父进程便可通过status来获取子进程退出信息,子进程可以被操作系统回收。

五、阻塞等待和非阻塞等待

 前面我们介绍waitpis接口时提到过,options参数设为0,表示父进程进行的时阻塞等待;设为WNOHANG表示父进程以==(非阻塞方式),即非阻塞轮询方式==进行进程等待。

 那两种等待方式究竟是什么?有什么区别呢?

5.1 阻塞等待

 父进程以阻塞方式进行等待和普通阻塞进程一样。
 当父进程调用waitpid接口等待子进程时,如果此时子进程没有退出,操作系统会将父进程设置为阻塞进程,然后将父进程的PCB链入到子进程的等待队列中。一旦子进程退出,操作系统会将父进程PCB重新加载到运行队列中等待调度!

5.2 非阻塞等待(非阻塞 + 轮询方案)

  非阻塞等待是指父进程在等待子进程时发现子进程还未退出,此时父进程和阻塞等待一样一直在"原地等地子进程运行结束"。父进程会执行一些其他任务,并每隔一段时间查看子进程是否退出。一旦子进程退出后,父进程才会开始执行后续程序。
 非阻塞等待的好处就是让父进程在等待时,可以做一些自己占据时间不多的任务!!

六、非阻塞轮询方案示例演示

 下面我们通过fork创建子进程。然后让子进程做一些工作(打印输出一些信息,整个过程约10s),此时父进程通过waitpid接口进行非阻塞轮询方案进行等待。在等待过程中,我们让父进程做一些自己的“小任务”(这些小任务,博主同样采用输出信息代替,各位可根据实际情况修改)

【源代码】:

轮询时,父进程执行任务:
 博主将任务简化为输出一些信息,各位可自行更改任务

void download()
{
    printf("This download task is running!\n");
}

void writelog()
{
    printf("This write log task is running!\n");
}

void printinfo()
{
    printf("This print info task is running!\n");
}

大致框架,父进程和子进程执行任务:

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


#define TASK_NUM 5
void worker(int cnt)                                                                                                                                     
{
    printf("I am child, pid:%d, cnt:%d\n", getpid(), cnt);
}

int main()
{
	//下面5行模拟父进程的任务被加载好了,方便父进程等待子进程时被执行
    task tasks[TASK_NUM];
    Init(tasks, TASK_NUM);//初始化
    taskadd(tasks, download); //加载任务
    taskadd(tasks, writelog); 
    taskadd(tasks, printinfo); 

    pid_t id = fork();
    if(id == 0)
    {//child
         int cnt = 5;
         while(cnt)
         {
             worker(cnt--);
             sleep(1);
         }
         exit(3);
    }
                                                                                                                                                        
    //parent
    while(1)
    {
         int status = 0;
         pid_t rid = waitpid(id, &status, WNOHANG);
         if(rid > 0)
         {//子进程正常退出
              printf("wait sucess!\n, pid:%d, rid:%d, exit code:%d, signal:%d\n",getpid(), rid, (status>>8)&0xFF, status&0x7F);
              break;
         }
         else if(rid == 0)
         {//父进程等待成功,但子进程没有退出。父进程开始做自己的小任务,一段时间后在查询子进程是否退出
             printf("------------------------------------------------\n");
             printf("wait sucess, but chils alive, wait again!\n");
             executeTask(tasks, 3);
             printf("------------------------------------------------\n");
         }
         else
         {//子进程退出异常
             printf("wait failed!\n");
             break;
         }
         sleep(1);
    }
    return 0;
}

父进程加载任务代码:

void Init(task tasks[], int num)
{
    for(int i = 0; i < num; i++)
    {
        tasks[i] = NULL;
    }                                                                                                                                                    
}

int taskadd(task tasks[], task t)
{
    for(int i = 0; i < TASK_NUM; i++)
    {
        if(tasks[i] == NULL)
        {
            tasks[i] = t;
            return 1;//增加任务成功
        }
    }
    return 0;//增加任务失败
}

void executeTask(task tasks[], int num)
{
    for(int i = 0; i < num; i++)
    {
        tasks[i]();
    }
}

运行结果:
请添加图片描述

卡尔曼滤波调参避坑指南:Q/R参数怎么设?实测5种传感器场景 本文针对卡尔曼滤波在工程实践中Q/R参数设置的难题,提供了一套实用的调参避坑指南。通过分析过程噪声方差Q与测量噪声方差R的物理意义与博弈关系,并结合温度传感器、陀螺仪、超声波测距等五种典型传感器场景的实测效果对比,总结出从噪声估算、离线仿真到收敛测试的系统性调参方法论,帮助工程师快速实现稳定有效的滤波效果。 阅读详情

相关推荐

海康威视Web3.0插件实战:快速部署摄像头实时预览方案

本文详细介绍了海康威视Web3.0插件的实战应用,包括快速部署摄像头实时预览方案的完整流程。从插件下载安装到摄像头连接配置,再到浏览器兼容性解决方案,帮助开发者和IT人员高效实现网页端监控功能。特别针对预览功能调优和多摄像头管理等高级应用提供了实用技巧。

weixin_29266679的博客 344

Linux僵死(尸)进程(详解)||概念||产生条件||解决方法(wait(waitpid)、信号、两次fork)

本文详细介绍了进程结束的处理机制、僵尸进程以及僵尸进程的四(三)种处理方法,同时涉及到孤儿进程的概念和处理机制。

李志杰的博客 3558

【Matlab/simulink/carsim】再生制动

【Matlab/simulink/carsim】再生制动

weixin_62311849的博客 618

Linux下什么是进程等待?为什么进程需要等待?

当一个进程创建了新的进程时,父进程(原进程)往往需要读取子进程(新进程)的运行结果。如果子进程先于父进程退出,而父进程不能及时读取子进程的退出状态的话,子进程便会一直存在,此时子进程便会变成僵尸状态。久而久之,便会造成内存泄漏! 那么为了避免内存泄漏,我们可以通过kill指令销毁掉父进程,那么僵尸进程便也会随之销毁。但是这样做只是治标不治本,我们应该从根源上来处理,那就是应该避免产生僵尸进程,即...

ty6693的博客 1268

【SystemVerilog项目实践】4.AHB-SRAMC(验证计划)

验证计划

托马斯的博客 3659

【异常】解决JDK21升级中的SecurityException: JCE无法验证BC提供者问题

本文详细解析了JDK21升级中常见的SecurityException: JCE无法验证BC提供者问题的根源与解决方案。核心在于JDK21强化了JCE安全验证,导致签名不合规的老版本Bouncy Castle库被拒绝。文章提供了通过Maven依赖树精准定位、排除旧版本并引入兼容的bcprov-jdk18on新库的完整步骤,以及清理缓存等实战技巧,帮助开发者彻底解决此兼容性问题。

weixin_29197051的博客 487

Linux实现孤儿进程和僵尸进程

Linux实现孤儿进程和僵尸进程 孤儿进程: 子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束。当一个父进程由于正常完成工作而退出或由于其他情况被终止,它的一个或多个子进程却还在运行,那么那些子进程将成为孤儿进程。为避免孤儿进程退出时无法释放所占用的资源而僵死,进程号为1的init进程将会接受这些孤儿进程,这一过...

a_hang_szz的博客 661

蓝牙Channel Sounding为真正的距离感知赋能

1:工作原理蓝牙™信道探测使用两种经过验证的测距方法——PBR(PBR)和RTT(RTT),实现蓝牙设备之间的安全精密测距。

weixin_43720159的博客 805

linux僵尸进程产生的原因以及如何避免产生僵尸进程

给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。         但通常情况下,我

LXJSLK的专栏 9193

Windows 11 上 WSL2 安装 Ubuntu 22.04 保姆级教程(含桌面环境配置)

本文提供在Windows 11上通过WSL2安装Ubuntu 22.04并配置桌面环境的详细教程。涵盖WSL2核心安装、性能调优、Xfce桌面环境部署以及开发环境集成,帮助开发者构建高效、无缝的Windows-Linux混合开发工作站。

Jerry的专栏 633

lammps案例:reaxff势模拟Fe(OH)3高温反应过程

所有的reaxff模拟,如:氧化、腐蚀、热裂解、燃烧等模拟,均可采用此模拟框架,只需要更换data文件、势文件,代码中的control文件可以不写,电荷平衡文件也可以用reaxff替换。该案例主要模拟Fe(OH)3在高温下的反应过程,主要代码来自lammps自带的案例。lammps自带案例没有产物输出,故在此基础上稍加修改,增加了产物输出命令。如果需要系统学习反应势的模拟,可考虑报名培训课程。本文分享一个reaxff反应势的案例。in文件代码已经注释,以供参考。大家好,我是小马老师。

lammps_jiayou的博客 1965

Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?(1)

最全的Linux教程,Linux从入门到精通第一份《Linux从入门到精通》466页内容简介====本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。

393

基于YOLOv8的人群聚集检测系统:含UI界面设计与数据集应用

人群聚集检测是智慧城市、公共安全和疫情防控等领域的重要任务。本文结合YOLOv8最新目标检测技术,设计并实现了一个高效的人群聚集检测系统,支持可视化UI界面操作,方便用户实时检测和分析视频中的人群密度。本文详细介绍了系统设计思路、使用的数据集、YOLOv8模型训练和推理过程,以及UI界面的开发与整合。最后通过实验验证了该系统的实用性和性能表现。更灵活的模型架构强化的特征融合网络(PAN++)自动混合精度训练支持支持多任务学习(目标检测+分类+分割)预训练模型丰富,适应多种应用场景。

YOLO 432

Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?(2)

/下面5行模拟父进程的任务被加载好了,方便父进程等待子进程时被执行。

514

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

【Linux】进程概念——父子进程、僵尸进程和孤儿进程

透彻了解父子进程、僵尸进程、孤儿进程

Dooo_yh的博客 1万+

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Linux系统 —— 进程控制系列 - 进程的等待:wait 与 waitpid

目录1. 进程的等待1.1 为什么需要等待2. 进程等待的方法1. wait2. waitpid3. 获取子进程status4. 阻塞与非阻塞等待 未完待续~

hedhjd的博客 1935

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

【Linux】僵尸与孤儿 && 进程等待

【Linux】僵尸与孤儿 && 进程等待 一,僵尸进程    1,僵尸进程    2,僵尸进程的危害   二,孤儿进程    1,孤儿进程   三,进程等待    1,进程等待的必要性    2,wait 方法    3,waitpid 方法    4,回收小结

m0_71676870的博客 8128

【Linux探索学习】第十八弹——进程等待:深入解析操作系统中的进程等待机制

【Linux探索学习】进程等待:探索子进程退出的最后一道机制,僵尸进程如何避免,相信会对大家有所帮助!!!

2301_80220607的博客 3536

【Linux系统编程】第二十三弹---理解进程等待:为何、何时以及如何等待

进程等待必要性,进程等待的方法,获取子进程status,阻塞等待,非阻塞等待~~~

小林熬夜学习编程的博客 1554

Linux 进程等待

当waitpid函数以 waitpid(-1,NULL,0) 进行传参,效果跟wait是一样的,都是对任意一个进程等待。僵尸进程 是一个比较麻烦的问题,如果不对其做出处理,僵尸进程 就会越来越多,导致 内存泄漏 和 标识符 占用问题。前面我们写的都是阻塞等待的代码,但是都是我们自己手动判断结果,此处还有两个宏,下面使用宏来演示阻塞等待的代码。WIFEXITED(status) 判断进程退出情况,当宏为真时,表示进程正常退出。,OS 无法释放对应的 内核数据结构+代码和数据,出现 僵尸进程。

2401_83603768的博客 1525

初识Linux · 进程等待

通过上文的学习,我们了解了进程终止,知道终止是在干什么,终止的三种情况,以及有了退出码,错误码的概念,对于错误码,我们知道不同的人对于错误码有自己的一套体系,对于退出码,我们知道可以使用echo $?来查看,并且知道了如果终止进程。那么本文,我们来学习进程等待,我们从三个方面来看,进程等待是什么?为什么要等待?等待是在做什么?从以上几个方面,相信同学对于Linux中的进程等待有更深层次的理解。

更新各种计算机技能~ 2446

[Linux 进程控制(一)] 进程等待

进程等待

Ljy_cx_21_4_3的博客 2317

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

本篇文章着重讲解了进程等待的必要性以及进程等待的系统调用:waitpid函数.以及详情分析了waitpid函数的三个参数的不同用法

杭电码农-NEO的博客 1427

【Linux】 进程控制(二):进程等待(wait/waitpid) 与 子进程获取(status)

在Linux进程管理中,子进程退出后若父进程未及时回收其资源,会产生僵尸进程并引发内存泄漏问题,而`wait`与`waitpid`作为核心的进程等待系统调用,不仅能解决僵尸进程问题,还能让父进程获取子进程的退出状态;其中`wait`仅支持阻塞等待任意子进程,`waitpid`则扩展了指定子进程等待、非阻塞等待等能力,本文将详细讲解进程等待的必要性、两种等待方法的使用及核心原理,帮助理解Linux进程资源回收的底层逻辑。

qq_2778465975的博客 7289

【Linux指南】进程控制系列(三)进程等待 ——wait waitpid 与僵尸进程防治

进程等待是解决僵尸进程问题的关键机制,通过wait和waitpid函数,父进程可以主动回收子进程的内核资源并获取其退出状态。本文首先分析了僵尸进程的危害及其成因,通过代码演示了不进行进程等待时子进程残留的问题。随后详细讲解了wait函数的使用方法,包括阻塞等待、status参数解析以及相关宏的应用。文章还通过示例代码展示了如何区分正常退出和信号终止的场景,并解析进程退出信息的存储方式。这些知识为后续学习更灵活的waitpid函数奠定了基础,帮助开发者彻底解决僵尸进程问题。

倔强的石头的博客 1万+

linux进程控制——进程等待——wait、waitpid

linux进程控制——等待

strive_mianyang的博客 2260
上一篇: 算法沉淀 —— 动态规划(子序列问题(上))
下一篇: 进程并发究竟是如何进行进程切换的?Linux内核原理解析
小白debug~
小白debug~ 新星创作者: C/C++技术领域 新星创作者: C/C++技术领域
博客等级 码龄4年 3510粉丝 128原创
评论 96
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白debug~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值