Linux-等待子进程

news2024/11/25 22:30:35

参考资料:《Linux环境编程:从应用到内核》

僵尸进程

进程退出时会进行内核清理,基本就是释放进程所有的资源,这些资源包括内存资源、文件资源、信号量资源、共享内存资源,或者引用计数减一,或者彻底释放。不过,进程的退出其实并没有将所有的资源完全释放,仍保留了少量的资源,比如进程的PID依然被占用着,不可被系统分配。此时的进程不可运行,事实上也没有地址空间让其运行,进程进入僵尸状态。

僵尸进程依然保留的资源有进程控制块task_struct、内核栈等。这些资源不释放是为了提供一些重要的信息,比如进程为何退出,是收到信号退出还是正常退出,进程退出码是多少,进程一共消耗了多少系统CPU时间,多少用户CPU时间,收到了多少信号,发生了多少次上下文切换,最大内存驻留集是多少,产生多少缺页中断?等等。

父进程通过fork()函数创建子进程后,子进程退出,但父进程没有调用wait()或waitpid()回收子进程的资源的话,这个时候子进程变成僵尸进程。

清除僵尸进程有以下两种方法:

  • 父进程调用wait函数或waitpid函数,为子进程“收尸”。
  • 父进程退出,init进程会为子进程“收尸”。

一般而言,系统不希望大量进程长期处于僵尸状态,因为会浪费系统资源。除了少量的内存资源外,比较重要的是进程ID。僵尸进程并没有将自己的进程ID归还给系统,而是依然占有这个进程ID,因此系统不能将该ID分配给其他进程。

如果我们不关心子进程的退出状态,就应该将父进程对SIGCHLD的处理函数设置为SIG_IGN,或者在调用sigaction函数时设置SA_NOCLDWAIT标志位。这两者都会明确告诉子进程,父进程很“绝情”,不会为子进程“收尸”。子进程退出的时候,内核会检查父进程的SIGCHLD信号处理结构体是否设置了SA_NOCLDWAIT标志位,或者是否将信号处理函数显式地设为SIG_IGN。如果是,则autoreap为true,子进程发现autoreap为true也就“死心”了,不会进入僵尸状态,而是调用release_task函数“自行了断”了。

等待子进程之wait()

Linux提供了wait()函数来获取子进程的退出状态:

#include <sys/wait.h>

pid_t wait(int* status);

成功时,返回已退出子进程的进程ID;失败时,则返回-1并设置errno,常见的errno。

在这里插入图片描述

注意父子进程是两个进程,子进程退出和父进程调用wait()函数获取子进程状态在时间上是独立的,所以会出现以下两种情况:

  • 子进程先退出,父进程后调用wait()函数
  • 父进程先调用wait()函数,子进程后退出

对于第一种情况,子进程执行完毕已经退出,只留下了少量的信息等待父进程回收。当父进程调用wait()函数时候,父进程获取到子进程的状态信息,wait函数立刻返回。

对于第二种情况,如果父进程先调用wait()函数,此时子进程还没退出,wait()函数就会阻塞在这里,直到某个子进程退出。

// 示例

#include <iostream>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
    pid_t pid = fork();

    if (pid < 0)
    {
        std::cout << "子进程创建失败" << std::endl;

        return -1;
    }
    else if (pid == 0)
    {
        // 这是子进程
        std::cout << "打印子进程的进程ID: " << getpid() << std::endl;

        sleep(10);
    }
    else
    {
        // 子进程还没退出,会阻塞在wait函数这里
        std::cout << "子进程还没退出" << std::endl;

        // 这是父进程
        pid_t pc = wait(nullptr);

        std::cout << "子进程退出, 进程ID: " << pc << std::endl;
    }

    return 0;
}

[root@Zhn test4]# g++ test1.cpp -o test1
[root@Zhn test4]# ./test1
子进程还没退出
打印子进程的进程ID: 3400
子进程退出, 进程ID: 3400
[root@Zhn test4]# 

可以看到,父进程阻塞在wait()函数,等待子进程退出后执行。

wait()函数等待的是任意一个子进程,任何一个子进程退出都会让其立刻返回。当多个子进程都处于僵尸状态时,wait()函数获取到其中一个子进程的信息后立刻返回。由于wait()函数不会接收pid_t类型的参数,所以也不能明确等待某一个子进程的退出。

那么一个进程如何等待所有子进程都返回呢?

wait()函数返回有三种可能性:

  • 等到了子进程退出,获取其退出信息,返回值是该子进程的进程ID;
  • 等待过程中,收到了信号,信号打断了系统调用,并且注册信号处理函数时并没有设置SA_RESTART标志位,这样的话系统调用就不会重启wait()函数,wait()函数会返回-1,并且errno设置为EINTR;
  • 已经成功等待了所有子进程退出,没有子进程的退出信息需要接收,这种情况下,wait()函数返回-1并且errno设置为ECHILD。

《Linux/Unix系统编程手册》给出下面的代码来等待所有子进程的退出:

while((childPid = wait(NULL)) != -1)
    continue;
    
if(errno !=ECHILD)
    errExit("wait");

但是这种方法忽略了wait()函数被信号中断这种情况,如果wait()函数被信号中断,上述代码就不能成功等待所有子进程退出。

所以我们需要把上面代码封装以下:

pid_t r_wait(int *stat_loc)
{
    int retval;
    
    // 如果被信号中断,表达式为真,重启wait()函数
    while(((retval = wait(stat_loc)) == -1 && (errno == EINTR));
    
    return retval;
}

while((childPid = r_wait(NULL)) != -1)
    continue;
    
If(errno != ECHILD)
{
    /*some error happened*/
}

由上面可以看出wait()函数具有一些局限性:

  • 不能等待特定的子进程:如果进程存在多个子进程,而它只想获取某一个子进程的退出状态,就需要一一等待,通过返回的进程ID判断是不是自己关心的子进程;
  • 如果不存在子进程退出,wait函数就会阻塞:有时候,只是想尝试获取子进程退出的状态,如果没有子进程退出就立刻返回,不需要阻塞等待;
  • wait()函数只能发现子进程的终止事件:如果某些子进程因某信号而停止,或者停止的子进程收到SIGCONT信号又恢复执行,这些事wait函数无法获知。

为了解决这三个缺点,引入了waitpid()函数。

等待子进程之waitpid()

waitpid()函数接口如下:

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

waitpid()与wait()相同的地方:

  • 返回值的含义相同,都是终止子进程或因信号停止或因信号恢复而执行的子进程的进程ID。
  • status的含义相同,都是用来记录子进程的相关事件,后面将会详细介绍。

接下来介绍waitpid()函数特有的功能:

第一个参数是pid_t类型,所以waitpid()可以明确指定要等待哪一个子进程的退出:

  • pid > 0:表示等待进程ID为pid的子进程;
  • pid = 0:表示等待与调用进程同一个进程组的任意子进程,因为子进程可以设置进程组,那么如果某些子进程和父进程不在同一个进程组,这样的进程就不关心它的退出状态;
  • pid = -1:表示等待任意子进程,同wait类似,waitpid(-1, &status, 0)与wait(&status)完全等价;
  • pid < -1:等待所有子进程中,进程组ID与pid绝对值相等的子进程。

第二个参数是int*类型:

无论是wait()还是waitpid(),都有一个status变量,这个变量是一个int类型指针。可以传递mullptr,表示不关心子进程退出状态,不为空就可以获得更多子进程的状态。

  1. 子进程是正常退出的:
    在这里插入图片描述

  2. 进程收到信号,导致退出:
    在这里插入图片描述

  3. 进程收到信号,被停止:
    在这里插入图片描述

  4. 子进程恢复执行
    在这里插入图片描述

第三个参数options是一个位掩码,可以同时存在多个标志位。如果options没有设置任何标志位,其行为与wait类似,即阻塞等待与pid匹配的进程退出。

options的标志位可以是如下标志位的组合:

  • WUNTRACED:除了关心终止子进程的信息,也关心那些因信号而停止的子进程信息。
  • WCONTINUED:除了关心终止子进程的信息,也关心那些因收到信号而恢复执行的子进程的状态信息。
  • WNOHANG:指定的子进程并未发生状态变化,立刻返回,不会阻塞。这种情况下返回值是0。如果调用进程并没有与pid匹配的子进程,则返回-1,并设置errno为ECHILD,根据返回值和errno可以区分这两种情况。

Linux提供了SIGSTOP(信号值19)和SIGCONT(信号值18)两个信号,来完成暂停和恢复的动作,可以通过执行kill-SIGSTOP或kill-19来暂停一个进程的执行,通过执行kill-SIGCONT或kill-18来让一个暂停的进程恢复执行。

// 示例

// 等待子进程之waitpid

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

int main()
{
    int status;

    pid_t pid = fork();

    if (pid < 0)
    {
        std::cout << "子进程创建失败" << std::endl;

        return -1;
    }
    else if (pid == 0)
    {
        std::cout << "这是子进程: " << getpid() << std::endl;

        sleep(3);

        exit(3);
    }
    else
    {
        std::cout << "这是父进程: " << getpid() << std::endl;

        pid_t pc = waitpid(0, &status, WNOHANG);

        if (pc == 0)
            std::cout << "此时没有子进程退出" << std::endl;
        else if (WIFEXITED(status))
            std::cout << "子进程: " << pc << "正常退出, 退出状态为" << WEXITSTATUS(status) << std::endl;
        else
            std::cout << "子进程: " << pc << "非正常退出" << std::endl;
    }

    return 0;
}

[root@Zhn test4]# g++ test2.cpp -o test2
[root@Zhn test4]# ./test2
这是父进程: 4551
此时没有子进程退出
这是子进程: 4552
[root@Zhn test4]# 

示例中,父进程waitpid函数的参数设置的意思是等待父进程同一进程组的任意子进程退出事件,如果没有子进程退出,则返回值为0,子进程中睡眠了3秒,这时父进程调用waitpid,发现没有子进程退出,所以返回值为0。

这是一个简单的小例子,其他例子都是举一反三。

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

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

相关文章

14届蓝桥杯 C/C++ B组 T5 接龙排序 (最长上升子序列DP+优化)

不难发现这是一个LIS问题&#xff0c;但是如果直接套用LIS的模版&#xff0c;在数据范围到达 1 e 5 1e5 1e5 的情况下&#xff0c;就只能够得到一半的分数&#xff0c;所以我们需要对其进行优化。 首先给出暴力的代码&#xff1a; #include<iostream> using namespace…

python+django教师业绩考评考核评分系统flask

在设计过程中&#xff0c;将参照一下国内外的一些同类网站&#xff0c;借鉴下他们的一些布局框架&#xff0c;将课题要求的基本功能合理地组织起来&#xff0c;形成友好、高效的交互过程。开发的具体步骤为&#xff1a;   第一步&#xff0c;进行系统的可行性分析&#xff0c…

Java 基于微信小程序的校园请教小程序的研究与实现,附源码

博主介绍&#xff1a;✌程序员徐师兄、10年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447…

基于单片机手机屏蔽器系统仿真设计

**单片机设计介绍&#xff0c;基于单片机手机屏蔽器系统仿真设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机手机屏蔽器系统的仿真设计主要涉及到手机信号屏蔽的原理、单片机控制逻辑设计、仿真软件的选择与使用以…

2024北京安全生产展|劳保用品展|安全生产防护用品展会

​作为安全生产与防护用品领域的行业盛会&#xff0c;2024北京安全生产与防护用品展览会将于2024年6月26日至28日在北京.首钢国际会展中心隆重举行。展会紧跟安全生产与防护用品行业发展&#xff0c;充分调研行业需求&#xff0c;以前瞻性的技术研讨、产品展示、产学研对接、需…

VSCode输入花括号{}}会多一个解决方案

打开设置 搜索Closing Brackets 选择BeforeWhitespace 选完后重启下VSCode即可

rust学习(tokio中tcp_stream调用的问题)

问题&#xff1a; 我们涉及了一个socket连接的类&#xff0c;每次收到数据以后&#xff0c;我们都会把tokio::net::TcpStream对应的tcp_stream传递给其他线程。 起初设计如下&#xff1a; pub struct TarNetStream {stream:TcpStream, //1... }pub trait TarListener {fn on…

【C++】类和对象①(什么是面向对象 | 类的定义 | 类的访问限定符及封装 | 类的作用域和实例化 | 类对象的存储方式 | this指针)

目录 前言 什么是面向对象&#xff1f; 类的定义 类的访问限定符及封装 访问限定符 封装 类的作用域 类的实例化 类对象的存储方式 this指针 结语 前言 最早的C版本&#xff08;C with classes&#xff09;中&#xff0c;最先加上的就是类的机制&#xff0c;它构成…

【大数据】大数据概论与Hadoop

目录 1.大数据概述 1.1.大数据的概念 1.2.大数据的应用场景 1.3.大数据的关键技术 1.4.大数据的计算模式 1.5.大数据和云计算的关系 1.6.物联网 2.Hadoop 2.1.核心架构 2.2.版本演进 2.3.生态圈的全量结构 1.大数据概述 1.1.大数据的概念 大数据即字面意思&#x…

tensorflow.js 如何使用opencv.js通过面部特征点估算脸部姿态并绘制示意图

文章目录 前言一、实现步骤1. 获取所需特征点的索引2. 使用opencv.js 计算俯仰角、水平角和翻滚角cv.solvePnP介绍cv.solvePnP原理运行代码查看效果 3.绘制姿态示意直线添加canvas元素计算姿态直线坐标并绘制 总结 前言 在计算机视觉领域&#xff0c;估算脸部姿态是一项具有挑…

基于大数据的汽车信息可视化分析预测与推荐系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 本项目通过集成网络爬虫技术&#xff0c;实时获取海量汽车数据&#xff1b;运用先进的ARIMA时序建模算法对数据进行深度挖掘和分析&#xff1b;结合flask web系统和echarts可视化工具&#xff0c;…

vue做游戏vue游戏引擎vue小游戏开发

Vue.js 是一个构建用户界面的渐进式JavaScript框架&#xff0c;它同样可以用于游戏开发。使用 Vue 开发游戏通常涉及以下几个关键步骤和概念&#xff1a; 1. 了解 Vue 的核心概念 1 在开始使用 Vue 进行游戏开发之前&#xff0c;你需要理解 Vue 的一些核心概念&#xff0c;如…

Python可视化之seaborn

文章目录 seaborn介绍1.解决坐标轴刻度负号乱码2. 解决中文乱码问题3. 忽略警告4.风格选择5.自定义坐标轴6.自定义绘图元素比例7.一元分布图8.二元分布图9.多元矩阵图10.其他常见图形散点图线图柱状图计数图 seaborn介绍 seaborn是在matplotlib基础上开发的一套API&#xff0c…

网络驱动器设备:ISCSI服务器

文章目录 使用ISCSI服务部署网络存储ISCSI技术介绍创建RAID磁盘整列配置ISCSI服务端配置Windows端配置Linux客户端iSCSI服务器CHAP单向认证配置Linux端具体步骤Windows端具体步骤 使用ISCSI服务部署网络存储 主机名IPISCSI服务端192.168.200.10ISCSI客户端192.168.200.20Windo…

Ubuntu22.04修改默认窗口系统为X11

Ubuntu22.04安装默认窗口系统为Wayland&#xff08;通过设置->关于可以看到&#xff09;。 一、用Ubuntu on Xorg会话登录 用户登录时&#xff0c;点“未列出”&#xff0c;输入用户名后&#xff0c;在登录界面底部的齿轮图标中&#xff0c;选择 "Ubuntu on Xorg&quo…

Stable Diffusion——SDXL Turbo让 AI 出图速度提高10倍

摘要 在本研究中&#xff0c;我们提出了一种名为对抗扩散蒸馏&#xff08;ADD&#xff09;的创新训练技术&#xff0c;它能够在1至4步的采样过程中&#xff0c;高效地对大规模基础图像扩散模型进行处理&#xff0c;同时保持图像的高质量。该方法巧妙地结合了分数蒸馏技术&…

用TensorBoard可视化PyTorch

一、TensorBoard与PyTorch配合使用的基本步骤 PyTorch可以直接与TensorBoard进行集成&#xff0c;因为TensorBoard是一个独立于TensorFlow之外的可视化工具。TensorBoard被设计为支持机器学习实验的可视化&#xff0c;如训练的进度和结果等。PyTorch中的torch.utils.tensorboa…

【数据结构】考研真题攻克与重点知识点剖析 - 第 6 篇:图

前言 本文基础知识部分来自于b站&#xff1a;分享笔记的好人儿的思维导图与王道考研课程&#xff0c;感谢大佬的开源精神&#xff0c;习题来自老师划的重点以及考研真题。此前我尝试了完全使用Python或是结合大语言模型对考研真题进行数据清洗与可视化分析&#xff0c;本人技术…

智慧安防系统EasyCVR视频汇聚平台接入大华设备无法语音对讲的原因排查与解决

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流&#xff0c;视频画面1、4、9、16个可选&#xff0c;支持自定义视频轮播。EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标…

数据可视化-ECharts Html项目实战(10)

在之前的文章中&#xff0c;我们学习了如何在ECharts中编写雷达图&#xff0c;实现特殊效果的插入运用&#xff0c;函数的插入&#xff0c;以及多图表雷达图。想了解的朋友可以查看这篇文章。同时&#xff0c;希望我的文章能帮助到你&#xff0c;如果觉得我的文章写的不错&…