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

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

  • 一、进程等待的概念
  • 二、进程等待存在的意义
  • 三、如何进行进程等待
    • 3.1 wait()是实现进程等待
      • 1、wait()原型
      • 2. 验证wait()能回收僵尸子进程的空间
    • 3.2 waitpid()实现进程等待
      • 1、系统调用接口waitpid()原型
  • 四、获取子进程status实现机制
  • 五、阻塞等待和非阻塞等待
    • 5.1 阻塞等待
    • 5.2 非阻塞等待(非阻塞 + 轮询方案)
  • 六、非阻塞轮询方案示例演示

一、进程等待的概念

 进程等待通常是指:父进程通过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){//childworker();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){//childint cnt = 5;while(cnt){worker(cnt--);sleep(1);}exit(3);}//parentwhile(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]();}
}

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

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

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

相关文章

560.和为K的子数组

560.和为K的子数组 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xf…

Win10 下 git error unable to create file Invalid argument 踩坑实录

原始解决方案参看&#xff1a;https://stackoverflow.com/questions/26097568/git-pull-error-unable-to-create-file-invalid-argument 本问题解决于 2024-02-18&#xff0c;使用 git 版本 2.28.0.windows.1 解决方案 看 Git 抛出的出错的具体信息&#xff0c;比如如下都来自…

c51 单片机如何控制小灯闪烁?

目录 硬件电路设计 软件编程 烧录程序 测试 调整和优化 C51单片机是一种经典的8位微控制器&#xff0c;广泛应用于各种嵌入式系统和智能控制项目中。 C51单片机控制小灯闪烁主要涉及到硬件电路设计和软件编程两个方面。下面是一个基本的步骤说明&#xff1a; 硬件电路设计…

铸铁平台合理布局的重要性

铸铁平台合理布局的重要性是为了确保工作环境的安全和效率。以下是一些重要的原因&#xff1a; 安全性&#xff1a;合理布局可以最大限度地减少工作场所的事故和伤害。通过将设备和材料放置在正确的位置&#xff0c;可以降低工作人员被危险物体击中或跌倒的风险。此外&#xff…

【瑞萨RA6M3】1. 基于 vscode 搭建开发环境

基于 vscode 搭建开发环境 1. 准备2. 安装2.1. 安装瑞萨软件包2.2. 安装编译器2.3. 安装 cmake2.4. 安装 openocd2.5. 安装 ninja2.6. 安装 make 3. 生成初始代码4. 修改 cmake 脚本5. 调试准备6. 仿真 1. 准备 需要瑞萨仓库中的两个软件&#xff1a; MDK_Device_Packs.zipse…

Android 代码自定义drawble文件实现View圆角背景

简介 相信大多数Android开发都会遇到一个场景&#xff0c;给TextView或Button添加背景颜色&#xff0c;修改圆角&#xff0c;描边等需求。一看到这样的实现效果&#xff0c;自然就是创建drawble文件&#xff0c;设置相关属性shap&#xff0c;color&#xff0c;radius等。然后将…

基于单片机电流变送器系统仿真设计

**单片机设计介绍&#xff0c;基于单片机电流变送器系统仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机电流变送器系统的仿真设计&#xff0c;主要目标是利用仿真技术&#xff0c;模拟单片机与电流变送器之间…

二分答案 蓝桥杯 2022 省A 青蛙过河

有些地方需要解释&#xff1a; 1.从学校到家和从家到学校&#xff0c;跳跃都是一样的&#xff0c;直接看作2*x次过河就可以。 2.对于一个跳跃能力 y&#xff0c;青蛙能跳过河 2x 次&#xff0c;当且仅当对于每个长度为 y 的区间&#xff0c;这个区间内 h 的和都大于等于…

hololens 2 投屏 报错

使用Microsoft HoloLens投屏时&#xff0c;ip地址填对了&#xff0c;但是仍然报错&#xff0c;说hololens 2没有打开&#xff0c; 首先检查 开发人员选项 都打开&#xff0c;设备门户也打开 然后检查系统–体验共享&#xff0c;把共享都打开就可以了

计算机网络—HTTP协议:深入解析与应用实践

​ &#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;ヒステリックナイトガール 1:03━━━━━━️&#x1f49f;──────── 5:06 &#x1f504; ◀️ ⏸ ▶️ ☰…

java算法day45 | 动态规划part07 ● 70. 爬楼梯 (进阶) ● 322. 零钱兑换 ● 279.完全平方数

70. 爬楼梯 &#xff08;进阶&#xff09; 题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整数。 输入描述&#xff1a;输入…

java对象是怎么在jvm中new出来的

java对象是怎么在jvm中new出来的 查看java对象字段属性在内存中的值 java 对象 创建 流程 附上java源码 public class MiDept {private int innerFiled999;public MiDept() {System.out.println("new MiDept--------------");}public String show(int data) {Sy…

[Win10] VMware Workstation Pro 17.5.1 Build 23298084 Win64安装教程

VMware Workstation Pro 17.5.1 Build 23298084 Win64安装教程 下载 https://download.csdn.net/download/u012621175/89088925 安装 激活 备注 如果激活不成功可以私信获取私钥

android APP monkey 测试

monkey 测试 一、电脑ADB安装及使用详解1、什么是 Monkey 测试2、什么是ADB3、ADB的作用4、安装前提条件5、ADB下载6、ADB安装与配置 二、连接安卓手机检查是否连接上安卓手机windows端安装ADB驱动 三、 monkey测试操作指令演示指令APP包名查看方式测试效果 一、电脑ADB安装及使…

画图理解JVM相关内容

文章目录 1. JVM视角下&#xff0c;内存划分2. 类内存分布硬核详解1. 获取堆内存参数2. 扫描堆内存&#xff0c;定位实例3. 查看实例所在地址的数据4. 找到实例所指向的类信息的地址5. 查看class信息6. 结论 3. Java的对象创建流程4. 垃圾判别算法4.1 引用计数法4.2 可达性分析…

ubuntu16如何使用高版本cmake

1.引言 最近在尝试ubuntu16.04下编译开源项目vsome&#xff0c;发现使用apt命令默认安装cmake的的版本太低。如下 最终得知&#xff0c;ubuntu16默认安装确实只能到3.5.1。解决办法只能是源码安装更高版本。 2.源码下载3.20 //定位到opt目录 cd /opt 下载 wget https://cmak…

Spring Boot 接入 Redis

Spring Boot 接入 Redis 简介 Redis 是一种访问速度非常快的内存数据结构存储&#xff0c;用作数据库、缓存、消息代理和流引擎。提供 strings、hashes、lists、sets 等数据结构。可以解决会话缓存、消息队列、分布式锁、定期将数据集存储到硬盘等功能。 通过 Redis 设计实现…

具身智能机器人实现新里程碑!新型3D世界模型问世

随着人工智能技术的不断进步&#xff0c;视觉-语言-动作&#xff08;VLA&#xff09;模型在机器人控制、自动驾驶、智能助手等领域展现出了广阔的应用前景。这类模型能够将视觉、语言、动作等多模态信息进行融合&#xff0c;实现从感知到决策的端到端学习。然而&#xff0c;现有…

基于SpringBoot和Vue的校园周边美食探索以及分享系统

今天要和大家聊的是基于SpringBoot和Vue的校园周边美食探索以及分享系统 &#xff01;&#xff01;&#xff01; 有需要的小伙伴可以通过文章末尾名片咨询我哦&#xff01;&#xff01;&#xff01; &#x1f495;&#x1f495;作者&#xff1a;李同学 &#x1f495;&#x1f…

Linux目录结构知识

一、认识Linux目录 1) Linux目录结构知识 1&#xff09; win: 目录顶点是盘符 C/D/E 。所有的目录结构都在不同的盘符下面&#xff0c;不同的盘之间不能沟通的。 2&#xff09; Linux: 目录顶点是 / &#xff0c;称为根。所有的目录结构都在根下面&#xff0c;他的目录之间都…