当前位置: 首页 > news >正文

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 退出。

代码解释:

  1. 父进程调用fork()创建子进程。
  2. 子进程执行自己的任务后,以状态42退出。
  3. 父进程调用wait()阻塞等待子进程结束,并获取子进程的退出状态。
  4. 父进程通过宏WIFEXITEDWEXITSTATUS判断子进程是否正常退出及其退出状态。

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

代码解释:

  1. 注册SIGCHLD信号处理器
    • 使用sigaction结构体注册sigchld_handler函数作为SIGCHLD信号的处理器。
    • SA_RESTART标志用于在信号处理后自动重启被中断的系统调用。
    • SA_NOCLDSTOP标志表示当子进程停止或继续时,父进程不接收SIGCHLD信号。
  2. 创建子进程
    • 子进程执行自己的任务并以状态24退出。
  3. 父进程执行其他任务
    • 父进程在等待子进程结束的同时,继续执行其他任务,不会被阻塞。
  4. 信号处理函数sigchld_handler
    • 当子进程终止时,SIGCHLD信号会被触发,sigchld_handler函数会被调用。
    • 在函数内部,使用waitpidWNOHANG选项非阻塞地回收所有已终止的子进程,防止僵尸进程的产生。

4. 避免僵尸进程的策略

  • 及时调用wait()waitpid():确保父进程在子进程终止后,立即回收其资源。
  • 使用信号处理器:如上文所示,通过注册SIGCHLD信号处理器,可以在子进程终止时自动回收资源,而不需要父进程主动等待。
  • 设置SIGCHLDSIG_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 退出。

代码解释:

  1. 创建多个子进程:循环调用fork()创建三个子进程,每个子进程有不同的工作时间。
  2. 信号处理函数sigchld_handler会被多次调用,以回收每个子进程的资源。
  3. 父进程执行其他任务:父进程在子进程运行期间继续执行其他任务,不会被阻塞。
  4. 无需显式等待:由于信号处理器已经负责回收子进程,父进程无需再次调用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系统中&#xff0c;当子进程终止时&#xff0c;会向父进程发送一个SIGCHLD信号。父进程需要捕捉这个信号&#xff0c;并通过调用wait()或waitpid()等函数来回收子进程的资源。这一过程被称为“回收僵尸进程”。 如果父进程没有及时调用w…...

Docker 不再难懂:快速掌握容器命令与架构原理

1. Docker 是容器技术的一种 容器&#xff08;Container&#xff09;概述 容器&#xff08;Container&#xff09;是一种轻量级的虚拟化技术&#xff0c;它将应用程序及其所有依赖环境打包在一个独立的、可移植的运行时环境中。容器通过操作系统级的虚拟化提供隔离&#xff0…...

取消票证会把指定的票证从数据库中删除,同时也会把票证和航班 等相关表中的关联关系一起删除。但在删除之前,它会先检查当前用户是否拥有这张票

在做航班智能客服问答系统时会遇到取消票证的场景&#xff0c;这里涉及数据库的操作时会把指定的票证从数据库中删除&#xff0c;同时也会把票证和航班等相关表中的关联关系一起删除。但在删除之前&#xff0c;需要先检查当前用户是否拥有这张票&#xff0c;只有票主才有权限取…...

力扣-贪心-763 划分字母区间

思路 先统计字符串中每一个字母出现的最后下标&#xff0c;然后从end初始化为第一个字母出现的最后下标&#xff0c;在i<end时&#xff0c;不断更新end&#xff0c;因为一旦囊括新的字母就最起码要遍历到新字母出现的最后下标&#xff0c;在i>end时&#xff0c;说明遍历…...

【Redis 原理】网络模型

文章目录 用户空间 && 内核空间阻塞IO非阻塞IO信号驱动IO异步IOIO多路复用selectpollepoll Web服务流程Redis 网络模型Redis单线程网络模型的整个流程Redis多线程网络模型的整个流程 用户空间 && 内核空间 为了避免用户应用导致冲突甚至内核崩溃&#xff0c;用…...

cpp中的继承

一、继承概念 在cpp中&#xff0c;封装、继承、多态是面向对象的三大特性。这里的继承就是允许已经存在的类&#xff08;也就是基类&#xff09;的基础上创建新类&#xff08;派生类或者子类&#xff09;&#xff0c;从而实现代码的复用。 如上图所示&#xff0c;Person是基类&…...

DeepSeek全栈接入指南:从零到生产环境的深度实践

第一章:DeepSeek技术体系全景解析 1.1 认知DeepSeek技术生态 DeepSeek作为新一代人工智能技术平台,构建了覆盖算法开发、模型训练、服务部署的全链路技术栈。其核心能力体现在: 1.1.1 多模态智能引擎 自然语言处理:支持文本生成(NLG)、语义理解(NLU)、情感分析等计算…...

CSS 真的会阻塞文档解析吗?

在网页开发领域&#xff0c;一个常见的疑问是 CSS 是否会阻塞文档解析。理解这一问题对于优化网页性能、提升用户体验至关重要。要深入解答这个问题&#xff0c;需要从浏览器渲染网页的原理说起。 浏览器渲染网页的基本流程 浏览器在接收到 HTML 文档后&#xff0c;会依次进行…...

大模型的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 专栏介绍 &#x1f525;课设、毕设、创新竞赛必备&#xff01;&#x1f525;本专栏涉及更高阶的运动规划算法轨迹优化实战&#xff0c;包括&#xff1a;曲线…...

Python 基本数据类型

目录 1. 字符串&#xff08;String&#xff09; 2. 列表&#xff08;List&#xff09; 3. 字典&#xff08;Dictionary&#xff09; 4. 集合&#xff08;Set&#xff09; 5. 数字&#xff08;Number&#xff09; 6. 布尔值&#xff08;Boolean&#xff09; 1. 字符串&…...

突破“第一崇拜“:五维心理重构之路

一、视频介绍 在这个崇尚"第一"的时代&#xff0c;我们如何找到自己的独特价值&#xff1f;本视频将带您踏上五维心理重构之旅&#xff0c;从诗意人生的角度探讨如何突破"圣人之下皆蝼蚁"的局限。我们将穿越人生的不同阶段&#xff0c;从青春的意气风发到…...

KubeKey一键安装部署k8s集群和KubeSphere详细教程

目录 一、KubeKey简介 二、k8s集群KubeSphere安装 集群规划 硬件要求 Kubernetes支持版本 操作系统要求 SSH免密登录 配置集群时钟 所有节点安装依赖 安装docker DNS要求 存储要求 下载 KubeKey 验证KubeKey 配置集群文件 安装集群 验证命令 登录页面 一、Ku…...

UE5网络通信架构解析

文章目录 前言一、客户端-服务器架构&#xff08;C/S Model&#xff09;二、对等网络架构&#xff08;P2P&#xff0c;非原生支持&#xff09;三、混合架构&#xff08;自定义扩展&#xff09;四、UE5网络核心机制 前言 UE5的网络通信主要基于客户端-服务器&#xff08;C/S&am…...

实验3 知识表示与推理

实验3 知识表示与推理 一、实验目的 &#xff08;1&#xff09;掌握知识和知识表示的基本概念&#xff0c;理解其在AI中的深刻含义与意义&#xff1b; &#xff08;2&#xff09;熟悉AI中常用的知识表示方法的优缺点及其应用场景&#xff1b; &#xff08;3&#xff09;掌握产…...

基于Springboot银行信用卡额度管理系统【附源码】

基于Springboot银行信用卡额度管理系统 效果如下&#xff1a; 系统登陆页面 用户个人中心页面 新增信用卡申请页面 评估审核页面 管理员主页面 评估审核页面 操作日志管理页面 消费页面 研究背景 随着金融行业的快速发展和信息技术的不断进步&#xff0c;信用卡作为一种便捷…...

达梦数据库学习笔记@1

目录 达梦数据库学习笔记一、表空间管理&#xff08;一&#xff09;默认表空间&#xff08;二&#xff09;相关数据字典&#xff08;三&#xff09;表空间操作&#xff08;四&#xff09;临时表空间管理 二、重做日志管理&#xff08;一&#xff09;系统视图&#xff08;二&…...

图像处理篇---图像处理中常见参数

文章目录 前言一、分贝&#xff08;dB&#xff09;的原理1.公式 二、峰值信噪比&#xff08;PSNR, Peak Signal-to-Noise Ratio&#xff09;1.用途2.公式3.示例 三、信噪比&#xff08;SNR, Signal-to-Noise Ratio&#xff09;1.用途2.公式3.示例 四、动态范围&#xff08;Dyna…...

AI Agent实战:打造京东广告主的超级助手 | 京东零售技术实践

前言 自2022年末ChatGPT的问世&#xff0c;大语言模型&#xff08;LLM&#xff09;技术引发全球关注。在大模型技术落地的最佳实践中&#xff0c;智能体&#xff08;Agent&#xff09;架构显现出巨大潜力&#xff0c;成为业界的普遍共识&#xff0c;各大公司也纷纷启动Agent技…...

50周学习go语言:第1周 环境搭建

以下是为零基础学习者准备的详细第1周教程&#xff0c;包含环境搭建、工具配置和首个Go程序的完整操作指南&#xff1a; 一、Go语言环境安装&#xff08;Windows/macOS/Linux通用&#xff09; 1. 下载安装包 官网地址&#xff1a;https://go.dev/dl//根据系统选择对应版本&am…...

Spark 之 入门讲解详细版(1)

1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室&#xff08;Algorithms, Machines, and People Lab&#xff09;开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目&#xff0c;8个月后成为Apache顶级项目&#xff0c;速度之快足见过人之处&…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

通过MicroSip配置自己的freeswitch服务器进行调试记录

之前用docker安装的freeswitch的&#xff0c;启动是正常的&#xff0c; 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...

Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解

文章目录 一、开启慢查询日志&#xff0c;定位耗时SQL1.1 查看慢查询日志是否开启1.2 临时开启慢查询日志1.3 永久开启慢查询日志1.4 分析慢查询日志 二、使用EXPLAIN分析SQL执行计划2.1 EXPLAIN的基本使用2.2 EXPLAIN分析案例2.3 根据EXPLAIN结果优化SQL 三、使用SHOW PROFILE…...

【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战

🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...