(16)Linux 进程等待 wait/waitpid 的 status 参数

前言:我们开始讲解进程等待,简单地讲解 wait 函数,然后我们主要讲解 waitpid 函数。由于 wait 只有一个参数 status,且 waitpid 有三个参数且其中一个也是 status,我们本章重点讲解这个 status 参数。

一、进程等待(Process wait)

1、进程等待的必要性

为什么要进行进程等待?不知道大家是否还记得我们在之前的章节讲过的 "僵尸进程" 的概念。

僵尸状态:当一个 \textrm{Linux} 中的进程退出的时候,一般不会直接进入 X 状态(死亡,资源可以立马被回收),而是进入 Z 状态。 

子进程退出如果父进程不管不顾,就 可能造成僵尸进程的问题,一直占内存进而引发内存泄露。 

所以我们必须让其从 Z 状态变为 X 状态: 

进而允许操作系统能去释放它(将代码和数据 free 掉,将相关数据结构归还给 slab 分派器)。

 上面我们讲的实际上就是我们需要进程等待的一个原因 —— 解决内存泄露问题

然而不仅仅这一个原因,我们还需要进程等待来 获取子进程的退出状态 。

我们需要知道父进程派给子进程的任务完成的如何。

比如子进程运行完成,结果对还是不对,是否正常退出?

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

获取子进程的退出状态是否需要将曾经子进程的退出信息保存起来,然后被恢复、读取呢? 

这和我们刚才讲的进程退出有着大大的关系!我们知道了进程退出是有退出码的。

我们需要让子进程退出时它的 return 结果或者 exit 的结果是需要被父进程读到的。

 总结:需要进程等待的原因:① 解决内存泄露问题     ② 获取子进程的退出状态

2、wait 函数

函数原型是
 
#include <sys/types.h>
 
#include <wait.h>
 
int wait(int *status)

函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。 

我们看到,wait 有一个叫 status 的参数

我们下面验证一下这个函数是否可以 Z\rightarrow X,我们会把 status 暂且设置为空。

验证: 

简单来说,就是让父子进程都跑起来,并各自打印消息,子进程一直死循环运行,父进程将自己休眠 20 秒,在这 20 秒之内我们手动把处于死循环的子进程给 kill 掉,此时子进程会处于 Z 状态,此时父进程还在休眠不会立即调用 wait(这么做是为了便于观察)。

当 20 秒过后,父进程苏醒后执行了 wait 函数,用一个变量 ret 去接收 wait 的返回值,通过返回值来确定是否等待成功。如果等待成功了就会成功 Z\rightarrow X。 

为了观察进程状态,我们写一段监控脚本,做到每隔一秒就监控一下我们的 mytest 进程。

监控脚本:

$ while :; do ps ajx | head -1 && ps ajx | grep mytest | grep -v grep; echo "-----------------------------------------------------------------------"; sleep 1; done

运行后我们只需要死死盯着 \textrm{STAT},观察状态的变化即可!

开三个窗口,分别用于运行监控脚本、运行 mytest 和 kill 子进程:

 

至此,我们成功验证了父进程等待了子进程。

通过 wait() 的方案,我们可以解决回收子进程的 Z 状态,让子进程进入 X 状态。

3、cwaitpid 函数初识

刚才讲的 wait 并不是主角,因为其功能比较简单,在进程等待时用的更多的是 waitpid

waitpid 可以把 wait 完全包含,wait 是 waitpid 的一个子功能。

waitpid:等待任意一个退出的子进程。

#include <sys/types.h>
#include <sys/wait.h>
 
pid_t waitpid(pid_t pid, int* status, int options);

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下这两个参数: 

对于返回值 pid_t:

  • 如果 pid_t > 0:等待子进程成功,返回值就是子进程的 pid;
  • 如果 pid_t < 0:等待失败。

对于参数 pid:

  • 设置参数 pid > 0:是几,就代表等待哪一个子进程,比如 pid=1234,指定等待。
  • 设置参数 pid = -1:待任意进程。

options:

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

     ret = waitpid(-1,  NULL,  0);

对于参数 status:

  • 该参数是一个输出型参数,通过调用该函数,从函数内部拿出特定的数据。

 值得强调的是,wait/waitpid 是系统调用!

 

说明:父进程等待子进程,子进程也会执行自己的代码。当子进程执行了 return/exit 退出后,子进程会将自己的退出码信息写入自己的进程控制块 PCB中。子进程退出了,代码可以释放,子进程退出后变成 Z 状态,其本质上就是将自己的 task_struct 维护起来(代码可以释放,但是 task_struct 必须维护)。所谓的 wait/waitpid 的退出信息,实际上就是从子进程的 task_struct 中拿出来的,即 从子进程的 task_struct 中拿出子进程退出的退出码

所以,我们的父进程在等待子进程死亡,等子进程一死,就直接把子进程的退出码信息拷贝过去,通过 wait/waitpid 传进来的参数后,父进程就拿到了子进程的退出结果。即 子进程会将自己的退出信息写入 task_struct  。 

子进程的 task_struct 是操作系统的内部数据结构这个 waitpid 自然就是系统调用,实际上就是调用操作系统的功能。 

总结:父进程通过调用 wait/waitpid,通过 status 参数,就可以将子进程的信息拿到手。

二、wait/waitpid 的 status 参数

1、status 参数是位图结构 

wait 和 waitpid,都有这个 status 参数,如果传递 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 

该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。

并且,status 参数是由操作系统填充的!是一个整数,该整数就是下面我们要详细研究的。

它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 看待。

不过,关于 status 我们只需要关心该整数的 低 16 个比特位!

我们不必去关心它的高 16 位,因为凭借低 16 位就足以判断了。

然而,整数的低 16 位,其中又可以分为 最低八位次低八位(具体细节看图):

 

 2、次低八位:拿子进程退出码

重点:通过提取 status 的次低八位,就可以拿到子进程的退出码。 

验证:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main () 
{
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;   // 循环5次
        // child
        while (cnt--) {
            // 五秒之内运行状态
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
 
            // 五秒之后子进程终止
            
        }
 
        exit(233);   // 方便辨识,退出码我们设置为233,这是我们的预期结果
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        
        // ***** 使用waitpid进行进程等待
        int status = 0;  // 接收 waitpid 的 status 参数
 
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) {   // 等待成功
            printf (
                "等待成功,ret: %d, 我所等待的子进程退出码: %d\n", 
                ret,
                (status>>8)&0xFF
            );
        }
 
    }
}

我们说了,status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。我们可以用 位操作 来完成,将 status 右移八位再按位与上 \textrm{0xFF},即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。

3、 最低七位:提取子进程的退出信号

 重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。

我们的 status 的低八位用于表示处理异常的地方,其中有 1 位是 core dump,我们下面讲。

除去 core dump,剩余七位用于进程中的退出信号,这就是 最低七位

进程退出,如果异常退出,是因为这个进程收到了特定的信号。

我们虽然还没有开始讲解信号,但是我们前几张就介绍了 kill -9 这样的杀进程操作。

这个 -9 我们当时说了,就是一个信号,发送该信号也确实可以终止进程。

刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程的 正常退出。

如果进程 异常退出 呢?我们来模拟一下进程的异常退出。

代码演示:

结果:

因为子进程是个死循环,父进程又调了 waitpid,导致父进程一直在 "阻塞式" 地等待子进程。

父进程在等待子进程期间什么都没有干,就搬了张板凳坐在那等子进程死。

信号是可以杀掉进程的,我们现在主动输入 kill -9:

此时我们就成功拿到了子进程的退出信号,9 是因为我们输入的信号就是 9。

此时父进程看到子进程寄了,终于可以不用等了,可以给子进程收尸了(Z\rightarrow X

还是那句话,代码跑完结果是什么已经不重要了,我们最关心的是因为什么原因退出的。

当进程收到信号时,就代表进程异常了。进程程出,如果是异常退出,是因为该进程收到了特定的信号。其实除了 9 号信号还有很多信号,输入 kill -l 就可以查看这些;

总结:退出信号代表进程是否异常,退出码代表进程在退出之时代码对还是不对。

4、进程退出的宏

我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。

实际上,你也可以不用位操作,因为 \textrm{Linux} 已经给我们提供了一些宏供我们直接调用。

它们是 WEXITSTATUS 和 WIFEXITED,在这之前,我们再思考一个问题:

思考:一个进程退出时,可以拿到退出码和推出信号,我们先看谁?

一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。

所以,我们先关注退出信号,如果有异常了我们再去关注退出码。

WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。

WEXITSTATUS(status)

WIFEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。

WIFEXITED(status)

 代码演示:运用 WEXITSTATUS 和 WIFEXITED 宏

 

结果为:

当然了,如果你压根就不关注推出信息和退出码,你直接把 status 设置为 NULL 就行。

感谢观看!!!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[ Tool ] celery分布式任务框架基本使用

celery官方 Celery 官网&#xff1a;www.celeryproject.org/ Celery 官方文档英文版&#xff1a;docs.celeryproject.org/en/latest/i… Celery 官方文档中文版&#xff1a;docs.jinkan.org/docs/celery… Celery是一个简单、灵活且可靠的&#xff0c;处理大量消息的分布式系…

DRF从入门到精通七(djangorestframework-simplejwt、定制返回格式、多方式登录)

文章目录 一、djangorestframework-simplejwt快速使用1.基础使用步骤2.自己配置视图校验访问局部配置认证及权限类全局配置认证及权限类 3.关于双token认证问题 二、定制返回格式三、多方式登录 一、djangorestframework-simplejwt快速使用 JWT主要用于签发登录接口需要配合认证…

阿里云服务器云盘ESSD Entry、SSD、高效云盘性能测评

阿里云服务器系统盘或数据盘支持多种云盘类型&#xff0c;如高效云盘、ESSD Entry云盘、SSD云盘、ESSD云盘、ESSD PL-X云盘及ESSD AutoPL云盘等&#xff0c;阿里云百科aliyunbaike.com详细介绍不同云盘说明及单盘容量、最大/最小IOPS、最大/最小吞吐量、单路随机写平均时延等性…

yolo增加Shape-IoU,完美超越SIoU/EIoU/CIoU

论文地址&#xff1a;https://arxiv.org/pdf/2312.17663.pdf 代码地址&#xff1a;GitHub - malagoutou/Shape-IoU 摘要 作为检测定位分支的重要组成部分&#xff0c;边界框回归损失在目标检测任务中起着重要作用。现有的边界框回归方法通常考虑GT框和预测框之间的几何关系&…

C语言实例_math.h库函数功能及其用法详解

一、前言 数学在计算机编程中扮演着至关重要的角色&#xff0c;C语言的math.h头文件提供了一系列的函数和工具&#xff0c;用于数学计算和常用数学函数的实现。这些函数包括数值运算、三角函数、指数对数函数等&#xff0c;为开发人员提供了强大的数学处理能力。本文将对math.…

从零开始了解大数据(七):总结

系列文章目录 从零开始了解大数据(一)&#xff1a;数据分析入门篇-CSDN博客 从零开始了解大数据(二)&#xff1a;Hadoop篇-CSDN博客 从零开始了解大数据(三)&#xff1a;HDFS分布式文件系统篇-CSDN博客 从零开始了解大数据(四)&#xff1a;MapReduce篇-CSDN博客 从零开始了解大…

多模态大模型Vary:扩充视觉Vocabulary,实现更细粒度的视觉感知

前言 现代大型视觉语言模型(LVLMs)具有相同的视觉词汇- CLIP&#xff0c;它可以涵盖大多数常见的视觉任务。然而&#xff0c;对于一些需要密集和细粒度视觉感知的特殊视觉任务&#xff0c;例如文档级OCR或图表理解&#xff0c;特别是在非英语场景下&#xff0c;clip风格的词汇…

电子电路快速入门

参考&#xff1a; 电子电路设计入门篇一 https://www.bilibili.com/video/BV18C4y1p7p9Proteus与protel的区别 https://www.zhihu.com/question/385796380数字集成电路的设计流程简介 https://zhuanlan.zhihu.com/p/24476011?tt_fromweixin硬件电路设计的基本流程、作用和注意…

StratifiedGroupKFold解释和代码实现

StratifiedGroupKFold解释和代码实现 文章目录 一、StratifiedGroupKFold解释和代码实现是什么&#xff1f;二、 实验数据设置2.1 实验数据生成代码2.2 代码结果 三、实验代码3.1 实验代码3.2 实验结果3.3 结果解释 四、样本类别类别不平衡 一、StratifiedGroupKFold解释和代码…

简单Diff算法

简单Diff算法 渲染器的核心 Diff算法 解决的问题 比较新旧虚拟节点的子节点&#xff0c;实现最小化更新。 虚拟节点key属性的作用 就像虚拟节点的“身份证号”&#xff0c;在更新时&#xff0c;渲染器会通过key属性找到可复用的节点&#xff0c;然后尽可能地通过DOM移动操…

Hexo 部署 Github Pages, Github Actions自动部署

想整个静态的博客部署在github pages 历经两天的折磨终于是摸索成功了&#xff0c;官网的文档太简陋了&#xff0c;很多东西没说清楚。 欢迎大家访问我的博客&#xff01; CanyueThis is Canyues blog.https://mobeicanyue.github.io/ 最终实现的效果&#xff0c;一个项目仓库…

polar CTF 简单rce

一、题目 <?php /*PolarD&N CTF*/ highlight_file(__FILE__); function no($txt){if(!preg_match("/cat|more|less|head|tac|tail|nl|od|vim|uniq|system|proc_open|shell_exec|popen| /i", $txt)){return $txt;}else{ die("whats up");}} $yyds(…

【openGauss服务器端工具的使用】

【openGauss服务器端工具的使用】 gs_checkperf openGauss 不仅提供了gs_checkperf工具来帮助用户了解openGauss的负载情况。 使用数据库安装用户登录服务器&#xff0c;执行如下命令进行查看数据库性能&#xff1a; 简要信息展示&#xff1a;[ommopengauss03 ~]$ gs_checkperf…

Ubuntu Server 22.04 连接Wifi并配置静态IP

Ubuntu Server 22.04 连接Wifi并配置静态IP 前言&#xff1a;我家最近好几台电脑&#xff0c;我都想跑着Ubuntu Server做服务器&#xff0c;但是近几年的超级本已经不自带网口了&#xff0c;所以我就考虑用Wifi来联网&#xff0c;速度也还可以&#xff0c;但是既然是跑服务&…

《算法导论》复习——CHP1、CHP2 算法基础

基本定义&#xff1a; 算法是一组有穷的规则&#xff0c;规定了解决某一特定类型问题的一系列运算。 关心算法的正确性和效率。 算法的五个重要特性&#xff1a;确定性、能行性、输入、输出、有穷性。 基础方法&#xff1a; 伪代码&#xff08;Pseudocode&#xff09;&#xff…

Springboot集成RabbitMq二

接上一篇&#xff1a;Springboot集成RabbitMq一-CSDN博客 1、搭建项目-消费者 与之前一样 2、创建配置类 package com.wym.rabbitmqconsumer.utils;import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.spring…

11.盛水最多的容器(双指针,C解法)

题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;…

床垫选得好孩子睡得香!康姿百德学生床垫让孩子拥有甜美梦乡

睡眠与健康密切相关,而床垫的选购则直接关系到人们睡眠质量的好坏。而孩子的床垫选择更是重中之重,青少年儿童正处于生长发育的重要时期,床垫选不好很容易导致孩子睡眠不足,影响孩子的学习,甚至会影响孩子的脊椎发育。所以,给孩子挑张好用又合适的床垫十分重要,现在让我们来看看…

SpringMVC-域对象共享数据

一、request域对象共享数据 1.1 通过ServletAPI共享数据 RequestMapping("/servletAPI")public String servletAPI(HttpServletRequest request){request.setAttribute("requestAttribute","helloworld");return "servletAPI";}<!…

(学习打卡1)重学Java设计模式之设计模式介绍

前言&#xff1a;听说有本很牛的关于Java设计模式的书——重学Java设计模式&#xff0c;然后买了(*^▽^*) 开始跟着小傅哥学Java设计模式吧&#xff0c;本文主要记录笔者的学习笔记和心得。 打卡&#xff01;打卡&#xff01; 设计模式介绍 一、设计模式是什么&#xff1f; …