Linux进程控制(二)--进程等待(一)
前言:之前我们讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。 另外,进程一旦变成僵尸状态,那就刀枪不入,就连 kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。 但是最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。本次,我们将对上次讲的进程退出进行收尾,并开始讲解进程等待的相关知识~
目录
1.进程退出的三种方式
exit函数和_exit函数
return函数
2.进程等待
为什么要进程等待
进程等待的两种方法
wait方法(系统调用)
waitpid方法(系统调用)
status的结构
宏函数WIFEXITED和WEXITSTATUS
options参数
非阻塞轮询等待状态
为何需要通过系统调用才能获取子进程的信息
父进程如何获得子进程的退出信息
多进程的进程等待
1.进程退出的三种方式
exit函数和_exit函数
在Linux系统中, _exit
和 exit
,这两个函数都用于终止程序的执行,但存在一些差异。更加详细的内容,我们在后续学习基础IO的时候还会继续深入学习。
-
_exit
函数:- 原型:
void _exit(int status)
_exit
函数是系统调用(man手册中存在于2号手册),直接终止当前进程的执行,并返回一个表示终止状态的整数值。- 它不会做任何清理工作,例如关闭文件描述符、刷新缓冲区等,直接进入内核的终止处理流程。
_exit
函数很常用,特别是在子进程中调用,以避免父子进程共享文件描述符、缓冲区等资源时引发的问题。
- 原型:
-
exit
函数:- 原型:
void exit(int status)
exit
函数是C库函数(man手册中存在于3号手册),它调用了一些清理函数,例如执行stdio
库的清理工作等,然后终止当前进程的执行。exit
函数会先刷新缓冲区,并执行一些其他清理工作(例如fclose
关闭所有打开的文件描述符),并通过参数status
返回终止状态。- 由于
exit
函数会进行一些清理工作,因此它的执行时间较长。
- 原型:
man手册的常见分类
1:可执行的程式或是shell 指令。
2:系统调用(system calls,Linux 核心所提供的函数)。
3:一般函式库函数。
4:特殊档案(通常位于/dev)。
5:档案格式与协定,如 /etc/passwd
6:游戏。
7:杂项(巨集等,如man(7)、groff(7))。
8:系统管理者指令(通常是管理者 root 专用的)。
9:Kernel routines(非标准)。
return函数
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做exit的参数。
2.进程等待
进程等待,简单的说,就是通过wait/waitpid的方式,让父进程(一般)对子进程进行资源等待的过程。
为什么要进程等待
首先,我们开篇提到的,我们需要解决子进程的僵尸问题造成的内存泄漏问题,其次,父进程创建子进程的目的,是要让子进程完成某些任务,所以父进程在需要的时候(虽然这个功能不是必须的,但是这是一种“我想要你就得有”式的基础类型的功能,所以系统需要提供),需要得到子进程的执行情况,所以就需要通过进程等待,获取子进程的退出信息,也就是我们在进程退出部分说过的退出码或者是错误码。
进程等待的两种方法
wait方法(系统调用)
返回值: 成功返回被等待进程pid,失败返回-1。
参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
下面我们利用单进程代码来测试其功能:
此时我们编译执行,同时开启另一个ssh渠道进行监视,输出监视语句:
while :; do ps -axj | head -1 && ps -axj | grep mycode | grep -v grep; sleep 1; echo "----------------------"; done;
运行代码的同时执行监视语句:
当我们开始运行后的前5秒,由于子进程正在执行,而父进程前10秒都在等待的状态,此时我们能看到父子进程都在监视窗口中,
当子进程运行结束并开始退出时,由于父进程还没有结束,所以无法回收子进程的资源,导致其变成了僵尸进程,
接下来,父进程开始通过wait函数回收子进程的资源,并以子进程的id作为wait函数的返回值,此时子进程由僵尸状态进入终止状态至进程退出,最终,父进程运行结束,程序结束。
那现在我们产生了一个疑问?当子进程正在运行的时候,父进程难道一直在等待着子进程退出吗?换句话说,在子进程执行过程中,父进程在干什么?我们可以通过输出打印的方式来验证一下:
我们只需要在父进程执行wait函数之前输出一个标志,在wait函数之后输出一个标志,再运行代码判断父子进程执行过程即可,比如,我们可以采用如下的方式修改父进程代码:
再次编译运行,我们发现,我们的父进程在等待的时候,子进程在结束之前,父进程都将保持一种阻塞式等待的状态,等到子进程退出之后才能继续执行父进程。
所以,一般而言,都是父进程最后退出,因为父进程需要回收子进程的运行结果和资源,就需要等待子进程运行结束才能结束。
waitpid方法(系统调用)
waitpid的三个参数分别有不同的含义:
pid
:用来指定要等待的子进程的 ID。参数值可以有以下几种情况:
- 正整数:等待指定进程 ID 的子进程。
-1
:等待任意子进程。相当于wait
函数。0
:等待与调用进程属于同一进程组的任意子进程。- 负整数:等待进程组 ID 等于参数绝对值的任意子进程。
status
:输出型参数,指向一个整型变量的指针。status
用于存储子进程的退出状态和其他相关信息,例如导致子进程终止的信号编号等。如果不关心子进程的退出状态,则可以将status
设置为NULL
。
options
:可以用来指定额外的选项来控制waitpid
函数的行为。常用的选项有以下几种:
WNOHANG
:如果没有子进程终止,不会阻塞等待,立即返回。WUNTRACED
:除了等待子进程终止,还会等待暂停的子进程。WCONTINUED
:等待已暂停的子进程继续执行。- 0:阻塞等待。
- 函数返回值: 1. >0 :进程等待成功; 2. ==0 :进程等待成功,但是被等待的进程仍然在运行并没有退出; 3. <0 :进程等待失败。
status的结构
对于第二个参数status,我们来重点说一下,status实际上是一个整形指针,其实也是一个int型的整数,占32个比特位,其中,我们只看低16位作为有效的退出信号,这16位之中,其中高8位是退出码,低7位是退出信号。
进程正常退出代码演示:
进程异常退出代码演示:
我们在上面代码的基础上,添加一个空指针的解引用,这种行为会导致异常报错,我们来观察此时的status的状态信息:
那么,我们该如何判断进程是否异常(收到了信号呢),由上面运行的程序来看,我们能够得出结论,进程在成功正常退出时返回的退出信号是0,像我们常用的return 0,当然,我们也可以直接手动通过另外的窗口杀死进程,此时对应的退出信号也会变成我们杀死进程时所使用的信号。
宏函数WIFEXITED和WEXITSTATUS
如果,用户不熟悉位操作的话,那不是干瞪眼吗?不不不,在Linux中,专门为用户提供了判断是否是正常退出,和正常退出情况下获取进程的退出码的宏函数,如下:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
至于进程异常退出,我们就没有了关心该进程退出码的必要,虽然其依旧能正常产生退出码,但是我们此时更关注的是它的错误码,就好像一个同学考试作弊了,那他返回的结果(考试的成绩)还重要吗?答案显而易见,此时我们只是关心他的惩罚结果罢了。
options参数
options
参数可以控制 waitpid()
的行为,其参数值可以是以下的一个或几个值的位或:
WNOHANG
:如果子进程还没有运行结束,那么waitpid()
不会阻塞,而是立即返回,这种方式称之为非阻塞方式的等待,举个例子,我们如果和朋友约好在某个时间和地点见面,那么在和朋友见面之前,如果我们选择约定好时间之后就马上到指定的地点等着朋友而不再处理其他的事务,那么这种等待方式就叫做阻塞等待,反之则叫做非阻塞等待。WUNTRACED
:除了等待子进程结束,如果子进程被暂停(例如,由于接收到SIGSTOP
或SIGTTIN
信号),waitpid()
也会返回。这样,调用者可以决定是否要继续等待或者采取其他行动。WCONTINUED
:这个选项用于在子进程被暂停后继续等待其结束。在某些系统中,如果没有这个选项,那么当子进程被暂停时,waitpid()
将返回一个错误。WNOWAIT
:这个选项使得waitpid()
不在等待子进程结束时收集其退出状态。相反,它立即返回,调用者可使用waitid()
或wait4()
来收集退出状态。- 0:表示进程正在阻塞等待一个子进程。
非阻塞轮询等待状态
非阻塞等待的和阻塞等待的区别,其一在于wait函数的返回值不同(阻塞等待返回的值>0,而非阻塞等待返回的值==0),其二就是在于在父进程采用非阻塞等待的方式等待子进程时,子进程和父进程各自都有自己的工作可以干,也就是说,父进程可以在等待子进程的过程中,先干着其他的事情,我们可以通过下面的代码来进行验证:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
void worker(int cnt)
{printf("i am child ,pid= %d, cnt= %d\n",getpid(),cnt);
}
int main()
{pid_t id=fork();//创建一个子进程if(id==0)//子进程返回值{int cnt=10;while(cnt){worker(cnt);sleep(1); //如果程序执行过快,那么监视窗口容易监视不到cnt--;}exit(0);//直接退出子进程}//走到这一步的一定是父进程了int status=0;pid_t rid=waitpid(id,&status,WNOHANG);//以非阻塞的方式等待if(rid>0)//等待成功,且子进程退出{// printf("wait success ,child exit now\n"); printf("exit code =%d. exit signal =%d\n",(status>>8)&0xFF,status&0x7F);//分别取出进程的退出码额退出信号 } else if(rid==0)//等待子进程成功,但是子进程还没有退出,也就是非阻塞等待{printf("do else things\n");}//接下去就是子进程退出失败的情况return 0;
}
配合下面的监控语句,我们可以掌握程序运行期间进程的情况:
while :; do ps -axj | head -1 && ps -axj | grep mycode | grep -v grep; sleep 1; echo "#############################"; done;
然后我们运行监视窗口中的语句,接着运行我们的代码,我们可以发现以下的结果:
这个运行结果看起来很奇怪,首先,我们知道父子进程是同时并行执行的,当子进程开始执行到打印的语句时,父进程恰好执行等待语句,此时由于我们设置的是非阻塞的等待模式,所以,此时的返回值是0,父进程也就执行rid==0的代码部分了且只能执行一次,此后父进程退出,子进程变为了孤儿进程被bash进程所接受,直到子进程退出,但是这只是父进程检测子进程退出状态的第一轮的结果,在非阻塞等待中,父进程需要进行轮询等待,父进程需要以一定的频率重复访问到子进程的退出状态,才能达到子进程的退出信息能够被父进程所掌握的目的,因此,父进程等待子进程的代码部分,我们需要用循环来实现,修改后父进程实现的代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
void worker(int cnt)
{ printf("i am child ,pid= %d, cnt= %d\n",getpid(),cnt);
}
int main()
{ pid_t id=fork();//创建一个子进程 if(id==0)//子进程返回值 { int cnt=5; while(cnt) { worker(cnt); sleep(2); //为了便于观察,我们将子进程的执行时间设置的长于父进程,那么父进程的等待时间就要多一些 cnt--; } exit(0);//直接退出子进程 } //走到这一步的一定是父进程了 while(1) //循环来实现父进程的非阻塞轮询等待 { int status=0; pid_t rid=waitpid(id,&status,WNOHANG);//以非阻塞的方式等待 if(rid>0)//等待成功,且子进程退出 { // printf("wait success ,child exit now\n"); printf("exit code =%d. exit signal =%d\n",(status>>8)&0xFF,status&0x7F);//分别取出进程的退出码额退出信号 break; }else if(rid==0)//等待子进程成功,但是子进程还没有退出,也就是非阻塞等>待{printf("do else things\n");}else //进程退出失败{break;}sleep(1);//等待一段时间,否则循环速度太快}return 0;
}
为何需要通过系统调用才能获取子进程的信息
在Linux 系统中,进程是独立运行的,每个进程有自己独立的内存空间、文件描述符等资源,并且与其他进程独立运行。因此,在一个进程中想要获取另一个进程的信息,必须通过系统提供的特定接口来实现。
获取子进程信息的过程需要涉及内核态和用户态的切换,因为进程的状态信息(如退出状态)等都保存在内核中,因此需要通过系统调用来从内核中获取这些信息。进程等待的过程中会进入阻塞状态,也需要操作系统内核来协调多个进程之间的执行,并发挥到优秀的调度策略来合理地分配CPU资源。
像全局变量这类的变量,并不能用来保存子进程的退出信息,我们在前面讲写时拷贝技术的时候也曾经谈过,全局变量看似是一份,实则当父子进程中的任意一个进程想要进行写操作时,就会发生写时拷贝,原来父子进程共享的全局变量就会各自变成独立的两份,也就成了两个全局变量的值,所以父进程通过自己的全局变量无法直接读到子进程的全局变量信息。
父进程如何获得子进程的退出信息
总之,通过上述的解释,我们不能形成固定思维,只有外设中才能有自己的阻塞等待队列,进程中也可以有自己的等待队列。
多进程的进程等待
我们前面的代码都是单进程等待的代码,其实多进程等待的代码和单进程高的代码并无特别的不同之处,只是多进程需要多个进程等待处理而已,前面我们已经学过了多进程的创建,我们只需要使用进程等待函数,使得父进程等待它的多个子进程退出即可,下面是一段演示代码及其结果:
这里我们使用了waitpid函数进行等待,有一个好处,就是waitpid可以等待任意一个子进程的退出,不需要考虑顺序,我们从运行结果中也不难看出,进程的退出是没有顺序的,这样我们无需为了一个比较慢的进程而过多的等待,可以转而先执行较快退出的子进程的等待回收,提高了效率。
相关文章:

Linux进程控制(二)--进程等待(一)
前言:之前我们讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。 另外,进程一旦变成僵尸状态,那就刀枪不入,就连 kill -9 也无能为力࿰…...

【C++】C++11常用特性梳理
C11特性梳理 1. 列表初始化2. auto & decltype3. 右值引用3.1. 左右值引用比较3.2. 右值引用的意义3.3. 万能引用与完美转发3.4. 移动构造与移动赋值 4. default & delete5. 可变参数模板6. push_back 与 emplace_back7. lambda表达式7.1. 捕捉列表 8. function包装器8…...

修改iframe生成的pdf的比例
如图想要设置这里的默认比例 在iframe连接后面加上#zoom50即可,50是可以随便设置的,设置多少就是多少比例 <iframe src"name.pdf#zoom50" height"100%" width"100%"></iframe>...
C++之list的用法介绍
C之list的用法介绍 1)定义和初始化: #include <list> std::list<int> my_list; // 定义一个整数类型的list std::list<std::string> my_other_list {"apple", "banana", "cherry"}; // 初始化一个…...

Mybatis-plus 内部提供的 ServiceImpl<M extends BaseMapper<T>, T> 学习总结
作用 当集成Mybatis-Plus 后,我们的大部分数据库操作都可以通过 XxxxxMapper ,同时 Mybatis-plus 在Mapper 提供基本操作方法的同时,也提供类基础的 serviceImpl 来帮助我们完成一些常见的基本操作。 使用 一般情况下,我们首先…...

yolov5 利用Labelimg对图片进行标注
首先打开yolov5-master,在data文件中新建一个文件夹来存放你需要跑的数据,例如我这次跑的是羽毛球,文件把文件取名为badminton。使用其他文件夹例如images也可以,就是跑多了以后不好整理,然后点击 选中刚刚你存放数据的…...

完整版付费进群带定位源码
看到别人发那些不是挂羊头卖狗肉,要么就是发的缺少文件引流的。恶心的一P 这源码是我付费花钱买的分享给大家,功能完整。 搭建教程 nginx1.2 php5.6--7.2均可 最好是7.2 第一步上传文件程序到网站根目录解压 第二步导入数据库(shujuk…...

华为L410上制作内网镜像模板01
原文链接:华为L410上制作离线安装软件模板01 hello,大家好啊,今天给大家带来一篇在内网搭建Apache服务器,用于安装完内网操作系统后,在第一次开机时候,为系统安装软件,今天给大家用WeChat举例&a…...

linuxC语言缓冲区及小程序的实现
文章目录 1.文件缓冲区1.1介绍1.2缓冲文件系统1.3冲刷函数fflush1.4认识linux下的缓冲区 2.linux小程序的实现2.1 回车\r和换行\n2.2倒计时程序2.3进度条小程序sleep/usleep代码运行结果 1.文件缓冲区 1.1介绍 为缓和 CPU 与 I/O 设备之间速度不匹配,文件缓冲区用以…...
MySQL数据库基本操作-DDL 数据库基础知识
目录标题 1、数据库操作1-1 查询所有数据库1-2 创建数据库1-3 选择使用那个数据库1-4 删除数据库 2、数据库表操作2-1 创建数据库表2-2 查看当前数据库所有表名称2-3 查看指定某个表的创建语句2-4 查看表结构2-5 删除表 3、修改表结构格式3-1 修改表添加列3-2 修改列名和类名3-…...

基于JavaWeb+SpringBoot+Vue摩托车商城微信小程序系统的设计和实现
基于JavaWebSpringBootVue摩托车商城微信小程序系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 近年来,随着移动互联网的快速发展,电子商务越来越受到…...
idea代码快捷键Mac版
1、查询任何东西 双击 Shift2、文件内查找 Command F 3、文件内替换 Command R4、全局查找(根据路径) Command Shift F5、在当前文件跳转到某一行的指定处 Command L6、退回 / 前进到上一个操作的地方 Command Option 方向键左Command Opt…...

分享76个Python管理系统源代码总有一个是你想要的
分享76个Python管理系统源代码总有一个是你想要的 学习知识费力气,收集整理更不易。 知识付费甚欢喜,为咱码农谋福利。 下载链接:https://pan.baidu.com/s/1JtcEHG9m8ro4-dc29kVyDg?pwd8888 提取码:8888 项目名称 A simpl…...

Springboot养老院信息管理系统的开发-计算机毕设 附源码27500
Springboot养老院信息管理系统的开发 摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理。在现实运用中,应用软件的工作规则和开发步骤,…...
在虚拟机中安装vim和net-tools,mysql
首先在虚拟机中创建vim目录 sudo mkdir -p /home/user/tools/vim然后开始进行安装 yum install vim -yyum install net-toolsmysql参考链接 安装mysql在虚拟机中...

【Excel】函数sumif范围中符合指定条件的值求和
SUMIF函数是Excel常用函数。使用 SUMIF 函数可以对报表范围中符合指定条件的值求和。 Excel中sumif函数的用法是根据指定条件对若干单元格、区域或引用求和。 sumif函数语法是:SUMIF(range,criteria,sum_range) sumif函数的参数如下ÿ…...

k8s上对Pod的管理部分详解
目录 一.Pod结构介绍 1.user container 2.pause 二.Pod工作形式介绍 1.自主式pod 2.控制器管理下的pod 三.对Pod的操作介绍 1.run/apply -f/create -f运行pod 2.get查看pod信息 3.exec操作运行中的pod (1)进入运行中的pod (2&…...
4.4.2 结构可以将 string类作为成员吗
// structure template { }; 4.4.2 结构可以将 string类作为成员吗 可以将成员name指定为string类对象而不是字符数组吗?即可以像下面这样声明结构吗? #include <string> struct inflatable std :: string name; float volume; double price; 大体上说,答案是肯定的。实…...
npm install 安装总结
npm install moduleName 会把moduleName 包安装到node_modules目录中不会修改package.json之后运行npm install命令时,不会自动安装moduleName npm install moduleName -g 安装模块到全局,不会在项目node_modules目录中保存模块包。不会将模块依赖写入de…...
二十三种设计模式全面解析-组合模式与享元模式的结合应用:实现对象的共享和高效管理
在前文中,我们介绍了组合模式的基本原理和应用,以及它在构建对象结构中的价值和潜力。然而,组合模式的魅力远不止于此。在本文中,我们将继续探索组合模式的进阶应用,并展示它与其他设计模式的结合使用,以构…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...

label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...