Linux系统中处理子进程的终止问题
1. 理解子进程终止的机制
在Unix/Linux系统中,当子进程终止时,会向父进程发送一个SIGCHLD信号。父进程需要捕捉这个信号,并通过调用wait()或waitpid()等函数来回收子进程的资源。这一过程被称为“回收僵尸进程”。
如果父进程没有及时调用wait()或相关函数,子进程将会成为僵尸进程,占用系统资源,直到父进程终止或调用相应的等待函数。
2. 使用wait()和waitpid()函数
wait():使父进程阻塞,直到任一子进程终止。它会返回终止子进程的PID,并存储子进程的退出状态。waitpid():提供更精细的控制,可以等待特定的子进程或采用非阻塞方式。
例子:阻塞等待子进程终止
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid = fork(); // 创建子进程if (pid < 0) {std::cerr << "Fork failed!" << std::endl;return 1;}else if (pid == 0) {// 子进程执行的代码std::cout << "子进程PID:" << getpid() << " 启动。" << std::endl;sleep(2); // 模拟子进程工作std::cout << "子进程PID:" << getpid() << " 结束。" << std::endl;return 42; // 子进程以状态42退出}else {// 父进程执行的代码std::cout << "父进程PID:" << getpid() << " 等待子进程结束。" << std::endl;int status;pid_t terminated_pid = wait(&status); // 阻塞等待任一子进程结束if (terminated_pid > 0) {if (WIFEXITED(status)) {std::cout << "子进程PID:" << terminated_pid << " 以状态 " << WEXITSTATUS(status) << " 退出。" << std::endl;}else if (WIFSIGNALED(status)) {std::cout << "子进程PID:" << terminated_pid << " 被信号 " << WTERMSIG(status) << " 终止。" << std::endl;}}else {std::cerr << "等待子进程失败。" << std::endl;}}return 0;
}
输出示例:
父进程PID:12345 等待子进程结束。
子进程PID:12346 启动。
子进程PID:12346 结束。
子进程PID:12346 以状态 42 退出。
代码解释:
- 父进程调用
fork()创建子进程。 - 子进程执行自己的任务后,以状态42退出。
- 父进程调用
wait()阻塞等待子进程结束,并获取子进程的退出状态。 - 父进程通过宏
WIFEXITED和WEXITSTATUS判断子进程是否正常退出及其退出状态。
3. 使用SIGCHLD信号处理异步回收
为了避免父进程被阻塞,可以通过信号处理函数异步处理子进程的终止。这在需要父进程继续执行其他任务时非常有用。
例子:使用SIGCHLD处理子进程终止
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstring>// 信号处理函数
void sigchld_handler(int signum) {// 循环回收所有已终止的子进程while (true) {int status;pid_t pid = waitpid(-1, &status, WNOHANG);if (pid <= 0) {break;}if (WIFEXITED(status)) {std::cout << "[Signal Handler] 子进程PID:" << pid << " 以状态 " << WEXITSTATUS(status) << " 退出。" << std::endl;}else if (WIFSIGNALED(status)) {std::cout << "[Signal Handler] 子进程PID:" << pid << " 被信号 " << WTERMSIG(status) << " 终止。" << std::endl;}}
}int main() {// 注册SIGCHLD信号处理函数struct sigaction sa;sa.sa_handler = sigchld_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;if (sigaction(SIGCHLD, &sa, NULL) == -1) {std::cerr << "无法注册SIGCHLD处理器:" << strerror(errno) << std::endl;return 1;}pid_t pid = fork(); // 创建子进程if (pid < 0) {std::cerr << "Fork failed!" << std::endl;return 1;}else if (pid == 0) {// 子进程执行的代码std::cout << "子进程PID:" << getpid() << " 启动。" << std::endl;sleep(2); // 模拟子进程工作std::cout << "子进程PID:" << getpid() << " 结束。" << std::endl;return 24; // 子进程以状态24退出}else {// 父进程执行的其他任务std::cout << "父进程PID:" << getpid() << " 正在执行其他任务。" << std::endl;// 模拟父进程执行其他任务for (int i = 0; i < 5; ++i) {std::cout << "父进程执行中:" << i + 1 << std::endl;sleep(1);}// 父进程结束前确保所有子进程已被回收// 可以调用wait(NULL)或者让信号处理器完成回收}return 0;
}
输出示例:
父进程PID:12345 正在执行其他任务。
子进程PID:12346 启动。
父进程执行中:1
父进程执行中:2
子进程PID:12346 结束。
[Signal Handler] 子进程PID:12346 以状态 24 退出。
父进程执行中:3
父进程执行中:4
父进程执行中:5
代码解释:
- 注册
SIGCHLD信号处理器:- 使用
sigaction结构体注册sigchld_handler函数作为SIGCHLD信号的处理器。 SA_RESTART标志用于在信号处理后自动重启被中断的系统调用。SA_NOCLDSTOP标志表示当子进程停止或继续时,父进程不接收SIGCHLD信号。
- 使用
- 创建子进程:
- 子进程执行自己的任务并以状态24退出。
- 父进程执行其他任务:
- 父进程在等待子进程结束的同时,继续执行其他任务,不会被阻塞。
- 信号处理函数
sigchld_handler:- 当子进程终止时,
SIGCHLD信号会被触发,sigchld_handler函数会被调用。 - 在函数内部,使用
waitpid和WNOHANG选项非阻塞地回收所有已终止的子进程,防止僵尸进程的产生。
- 当子进程终止时,
4. 避免僵尸进程的策略
- 及时调用
wait()或waitpid():确保父进程在子进程终止后,立即回收其资源。 - 使用信号处理器:如上文所示,通过注册
SIGCHLD信号处理器,可以在子进程终止时自动回收资源,而不需要父进程主动等待。 - 设置
SIGCHLD为SIG_IGN:在一些系统上,可以通过将SIGCHLD信号的处理方式设置为忽略,从而自动回收子进程资源。这种方法不适用于所有情况,需谨慎使用。
例子:设置SIGCHLD为忽略
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <cstring>int main() {// 设置SIGCHLD为忽略signal(SIGCHLD, SIG_IGN);pid_t pid = fork(); // 创建子进程if (pid < 0) {std::cerr << "Fork failed!" << std::endl;return 1;}else if (pid == 0) {// 子进程执行的代码std::cout << "子进程PID:" << getpid() << " 启动。" << std::endl;sleep(2); // 模拟子进程工作std::cout << "子进程PID:" << getpid() << " 结束。" << std::endl;return 0;}else {// 父进程执行其他任务std::cout << "父进程PID:" << getpid() << " 正在执行其他任务。" << std::endl;sleep(5); // 父进程等待子进程结束}return 0;
}
注意事项:
- 这种方法依赖于系统对
SIGCHLD的具体实现,不保证在所有Unix/Linux系统中都有效。 - 尽管简便,但可能无法获取子进程的退出状态,限制了错误处理和日志记录的能力。
5. 处理多个子进程的终止
当父进程创建多个子进程时,需要确保所有子进程的终止都被正确处理,以避免僵尸进程。可以在SIGCHLD处理函数中使用循环调用waitpid,直到所有终止的子进程都被回收。
例子:处理多个子进程
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <vector>
#include <cstring>// 信号处理函数
void sigchld_handler(int signum) {// 循环回收所有已终止的子进程while (true) {int status;pid_t pid = waitpid(-1, &status, WNOHANG);if (pid <= 0) {break;}if (WIFEXITED(status)) {std::cout << "[Signal Handler] 子进程PID:" << pid << " 以状态 " << WEXITSTATUS(status) << " 退出。" << std::endl;}else if (WIFSIGNALED(status)) {std::cout << "[Signal Handler] 子进程PID:" << pid << " 被信号 " << WTERMSIG(status) << " 终止。" << std::endl;}}
}int main() {// 注册SIGCHLD信号处理函数struct sigaction sa;sa.sa_handler = sigchld_handler;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;if (sigaction(SIGCHLD, &sa, NULL) == -1) {std::cerr << "无法注册SIGCHLD处理器:" << strerror(errno) << std::endl;return 1;}std::vector<pid_t> child_pids;// 创建多个子进程for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid < 0) {std::cerr << "Fork failed!" << std::endl;return 1;}else if (pid == 0) {// 子进程执行的代码std::cout << "子进程PID:" << getpid() << " 启动。" << std::endl;sleep(2 + i); // 模拟不同的工作时间std::cout << "子进程PID:" << getpid() << " 结束。" << std::endl;return i;}else {// 父进程记录子进程PIDchild_pids.push_back(pid);}}// 父进程执行其他任务std::cout << "父进程PID:" << getpid() << " 正在执行其他任务。" << std::endl;sleep(6); // 等待所有子进程结束// 由于SIGCHLD处理器已经回收了子进程,父进程无需再次调用waitreturn 0;
}
输出:
父进程PID:12345 正在执行其他任务。
子进程PID:12346 启动。
子进程PID:12347 启动。
子进程PID:12348 启动。
子进程PID:12346 结束。
[Signal Handler] 子进程PID:12346 以状态 0 退出。
子进程PID:12347 结束。
[Signal Handler] 子进程PID:12347 以状态 1 退出。
子进程PID:12348 结束。
[Signal Handler] 子进程PID:12348 以状态 2 退出。
代码解释:
- 创建多个子进程:循环调用
fork()创建三个子进程,每个子进程有不同的工作时间。 - 信号处理函数:
sigchld_handler会被多次调用,以回收每个子进程的资源。 - 父进程执行其他任务:父进程在子进程运行期间继续执行其他任务,不会被阻塞。
- 无需显式等待:由于信号处理器已经负责回收子进程,父进程无需再次调用
wait()或waitpid()。
6. 使用prctl设置子进程终止时的行为
在某些情况下,可以通过prctl系统调用设置子进程终止时的行为。例如,可以设置PR_SET_CHILD_SUBREAPER,使得特定的进程成为“子进程的收割者”,用于复杂的进程管理。
例子:设置PR_SET_CHILD_SUBREAPER
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <cstring>
#include <sys/prctl.h>int main() {// 将当前进程设置为子进程的收割者if (prctl(PR_SET_CHILD_SUBREAPER, 1) == -1) {std::cerr << "prctl failed: " << strerror(errno) << std::endl;return 1;}pid_t pid = fork();if (pid < 0) {std::cerr << "Fork failed!" << std::endl;return 1;}else if (pid == 0) {// 子进程执行的代码std::cout << "子进程PID:" << getpid() << " 启动。" << std::endl;sleep(2);std::cout << "子进程PID:" << getpid() << " 结束。" << std::endl;return 0;}else {// 父进程作为子进程的收割者,等待子进程结束std::cout << "父进程设置为子进程的收割者,等待子进程结束。" << std::endl;int status;pid_t terminated_pid = wait(&status);if (terminated_pid > 0) {if (WIFEXITED(status)) {std::cout << "子进程PID:" << terminated_pid << " 以状态 " << WEXITSTATUS(status) << " 退出。" << std::endl;}}}return 0;
}
代码解释:
prctl(PR_SET_CHILD_SUBREAPER, 1):将当前进程设置为子进程的收割者,使其能够回收其子孙进程的资源。- 这种设置在需要复杂的子进程管理,或在容器化环境中非常有用。
相关文章:
Linux系统中处理子进程的终止问题
1. 理解子进程终止的机制 在Unix/Linux系统中,当子进程终止时,会向父进程发送一个SIGCHLD信号。父进程需要捕捉这个信号,并通过调用wait()或waitpid()等函数来回收子进程的资源。这一过程被称为“回收僵尸进程”。 如果父进程没有及时调用w…...
Docker 不再难懂:快速掌握容器命令与架构原理
1. Docker 是容器技术的一种 容器(Container)概述 容器(Container)是一种轻量级的虚拟化技术,它将应用程序及其所有依赖环境打包在一个独立的、可移植的运行时环境中。容器通过操作系统级的虚拟化提供隔离࿰…...
取消票证会把指定的票证从数据库中删除,同时也会把票证和航班 等相关表中的关联关系一起删除。但在删除之前,它会先检查当前用户是否拥有这张票
在做航班智能客服问答系统时会遇到取消票证的场景,这里涉及数据库的操作时会把指定的票证从数据库中删除,同时也会把票证和航班等相关表中的关联关系一起删除。但在删除之前,需要先检查当前用户是否拥有这张票,只有票主才有权限取…...
力扣-贪心-763 划分字母区间
思路 先统计字符串中每一个字母出现的最后下标,然后从end初始化为第一个字母出现的最后下标,在i<end时,不断更新end,因为一旦囊括新的字母就最起码要遍历到新字母出现的最后下标,在i>end时,说明遍历…...
【Redis 原理】网络模型
文章目录 用户空间 && 内核空间阻塞IO非阻塞IO信号驱动IO异步IOIO多路复用selectpollepoll Web服务流程Redis 网络模型Redis单线程网络模型的整个流程Redis多线程网络模型的整个流程 用户空间 && 内核空间 为了避免用户应用导致冲突甚至内核崩溃,用…...
cpp中的继承
一、继承概念 在cpp中,封装、继承、多态是面向对象的三大特性。这里的继承就是允许已经存在的类(也就是基类)的基础上创建新类(派生类或者子类),从而实现代码的复用。 如上图所示,Person是基类&…...
DeepSeek全栈接入指南:从零到生产环境的深度实践
第一章:DeepSeek技术体系全景解析 1.1 认知DeepSeek技术生态 DeepSeek作为新一代人工智能技术平台,构建了覆盖算法开发、模型训练、服务部署的全链路技术栈。其核心能力体现在: 1.1.1 多模态智能引擎 自然语言处理:支持文本生成(NLG)、语义理解(NLU)、情感分析等计算…...
CSS 真的会阻塞文档解析吗?
在网页开发领域,一个常见的疑问是 CSS 是否会阻塞文档解析。理解这一问题对于优化网页性能、提升用户体验至关重要。要深入解答这个问题,需要从浏览器渲染网页的原理说起。 浏览器渲染网页的基本流程 浏览器在接收到 HTML 文档后,会依次进行…...
大模型的UI自动化:Cline 使用Playwright MCP Server完成测试
大模型的UI自动化:Cline 使用Playwright MCP Server完成测试 MCP MCP(Model Context Protocol),是一个开发的协议,标准化了应用程序如何为大模型提供上下文。MCP提供了一个标准的为LLM提供数据、工具的方式,使用MCP会更容易的构建Agent或者是基于LLM的复杂工作流。 最近…...
碰撞检测 | 图解凸多边形分离轴定理(附ROS C++可视化)
目录 0 专栏介绍1 凸多边形碰撞检测2 多边形判凸算法3 分离轴定理(SAT)4 算法仿真与可视化4.1 核心算法4.2 仿真实验 0 专栏介绍 🔥课设、毕设、创新竞赛必备!🔥本专栏涉及更高阶的运动规划算法轨迹优化实战,包括:曲线…...
Python 基本数据类型
目录 1. 字符串(String) 2. 列表(List) 3. 字典(Dictionary) 4. 集合(Set) 5. 数字(Number) 6. 布尔值(Boolean) 1. 字符串&…...
突破“第一崇拜“:五维心理重构之路
一、视频介绍 在这个崇尚"第一"的时代,我们如何找到自己的独特价值?本视频将带您踏上五维心理重构之旅,从诗意人生的角度探讨如何突破"圣人之下皆蝼蚁"的局限。我们将穿越人生的不同阶段,从青春的意气风发到…...
KubeKey一键安装部署k8s集群和KubeSphere详细教程
目录 一、KubeKey简介 二、k8s集群KubeSphere安装 集群规划 硬件要求 Kubernetes支持版本 操作系统要求 SSH免密登录 配置集群时钟 所有节点安装依赖 安装docker DNS要求 存储要求 下载 KubeKey 验证KubeKey 配置集群文件 安装集群 验证命令 登录页面 一、Ku…...
UE5网络通信架构解析
文章目录 前言一、客户端-服务器架构(C/S Model)二、对等网络架构(P2P,非原生支持)三、混合架构(自定义扩展)四、UE5网络核心机制 前言 UE5的网络通信主要基于客户端-服务器(C/S&am…...
实验3 知识表示与推理
实验3 知识表示与推理 一、实验目的 (1)掌握知识和知识表示的基本概念,理解其在AI中的深刻含义与意义; (2)熟悉AI中常用的知识表示方法的优缺点及其应用场景; (3)掌握产…...
基于Springboot银行信用卡额度管理系统【附源码】
基于Springboot银行信用卡额度管理系统 效果如下: 系统登陆页面 用户个人中心页面 新增信用卡申请页面 评估审核页面 管理员主页面 评估审核页面 操作日志管理页面 消费页面 研究背景 随着金融行业的快速发展和信息技术的不断进步,信用卡作为一种便捷…...
达梦数据库学习笔记@1
目录 达梦数据库学习笔记一、表空间管理(一)默认表空间(二)相关数据字典(三)表空间操作(四)临时表空间管理 二、重做日志管理(一)系统视图(二&…...
图像处理篇---图像处理中常见参数
文章目录 前言一、分贝(dB)的原理1.公式 二、峰值信噪比(PSNR, Peak Signal-to-Noise Ratio)1.用途2.公式3.示例 三、信噪比(SNR, Signal-to-Noise Ratio)1.用途2.公式3.示例 四、动态范围(Dyna…...
AI Agent实战:打造京东广告主的超级助手 | 京东零售技术实践
前言 自2022年末ChatGPT的问世,大语言模型(LLM)技术引发全球关注。在大模型技术落地的最佳实践中,智能体(Agent)架构显现出巨大潜力,成为业界的普遍共识,各大公司也纷纷启动Agent技…...
50周学习go语言:第1周 环境搭建
以下是为零基础学习者准备的详细第1周教程,包含环境搭建、工具配置和首个Go程序的完整操作指南: 一、Go语言环境安装(Windows/macOS/Linux通用) 1. 下载安装包 官网地址:https://go.dev/dl//根据系统选择对应版本&am…...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
