Linux-等待子进程

参考资料:《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;elsestd::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.rhkb.cn/news/301531.html

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

相关文章

Qt 实现的万能采集库( 屏幕/相机/扬声器/麦克风采集)

【写在前面】 之前应公司需要&#xff0c;给公司写过一整套直播的库( 推拉流&#xff0c;编解码)&#xff0c;类似于 libobs。 结果后来因为没有相关项目&#xff0c;便停止开发&维护了。 不过里面很多有用的组件&#xff0c;然后也挺好用的&#xff0c;遂开源出来一部分。…

Leetcode - 2009. 使数组连续的最少操作数

文章目录 解析排序 原地去重 滑动窗口AC CODE 题目链接&#xff1a;Leetcode - 2009. 使数组连续的最少操作数 解析 题中所述的连续数组就是一串连续的自然数&#xff0c;想问需要多少次操作能将原数组变为连续的数。 我们排序去重&#xff0c;用逆向思维想能保留的数字数目…

红外疼痛医学分会成立大会暨首届学术交流会即将盛大开幕

2024年4月7日&#xff0c;中国中医药研究促进会官网发布“关于召开红外疼痛医学分会成立大会暨首届学术交流会的第三轮通知”通知&#xff0c;大会开幕在即&#xff0c;这充分显示了官方对此次活动的高度重视。 本次大会将于 2024年4月19日至21日在重庆海兰云天海琴酒店隆重举行…

QT 使用redis ,连接并使用

一.redis安装 链接&#xff1a;https://pan.baidu.com/s/17fXKOj5M4VIypR0y5_xtHw 提取码&#xff1a;1234 1.下载得到文件夹如图 course_redis为安装包。 2.启动Redis服务 把安装包解压到某个路径下即可。 打开cmd窗口&#xff0c;切换到Redis安装路径&#xff0c;输入 r…

从人机界面设计黄金三法则视角看 ChatGPT 的界面设计的“好”与“坏”

热门文章推荐&#xff1a; &#xff08;1&#xff09;《为什么很多人工作 3 年 却只有 1 年经验&#xff1f;》&#xff08;2&#xff09;《一文掌握大模型提示词技巧&#xff1a;从战略到战术巧》&#xff08;3&#xff09;《AI 时代&#xff0c;程序员的出路在何方&#xff1…

Jenkins 安装部署

1、安装下载 官网地址&#xff1a;Jenkins 下载 war 包 1、前置环境 JDK 环境&#xff08;根据 Jenkins 版本不同&#xff0c;需要的 JDK 版本不同&#xff0c;目前需要 JDK11 的版本来支持&#xff09;Maven maven 官网下载压缩包 &#xff0c;并将其传输到服务器&#xf…

模拟多模态大模型gradio使用教程

文章目录 前言一、使用change监听demo1、代码3、网页结果显示4、输入信息结果显示 二、Button功能的demo1、代码2、结果显示3、变换参数结果显示 三、参数gr.Slider滑动demo1、代码2、结果显示3、具有下拉参数代码4、具有下拉结果显示 四、模拟多模态大模型网页demo1、代码2、界…

【机器学习300问】59、计算图是如何帮助人们理解反向传播的?

在学习神经网络的时候&#xff0c;势必会学到误差反向传播&#xff0c;它对于神经网络的意义极其重大&#xff0c;它是训练多层前馈神经网络的核心算法&#xff0c;也是机器学习和深度学习领域中最为重要的算法之一。要正确理解误差反向传播&#xff0c;不妨借助一个工具——计…

测开面经(pytest测试案例,接口断言,多并发断言)

pytest对用户登录接口进行自动化脚本设计 a. 创建一个名为"test_login.py"的测试文件&#xff0c;编写以下测试脚本 import pytest import requests# 测试用例1&#xff1a;验证登录成功的情况 # 第一个测试用例验证登录成功的情况&#xff0c;发送有效的用户名和密…

linux:du和df区别

文章目录 1. 概述2. du 命令2. df 命令3. 区别总结 1. 概述 du 和 df 都是 Linux 系统中用于查看磁盘空间使用情况的命令&#xff0c;但它们的功能和用法有所不同。 2. du 命令 du 是 “disk usage” 的缩写&#xff0c;用于显示文件或目录的磁盘使用情况。du 命令用于查看指…

Redis客户端

2.Redis客户端 安装完成Redis&#xff0c;我们就可以操作Redis&#xff0c;实现数据的CRUD了。这需要用到Redis客户端&#xff0c;包括&#xff1a; 命令行客户端图形化桌面客户端编程客户端 2.1.Redis命令行客户端 Redis安装完成后就自带了命令行客户端&#xff1a;redis-…

二分查找 -- 力扣(LeetCode)第704题

题目 https://leetcode.cn/problems/binary-search/description/ 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 示例…

计算机网络针对交换机的配置

实验 目的 交换机的基本配置&#xff0c;交换机VLAN配置 实验条件 Windows&#xff0c;Cisco packet tracer 实验 内容 交换机的基本配置&#xff0c;交换机VLAN配置 实验 过程 一、交换机的基本配置 进入特权模式 Switch>enable 进入配置模式 Switch#configure ter…

腾讯电商运营起来竟然这么简单!视频号小店操作玩法一文详解!

大家好&#xff0c;我是电商小布。 在新型电商玩法的兴起下&#xff0c;很多的平台都在电商行业内分到了一杯羹。 腾讯自然也就坐不住了&#xff0c;背靠自身的视频号平台&#xff0c;推出了视频号小店这个项目。 有很多的小伙伴想要趁着这个初期阶段&#xff0c;来加入到其…

数据结构与算法:哈希表

目录 1.哈希表和哈希 1.1.知识引入 1.2.为什么需要哈希表呢&#xff1f; 2.简易的哈希表 2.1.哈希表的基础结构 2.2.如何实现基础的哈希表 2.2.1.增 2.2.2.删 2.2.3.查 2.3.泛型编程下的哈希表 3.简易的哈希桶 1.哈希表和哈希 1.1.知识引入 哈希表&#xff08;Hash …

代码随想录-算法训练营day02【滑动窗口、螺旋矩阵】

专栏笔记&#xff1a;https://blog.csdn.net/weixin_44949135/category_10335122.html https://docs.qq.com/doc/DUGRwWXNOVEpyaVpG?uc71ed002e4554fee8c262b2a4a4935d8977.有序数组的平方 &#xff0c;209.长度最小的子数组 &#xff0c;59.螺旋矩阵II &#xff0c;总结 建议…

【C++进阶】用哈希实现unordered_set和unordered_map的模拟

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;c大冒险 总有光环在陨落&#xff0c;总有新星在闪烁 前言&#xff1a; 之前我…

烧坏两块单片机,不知道原因?

没有看你的原理图&#xff0c;以下是造成烧毁芯片的几个环节&#xff1a; 1. 最大的可能性是你的单片机电机控制输出与电机驱动电路没有隔离。 我的经验&#xff0c;使用STM32控制电机&#xff0c;无论是直流电机脉宽调制&#xff0c;还是步进电机控制&#xff0c;控制电路与…

Ubuntu 20.04.06 PCL C++学习记录(十六)

[TOC]PCL中点云分割模块的学习 学习背景 参考书籍&#xff1a;《点云库PCL从入门到精通》以及官方代码PCL官方代码链接,&#xff0c;PCL版本为1.10.0&#xff0c;CMake版本为3.16 学习内容 用一组点云数据做简单的平面的分割 源代码及所用函数 源代码 #include<iostr…

机器学习笔记 - 深度学习遇到超大图像怎么办?使用 xT 对极大图像进行建模论文简读

作为计算机视觉研究人员,在处理大图像时,避免不了受到硬件的限制,毕竟大图像已经不再罕见,手机的相机和绕地球运行的卫星上的相机可以拍摄如此超大的照片,遇到超大图像的时候,我们当前最好的模型和硬件都会达到极限。 所以通常我们在处理大图像时会做出两个次优选择之一:…