【Linux系统】SIGCHLD 信号(选学了解)




在这里插入图片描述




SIGCHLD 信号


使用waitwaitpid函数可以有效地清理僵尸进程。父进程可以选择阻塞等待,直到子进程结束;或者采用非阻塞的方式,通过轮询检查是否有子进程需要被回收。

然而,无论是选择阻塞等待还是非阻塞的轮询方式,父进程与子进程之间都无法实现真正的异步执行,因为父进程仍需“分心”来管理子进程的状态。

当子进程终止时,会向父进程发送一个SIGCHLD信号,这个信号的默认行为是被忽略。不过,父进程可以通过设置自定义的SIGCHLD信号处理函数来改变这一行为。这样,父进程就可以专注于自己的任务,无需直接管理子进程的状态。一旦子进程终止,它会通知父进程,父进程只需要在其信号处理函数中调用waitwaitpid来清理子进程即可。

因此,我们能够通过自定义处理子进程发出的SIGCHLD信号,在接收到该信号时利用waitpid回收子进程资源。这种方法避免了主动等待子进程的结束,使得父子进程之间能够更加高效地异步执行。



演示代码如下:因为需要在函数 handler 中使用子进程 id,因此定义了一个全局变量 id

其实还有一种方法不用传id也不用定义全局变量:waitpid(-1, nullptr, 0);

-1 表示回收该父进程下的任意一个子进程

pid 参数为 -1 时,waitpid 函数会等待任何一个子进程的状态变化。这意味着它会捕获任何已经终止的子进程,并回收其资源。这对于处理多个子进程的情况非常有用,因为父进程不需要知道具体是哪个子进程终止了。

#include<iostream>  // 引入输入输出流库
#include<signal.h> // 引入信号处理库
#include<sys/wait.h> // 引入等待子进程状态改变的函数库
#include<sys/types.h> // 引入系统类型定义
#include<unistd.h> // 引入Unix标准函数库pid_t id; // 定义全局变量id,用于存储子进程ID// 定义信号处理函数
void handler(int signum)
{waitpid(id, nullptr, 0); // 等待子进程结束,回收子进程资源std::cout << "子进程退出, 我也退出了" << '\n'; // 输出子进程已退出的信息// 当接收到信号时,调用raise给自己发送9号信号(SIGKILL),强制终止进程raise(9);
}int main()
{id = fork(); // 创建子进程if(id < 0){perror("fork"); // 如果fork失败,输出错误信息return 1; // 返回错误码1}// 子进程逻辑if(id == 0){std::cout << "I am 子 process" << '\n'; // 子进程输出标识信息sleep(2); // 子进程暂停2秒exit(0); // 子进程正常退出}// 父进程逻辑else if (id > 0){std::cout << "I am 父 process" << '\n'; // 父进程输出标识信息signal(SIGCHLD, handler); // 设置SIGCHLD信号的处理函数为handlerint cnt = 0; // 初始化计数器while(1){   sleep(1); // 每秒暂停1秒std::cout << "cnt = " << cnt++ << '\n'; // 输出当前计数值}}   return 0; // 程序正常结束
}


运行结果如下:


在这里插入图片描述




问题一:如果同时多个子进程退出,是否会全部回收


但是这样通过信号回收子进程是有一定风险的!

因为信号是通过 pending 位图保存的,当一个父进程同时有多个子进程同时退出,同时发送 SIGCHLD 信号,则位图不能记录信号接收数量,就大概率会遗漏处理某些子进程,导致多个子进程僵尸的情况

验证如下:

#include <iostream>      // 引入输入输出流库
#include <signal.h>      // 引入信号处理库
#include <sys/wait.h>    // 引入等待子进程状态改变的函数库
#include <sys/types.h>   // 引入系统类型定义
#include <unistd.h>      // 引入Unix标准函数库// 定义信号处理函数
void handler(int signum)
{pid_t id = waitpid(-1, nullptr, 0); // 等待任意一个子进程结束,回收其资源std::cout << "回收子进程 id : " << id << '\n'; // 输出回收的子进程ID
}int main()
{pid_t id; // 定义变量id,用于存储子进程ID// 循环创建15个子进程for (int i = 1; i <= 15; ++i){id = fork(); // 创建子进程if (id < 0){perror("fork"); // 如果fork失败,输出错误信息return 1; // 返回错误码1}// 子进程逻辑if (id == 0){std::cout << "I am 子 process" << '\n'; // 子进程输出标识信息sleep(2); // 子进程暂停2秒exit(0); // 子进程正常退出}}// 父进程逻辑if (id > 0){std::cout << "I am 父 process" << '\n'; // 父进程输出标识信息signal(SIGCHLD, handler); // 设置SIGCHLD信号的处理函数为handler,当子进程结束时会触发此函数int cnt = 0; // 初始化计数器while (1){sleep(1); // 每秒暂停1秒std::cout << "cnt = " << cnt++ << '\n'; // 输出当前计数值}}return 0; // 程序正常结束
}


运行结果如下:不少子进程没有被回收,而是变成了僵尸进程


在这里插入图片描述




解决办法:循环等待回收子进程,否则退出


演示代码如下:

#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>void handler(int signum)
{while (true){pid_t id = waitpid(-1, nullptr, 0);if(id > 0){std::cout << "回收子进程 id : " << id << '\n';}else if(id < 0){std::cout << "回收完毕, 暂时结束回收\n";break;}}
}int main()
{pid_t id;for (int i = 1; i <= 15; ++i){id = fork();if (id < 0){perror("fork");return 1;}// 子进程if (id == 0){std::cout << "I am 子 process" << '\n';sleep(2);exit(0);}}// 父进程if (id > 0){std::cout << "I am 父 process" << '\n';signal(SIGCHLD, handler);int cnt = 0;while (1){sleep(1);std::cout << "cnt = " << cnt++ << '\n';}}return 0;
}


运行结果如下:自己可以去查询,可以确定当前没有僵尸子进程


在这里插入图片描述




问题二:如果有子进程不退出,问题一中的循环wait,是否会退出循环


演示代码:

// 创建一个不退出的子进程
id = fork();
if (id == 0)
{std::cout << "I am 不退出的子进程" << '\n';sleep(6);
}

结果就是 循环没退出,因为 waitpid 是阻塞式等待,会等待子进程退出,因为该子进程不退出则循环不退出一直阻塞等待


因此需要换成非阻塞式等待,同时当 waitpid 的返回值为 0,说明当前没有退出的子进程,则此时可以主动退出循环

pid_t id = waitpid(-1, nullptr, WNOHANG);

#include <iostream>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>void handler(int signum)
{while (true){pid_t id = waitpid(-1, nullptr, WNOHANG);if (id > 0){std::cout << "回收子进程 id : " << id << '\n';}else if(id == 0) // 表示没有子进程退出了(注意是没有退出的子进程了, 不是没有子进程){std::cout << "暂时没有子进程退出\n";break;}else if (id < 0) // 表示没有子进程了{std::cout << "waitpid error\n";break;}}
}int main()
{pid_t id;for (int i = 1; i <= 15; ++i){id = fork();if (id < 0){perror("fork");return 1;}// 子进程if (id == 0){std::cout << "I am 子 process" << '\n';sleep(2);exit(0);}}// 创建一个不退出的子进程id = fork();if (id == 0){std::cout << "I am 不退出的子进程" << '\n';sleep(6); // 时间长点, 模拟短时间内不退出}// 父进程if (id > 0){std::cout << "I am 父 process" << '\n';signal(SIGCHLD, handler);int cnt = 0;while (1){sleep(1);std::cout << "cnt = " << cnt++ << '\n';}}return 0;
}


运行结果如下


在这里插入图片描述



waitpid 系统调用的工作原理如下:

  • 阻塞等待:当 waitpid 被调用时,如果当前没有符合条件的已退出子进程,内核会让父进程进入阻塞状态。这意味着父进程会被挂起,不再占用CPU时间,直到有子进程的状态发生变化(通常是退出)。
  • 非阻塞等待:如果 waitpid 调用时传递了 WNOHANG 选项,内核会立即返回,即使没有子进程退出。在这种情况下,waitpid 不会阻塞父进程。
  • 状态变化通知:当一个子进程退出时,内核会检查该子进程的父进程是否正在等待子进程的状态变化。如果是,内核会唤醒父进程,使其从 waitpid 调用中返回,并传递子进程的退出状态。
  • 资源回收:父进程通过 waitpid 获得子进程的退出状态后,内核会释放子进程占用的资源,防止子进程变成僵尸进程。

意思是:父进程使用waitpid 系统调用时,若为阻塞等待,则OS将该父进程挂起(即阻塞),当目标子进程退出时,若该父进程正处于等待子进程退出的状态,则OS会传递子进程退出状态信息并使父进程退出阻塞状态(即使其从 waitpid 调用中返回)



子进程退出,OS是如何知道的,是因为OS需要轮询子进程的状态吗

当然不是OS轮询,前面讲解过 OS 就是一个躺在中断向量表上的一个代码块,OS的运行基本靠中断,因此进程退出也是通过中断通知OS,使其执行相应的后续”善后“工作


意思是子进程退出时,会向OS发送软件中断,此时进入内核态,执行该中断对应的中断处理例程:即更新子进程的 PCB,将子进程的状态标记为“已退出”(Zombie 状态),生成一个 SIGCHLD 信号并发送给父进程


子进程退出的详细过程

  1. 子进程调用 exitexit_group 系统调用
    • 子进程在调用 exitexit_group 系统调用时,会进入内核态。
      • 不是子进程退出子进程发送的软件中断,而是子进程在调用 exit 或 exit_group 系统调用触发的软件中断
  2. 进入内核态
    • 当子进程调用 exitexit_group 时,控制权转移到内核,进入内核态。
    • 内核会执行相应的中断处理例程(中断服务程序)。
  3. 中断处理例程
    • 内核的中断处理例程会执行以下操作:
      • 更新子进程的 PCB:内核会更新子进程的进程控制块(PCB),将子进程的状态标记为“已退出”(Zombie 状态)。
      • 生成 SIGCHLD 信号:内核会生成一个 SIGCHLD 信号并发送给父进程。
  4. 父进程接收 SIGCHLD 信号
    • 父进程接收到 SIGCHLD 信号后,会调用预先注册的信号处理函数(如 handler)默认为忽略


主动忽略子进程的 SIGCHLD

Linux下,将SIGCHLD的处理动作置为SIG IGN,这样fork出来的子进程在终止时会自动清理掉

由于UNIX 的历史原因,要想不产⽣僵⼫进程还有另外⼀种办法:⽗进程调 ⽤sigaction将
SIGCHLD的处理动作置为SIG_IGN,这样fork出来的⼦进程在终⽌时会⾃动清理掉,不 会产⽣僵⼫进程,

也不会通知⽗进程。系统默认的忽略动作和⽤⼾⽤sigaction函数⾃定义的忽略 通常是没有区别的,但这
是⼀个特例。此⽅法对于Linux可⽤,但不保证在其它UNIX系统上都可 ⽤。

signal(SIGCHLD, SIG_IGN);


底层原理:

父进程未调用 waitpid 的情况

  1. 子进程退出
    • 子进程调用 exitexit_group 系统调用,进入内核态。
    • 内核更新子进程的 PCB,将其状态标记为“已退出”(Zombie 状态)。
    • 内核生成 SIGCHLD 信号并发送给父进程。
  2. 父进程处理 SIGCHLD 信号
    • 如果父进程注册了 SIGCHLD 信号处理函数(如 handler),内核会调用该处理函数。
    • 在信号处理函数中,父进程可以调用 waitpid 来获取子进程的退出状态并释放资源。
  3. 父进程忽略 SIGCHLD 信号
    • 如果父进程将 SIGCHLD 信号的处理动作设置为 SIG_IGN,内核会自动回收子进程的资源,子进程不会变成僵尸进程。
    • 这意味着父进程不需要显式调用 waitwaitpid 来回收子进程的资源。


问题:父进程忽略了该信号,内核如何知道父进程忽略了,然后进行的自动回收子进程的资源

  1. 内核记录信号处理动作

    • 内核会记录每个进程的信号处理动作。当父进程调用 signalsigaction 设置 SIGCHLD 信号的处理动作时,内核会更新父进程的信号处理表。

    • 内核会记录 SIGCHLD 信号的处理动作为 SIG_IGN

子进程调用退出

  1. 内核生成 SIGCHLD 信号
    • 内核生成 SIGCHLD 信号并准备发送给父进程。
    • 内核会检查父进程的信号处理表,查看 SIGCHLD 信号的处理动作。
  2. 检查信号处理动作
    • 如果父进程的信号处理动作是 SIG_IGN,内核会知道父进程忽略了 SIGCHLD 信号。
    • 内核会自动回收子进程的资源,子进程不会变成僵尸进程。


问题:系统对该信号的默认处理不就是忽略吗,为什么我们还要自己主动忽略

系统默认的忽略动作和⽤⼾⽤sigaction函数⾃定义的忽略 通常是没有区别的,但这
是⼀个特例。此⽅法对于Linux可⽤,但不保证在其它UNIX系统上都可 ⽤。

其实,因为位图本身一次只能记录一个进程退出信号,因此即使循环等待等操作,还是会有极小概率处理不了某些退出子进程

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

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

相关文章

【R语言】获取数据

R语言自带2种数据存储格式&#xff1a;*.RData和*.rds。 这两者的区别是&#xff1a;前者既可以存储数据&#xff0c;也可以存储当前工作空间中的所有变量&#xff0c;属于非标准化存储&#xff1b;后者仅用于存储单个R对象&#xff0c;且存储时可以创建标准化档案&#xff0c…

Vim的基础命令

移动光标 H(左) J(上) K(下) L(右) $ 表示移动到光标所在行的行尾&#xff0c; ^ 表示移动到光标所在行的行首的第一个非空白字符。 0 表示移动到光标所在行的行首。 W 光标向前跳转一个单词 w光标向前跳转一个单词 B光标向后跳转一个单词 b光标向后跳转一个单词 G 移动光标到…

Guided Decoding (借助FSM,有限状态自动机)

VLLM对结构化输出的支持&#xff1a; vllm/docs/source/features/structured_outputs.md at main vllm-project/vllm GitHub VLLM对tool call的支持&#xff1a; vllm/docs/source/features/tool_calling.md at main vllm-project/vllm GitHub 以上指定输出格式&#xf…

IFeatureWorkspace.CreateFeatureClass(),报错对COM组件的调用返回了错误 HRESULT E_FAIL

1、问题描述&#xff1a;在AE开发中&#xff0c;新增一个空的shpfile文件的时候&#xff0c;报错&#xff0c;如下图&#xff1a; 2、原因分析&#xff1a;产生此问题的原因是未设置默认字段的默认参数&#xff0c;特别是未设置IGeometryDef 参数。 3、解决方案&#xff1a;在…

算法题(48):反转链表

审题&#xff1a; 需要我们将链表反转并返回头结点地址 思路&#xff1a; 一般在面试中&#xff0c;涉及链表的题会主要考察链表的指向改变&#xff0c;所以一般不会允许我们改变节点val值。 这里是单向链表&#xff0c;如果要把指向反过来则需要同时知道前中后三个节点&#x…

Java 大视界 -- Java 大数据在智能医疗影像诊断中的应用(72)

💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也期待你毫无保留地分享独特见解,愿我们于此携手成长,共赴新程!💖 一、…

list容器(详解)

list的介绍及使用&#xff08;了解&#xff0c;后边细讲&#xff09; 1.1 list的介绍&#xff08;双向循环链表&#xff09; https://cplusplus.com/reference/list/list/?kwlist&#xff08;list文档介绍&#xff09; 1. list是可以在常数范围内在任意位置进行插入和删除的序…

MapReduce分区

目录 1. MapReduce分区1.1 哈希分区1.2 自定义分区 2. 成绩分组2.1 Map2.2 Partition2.3 Reduce 3. 代码和结果3.1 pom.xml中依赖配置3.2 工具类util3.3 GroupScores3.4 结果 参考 本文引用的Apache Hadoop源代码基于Apache许可证 2.0&#xff0c;详情请参阅 Apache许可证2.0。…

【C++STL标准模板库】二、STL三大组件

文章目录 1、容器2、算法3、迭代器 二、STL三大组件 1、容器 容器&#xff0c;置物之所也。 研究数据的特定排列方式&#xff0c;以利于搜索或排序或其他特殊目的&#xff0c;这一门学科我们称为数据结构。大学信息类相关专业里面&#xff0c;与编程最有直接关系的学科&…

算法题(57):找出字符串中第一个匹配项的下标

审题: 需要我们根据原串与模式串相比较并找到完全匹配时子串的第一个元素索引&#xff0c;若没有则返回-1 思路&#xff1a; 方法一&#xff1a;BF暴力算法 思路很简单&#xff0c;我们用p1表示原串的索引&#xff0c;p2表示模式串索引。遍历原串&#xff0c;每次遍历都匹配一次…

求组合数(递推法、乘法逆元、卢卡斯定理、分解质因数)

文章目录 递推法 10^4代码 乘法逆元 10^6代码 卢卡斯定理 1 0 18 m o d 1 0 6 10^{18}mod 10^6 1018mod106代码 分解质因数 常规的解法就不多加赘述了&#xff0c;如&#xff08;分子/分母&#xff0c;边乘边除&#xff09;&#xff0c;本文讲述以下方法&#xff1a; 递推法 了…

WPF进阶 | WPF 动画特效揭秘:实现炫酷的界面交互效果

WPF进阶 | WPF 动画特效揭秘&#xff1a;实现炫酷的界面交互效果 前言一、WPF 动画基础概念1.1 什么是 WPF 动画1.2 动画的基本类型1.3 动画的核心元素 二、线性动画详解2.1 DoubleAnimation 的使用2.2 ColorAnimation 实现颜色渐变 三、关键帧动画深入3.1 DoubleAnimationUsin…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.27 NumPy+Pandas:高性能数据处理的黄金组合

2.27 NumPyPandas&#xff1a;高性能数据处理的黄金组合 目录 #mermaid-svg-x3ndEE4hrhO6WR6H {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-x3ndEE4hrhO6WR6H .error-icon{fill:#552222;}#mermaid-svg-x3ndEE4hr…

swagger使用指引

1.swagger介绍 在前后端分离开发中通常由后端程序员设计接口&#xff0c;完成后需要编写接口文档&#xff0c;最后将文档交给前端工程师&#xff0c;前端工程师参考文档进行开发。 可以通过一些工具快速生成接口文档 &#xff0c;本项目通过Swagger生成接口在线文档 。 什么…

DeepSeek API文档解读(对话模块)

对话&#xff08;Chat&#xff09; 对话补全 报文message对象数组 System message name 一个在线聊天系统&#xff0c;其中涉及多个用户和一个系统管理员。在这个系统中&#xff0c;每个用户都可以发送消息&#xff0c;并且系统管理员可以监控和回复这些消息。为了区分不同…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.19 线性代数核武器:BLAS/LAPACK深度集成

2.19 线性代数核武器&#xff1a;BLAS/LAPACK深度集成 目录 #mermaid-svg-yVixkwXWUEZuu02L {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-yVixkwXWUEZuu02L .error-icon{fill:#552222;}#mermaid-svg-yVixkwXWUEZ…

Linux——文件与磁盘

1. 磁盘结构 磁盘在我们的计算机中有着重要的地位&#xff0c;当文件没有被打开时其数据就存储在磁盘上&#xff0c;要了解磁盘的工作原理先要了解磁盘的结构。 1.1 磁盘的物理结构 以传统的存储设备机械硬盘为例&#xff0c;它通过磁性盘片和磁头来读写数据。磁盘内部有多个旋…

【Envi遥感图像处理】010:归一化植被指数NDVI计算方法

文章目录 一、NDVI简介二、NDVI计算方法1. NDVI工具2. 波段运算三、注意事项1. 计算结果为一片黑2. 计算结果超出范围一、NDVI简介 归一化植被指数,是反映农作物长势和营养信息的重要参数之一,应用于遥感影像。NDVI是通过植被在近红外波段(NIR)和红光波段(R)的反射率差异…

UE虚幻引擎No Google Play Store Key:No OBB found报错如何处理

UE虚幻引擎No Google Play Store Key&#xff1a;No OBB found报错如何处理&#xff1f; 问题描述&#xff1a; UE成功打包APK并安装过后&#xff0c;启动应用时提示&#xff1a; No Google Play Store KeyNo OBB found and no store key to try to download. Please setone …

【Redis】主从模式,哨兵,集群

主从复制 单点问题&#xff1a; 在分布式系统中&#xff0c;如果某个服务器程序&#xff0c;只有一个节点&#xff08;也就是一个物理服务器&#xff09;来部署这个服务器程序的话&#xff0c;那么可能会出现以下问题&#xff1a; 1.可用性问题&#xff1a;如果这个机器挂了…