【Linux庖丁解牛】——进程等待!

1. 进程退出场景

进程退出一般有三种场景:

。代码运行完毕,结果正确

。代码运行完毕,结果错误【比如,我们要对某个文件进行写入,但写入的文件路径出错,代码运行完毕,可是结果出错】

。代码异常终止【这种情况的原因有很多种,我们在写C/C++代码时肯定遇到过】

一般进程的退出结果是交给父进程的,因为父进程创建子进程是为了让子进程帮父进程完成某种任务,所以父进程肯定需要拿到子进程的退出结果。

那父进程是如何拿到子进程的退出结果呢?我们下面慢慢说。

2. 进程常见退出方法

2.1 从main函数返回

我们以往在写C/C++程序时,通常会在main函数的结尾返回0,但至于这个返回值的作用是什么,我们不知道,并且我们也不关心!但是,我们现在有必要知道了:

main函数的返回值通常表明你程序的执行情况!

。return 0;表明代码运行完毕,结果正确

。return 非0;表面代码运行完毕,结果错误【不同的值表明不同的出错原因】

下面,我们来写一段代码来见一见main函数的不同返回值:

以上代码,我们打开一个文件进行读的操作,但是在该目录下,我并没有这个文件,所以肯定是打开失败的。所以函数会返回1。 

但是,我们怎么知道这个进程的退出结果呢?

这里补充一个概念:退出码【退出码(退出状态)可以告诉我们最后⼀次执行的命令的状态。在命令结束以后,我们可以知道命令 是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码0 时表示执行成功,没有问题。 代码1 或0 以外的任何代码都被视为不成功

每个进程退出后进入僵尸状态,其退出码都会写入自己的task_struct中【exit_code】。

我们只要在命令行上敲下以下指令就可以查看到最近一个进程退出时的退出码:

其实,在C标准中,规定了每个退出码都有自己对应的错误信息,strerror就可以把退出码转化成对应的错误信息!

下面我们就来看一下C标准规定了多少错误信息:

可以看到从0开始一共有134个退出码!

最后,如果进程异常终止,退出码就不重要了,就好比你考试作弊被发现了,你的结果也就不重要了。

2.2 exit函数

其实除了从main中用return返回结束进程,我们还可以用exit函数结束进程。

并且在程序的任何地方调用该函数,进程都会直接退出并返回退出码给父进程!

 只打印“fun begin”验证了这一点!

2.3 _exit函数

如果exit退出的时候,进程退出的时候,会进行缓冲区刷新。

如果_exit退出的时候,进程退出的时候,会进行缓冲区刷新。

以上这一点本身并不重要,了解即可。

我们知道库函数和系统调用是上下级关系,即exit的底层还是_exit,但为什么_exit却不会刷新缓冲区呢?

因为缓冲区是库级别的【C语言提供的】,也就是库缓冲区,这里只点出来,更多细节,后面再说。

3. 进程等待

3.1 为什么要有进程等待

• 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

• 另外,进程⼀旦变成僵尸状态,那就刀枪不入,“杀⼈不眨眼”的kill-9也无能为力,因为谁也没有办法杀死⼀个已经死去的进程。

• 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是 不对,或者是否正常退出。

• 父进程通过进程等待的方式,回收子进程资源获取子进程退出信息

3.2 进程等待是什么

要搞清楚这个问题,我们先来见一见进程等待先简单使用一下!

a. wait方法

wait其中的参数wstatus其实是输出型参数,这点我会在waitpid处再详细说明。 

这里我们先使用wait方法解决僵尸进程的问题:

以下代码我先创建了一个子进程,在子进程运行3秒后退出,而父进程先休眠5秒后,再使用wait等待子进程,最后让父进程运行10秒后退出。这样做的原因是方便我们检测时,先看到子进程的僵尸状态,父进程等待成功后,我们可以看到子进程从僵尸状态被回收的过程

  1#include <stdio.h>2 #include <sys/types.h>3 #include <unistd.h>4 #include <wait.h>5 #include <stdlib.h>6 7 int main()8 {9     pid_t id=fork();//创建子进程10     if(id==0)//子进程11     {12         int cnt=3;13         while(cnt--)14         {15             printf("子进程pid->%d\n",getpid());16             sleep(1);17         }18         exit(0);19     }20     //父进程等待子进程21     sleep(5);//父进程先等待5秒,可以看到子进程的僵尸状态22     int exit_code=0;                                                                                                                            23     pid_t rid=wait(&exit_code);24     if(rid>0) printf("等待子进程成功!rid->%d\n",rid);25     sleep(10);26     return 0;27 }

运行结果:

下面是检测写的简单shell脚本: 

while :; do ps axj | head -1 && ps axj | grep code; sleep 1;done

检测结果:

结果也确实和我们预期的一样!

这里还有两个细节 ,如果父进程在等待子进程的中,子进程没有退出,那么父进程就在wait处等待【可以理解为scanf】!父进程wait时会等待它任意一个子进程退出并获得其pid。

b. waitpid方法

waitpid有3个参数,其中第一个参数有以下四种填写方法,目前我们先不管<-1和=0,-1表明等待任意子进程退出,>0则表明等待指定子进程退出。 

 第二个参数为输出型参数,我们之前便说过,父进程创建子进程是为了让子进程帮父进程完成某些任务。所以父进程应该有能力知道【不管它想不想知道】子进程的退出结果【即退出信息】,而第二个指针参数就是为了做到这一点而存在的,而wstatus获得的正好就是子进程的退出码!

第三个参数可以控制阻塞【我们先暂时不管】

下面我们来看看waitpid到底能不能获得子进程的退出码:

按照我们的预期,exit_code应该是1!

但是结果似乎和我们预期的不太一样!

当实际情况和预期不一样时,那一定是我们的认知出了问题!

这里就直说了,整形的exit_code接收退出码时,只用了32个比特位中的8~15这8个比特位来记录退出码【我们可以通过位图来理解】!而16~32这16个比特位没有被使用,至于1~8这8个比特位在子进程正常退出时都默认是0,而只有在子进程异常退出时,这八个比特位才会被使用,这和信号有关,细节我们以后再说!

既然如此,那我们拿到记录退出码的那八个比特位就可以看到正确的退出码了!

修改这一句代码即可!

if(rid>0) printf("等待子进程成功!rid->%d,exit_code->%d\n",rid,(exit_code>>8)&0xFF);

还有一个问题,我们为何不定义一个全局变量来记录子进程的退出码呢,在子进程退出时,修改这个变量!原因也很简单,你子进程修改它,发生写时拷贝,父进程和子进程看到的全局变量根本就不是同一个!

最后,如果进程异常退出,退出码就没有意义了,但是,我们还是可以通过waitpid的输出型参数拿到退出信号。

稍微修改一下代码,做一个实验:

手动杀掉进程!父进程得到相应的退出信号!

 我们可以通过指令kill -l来查找所有的信号:

这里只是简单的见了见信号,具体细节还没有说!

 3.3 进程等待怎么办到

现在,我们理解了什么是进程等待,为什么要有进程等待,但是我们还是不知道,父进程通过进程等待是如何拿到子进程的退出码和退出信号的!

要明白这个问题,我们首先需要知道这些信息存放到哪里!我们都知道一个进程退出后,它的进程虚拟空间 | 页表 | 代码和数据 都会被系统回收和释放!但是,他的PCB【task_struct】却会被保存起来,进入僵尸状态!所以,子进程的退出码和退出信号势必在它的PCB中!所以,进程退出时,它的退出信息会被记录进它的PCB中的某些变量中!

事实也是如此:在源码中记录信息的变量如下所示

所以,父进程通过系统调用的方式让系统帮它去子进程的PCB中拿到子进程的退出信息返回给父进程。至此,我们也就明白了,进程等待的原理了。而且,我们现在也更加明白了,为什么进程要有僵尸状态【1. 为了方便父进程回收子进程 2. 为了父进程方便拿到子进程的退出信息

3.4 阻塞与非阻塞等待

上面我们提到过waitpid的第三个参数使用来控制阻塞和非阻塞调用的,在默认情况下【不填参数】,waitpid进行的是阻塞调用,什么是阻塞调用呢?

阻塞调用:父进程在waitpid处等待子进程退出,如果子进程一直不退出,那父进程则会一直等待,并且只做这一件事情!

如果,我们将参数填写为WNOHANG,则为非阻塞调用,非阻塞调用:父进程每隔一段时间去看一看子进程是否退出,如果退出,则返回值大于0,如果调用结束,子进程没有退出,则返回0,如果返回值小于0,则调用失败。在子进程未退出之前,父进程可以利用其他时间做一些其他的事情,所以非阻塞调用一般效率更高【并不是指子进程退出的效率高】。

如何做到非阻塞等待,其实也很简单,我仅们需要做一次非阻塞轮询,说白了,就是循环!

下面一个例子就很好的体现了非阻塞调用下父进程利用等待时间完成其他的任务。 

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>
typedef void (*handler_t)(); // 函数指针类型 
std::vector<handler_t> handlers; // 函数指针数组 
void fun_one() {printf("这是⼀个临时任务1\n");
}
void fun_two() {printf("这是⼀个临时任务2\n");
}
void Load() {handlers.push_back(fun_one);handlers.push_back(fun_two);
}
void handler() {if (handlers.empty())Load();for (auto iter : handlers)iter();
}
int main() {pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { // childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);}else {int status = 0;pid_t ret = 0;do {ret = waitpid(-1, &status, WNOHANG); // ⾮阻塞式等待 if (ret == 0) {printf("child is running\n");}handler();} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}

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

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

相关文章

鸿蒙OSUniApp 制作简洁高效的标签云组件#三方框架 #Uniapp

UniApp 制作简洁高效的标签云组件 在移动端应用中&#xff0c;标签云&#xff08;Tag Cloud&#xff09;是一种常见的UI组件&#xff0c;它以视觉化的方式展示关键词或分类&#xff0c;帮助用户快速浏览和选择感兴趣的内容。本文将详细讲解如何在UniApp框架中实现一个简洁高效的…

ubuntu14.04/16.06 安装vscode(实测可以用)

地址&#xff1a;https://code.visualstudio.com/updates/v1_38 选择deb 这个版本还支持ubuntu14.04和16.06 sudo dpkg -i code_1.38.1-1568209190_amd64.deb sudo apt-get install -f安装成功&#xff0c;正常使用

WebRTC技术EasyRTC音视频实时通话驱动智能摄像头迈向多场景应用

一、方案背景​ 在物联网蓬勃发展的当下&#xff0c;智能摄像头广泛应用于安防、家居、工业等领域。但传统智能摄像头存在视频传输延迟高、设备兼容性差、网络波动时传输不稳定等问题&#xff0c;难以满足用户对实时流畅交互视频的需求。EasyRTC凭借低延迟、高可靠、跨平台特性…

Java EE进阶1:导读

1.发展历程 2.学习内容 前⾯的课程中,学习的是Java基础,JavaEE主要学习Java的应用,也就是学习Java在企业中是如何应用的 Java更多场景是业务开发,更狭义点可以理解为web开发.所以咱们的学习也是围绕着如何使用Java来做web开发 2.1 什么是Web开发&#xff1f; web&#xff08…

APPtrace 智能参数系统:重构 App 用户增长与运营逻辑

一、免填时代&#xff1a;APPtrace 颠覆传统参数传递模式 传统 App 依赖「邀请码 / 手动绑定」实现用户关联&#xff0c;流程繁琐导致 20%-30% 的用户流失。APPtrace 通过 **「链接参数自动传递 安装后智能识别」** 技术&#xff0c;让用户在无感知状态下完成关系绑定、场景还…

bisheng系列(一)- 本地部署(Docker)

目录 一、导读 二、说明 1、镜像说明 2、本节内容 三、docker部署 1、克隆代码 2、运行镜像 3、可能的错误信息 四、页面测试 1、注册用户 2、登陆成功 3、添加模型 一、导读 环境&#xff1a;Ubuntu 24.04、Windows 11、WSL 2、Python 3.10 、bisheng 1.1.1 背景…

docker介绍与常用命令汇总

docker简介 docker是什么&#xff1f; Docker 是一个开源的应用容器引擎&#xff0c;它可以让开发者将应用与运行环境打包成一个标准的、可移植的容器&#xff08;Container&#xff09;&#xff0c;在任何地方都可以快速部署和运行&#xff0c;无需关心底层环境是否一致。 …

Android 中拖拽从一个组件到另外一个组件的写法(跨容器拖拽)

在 Android 中&#xff0c;拖拽一个图片&#xff08;例如 ImageView&#xff09;到另一个组件&#xff08;如 LinearLayout、FrameLayout 等容器&#xff09;涉及以下步骤&#xff1a; 准备工作 源组件&#xff1a;你从哪里开始拖动&#xff08;如 ImageView&#xff09;。 目…

火绒互联网安全软件:自主引擎,精准防御

在数字时代&#xff0c;网络安全是每一个用户都必须重视的问题。无论是个人用户还是企业用户&#xff0c;都需要一款高效、可靠的反病毒软件来保护设备免受恶意软件的侵害。今天&#xff0c;我们要介绍的 火绒互联网安全软件&#xff0c;就是这样一款由资深工程师主导研发并拥有…

使用亮数据代理IP+Python爬虫批量爬取招聘信息训练面试类AI智能体(手把手教学版)

文章目录 一、为什么要用代理IP&#xff1f;(重要&#xff01;&#xff01;&#xff01;)二、环境准备&#xff08;三件套走起&#xff09;2.1 安装必备库&#xff08;pip大法好&#xff09;2.2 获取亮数据代理&#xff08;官网注册送试用&#xff09; 三、编写爬虫代码&#x…

Android屏幕采集编码打包推送RTMP技术详解:从开发到优化与应用

在现代移动应用中&#xff0c;屏幕采集已成为一个广泛使用的功能&#xff0c;尤其是在实时直播、视频会议、远程教育、游戏录制等场景中&#xff0c;屏幕采集技术的需求不断增长。Android 平台为开发者提供了 MediaProjection API&#xff0c;这使得屏幕录制和采集变得更加简单…

互联网大厂Java求职面试:Spring AI与大模型交互的高级模式与自定义开发

互联网大厂Java求职面试&#xff1a;Spring AI与大模型交互的高级模式与自定义开发 在当今技术领域&#xff0c;随着AI和大模型技术的广泛应用&#xff0c;如何在复杂的系统架构中高效地集成这些技术成为了各大互联网公司关注的重点。本文将通过一场模拟的面试对话&#xff0c…

MySQL 8.0 OCP 1Z0-908 161-170题

Q161.Examine this command, which executes successfully: cluster.addInstance ( ‘:’,{recoveryMethod: ‘clone’ 1}) Which three statements are true? (Choose three.) A)The account used to perform this recovery needs the BACKUP_ ADMIN privilege. B)A target i…

蓝桥杯1447 砝码称重

问题描述 你有一架天平和 N 个砝码&#xff0c;这 N 个砝码重量依次是 W1,W2,⋅⋅⋅,WN​。 请你计算一共可以称出多少种不同的重量&#xff1f; 注意砝码可以放在天平两边。 输入格式 输入的第一行包含一个整数 N。 第二行包含 N 个整数&#xff1a;W1,W2,W3,⋅⋅⋅,WN​…

金融量化智能体,如何开发一个有效的策略?

原创内容第887篇&#xff0c;专注智能量化投资、个人成长与财富自由。 本周重构了网站&#xff0c;升级了最新的回测引擎&#xff0c;以及升级了论坛。 策略年化210%&#xff0c;夏普比3.47&#xff0c;系统源代码及策略均可下载 年化37.5%&#xff0c;回撤控制在16.8%&…

JavaScript 性能优化:调优策略与工具使用

引言 在当今的 Web 开发领域&#xff0c;性能优化已不再是锦上添花&#xff0c;而是产品成功的关键因素。据 Google 研究表明&#xff0c;页面加载时间每增加 3 秒&#xff0c;跳出率将提高 32%。而移动端用户如果页面加载超过 3 秒&#xff0c;有 53% 的用户会放弃访问。性能…

为 Jenkins添加 Windows Slave远程执行 python项目脚本

测试环境 JAVA JDK 1.7.0_13 (jdk-7u13-windows-i586.exe) Jenkins Win11 64 python项目环境 实践操作 1、新建与配置结点 【系统管理】-> 【管理结点】-> 【新建结点】, 如上&#xff0c;输入结点名称&#xff0c;勾选 【Dumb Slave】&#xff0c;点击【OK】 说明&am…

基于springboot3 VUE3 火车订票系统前后端分离项目适合新手学习的项目包含 智能客服 换乘算法

​ 博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆…

btc交易所关键需求区 XBIT反弹与上涨潜力分析​​

在加密货币市场的浪潮中&#xff0c;狗狗币&#xff08;DOGE&#xff09;近期的走势吸引了众多投资者的目光。根据XBIT分析&#xff0c;狗狗币刚刚踏入关键需求区&#xff0c;此前虽从高点大幅下跌了10%&#xff0c;但XBIT去中心化交易所平台分析师认为&#xff0c;短期内它有望…

宝塔+fastadmin:给项目添加定时任务

一、定时任务脚本编写 1. 使用 shebang 声明执行器 #!/usr/bin/env php 这是 Unix/Linux 系统中脚本文件的标准开头。表示这个脚本使用系统环境变量中的 php 来执行。2. 定义 ThinkPHP 入口路径并加载框架 define(APP_PATH, __DIR__ . /../../application/); require __DIR__…