【Linux详解】进程等待 | 非阻塞轮询

引入

为什么?是什么?怎么办

是什么?

进程等待是指父进程暂停自己的执行,直到某个特定的子进程结束或发生某些特定的事件。

为什么?

  • 僵尸进程刀枪不入,不可被杀死,存在内存泄露
  • 获取进程的执行情况,知道我布置给子进程的任务,它完成的怎么样了–可选

怎么办

wait 函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int* status);

测试:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        // child
        while (1) {
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
        }
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        sleep(20);   // 为了便于观察,我们让父进程休眠20s
 
        // 苏醒后,父进程执行 wait,耐心地等待子进程
        pid_t ret = wait(NULL);  // 暂且将status参数设置为NULL
        if (ret < 0) {
            printf("等待失败!\n");
        }
        else {
            printf("等待成功!\n");   // 此时 Z → X
        }
 
        sleep(20);  // 子进程退出后,再让父进程存在一段时间
    }
}

在这里插入图片描述

为什么是交替型,不是一个执行完?//了解循环的细节

fork 创建子进程后,父进程会首先执行并打印这条信息,其中 <父进程的pid> 是父进程的进程ID。截不下就没截了

  • 父进程打印一次消息后休眠20秒:
    • 父进程仅在创建子进程后打印一次消息,然后休眠20秒等待观察子进程的活动,不再进行其他操作。

通过这种方式,父进程和子进程能够并发地执行各自的任务,并且我们通过延迟父进程的退出能观察到子进程的连续活动。

整个程序执行流程如下:
  1. 父进程打印:

    yamlCopy code
    我是父进程: pid: 1234,我将耐心地等待子进程!
    
  2. 子进程每秒打印一次:

    yamlCopy code
    我是子进程,我正在运行... Pid: 5678
    

    (此循环持续到子进程被终止)

  3. 通过终端输入 kill 5678 终止子进程。

  4. 父进程20秒后苏醒并等待子进程结束后,打印:

    textCopy code
    等待成功!
    

之后,父进程再休眠20秒并继续存在一段时间后退出


waitpid

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

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

参数

  • pid
    

    :要等待的子进程的进程ID。根据传入的值可以指定等待任何子进程、特定进程ID的子进程、任何同一进程组的子进程或者任何同一会话的子进程。

    • 如果 pid 大于零,waitpid 将等待指定进程ID的子进程结束。
    • 如果 pid 等于 -1,waitpid 将等待任意子进程结束,等同于 wait 函数。
  • status:一个指向整型的指针,是一个输出型参数,它将用于存储子进程的终止状态

  • options
    

    :用于指定等待行为的附加选项。

    • 传入0表示以默认行为等待子进程。(阻塞等待中再讲)

返回值

  • 如果 waitpid 成功,返回值是已终止子进程的进程ID
  • 如果出现错误,返回-1,并且会设置 errno 变量来指示具体错误原因。

Z状态,其本质上就是将自己的 task_struct 维护起来(代码可以释放,但是 task_struct 必须维护)。所谓的 wait/waitpid 的退出信息,实际上就是从子进程的 task_struct 中拿出来的,即 从子进程的 task_struct 中拿出子进程退出的退出码,拷贝到父进程中

在这里插入图片描述

status

该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。整数的低 16 位,其中又可以分为 最低八位次低八位(具体细节看图):

img

1.次低八位:拿子进程退出码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        int cnt = 5;   // 循环5次
        // child
        while (1) {
            // 五秒之内运行状态
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
 
            // 五秒之后子进程终止
            cnt--;
            if (cnt == 0) {
                break; 
            }
        }
 
        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右移八位再按位与上 0XFF,即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。

在这里插入图片描述

waitpid 经过系统调用,来读取子进程的pcb(eg. task_st…),这是为什么呢

操作系统不相信任何人,父进程用户无法直接读取子进程的 pcb ,要通过系统调用的接口

在这里插入图片描述

2.初识 core dump(核心转储)

在这里插入图片描述

它是操作系统在进程收到某些信号而终止运行时,将此时进程地址空间的内容以及有关进程状态的其他信息写出的一个磁盘文件。目前只需要知道,该信息是用于调试的。

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

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

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

💬 模拟异常退出的情况,让子进程一直跑,父进程一直等。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
 
int main (
    void
    ) 
{
    pid_t id = fork();
    if (id == 0) {
        // 子进程一直不退出,父进程会一直等待。
        // child
        while (1) {
            printf("我是子进程,我正在运行... Pid: %d\n", getpid());
            sleep(1);
        }
 
        exit(13);
    }
    else {
        printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());
        
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) {   // 等待成功
            printf(
                "等待成功,ret: %d, 我所等待的子进程退出码: %d\n, 退出信号是: %d", 
                ret, (status>>8)&0xFF, 
                status&0x7F
            );
        }
 
    }
}

现在我们直接 while(1) 死循环让子进程往死里跑,此时父进程由于调用了 waitpid,就会一直等待子进程,父进程就会持续阻塞

父进程看到子进程kill了,终于可以不用等了,可以给子进程收尸了

在这里插入图片描述

可以发现,使用-9号信号kill掉进程时,进程的退出信号就是9,然而当进程由于信号异常终止时,此时进程退出码是无意义的!

所以进程的等待可以理解为是 父进程在等给子进程退出记录


非阻塞轮询

options ->阻塞方式

waitpid(pid,&status,WNOHANG);

WNOHANG就是wait no hang,hang也就是悬挂,也就是非阻塞,等待子进程死亡,若父进程执行到waitpid时,子进程还没退出,则函数返回0后接着运行下面的代码,若执行到waitpid后子进程已经退出则返回退出子进程的pid

假如要期末考试了,我想找小张去自习室帮我复习,小张自己也在复习,我打电话问他什么时候复习完了,可以出门,情况解释如下

WNOHANG 夯住,非阻塞+循环

我给小张间隔打电话寻求帮忙,自己干等在楼下

阻塞式调用

打电话一直不挂

非阻塞轮询+自己的事情(最高效)

间隔打电话,自己也在复习

在这里插入图片描述

返回值

pid_t

ret_pid=0–所等待的条件还没有就绪,>0成功返回退出码,<0失败

代码验证

#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)
    {
        perror("fork");
        exit(1);
    }

    if(id==0)//子进程代码
    {
        int count = 5;
        while(count)
        {
            printf("[%d]我是子进程,我的pid是: %d\n",count,getpid());
            sleep(1);
            count--;
        }
        exit(55);//子进程执行完代码后退出
    }
    //父进程代码
    while(1)//循环访问子进程退出情况
    {
        int wait = waitpid(id,NULL,WNOHANG);
        if(wait>0)//子进程退出成功
        {
            printf("子进程退出成功,子进程pid: %d\n",wait);
            break;
        }
        else if(wait==0)//子进程还没退出,父进程干自己的事情
        {
            //此处简单模拟父进程干的事情
            printf("我是父进程,我现在要干一些别的事情\n");
        }
        else //等待子进程退出失败
        {
            perror("waitpid");
            exit(1);
        }
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

⚠️注:这里父进程可以执行任一任务,我使用printf打印,只是为了明显的看到父进程是没有阻塞等待的!


进程退出的宏

为了增加可读性,定义了接口宏,来查找退出码

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

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

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

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

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

WEXITSTATUS(status)

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

WIFEXITED(status)

waitpid意义:

  1. 返回记录子进程内核的数据结构

  2. Z->X

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

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

相关文章

安卓备忘录App开发

安卓备忘录APP开发,文章末尾有源码和apk安装包 目标用户: 普通安卓手机用户,需要一个简单易用的备忘录App来记录和管理日常事务。 主要功能: 用户注册: 用户可以创建一个账号,输入用户名和密码。 用户登录: 用户可以通过用户名和密码登录到应用。 用户信息存储: 用户名和…

【python】OpenCV—Feature Detection and Matching

参考学习来自OpenCV基础&#xff08;23&#xff09;特征检测与匹配 文章目录 1 背景介绍2 Harris角点检测3 Shi-Tomasi角点检测4 Fast 角点检测5 BRIEF 特征描述子6 ORB(Oriented Fast and Rotated Brief) 特征描述子7 SIFT(Scale Invariant Feature Transform) 特征描述子8 SU…

从一个(模型设计的)想法到完成模型验证的步骤

从有一个大型语言模型&#xff08;LLM&#xff09;设计的想法到完成该想法的验证&#xff0c;可以遵循以下实践步骤&#xff1a; 需求分析&#xff1a; 明确模型的目的和应用场景。确定所需的语言类型、模型大小和性能要求。分析目标用户群体和使用环境。 文献调研&#xff1a…

【全面讲解下iPhone新机官网验机流程】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

实现多数相加,但是传的参不固定

一、情景 一般实现的加法和减法等简单的相加减函数的话。一般都是写好固定传的参数。比如&#xff1a; function add(a,b) {return a b;} 这是固定的传入俩个&#xff0c;如果是三个呢&#xff0c;有人说当然好办&#xff01; 这样写不就行了&#xff01; function add(a…

protobuf及其使用

首先打开proto文件&#xff0c;定义一个类&#xff08;数据结构&#xff09;&#xff0c;并编写成员变量 使用protobuf编译器protoc编译proto文件为.pb.h和.pb.c文件(c) 看绿色注释部分&#xff1a;从左至右为&#xff0c;编译器&#xff0c;.proto文件的路径&#xff0c;编译的…

YOLO V7网络实现细节(2)—网络整体架构总结

YOLO V7网络整体架构总结 YOLO v7网络架构的整体介绍 不同GPU和对应模型&#xff1a; ​​​​​​​边缘GPU&#xff1a;YOLOv7-tiny普通GPU&#xff1a;YOLOv7​​​​​​​云GPU的基本模型&#xff1a; YOLOv7-W6 激活函数&#xff1a; YOLOv7 tiny&#xff1a; leaky R…

微深节能 煤码头自动化翻堆及取料集控系统 格雷母线

微深节能格雷母线高精度位移测量系统是一种先进的工业自动化位置检测解决方案&#xff0c;它被广泛应用于煤码头自动化翻堆及取料集控系统中&#xff0c;以实现对斗轮堆取料机等大型机械设备的精准定位和自动化控制。 系统原理简述&#xff1a; 格雷母线系统的工作原理基于电磁…

有趣的算法

目录&#xff1a; 1、百钱买百鸡 2、韩信点兵 1&#xff09;概述 2&#xff09;正常取余算法 3&#xff09;循环算法 1、百钱买百鸡 我国古代《算经》中的“百钱买百鸡”问题&#xff1a; 鸡翁一&#xff0c;值钱五&#xff1b;鸡母一&#xff0c;值钱三&#xff1b;鸡…

机器学习第四十六周周报 FMP

文章目录 week46 FMP摘要Abstract1. 题目2. Abstract3. FMP3.1 优化框架3.2 优化器 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程 5. 结论6.代码复现1. FMP2. fairGNN小结参考文献 week46 FMP 摘要 本周阅读了题为Chasing Fairness in Graphs: A GNN Architecture Per…

初识java—jdk17的一些新增特性

文章目录 前言一 &#xff1a; yield关键字二 &#xff1a;var关键字三 &#xff1a;密封类四 &#xff1a;空指针异常&#xff1a;五&#xff1a;接口中的私有方法&#xff1a;六&#xff1a;instanceof关键字 前言 这里介绍jdk17相对于jdk1.8的部分新增特性。 一 &#xff…

Spring Boot的无缝衔接:深入解析与实践

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ &#x1f680;The begin&#x1f697;点点关注&#xff0c;收藏不迷路&#x1f6a9; 引言 在快速迭代的软件开发环境中&#xff0c;无缝衔接是提升开发效率、降低维护成本、增强系统稳定性的关键。Spring Boo…

STM32芯片系列与产品后缀解读

一. 产品系列 STM32单片机是一系列基于ARM Cortex-M内核的32位微控制器&#xff0c;广泛应用于嵌入式系统中。 STM32系列由STMicroelectronics&#xff08;意法半导体&#xff09;开发和生产&#xff0c;并凭借其灵活的设计、丰富的外设和强大的生态系统&#xff0c;成为嵌入式…

LLM - 卷积神经网络(CNN)

1. 卷积神经网络结构&#xff1a;分为输入层&#xff0c;卷积层&#xff0c;池化层&#xff0c;全连接层&#xff1b; &#xff08;1&#xff09;首先进入输入层&#xff0c;对数据数据进行处理&#xff0c;将输入数据向量化处理&#xff0c;最终形成输入矩阵。 &#xff08;…

C++ 什么是虚函数?什么是纯虚函数,以及区别?(通俗易懂)

&#x1f4da; 当谈到虚函数时&#xff0c;通常是指在面向对象编程中的一种机制&#xff0c;它允许在派生类中重写基类的函数&#xff0c;并且能够通过基类指针或引用调用派生类中的函数。 目录 前言 &#x1f525; 虚函数 &#x1f525; 纯虚函数 &#x1f525; 两者区别…

用 Echarts 画折线图

https://andi.cn/page/621503.html

leetcode每日一题-3033. 修改矩阵

题目描述&#xff1a; 解题思路&#xff1a;简单题目&#xff0c;思路非常直接。对列进行遍历&#xff0c;记录下最大值&#xff0c;然后再遍历一遍&#xff0c;把-1替换为最大值。需要注意的是进行列遍历和行遍历是不同的。 官方题解&#xff1a; class Solution { public:v…

VRay渲染有什么技巧?渲染100邀请码1a12

渲染是视觉行业非常重要的一环&#xff0c;没有渲染就没有效果图&#xff0c;常用的渲染器有Vray&#xff0c;而Vray渲染有很多技巧&#xff0c;可以让渲染更快更省&#xff0c;下面我们总结下。 1、删除无用对象 检查场景&#xff0c;看是否有一些不需要渲染的物体和灯光&am…

将大型语言模型模块化打造协作智能体

B UILDING C OOPERATIVE E MBODIED A GENTS MODULARLY WITH L ARGE L ANGUAGE M ODELS 论文链接&#xff1a; https://arxiv.org/abs/2307.02485https://arxiv.org/abs/2307.02485 1.概述 在去中心化控制及多任务环境中&#xff0c;多智能体合作问题因原始感官观察、高昂…

绝区肆--2024 年AI安全状况

前言 随着人工智能系统变得越来越强大和普及&#xff0c;与之相关的安全问题也越来越多。让我们来看看 2024 年人工智能安全的现状——评估威胁、分析漏洞、审查有前景的防御策略&#xff0c;并推测这一关键领域的未来可能如何。 主要的人工智能安全威胁 人工智能系统和应用程…