Linux | 进程终止与进程等待

目录

前言

一、进程终止

1、进程终止的几种可能 

2、exit 与 _exit

二、进程等待

1、为什么要进程等待

2、如何进行进程等待

(1)wait函数 

(2)waitpid函数

3、再次深刻理解进程等待


前言

        我们前面介绍进程时说子进程退出,父进程不对子进程进行资源回收,子进程会进入僵尸状态,对于操作系统来说,这是一种资源泄漏,而且还是操作系统层面的资源泄漏,除非父进程退出,否则子进程将一直处于僵尸状态,本章就介绍父进程如何回收子进程;

一、进程终止

1、进程终止的几种可能 

        在介绍回收子进程之前,我们必须对进程终止有一定的了解;所谓进程终止,就是进程的退出,这里我们首先要直到进程退出有以下三种可能;

1、程序正常运行结束,结果正确;

2、程序正常运行结束,结果不正确;

3、程序崩溃,结果不重要;

        可能我们之前对进程退出并没有什么概念,我们是如何区分以上三种情况呢?比如我们以前在virtual studio上运行我们的C/C++程序,我们总会写一个main函数,而一般我们都会在main函数的最后写一个return 0;实际上,这个0就是退出码,我们通过这个判定结果是否正确,退出码有对应的解释含义,我们可以通过 strerror 函数将退出码含义打印出来;这是区分情况一和情况二的方法,对于情况三,程序崩溃,我们的软件 virtual studio 一般会出来一个弹窗,告诉你是哪里引发了程序的崩溃,最常见的就是除零错误、空指针解引用等等,都会导致程序崩溃;下面我们来打印退出码;如下代码;

        我们编译运行上述程序,结果如下;

        我们发现错误码的信息最多编辑到了133号,前面几项我们也很熟悉,其中第一项0就是成功且结果正确;

2、exit 与 _exit

        前面我们说过,在main函数中,我们可以通过return语句让进程退出,并返回返回值;那么要是我们不在main函数呢?那么我们难道要返回main函数再调用return语句?那也太麻烦了吧,实际上,我们也可以通过exit函数和 _ exit函数来使进程终止;如下代码;

        我们编译代码,结果如下所示;这里介绍一条命令 echo $?;可以查看最近运行的一个程序的返回值,我们发现我们输入除-1以外的值时,返回值为0,也就是main函数中的return 语句,而我们输入-1时,返回值为14,是我们调用exit函数的返回值;

        上述的退出函数exit换成_exit也可以实现相同功能,那么其区别在哪呢?看如下代码;

        当我们使用exit函数后,运行结果如下;

        当我们使用_exit函数后,运行结果如下;

        我们已经看不到you can see me;这是因为我们的打印的内容还在C语言的缓冲区内,而我们的_exit是系统调用,在退出前并不能刷新缓冲区;而我们的exit为C语言库函数,会刷新我们的缓冲区;

补充:C语言的缓冲区刷新机制为行刷新,因为在打印时没有换行符,所以我们的使用exit函数时会打印,而使用_exit函数时不会打印;

二、进程等待

1、为什么要进程等待

        其一,这个原因早在我们前面就已经进行了阐述,当子进程退出时,父进程不对子进程进行资源回收,子进程将一直处于僵尸状态,而这种状态会造成系统资源泄漏,这时我们需要通过进程等待的方式,给子进程 “收尸” ;

        其二,我们让子进程去完成任务是否需要子进程完成的如何?对于某些时候,就有这样的需求,因此进程等待另一作用是获取子进程任务完成情况;

2、如何进行进程等待

(1)wait函数 

        关于如何进行进程等待,我们通常是通过系统调用wait和waitpid来实现;我们首先看啊可能wait的函数声明;

        这个函数只有一个参数,是一个输出型参数,所谓输出型参数就是我们传一个指针,函数内会给这个指针执行的值进行赋值返还给我们;这个输出型参数就是子进程的退出码和终止信号,这里我们暂时设置为NULL,等待会介绍waitpid时再做介绍;

        该函数的返回值,若调用成功,返回子进程pid,若失败返回-1.错误码被设置,我们写出如下代码;查看父进程是否回收了子进程;

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{int id = fork();if(id == -1){// fork调用失败exit(1);}else if(id == 0){// 子进程int cnt = 5;while(cnt--){printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());sleep(1);}exit(14); // 退出码}else  {// 父进程sleep(7);wait(NULL); // 进程等待sleep(2);}return 0;
}

        我们再在命令行输入以下脚本命令对这两个进程进行监控;

while :; do ps -axj | head -1 && ps -axj | grep test | grep -v grep; sleep 1; echo "-------------------"; done

        我们发现前面几秒确实都在运行,接着中间有两秒子进程处于僵尸状态,因为父进程比子进程多sleep两秒,正如我们所料;接着最后只剩父进程;子进程成功被父进程回收;

(2)waitpid函数

        下面为我们通过man手册查询结果;

参数pid:

pid作用
pid < -1等待进程组号为pid绝对值的任何子进程。
pid = -1等待任何子进程,此时的waitpid()函数就退化成了普通的wait()函数。
pid = 0等待进程组号与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
pid > 0等待进程号为pid的子进程。

 

注意:这里进程组号的暂不提及,我们用的也不多,平常用的较多的就是2和4;

参数status:

        这个参数为输出型参数,与我们wait函数相同;关于这个参数的使用,我们不能将里面的int值整体使用,得按比特位分开使用;

情况一:正常退出

        此时,我们使用第7到第15比特位,当作退出码;我们可以通过 (status >> 8) & 0xFF来获得这个退出码,还可以使用宏函数 WEXITSTATUS 来获取,通过宏函数 WIFEXITED 来获取进程是否正常退出,若正常退出返回真,否则返回假; 

情况二:信号终止退出(异常退出)

        这里我们用后面0到6的比特位来表示信号终止,我们可以使用 status & 0x7F来得到这个终止信号;至于这里的 core dump 标志位暂不讲解,这又是另一个话题了;

参数options:

        这个参数默认填0就好,表示阻塞等待,若填WNOHANG,则表示非阻塞等待;

返回值:

waitpid的返回值略比wait复杂一些,有三种情况;

1、正常返回,此时返回子进程pid;

2、若设置WNOHANG,且子进程还未退出,则返回0,子进程退出了,返回子进程pid;

3、调用失败,返回-1,错误码被设置;

代码实践:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{int id = fork();if(id == -1){// fork调用失败exit(1);}else if(id == 0){// 子进程int cnt = 5;while(cnt--){printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());sleep(1);}exit(14); // 退出码}else  {// 父进程sleep(7);int status = 0;// 进程等待int ret = waitpid(id, &status, 0);  // 此时与我们的wait函数功能相同if(WIFEXITED(status)){printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));}else{printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);}sleep(2);}return 0;
}

        代码输出结果如下;

        若我们将代码加上一个除零错误;

        运行结果如下;

        我们再通过kill -l查看信号;8号信号正是我们的浮点数计算问题;

        我们再将代码改一下,将我们的waitpid改成非阻塞等待的情况,如下;

#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>int main()
{int id = fork();if(id == -1){// fork调用失败exit(1);}else if(id == 0){// 子进程int cnt = 5;// 除零错误展示// int a = 10/ 0;while(cnt--){printf("我是子进程,我的pid:%d,ppid:%d\n", getpid(), getppid());sleep(1);}exit(14); // 退出码}else  {// 父进程int status = 0;// 进程等待while(true){int ret = waitpid(id, &status, WNOHANG); if(ret == -1){printf("waitpid调用失败\n");exit(-1);}else if(ret == 0){printf("子进程还未退出,我再干点别的\n");sleep(1);}else{printf("等待成功\n");break;}}if(WIFEXITED(status)){printf("子进程正常退出,退出码为%d\n", WEXITSTATUS(status));}else{printf("子进程异常退出,收到信号%d\n", (status) & 0x7F);}sleep(2);}return 0;
}

        运行结果如下,此时我们的父进程就不用阻塞等待子进程结束了,父进程只需要通过轮询的方式来来回收子进程;

3、再次深刻理解进程等待

        上述内容为进程等待的实操部分,我们现在再次回到理论部分,我有如下问题;

问题一:我们是否可以通过一个全局变量来获取子进程的退出码呢?

        不可以,虽然父进程和子进程共用一段代码,但是都有各自的进程地址空间,当我们使用全局变量时,子进程往这个全局变量里写入时,会发生写时拷贝,因此无法获得退出码,这也是进程的独立性;

问题二:既然进程具有独立性,那么wait和waitpid是如何获取子进程的退出码的呢?

        我们的wait和waitpid属于系统调用,既然是系统调用,当然是属于操作系统的一部分,我们在回收子进程时,实际上是销毁进程PCB等内核数据的过程,而PCB(task_struct)中有一个退出码和退出信号的字段,以下为Linux源码截图;

        我们在task_struct里确实发现了这几个字段,如果有兴趣的,可以去官网下载一份源码,task_strcut结构体在 include/linux/sche.h 中;

        回到正题,既然我们task_struct中有这些字段,那么我们父进程回收子进程的时候是否可以获取这些字段的信息呢?答案当然是肯定的,我们的wait和waitpid为系统调用,当然有资格获取这些字段,我们的父进程也就可以通过这两个系统调用拿到了子进程的退出码了;

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

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

相关文章

【机器学习合集】模型设计之分组网络 ->(个人学习记录笔记)

文章目录 分组网络1. 什么是分组网络1.1 卷积拆分的使用1.2 通道分离卷积的来源1.3 GoogLeNet/Inception1.4 从Inception到Xception(extreme inception)1.5 通道分组卷积模型基准MobileNet 2. 不同通道分组策略2.1 打乱重组的分组2.2 多尺度卷积核分组2.3 多分辨率卷积分组2.4 …

CDN加速技术海外与大陆优劣势对比

内容分发网络&#xff08;CDN&#xff09;是一项广泛应用于网络领域的技术&#xff0c;旨在提高网站和应用程序的性能、可用性和安全性。CDN是一种通过将内容分发到全球各地的服务器来加速数据传输的服务。本文将探讨使用CDN的优势以及国内CDN和海外CDN之间的不同优势和劣势。 …

【Proteus仿真】【Arduino单片机】简易电子琴

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用无源蜂鸣器、按键等。 主要功能&#xff1a; 系统运行后&#xff0c;按下K1-K7键发出不同音调。 二、软件设计 /* 作者&#xff1a;嗨小易&a…

【Docker】如何查看之前docker run命令启动的参数

个人主页&#xff1a;金鳞踏雨 个人简介&#xff1a;大家好&#xff0c;我是金鳞&#xff0c;一个初出茅庐的Java小白 目前状况&#xff1a;22届普通本科毕业生&#xff0c;几经波折了&#xff0c;现在任职于一家国内大型知名日化公司&#xff0c;从事Java开发工作 我的博客&am…

十大排序算法(C语言)

参考文献 https://zhuanlan.zhihu.com/p/449501682 https://blog.csdn.net/mwj327720862/article/details/80498455?ops_request_misc%257B%2522request%255Fid%2522%253A%2522169837129516800222848165%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&…

[Unity][VR]透视开发系列4-解决只看得到Passthrough但看不到Unity对象的问题

【视频资源】 视频讲解地址请关注我的B站。 专栏后期会有一些不公开的高阶实战内容或是更细节的指导内容。 B站地址: https://www.bilibili.com/video/BV1Zg4y1w7fZ/ 我还有一些免费和收费课程在网易云课堂(大徐VR课堂): https://study.163.com/provider/480000002282025/…

探索Vue 3和Vue 2的区别

目录 响应式系统 性能优化 Composition API TypeScript支持 总结 Vue.js是一款流行的JavaScript框架&#xff0c;用于构建用户界面。Vue 3是Vue.js的最新版本&#xff0c;相较于Vue 2引入了许多重大变化和改进。在本文中&#xff0c;我们将探索Vue 3和Vue 2之间的区别。 …

1.PPT高效初始化设置

1.PPT高效初始化设置 软件安装&#xff1a;Office 2019 主题和颜色 颜色可以在白天与黑夜切换&#xff0c;护眼 切换成了黑色 撤回次数 撤回次数太少&#xff0c;只有20次怎么办 自动保存 有时忘记保存就突然关闭&#xff0c;很需要一个自动保存功能 图片压缩 图片…

1.spark standalone环境安装

概述 环境是spark 3.2.4 hadoop版本 3.2.4&#xff0c;所以官网下载的包为 spark-3.2.4-bin-hadoop3.2.tgz 在具体安装部署之前&#xff0c;需要先下载Spark的安装包&#xff0c;进到 spark的官网&#xff0c;点击download按钮 使用Spark的时候一般都是需要和Hadoop交互的&a…

第16期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

MySQL的数据库操作、数据类型、表操作

目录 一、数据库操作 &#xff08;1&#xff09;、显示数据库 &#xff08;2&#xff09;、创建数据库 &#xff08;3&#xff09;、删除数据库 &#xff08;4&#xff09;、使用数据库 二、常用数据类型 &#xff08;1&#xff09;、数值类型 &#xff08;2&#xff0…

UI自动化测试是什么?什么项目适合做UI自动化测试

UI 测试是一种测试类型&#xff0c;也称为用户界面测试&#xff0c;通过该测试&#xff0c;我们检查应用程序的界面是否工作正常或是否存在任何妨碍用户行为且不符合书面规格的 BUG。了解用户将如何在用户和网站之间进行交互以执行 UI 测试至关重要&#xff0c;通过执行 UI 测试…