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

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

    sleep(3);
}
return 0;

}


【运行结果】:  
 ![请添加图片描述](https://img-blog.csdnimg.cn/direct/a4b2982625824de89cea366885d4f68c.gif)


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


![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/b3209827c7d1488e9c157e1fbeb9a574.png)  
 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;
}


【运行结果】:  
 ![请添加图片描述](https://img-blog.csdnimg.cn/direct/bc514c9d0cd34b57a95b9cf03e45fb94.gif)  
  我们发现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++)
{
tasksi;
}
}



### 最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

### 资料预览

给大家整理的视频资料:

![](https://img-blog.csdnimg.cn/img_convert/9286cf9bc0e493d12f52718e4cbff209.png)

给大家整理的电子书资料:

  

![](https://img-blog.csdnimg.cn/img_convert/0593c9a5053f4c81020cc29e99239795.png)



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618635766)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

转存中...(img-i3mFqz0f-1715834286105)]



**如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!**

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以点击这里获取!](https://bbs.csdn.net/topics/618635766)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值