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

这篇具有很好参考价值的文章主要介绍了Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、进程等待的概念

 进程等待通常是指:父进程通过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;
}

【运行结果】:
Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?,Linux经典收录,linux,网络,运维,进程等待,内存泄漏

 我们观察左边监视脚本发现,子进程在执行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)

Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?,Linux经典收录,linux,网络,运维,进程等待,内存泄漏
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;    
}  

【运行结果】:
Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?,Linux经典收录,linux,网络,运维,进程等待,内存泄漏
 我们发现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]();
    }
}

运行结果:
Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?,Linux经典收录,linux,网络,运维,进程等待,内存泄漏文章来源地址https://www.toymoban.com/news/detail-853195.html

到了这里,关于Linux:进程等待究竟是什么?如何解决子进程僵尸所带来的内存泄漏问题?的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

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

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:Linux从入门到精通⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你学更多操作系统知识   🔝🔝 ) 控制一个进程包括如何创建它,如何 终止它,并且如何回收它的资源! 本章重点: 本篇文章着重讲解进程等待的必要性 ,以及

    2024年02月05日
    浏览(31)
  • 『Linux从入门到精通』第 ⑭ 期 - 僵尸进程?孤儿进程?为什么他有如此惨烈的身世...

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 Linux从入门到精通 ,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

    2024年02月16日
    浏览(56)
  • 『Linux从入门到精通』第 ⑰ 期 - 那年我手执『wait』桃木剑,轻松解决僵尸进程~

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 Linux从入门到精通 ,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

    2024年02月16日
    浏览(38)
  • 【Linux】探索Linux进程状态 | 僵尸进程 | 孤儿进程

    最近,我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念,而且内容风趣幽默。我觉得它对大家可能会有所帮助,所以我在此分享。点击这里跳转到网站。 🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:Linux入门

    2024年02月05日
    浏览(34)
  • 僵尸进程?孤儿进程?为什么他有如此惨烈的身世...

    🌸作者简介: 花想云 ,在读本科生一枚,C/C++领域新星创作者,新星计划导师,阿里云专家博主,CSDN内容合伙人…致力于 C/C++、Linux 学习。 🌸 专栏简介:本文收录于 Linux从入门到精通 ,本专栏主要内容为本专栏主要内容为Linux的系统性学习,专为小白打造的文章专栏。

    2024年02月05日
    浏览(36)
  • 探索Linux下进程状态 | 僵尸进程 | 孤儿进程

    任何进程在运行时都会有自己的状态 下面的状态在kernel源代码里定义: 常见的几种状态: R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。 S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断

    2024年04月17日
    浏览(31)
  • 【Linux】详解进程状态之僵尸进程——孤儿进程

    目录 🌞专栏导读 🌛什么是进程 ⭐什么是PCB?  🌛查看进程  🌛如何通过系统调用查看进程PID 🌛fork 🌞认识进程状态 🌛查看进程状态  🌛R状态  ⭐例如: 🌛S状态  🌛D状态  🌛T状态 🌛t状态: 🌛X状态  🌛Z状态 ⭐僵尸进程 ⭐僵尸进程的危害  🌛孤儿进程 🌟作

    2024年02月13日
    浏览(60)
  • Linux—进程状态、僵尸进程、孤独进程、优先级

    📘北尘_ :个人主页 🌎个人专栏 :《Linux操作系统》《经典算法试题 》《C++》 《数据结构与算法》 ☀️走在路上,不忘来时的初心 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)

    2024年02月05日
    浏览(38)
  • 【Linux深入剖析】进程状态---进程僵尸与孤儿

    📙 作者简介 :RO-BERRY 📗 学习方向:致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 📒 日后方向 : 偏向于CPP开发以及大数据方向,欢迎各位关注,谢谢各位的支持 进程 = taskk_stuct + 可执行程序 进程不是一直在运行的 进程放在cpu上也不会一直运行的 它可能在等待某

    2024年03月18日
    浏览(46)
  • 【Linux取经路】探索进程状态之僵尸进程 | 孤儿进程

    进程状态是指在操作系统中,一个进程所处的不同运行状态,进程状态就决定了该进程接下来要执行什么任务。常见的进程状态有以下几种: 新建状态 :进程被创建但还没有被操作系统接受和分配资源。 就绪状态 :进程已经获得了所需的资源,并等待被调度执行。 运行状

    2024年02月12日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包