【Linux进程】进程控制(中) {进程等待:等待的必要性,进程等待的方法wait,waitpid,退出状态status,waitpid非阻塞等待}

news2024/10/10 10:32:53

三、进程等待

在这里插入图片描述

3.1 进程等待必要性

  1. 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  2. 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  3. 最后,父进程派给子进程的任务完成的如何,我们需要知道。子进程运行完成,结果对还是不对,或者是否正常退出。
  4. 父进程通过进程等待的方式,获取子进程退出信息,回收子进程资源

3.2 进程等待的方法

测试程序:

#include <iostream>    
#include <unistd.h>    
#include <sys/wait.h>    
using namespace std;    
    
int main(){    
  pid_t id = fork();    
  if(id < 0)    
  {    
    perror("fork");    
    exit(10);    
  }    
  else if(id == 0)    
  { 
   	//子进程执行流
    int cnt = 3;    
    while(cnt--){    
      cout << "I'm child process!";    
      cout << " pid:" << getpid();    
      cout << " ppid:" << getppid();    
      cout << "  " << cnt+1 << endl;                                                                                                              
      sleep(1);    
    }    
    exit(122); //为了便于测试,将子进程退出码设为122
  }    
  else{    
    //父进程等待回收子进程    
   	//此处插入下面的测试代码
	//..........
    //..........  
    while(1){
      cout << "I'm father process!";
      cout << " pid:" << getpid();
      cout << " ppid:" << getppid() << endl;
      sleep(1);
    }
  }
}

运行结果:父进程不等待回收子进程,子进程变僵尸。

在这里插入图片描述


3.2.1 wait方法

在这里插入图片描述

  • 参数:输出型参数status,用于获取子进程退出状态,如果不关心也设置为NULL。
  • 返回值:等待成功返回子进程pid,失败返回-1。

注意:wait是一种阻塞式的等待,也就是说父进程会停下来等待子进程结束,然后wait才会返回。

测试代码1:

//wait    
sleep(5);        
pid_t ret = wait(NULL);     
if(ret > 0)    
{    
  cout << "等待子进程成功!ret:" << ret << endl;    
}   
else{
  cout << "等待子进程失败!" << endl;
}

运行结果:父进程等待回收子进程,僵尸进程消失。

在这里插入图片描述

进程监视:

在这里插入图片描述

提示:以后我们编写多进程,基本写法就是fork+wait/waitpid


3.2.2 waitpid方法

在这里插入图片描述

参数:

  • pid:

    • Pid = -1,等待任一个子进程,与wait等效。
    • Pid > 0,等待指定的子进程。
  • status:

    • status输出型参数status,用于获取子进程退出状态,如果不关心也设置为NULL。

    • 可以直接通过位运算解析status,得到退出信号和退出码。还可以通过系统提供的宏进行解析:

      • WIFEXITED(status)宏: 如果子进程正常退出,则返回真。(查看进程是正常退出还是运行崩溃)

      • WEXITSTATUS(status)宏: 若WIFEXITED为真,则返回子进程退出码。(查看进程的退出码)

  • options:

    • 默认为0,表示阻塞等待。也就是说父进程会停下来等待子进程结束,然后waitpid才会返回,父进程才能继续执行。
    • WNOHANG宏(值为1),表示非阻塞等待。 若pid指定的子进程没有结束,则waitpid()函数直接返回0,不予以等待。若正常结束,则返回该子进程的PID。

返回值:

  1. 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

提示:

  1. waitpid(-1, NULL, 0)的效果与wait(NULL)相同
  2. Linux内核是C语言写的,所有的系统调用接口也都是C函数。所以系统提供的大写标记一般都是宏定义。

测试代码2:

//waitpid阻塞等待
sleep(5);
int status = 0;
pid_t ret = waitpid(id, &status, 0); 
if(ret > 0)
{
  cout << "等待子进程成功!ret:" << ret;
  cout << " status:" << status << endl;
}
else{
  cout << "等待子进程失败!" << endl;
}

运行结果:父进程等待子进程成功,并且获取到了子进程的退出状态

在这里插入图片描述

进程监视:

在这里插入图片描述

等等,为什么获取到的退出码不是我们要的122呢?这是因为退出状态status并不是整体使用的,进程退出码只是退出状态的一部分!


3.3 获取子进程status

退出状态status并不是整体使用的,而是按照比特位的方式,将32个比特位进行划分,我们暂时只研究低16位。

img

  1. 当进程正常退出时,status的次低8位是进程退出码。因此要获得退出码,要先将status右移8位,再判断位低8位(&0xFF)。
  2. 进程异常退出或者崩溃,本质是操作系统通过向该进程发送信号终止了该进程。
  3. 当进程异常退出时,status的低7位是进程收到的退出信号。因此要获得退出信号,直接判断位低7位(&0x7F)。
  4. 退出状态的正确打开方式:先检查退出信号,退出信号为0表示进程正常退出;再检查退出码,退出码为0表示进程运行结果正确。

正常退出

测试代码3:

sleep(5);
int status = 0;
pid_t ret = waitpid(id, &status, 0); 
if(ret > 0)
{
  cout << "等待子进程成功!ret:" << ret;
  cout << " SIGNUM:" << (status&0x7F);
  cout << " ExitCode:" << ((status>>8)&0xFF) << endl;
}
else{
  cout << "等待子进程失败!" << endl;
}

运行结果:

在这里插入图片描述

进程正常退出,不会收到退出信号,因此信号值为0。此时的退出码有意义!


异常退出

  • 测试一:子进程中故意加上除0操作,导致进程崩溃。

  • 测试二:子进程中故意访问野指针,导致进程崩溃。

  • 测试三:向子进程发送9号信号,使进程异常退出。

  • 父进程测试代码同上面的正常退出相同。

运行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  1. 除0操作崩溃,进程接受到8号信号,通过查表可知是浮点数错误。

  2. 访问野指针崩溃,进程接受到11号信号,通过查表可知是段错误。

  3. 进程异常退出,不光光是因为内部代码有问题,还有可能是外力直接杀掉,如发送9号信号。

  4. 进程异常退出,此时的退出码无意义,因为进程的return/exit未执行。

kill -l 显示信号列表:
在这里插入图片描述


利用系统提供的宏处理退出状态status

  • WIFEXITED(status): 如果子进程正常退出,则返回真。(查看进程是否是正常退出)

  • WEXITSTATUS(status): 若WIFEXITED为真,则返回子进程退出码。(查看进程的退出码)

测试代码4:

sleep(5);    
int status = 0;    
pid_t ret = waitpid(id, &status, 0);     
if(WIFEXITED(status)) //判断子进程是否正常退出    
{    
  cout << "子进程正常退出!child_pid:" << ret;    
  cout << " exit_code:" << WEXITSTATUS(status) << endl; //查看子进程的退出码    
}    
else{    
  cout << "子进程运行崩溃!child_pid:" << ret;    
  cout << " exit_signal:" << (status&0x7F) << endl; //查看子进程的退出信号    
}   

运行结果:

在这里插入图片描述

在这里插入图片描述


既然进程具有独立性,父进程又凭什么能拿到子进程的退出状态呢?

  • 父进程并不能直接获取子进程的退出状态,需要通过系统调用wait/waitpid来获取。
  • 系统调用,即操作系统有获取进程信息的权利。

wait/waitpid是如何获取到进程的退出状态的?

  1. 子进程终止,其占用的资源会被释放(包括代码和数据)但会保留进程控制块task_sturct等待父进程回收,这就是僵尸进程。而task_struct结构体中保留了进程的退出状态。

  2. task_struct结构体中定义了int exit_code, exit_signal;字段,分别表示退出码和退出信号。

  3. wait/waitpid实际上就是将子进程task_struct结构中的exit_code, exit_signal字段通过位操作写入status,最后回收子进程的进程控制块。


3.4 阻塞等待 VS 非阻塞等待

waitpid的第三个参数options:

  • 默认为0,表示阻塞等待。也就是说父进程会停下来等待子进程结束,然后waitpid才会返回,父进程才能继续执行。
  • WNOHANG宏(值为1),表示非阻塞等待。 若pid指定的子进程没有结束,则waitpid()函数直接返回0,不予以等待。若正常结束,则返回该子进程的PID。

3.4.1 阻塞等待

  1. 父进程调用wait/waitpid阻塞式等待子进程,让进程退出具有一定的顺序性,将来可以让父进程进行更多的收尾工作

  2. 进程阻塞本质上是进程阻塞在系统调用函数的内部(包括进程等待,IO操作等的相关系统调用)。程序计数器记录阻塞位置方便进程唤醒后继续向后执行,进程PCB被调度到阻塞队列中等待阻塞事件的发生。

  3. 我们一般说进程“Hang住了”或者“卡住了”,感觉到明显的卡顿。是因为进程在阻塞队列中等待被唤醒或是在运行队列中等待被调度执行(CPU过于繁忙)。


3.4.2 非阻塞等待

测试代码5:

typedef void(*pf)(); //函数指针类型
vector<pf> arrpf; //函数指针数组                                                                                                               
void func1(){    
  cout << "父进程临时任务1" << endl;    
}    
    
void func2(){    
  cout << "父进程临时任务2" << endl;    
}    
    
//想让父进程在等待子进程过程中执行临时任务,只需要在Load中注册任务即可。    
void Load(){ //加载临时任务(回调函数)   
  arrpf.push_back(func1);     
  arrpf.push_back(func2);    
}               

int main(){
    //.......
    int status = 0;
    while(1) //基于非阻塞等待的轮询检测方案
    {
      pid_t ret = waitpid(-1, &status, WNOHANG); //以非阻塞方式等待 
      if(ret > 0) //子进程退出
      {
        if(WIFEXITED(status)) //判断子进程是否正常退出
        {
          cout << "子进程正常退出!child_pid:" << ret;
          cout << " exit_code:" << WEXITSTATUS(status) << endl; //查看子进程的退出码
        }
        else{
          cout << "子进程运行崩溃!child_pid:" << ret;
          cout << " exit_signal:" << (status&0x7F) << endl; //查看子进程的退出信号
        }
        break;
      }
      else if(ret == 0) //子进程未退出
      {
        cout << "子进程正在运行..." << endl;
        if(arrpf.empty()) Load();
        for(auto iter : arrpf)
        {
          //等待子进程的过程中执行其他任务
          iter(); 
        }                                                                                                                        
      }
      else{ //等待子进程失败
        cout << "等待子进程失败!" << endl;
        break;
      }
      sleep(1);
    }
    //.......
}

运行结果:

在这里插入图片描述

可以看到,在子进程运行过程中,父进程采用基于非阻塞等待的轮询检测方案,不断检查子进程是否退出。如果子进程没有退出,父进程不会阻塞等待,而是可以同时执行一些临时任务


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

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

相关文章

easyexcel 将对应列的格式改为数字或者文本格式

1.在easyexcel 导出excel时 需要将某个列的格式指定&#xff0c;例如指定为数字格式 例如 需要把单元格格式设置为数值&#xff0c;并且保留小数点后三位&#xff0c;按道理来说应该是这样子设置 /** * test&#xff0c; 最终导出值 */ ExcelProperty(value "test")…

01 linux基础(1)

环境安装 解压&#xff0c;从vmware打开虚拟机。 设置密码&#xff1a;1 打开终端&#xff1a;ctrlaltt linux介绍 Linux的发展 1&#xff09;1969年&#xff0c;由kenthompson在AT&T贝尔实验室实现的。使用的是汇编语言。 2&#xff09;1970年&#xff0c;KenThompson…

【Linux后端服务器开发】HTTPS协议

目录 一、加密算法 二、中间人攻击 三、CA认证 一、加密算法 HTTPS协议是什么&#xff1f;HTTPS协议也是一个应用层协议&#xff0c;是在HTTP协议的基础上引入了一个加密层。 HTTP协议内容是按照文本的方式明文传输的&#xff0c;这就导致在传输过程中出现一些被篡改的情况…

Java使用FFmpeg实现mp4转m3u8

Java使用FFmpeg实现mp4转m3u8 前言FFmpegM3U8 一、需求及思路分析二、安装FFmpeg1.windows下安装FFmpeg2.linux下安装FFmpegUbuntuCentOS 三、代码实现1.引入依赖2.修改配置文件3.工具类4.Controlle调用5.Url转换MultipartFile的工具类 四、播放测试1.html2.nginx配置3.效果展示…

QT读写ini文件

QT读写ini文件 源代码文件结构mysql.hmysql.cppmain.cpp my.ini文件截图运行截图QSettings 最开始的需求是使用qt读取不同电脑上的MySQL的my.ini文件实现在不同电脑上也可以成功登录数据库&#xff0c;不用担心密码和用户名不同的问题 到之后发现其实并没有什么用&#xff0c;因…

一文搞定Java IO流,输入流、输出流、字符流、缓冲流,附详细代码示例

目录 一、InputStream1、FileInputStream的代码示例2、ByteArrayInputStream的代码示例3、PipedInputStream的代码示例 二、 OutputStream1、FileOutputStream代码示例2、ByteArrayOutputStream代码示例&#xff1a;3、PipedOutputStream代码示例&#xff1a; 三、字符输入流Re…

7.25 作业 QT

手动实现登录框&#xff1a; widget.cpp: #include "widget.h" #include <QMovie> Widget::Widget(QWidget *parent): QWidget(parent) {//设置尺寸this->resize(800,600); //设置宽高//设置固定尺寸this->setFixedSize(800,600);//窗口标题操作qDebu…

C数据结构与算法——顺序栈 应用(C语言纯享版 迷宫)

实验任务 (1) 掌握顺序栈及其C语言的表示&#xff1b; (2) 掌握入栈、出栈等基本算法的实现&#xff1b; (3) 掌握顺序栈的基本应用&#xff08;求解迷宫通路&#xff09;。 实验内容 使用C语言实现顺序栈的类型定义与算法函数&#xff1b;编写main()函数并根据需要修改、补…

❓“如何创业?互联网创业又该如何入手?

&#x1f31f;5大创业建议&#xff0c;让你轻松入门&#xff01; 作为一名互联网创业者&#xff0c;我想分享一下我的创业经验。下面是我的五个建议&#xff0c;希望对你有所帮助&#xff01; &#x1f31f;了解市场需求 在创业之前&#xff0c;了解市场需求非常重要。你需要研…

【Rust日报】2023-07-24 使用 Rust 重写的InfluxDB 3.0

使用 Rust 重写的InfluxDB 3.0 InfluxDB 是一个开源的、分布式的时序数据库&#xff0c;用于存储和分析时间序列数据, 于 2013 年由 Brian Bondy 和 Nicholas Zakhariev 创立。 InfluxDB 最初是用 Go 语言编写的。 2018 年&#xff0c;InfluxData 决定将 InfluxDB 重写为 Rust …

kotlin 编写一个简单的天气预报app(一)

使用Android Studio开发天气预报APP 今天我来分享一下如何使用Android Studio开发一个天气预报APP。在文中&#xff0c;我们将使用第三方接口获取实时天气数据&#xff0c;并显示在APP界面上。 步骤一&#xff1a;创建新项目 首先&#xff0c;打开Android Studio并创建一个新…

Flutter 最佳实践和编码准则

Flutter 最佳实践和编码准则 视频 前言 最佳实践是一套既定的准则&#xff0c;可以提高代码质量、可读性和可靠性。它们确保遵循行业标准&#xff0c;鼓励一致性&#xff0c;并促进开发人员之间的合作。通过遵循最佳实践&#xff0c;代码变得更容易理解、修改和调试&#xff…

【如何训练一个中译英翻译器】LSTM机器翻译seq2seq字符编码(一)

系列文章 【如何训练一个中译英翻译器】LSTM机器翻译seq2seq字符编码&#xff08;一&#xff09; 【如何训练一个中译英翻译器】LSTM机器翻译模型训练与保存&#xff08;二&#xff09; 【如何训练一个中译英翻译器】LSTM机器翻译模型部署&#xff08;三&#xff09; 【如何训…

新能源电动车充电桩控制主板的技术

新能源电动车充电桩控制主板的技术 你是否曾经遇到过电动车行驶到一半没电的情况?这不仅尴尬&#xff0c;还可能对你的生活造成困扰。然而&#xff0c;随着充电桩主板技术的出现&#xff0c;这个问题得到了有效的解决。那么&#xff0c;这个技术到底包括哪些方面呢?让我们一起…

IDEA代码自动格式化工具

1.自动import 在IDEA中&#xff0c;打开 IDEA 的设置&#xff0c;找到 Editor -> General -> Auto Import。勾选上 Add unambiguous imports on the flyOptimize imports on the fly (for current project) 2.gitee 提交格式化 设置方法如下: 1.打开设置 2.找到版本…

如何写好测试报告?

目录 一、目标 二、模板的使用 三、修订记录 四、内容应该清晰易懂&#xff0c;简明扼要 五、绝不放过一个错字 六、遗留问题单 七、产出成果恰当呈现 一、目标 本文介绍测试人员编写软件测试报告常见的疏漏&#xff0c;以便大家避免&#xff0c;更好让测试成果呈现给客…

Kotlin 协程 CoroutineScope

协程定义&#xff1a; 19年官方是这样说的&#xff1a;协程是轻量级的线程&#xff0c;协程就是 Kotlin 提供的一套线程封装的 API&#xff1b; 现在官方是这样说的&#xff1a;协程是一种并发设计模式&#xff1b; 协程作用&#xff1a; 1.处理耗时任务&#xff1b; 2.保…

【雕爷学编程】Arduino动手做(172)---WeMos D1开发板模块

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

RL 实践(4)—— 二维滚球环境【DQN Double DQN Dueling DQN】

本文介绍如何用 DQN 及它的两个改进 Double DQN & Dueling DQN 解二维滚球问题&#xff0c;这个环境可以看做 gym Maze2d 的简单版本参考&#xff1a;《动手学强化学习》完整代码下载&#xff1a;5_[Gym Custom] RollingBall (DQN and Double DQN and Dueling DQN) 文章目录…

智能喷涂机器人的制作分享

作者&#xff1a;朱家谊、吾丽江、管孝天 单位&#xff1a;天津工业大学 指导老师&#xff1a;李鹏 1. 概念说明 智能喷涂机器人是一种具有自主感知、决策和执行能力的机器人&#xff0c;专门用于自动化喷涂任务&#xff0c;它可以应用于各种领域&#xff0c;如汽车制造、建…