详谈进程等待

目录

  • 前言
  • 1. 进程等待的必要性
    • 1.1 进程等待的定义
  • 2. 如何进行进程等待
    • 2.1 wait 单进程
    • 2.2 wait 多进程
    • 2.3 status && 退出情况
      • 2.3.1 status 参数构成
      • 2.3.2 简证 status 参数构成
      • 2.3.3 进程等待失败
      • 2.3.4 宏调用查看退出信息
  • 3. 进程等待的原理

前言

本篇文章继上一篇文章 进程的创建、终止 ,继续介绍关于进程控制中的进程等待,从理解进程等待的必要性,进而理解什么是进程等待,以及如何进行进程等待。


1. 进程等待的必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题(也就是 Z 状态进程),进而造成内存泄漏。 所以进行进程等待的其中一个原因就是为了读取子进程状态,解决内存泄漏问题。
  • 另外,进程一旦变成僵尸状态,不管是 ctrl + c,还是 kill -9 命令都无法杀死这个进程,只能通过进程等待,将这个进程进行回收。所以进程等待的第二个原因是僵尸进程不可被 “杀死”。
  • 子进程的出现就需要回归到创建子进程的本质:为了帮助用户完成某些任务。所以既然是完成任务,用户怎么知道子进程完成的如何了,当子进程退出了,用户又该如何得知任务办完没有?结果是什么?结果正不正确?或者中间异常中止了?所以进程等待的第三个原因是为了获取子进程任务执行的结果,也即退出情况。

僵尸进程造成的内存泄露问题是必须解决的!而至于要不要关心子进程的退出情况,则是可选项,不一定每个子进程的退出可能都要关心。

1.1 进程等待的定义

通过系统调用 wait/waitpid,来对子进程进行状态检测与回收的功能。


2. 如何进行进程等待

如何进程等待呢? ----- 调用系统调用 wait/waitpid(即等待一个进程,直到进程状态发生改变)

2.1 wait 单进程

在这里插入图片描述

wait 就代表只有父进程有子进程,并且子进程退出了,父进程就可以通过 wait 等待子进程的退出,其中的 status 参数代表子进程的退出情况,如果不关心其退出情况,设置为 NULL 即可。返回值 > 0,代表的是等待的子进程的 pid,如果返回值 < 0,等待失败。

接下来,我们先简单看看进程等待是什么样子的。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){int cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt: %d\n", getpid(), getppid(), cnt$cnt--;sleep(1);}exit(11);}else{int cnt = 10;while(cnt){printf("I am father, pid:%d, ppid:%d, cnt: %d\n", getpid(), getppid(), cn$cnt--;sleep(1);}pid_t ret = wait(NULL);if(ret == id){printf("wait success! ret: %d\n", ret);}sleep(5);}return 0;
}

在这里插入图片描述

当子进程退出后,父进程通过系统调用 wait 进行进程等待,回收了子进程,因此监控中的子进程也由原来的 Z 状态变为 X 状态(看不见了),再经过 3s 睡眠后,父进程也退出了(由 bash 进行等待回收)。

如果是多进程的进程等待呢?? 又该如何进程等待?


2.2 wait 多进程

void RunChild()    
{    int cnt = 5;    while(cnt)    {    printf("I am Child Process, pid: %d, ppid:%d\n", getpid(), getppid());    sleep(1);    cnt--;    }    
}    int main()    
{    for(int i = 0; i < N; i++)    {    pid_t id = fork();    if(id == 0)    {    RunChild();    exit(i);    }    printf("create child process: %d success\n", id); 	// 只有父进程才会执行         }    // 等待    for(int i = 0; i < N; i++)    {    pid_t id = wait(NULL);    if(id > 0){printf("wait %d success\n", id);}}sleep(3);
}

在这里插入图片描述

借助循环结构,我们顺利的创建出多进程,并且对多个子进程进行等待回收。也即当任意一个进程退出时,wait 会回收子进程。

那如果任意一个子进程都不退出呢?----- 如果父进程在等待的子进程(一个或多个)不退出时,那么父进程也不退出,父进程会在 wait 处进行阻塞等待!换言之,wait 等待时,如果子进程不退出,父进程调用 wait 不返回,处于一直等待的状态,直到子进程退出时,父进程 wait 返回。

所以阻塞状态不一定就是等待硬件资源,这里的父进程阻塞在系统调用 wait 处,也即阻塞状态,只不过等待的不是硬件资源,而是子进程(即软件资源)。


2.3 status && 退出情况

pid_t waitpid(pid_t pid, int *status, int options);   	// 其中的 status 与 wait 一样可以置为 NULL(不关心)返回值:当正常返回的时候 waitpid 返回收集到的子进程的进程 ID;如果设置了选项 WNOHANG,而调用中 waitpid 发现没有已退出的子进程可收集,则返回 0;如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在;
参数:pid:Pid = -1 : 等待任一个子进程。与 wait 功能等效。Pid > 0 : 等待指定进程status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

2.3.1 status 参数构成

  • status 是一个输出型参数,用于将子进程的退出结果带出给父进程
  • 其 int 是被当作几个部分使用的(4字节)
// 修改部分代码:else if(id == 0)                                               {                                                              ......                                                                                    exit(1);    }                else    {        ......	int status = 0;              pid_t ret = waitpid(id, &status, 0);    if(ret == id)                           {                printf("wait success! ret: %d, status: %d\n", ret, status);                                                                   }    ......   }    

在这里插入图片描述

运行结果的 status 为 256,我们之前说过 status 即子进程的退出结果,但是子进程中明明是 eixt(1),退出码是 1 啊,怎么 waitpid 返回的退出结果是 256 呢??

这就需要我们弄清楚几个问题。

  1. 子进程婆出,一共会有几种退出场景呢? ------ 代码运行完毕,结果正确或者不正确;代码异常终止
  2. 父进程等待,期望获得子进程退出的哪些信息呢? ----- 子进程代码是否异常?没有异常,结果是否正确? 即退出码 exitcode,如果结果不正确,又是因为什么呢?(不同的退出码即可表面不同的错误信息)换言之,父进程所关心的问题,就是子进程的退出情况。

正是因为父进程所关心的内容不只一点,因此 wait / waitpid 中的 status 才要被划分成多个部分,以此兼顾到父进程关心的全部信息。父进程希望通过 waitpid 等待子进程,获得子进程的退出结果(代码是否异常中止,如果不是,结果是否正确)。

我们只考虑 status 的低 16 位,其中的低7位用来表示进程的异常情况,第 8 位是 core dump 标志位(信号章节介绍),接下来的次低 8 位 用于表示进程的退出状态。

在这里插入图片描述
因此虽然子进程中 exit(1),但最终整体的 status 打印出来为 256,就是因为,代码没有异常中止,status 的低7位为0,而退出码为1,因此次低8位为 000000001,结合起来就是 256。

换言之,想要检查一个进程执行时是否发生异常,只需要检查 status 的低7位即可,如果为0,说明没有异常中止,如果异常中止了,不同的位结合起来也可以涵盖所有的异常情况(异常中止的本质就是收到了某种信号,这也是为什么 kill -l 查看信号编号时,没有所谓的 0 编号的信号请求,因为 0 代表没有异常中止);低7位为0之后,再检查次低8位的退出状态即可确定子进程的退出结果是否正确!

  • 拓展问题:status 能不能直接定义成全局变量,而不使用系统调用 waitpid 获取?
    不行,因为要保证进程独立性,status是用于存储子进程的执行结果的,无论子进程如何修改,进程独立性需要保证父进程的视角是无感的, 而如果是全局变量,那么无法做到这一点。换言之,只要是一个进程想要获取另一个进程的信息,因为进程独立性,所以这件事,进程自己无法做到,需要通过操作系统(即系统调用)来完成获取。

2.3.2 简证 status 参数构成

if(ret == id)
{//status&0x7F: status的低7位,即终止信号//(status>>8)&0xFF: status的次低8位,即退出状态printf("wait success, ret: %d, exit sig: %d, exit code: %d\n", ret, status & 0x7F, (status >> 8) & 0xFF);
}

在这里插入图片描述

因为子进程退出时 exit(1),代码执行完毕,因此退出信号为 0 表无异常中止,退出码为 1.

else if(id == 0)                                               
{                                                              ......     int a = 10;a /= 10;                                                                               ......
}      

在这里插入图片描述

除 0 错误,父进程 waitpid 等待子进程,返回的 status 中的终止信号 8,即 kill -l 信号中的 8) SIGFPE,因为代码中途异常终止了,所以就没有退出码,因此退出状态就为 0。再者,只要你愿意,当你访问野指针,运行结果的 exit sig 就会是 11,当你 kill -9 杀掉一个死循环的进程时,exit sig 就会是 9 号信息,这不仅印证了 status 参数的构成,也再一次印证了,进程异常终止,其本质就是收到了某种信号!

2.3.3 进程等待失败

关于 status 的返回值:如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在;

pid_t ret = waitpid(id + 4, &status, 0);    
if(ret == id)    {    // 不变    }    
else    
{    printf("wait failed!\n");    
} 

在这里插入图片描述

如果父进程等待的并不是自己的子进程,那么就一定会等待失败。换言之,父进程在进行等待时,只能等待自己的子进程。

2.3.4 宏调用查看退出信息

status:WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
pid_t ret = waitpid(id, &status, 0);    
if(ret == id)    
{    if(WIFEXITED(status))    {    printf("process is normal, exit_code: %d\n", WEXITSTATUS(status));    }    else    {    printf("the process terminated abnormally! \n");                                                              }    
}    
else    
{    printf("wait failed!\n");    
} 

在这里插入图片描述

有了系统提供的宏,就不再需要我们自己通过位运算来获取进程的退出情况了。


3. 进程等待的原理

子进程执行完毕时,为了保证其退出结果被上层获取,它的代码和数据是允许被释放的,只不过需要将退出信息保存在子进程的 PCB 中而已。当进程收到信号时,会写入到 pcb 中的 exit_code,进程的退出码写入到 exit_signal 中,父进程再通过系统调用 wait / waitpid 检测子进程是否退出了,如果退出了,再读取子进程的退出信息,将退出信息合并成 status 传递给上层用户。

为什么不让上层用户直接访问子进程的退出信息呢?? ----- 与之前讲述的系统管理一样,因为操作系统不信任用户,子进程的退出信息就存储在子进程的 PCB 中,而用户是无法直接越过操作系统 访问 操作系统所管理的内核数据结构对象的,操作系统不允许任何用户访问它的底层数据。


关于进程等待,本篇文章就介绍到这里,后续还会介绍非阻塞轮询,并且非阻塞轮询的同时,是如何执行其它任务的。

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

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

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

相关文章

Hive SQL

一、基本数据类型 tinyint 1byte 有符号整数 smallint 2byte 有符号整数 int 4byte 有符号整数 bigint 8byte 有符号整数 boolean 布尔类型&#xff0c;true或者false float 单精度浮点数 double 双精度浮点数 decim…

C语言07---指针进阶

指针万能拆解法 char型指针 char型指针实质上跟别的类型的指针并无本质区别&#xff0c;但由于C语言中的字符串以字符数组的方式存储&#xff0c;而数组在大多数场合又会表现为指针&#xff0c;因此字符串在绝大多数场合就表现为char型指针。 定义&#xff1a; char *p &qu…

区块链国赛第六套样题(关于运维)

任务1-2&#xff1a;区块链系统部署与运维 围绕食品安全溯源区块链平台部署与运维需求&#xff0c;进行项目相关系统、节点以及管理工具的部署工作。通过监控工具完成对网络、节点服务的监控。最终利用业务需求规范&#xff0c;完成系统日志、网络参数、节点服务等系统结构的维…

Hadoop的HA配置与实现(ZooKeeper)

目录 一、Hadoop的HA架构二、配置实现Hadoop的HA三、效果 一、Hadoop的HA架构 集群规划 112&#xff1a;NameNode1 ResourceManager1 JournalNode1 113&#xff1a;NameNode2 ResourceManager2 JournalNode2 114&#xff1a;DataNode1 NodeManager1 115&#xff1a;DataNode2 N…

linux 云主机下载 rpm 包安装 oracle java jdk21 实录(华为云 EulerOS)

本来是想通过 yum install 相关的 openjdk 版本的, 但老是提示说找不到, 也不想去配置相关的仓库了, 所以改成去 oracle 官网下载 jdk21 的 rpm 包来安装. 云主机是华为云的 EulerOS , 具体为 Huawei Cloud EulerOS 2.0 标准版 64位(公共镜像), 相对于用的比较熟 centos, 差别…

学习之在window上安装MySQL server 并连接到Navicat

一、下载 下载地址&#xff1a;https://www.mysql.com/ 二、安装 1、双击软件安装2、点击yes

云计算实训36——mysql镜像管理、同步容器和宿主机时间、在容器外执行容器内命令、容器的ip地址不稳定问题、基础镜像的制作、镜像应用

一、线上考试系统的数据虚拟化技术部署 1.部署前段服务器 步骤一&#xff1a;将资源上传到服务器 将dist.zip上传给服务器 下载unzip的包 yum -y install unzip 解压 unzip dist.zip 步骤二&#xff1a;创建基础容器在服务器上 启动服务 systemctl start docker.servic…

用 Go 语言实现常见的十大排序算法(上)

十大常见的排序算法有&#xff1a; 冒泡排序&#xff08;Bubble Sort&#xff09; 选择排序&#xff08;Selection Sort&#xff09; 插入排序&#xff08;Insertion Sort&#xff09; 希尔排序&#xff08;Shell Sort&#xff09; 归并排序&#xff08;Merge Sort&#xf…

<数据集>考场行为识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2192张 标注数量(xml文件个数)&#xff1a;2192 标注数量(txt文件个数)&#xff1a;2192 标注类别数&#xff1a;2 标注类别名称&#xff1a;[cheating, good] 序号类别名称图片数框数1cheating128214412good1067…

气膜建筑与装配式建筑的对比分析—轻空间

在现代建筑中&#xff0c;气膜建筑和装配式建筑都作为新型建筑形式受到关注。然而&#xff0c;在很多应用场景中&#xff0c;气膜建筑展现出了比装配式建筑更为明显的优势。以下将着重对比气膜建筑相较于装配式建筑的独特优势。 气膜建筑的突出优势 1. 更快的施工速度 气膜建筑…

在 Debian 上安装 IntelliJ IDEA 笔记

在 Debian&#x1f4a9; 上安装 IntelliJ IDEA &#x1f4a1; 笔记 下载安装 JDK17安装 IntelliJ IDEA Community添加桌面启动项&#xff08;快捷方式&#xff09; 参考资料 下载 两个包已经下好了&#xff0c;一个JDK17&#xff0c;一个IntelliJ IDEA Community 使用 wget ur…

微信对话开放平台接口源码分享

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 接口源码 📒⚓️ 相关链接 ⚓️📖 介绍 📖 微信对话开放平台是微信官方授权的智能对话技术平台,旨在帮助开发者及非开发者快速搭建智能对话机器人(智能客服),并轻松接入微信公众号、小程序、企业微信等微信生态中的各…

netty编程之UDP

写在前面 源码 。 UDP&#xff0c;user datagram protocol,是internet协议簇中无连接的传输协议&#xff0c;因为无连接所以相比于TCP需要维护更少的信息以及网络交互&#xff0c;所以具有更高的效率。本文看下netty是如何实现的&#xff0c;和TCP方式差别不大&#xff0c;下面…

自动化作业批改系统的实现以及代码分析

作者主页: 知孤云出岫 目录 作者主页:1. 系统需求分析1.1 功能需求1.2 性能要求 2. 系统设计2.1 模块化设计2.2 数据库设计2.3 系统接口设计 3. 具体技术实现3.1 题目解析模块3.2 答案匹配模块3.3 评分模块3.4 反馈生成模块3.5 系统集成 1. 系统需求分析 在构建一个自动化的…

【数学分析笔记】第2章第4节收敛准则(4)

2.数列极限 2.4 收敛准则 上节课举了一个例子 a N 1 1 2 p 1 3 p . . . 1 n p a_{N}1\frac{1}{2^{p}}\frac{1}{3^{p}}...\frac{1}{n^{p}} aN​12p1​3p1​...np1​ p > 1 p>1 p>1&#xff0c; { a n } \{a_{n}\} {an​}收敛 0 < p ≤ 1 0<p\le 1 0<p≤…

ET6框架(一)介绍及环境部署

文章目录 一、什么是ET框架&#xff1f;二、ET框架特色&#xff1a;三、开发环境准备&#xff1a;四、.Net Core下载安装五、安装Visual Studio六、下载Mongodb七.安装Robo 3T八、下载ET版本分支 一、什么是ET框架&#xff1f; 1.ET(客户端&#xff0c;服务器端)是一个开源的双…

《机器学习》 决策树 ID3算法

目录 一、什么是决策树&#xff1f; 1、概念 2、优缺点 3、核心 4、需要考虑的问题 二、决策树分类标准&#xff0c;ID3算法 1、什么是ID3 算法 2、ID3算法怎么用 1&#xff09;熵值计算公式 2&#xff09;用法实例 三、实操 ID3算法 1&#xff09;求出play标签的熵…

欧姆龙PLC数据 转 IEC61850项目案例

目录 1 案例说明 2 VFBOX网关工作原理 3 准备工作 4 网关采集欧姆龙PLC数据 5 用IEC61850协议转发数据 6 网关使用多个逻辑设备和逻辑节点的方法 7 案例总结 1 案例说明 设置网关采集欧姆龙PLC数据把采集的数据转成IEC61850协议转发给其他系统。 2 VFBOX网关工作原理 VFBOX…

【JUC并发编程系列】深入理解Java并发机制:从用户态到内核态的探索(一、前置知识)

文章目录 【JUC并发编程系列】深入理解Java并发机制&#xff1a;从用户态到内核态的探索&#xff08;一、前置知识&#xff09;1.用户态与内核态区别2. 线程安全同步的方式3. 传统锁有哪些缺点4. 发生CPU上下文切换的原因5. 如何避免上下文切换6. 详细总结6.1 用户态与内核态6.…

Python3.11二进制AI项目程序打包为苹果Mac App(DMG)-应用程序pyinstaller制作流程(AppleSilicon)

众所周知&#xff0c;苹果MacOs系统虽然贵为Unix内核系统&#xff0c;但由于系统不支持N卡&#xff0c;所以如果想在本地跑AI项目&#xff0c;还需要对相关的AI模块进行定制化操作&#xff0c;本次我们演示一下如何将基于Python3.11的AI项目程序打包为MacOS可以直接运行的DMG安…