linux进程家族-管理子进程,确保进程族稳健运行

news2024/11/16 13:06:13

在Linux环境下,进程之间相互影响、相互依赖,就像一个大家庭一样。作为程序员,我们不仅需要创建子进程,更要熟练掌握监控和管理子进程的技能,确保整个进程族能稳健高效地运行。本文将为你揭开进程创建、监控子进程、处理SIGCHLD信号等核心知识,并通过丰富的C++示例代码,让你融会贯通。


一、创建子进程:fork()孕育新生命


我们首先来看一下如何创建子进程。

在Linux系统中,通过fork()系统调用可以让一个进程创建出一个新的进程,也就是所谓的子进程。

子进程会获得父进程的数据段、代码段等资源的拷贝,然后父子进程分道扬镳,各自独立运行。


在这里插入图片描述


#include <unistd.h>
#include <iostream>

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程代码
        std::cout << "Child process, pid=" << getpid() << std::endl;
    } else if (pid > 0) {
        // 父进程代码
        std::cout << "Parent process, pid=" << getpid() << std::endl;
    } else {
        // fork失败
    }
    
    return 0;
}

值得注意的是,fork()实际上并不会立即拷贝所有内存资源,而是采用写时拷贝的策略,只有在父子进程中的某一方首次试图修改资源时,内核才会进行实际拷贝。这种做法避免了不必要的浪费。


注意:

  • 内存拷贝并不一开始就发生

    • 从理论上来说,fork() 创建出来的子进程中的堆、栈、数据段应该是父进程的完整拷贝。

    • 但是,在大部分情况下,fork() 在调用后会直接调用
exec() 函数族来执行其它的程序或者是命令,重新的初始化堆、栈以及数据段等内容。

    • 因此,如果
fork() 在一开始就进行内存拷贝,很多情况下会被视为“无用之举”。

    • 因此,内核会使用写时复制的方式进行 lazy
 copy。也就是说当子进程确实需要这一份内存时,内核才会进行拷贝动作。


  • 在一般的业务需求下,通常都是由当前进程创建出多个子进程以供使用

int main(){
  pid_t pid;
  for(int i=0;i<4;i++){ 
    pid=fork();
    if(pid<=0)// 子进程不允许进入 for 循环内,保证进程只由父进程创建
    break; 
  } 
  if(pid==0)// 子进程跳出循环以后,我们可以在这里判断当前进程 
}

二、子进程监控:等待子进程的归来


作为父进程,我们通常需要监控子进程的运行状态,并在子进程结束时作出响应。

Linux提供了两个系统调用wait()和waitpid()来实现这个功能。

相比wait(),waitpid()功能更强大,可以更精确地指定等待哪个子进程,并提供了非阻塞等待的选项。

因此实际使用中我们一般选择waitpid():

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程
        std::cout << "Child process, pid=" << getpid() << std::endl;
        sleep(5); // 模拟子进程运行
    } else if (pid > 0) {
        // 父进程
        int status;
        pid_t child = waitpid(pid, &status, 0);
        if (child > 0) {
            std::cout << "Child process " << child << " exited" << std::endl;
            
            // 解析子进程退出状态
            if (WIFEXITED(status)) {
                std::cout << "Exit status: " << WEXITSTATUS(status) << std::endl;
            } else if (WIFSIGNALED(status)) {
                std::cout << "Killed by signal: " << WTERMSIG(status) << std::endl;
            }
        } else {
            // waitpid出错
        }
    }
    
    return 0;
}

1、waitpid()的参数详解
  • 第一个参数 pid: 可以是具体的子进程PID,或者是0(等待同组任意子进程),-1(等待任意子进程)。
  • 第二个参数 status:用于获取子进程的退出状态。子进程的终止状态将写入至该指针变量中,用来表示子进程是以哪种方式结束的。因为
status 虽然定义成一个整型,但是实际上只用到了其最低的两个字节,所以我们需要使用一些宏来对其进行断言。
  • 第三个参数 options :是附加选项 位掩码,可以按位或多个选项, 比如WNOHANG可以使waitpid()不阻塞。
    • WUNTRACED 除了返回终止子进程的信息外,还返回因信号而停止的子进程信息。
    • WCONTINUED 返回那些因收到
SIGCONT
信号而恢复执行的已停止子进程的状态信息。
    • WNOHANG 非阻塞的等待,若子进程的状态并未发生改变,那么
waitpid() 则返回
0。

2、调用
waitpid() 的必要性

  • 如果父进程创建了某一子进程,但并未执行
waitpid(),那么在内核的进程表中将为该子进程永久保留一条记录。并且, 即使是
SIGKILL 也无法“杀死”这个子进程,使其从进程记录表中移除,因此,这样的子进程我们通常称之为僵尸进程。

  • 如果存在大量此类僵尸进程,它们势必将填满内核进程表,从而阻碍新进程的创建。

  • 既然无法用信号杀死僵尸进程,那么从系统中将其移除的唯一方法就是杀掉它们的父进程
(或等待其父进程终止),此时 init 进程将接管和等待这些僵尸进程,从而从系统中将它们清理掉。


三、SIGCHLD信号:及时收割僵尸进程


如果父进程创建了子进程却未正确等待,那么当子进程结束时就会变成僵尸进程,浪费系统资源。虽然可以通过轮询的方式调用waitpid()来避免,但效率低下。

幸运的是,内核为我们提供了SIGCHLD信号,当子进程结束时会给父进程发送这个信号。我们可以捕获并处理这个信号,在信号处理函数中调用waitpid(),从而及时清理僵尸进程。

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

void sigchld_handler(int sig) {
    int status;
    while (waitpid(-1, &status, WNOHANG) > 0) {
        // 处理已结束的子进程
    }
}

int main() {
    // 为SIGCHLD注册信号处理函数
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;
    sigaction(SIGCHLD, &sa, NULL);
    
    pid_t pid = fork();
    
    if (pid == 0) {
        // 子进程代码
        sleep(2);
        std::cout << "Child process exiting" << std::endl;
        exit(0); 
    } else if (pid > 0) {
        // 父进程代码
        while (true) {
            // 父进程继续运行
        }
    }
    
    return 0;
}

需要注意的是,SIGCHLD信号不保证按子进程结束的顺序到达,也不保证能为每个子进程接收一次。因此我们在信号处理函数中要循环调用waitpid(),直到没有已结束的子进程为止。


通过掌握进程创建、监控子进程以及SIGCHLD信号处理等技术,相信你已经能够娴熟地在Linux环境下管理并维护一个"进程大家庭"了。


不过,这仅仅是进程管理的冰山一角,在多进程通信、进程同步等更高阶主题中,还有许多精彩的内容值得去探索。有兴趣一同前行吗?敬请期待我们的下一篇分享!


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

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

相关文章

【Vue】小黑记事本

文章目录 一、需求说明二、思路分析三、代码实现 一、需求说明 拆分基础组件渲染待办任务添加任务删除任务底部合计 和 清空功能持久化存储 二、思路分析 拆分基础组件 咱们可以把小黑记事本原有的结构拆成三部分内容&#xff1a;头部&#xff08;TodoHeader&#xff09;、列…

代码随想录训练营Day29

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、递增子序列二、全排列三、全排列2 前言 今天是跟着代码随想录刷题的第29天&#xff0c;今天主要学了以下几个内容&#xff1a;491.递增子序列、46.全排列、…

1877java项目建设平台管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 java 项目建设平台管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助采用了java设计&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统采用web模式&#xff0c;系统主要采用B/S模式开 发。开发环境为TOMCAT7.0,Myeclipse8.…

工频耐压试验仪

武汉凯迪正大智能5KV工频耐压仪是根据国家行业试验标准而设计的试验设备&#xff0c;其功能强、使用方便、维护简单。主要用于对各种电器产品、电气元件、绝缘材料等进行规定电压下的碳化试验&#xff0c;绝缘强度试验&#xff0c;以考核产品的绝缘水平&#xff0c;发现被试品的…

将小爱音箱接入 ChatGPT 和豆包ai改造成专属语音助手

这个GitHub项目&#xff0c;mi-gpt&#xff0c;旨在将小爱音箱和米家设备与ChatGPT和豆包集成&#xff0c;有效地将这些设备转变为个性化语音助手。以下是对其功能和设置的详细分析&#xff1a; 主要特点 角色扮演&#xff1a;该项目允许小爱适应不同的角色&#xff0c;如伴侣…

源码编译安装LNMP

目录 LNMP包含&#xff1a; LNMP的工作原理 &#xff1a; 编译安装&#xff1a; nginx服务器配置 安装依赖包 创建用户 上传软件包至/opt目录下并解压 编译安装 设置软链接&#xff0c;可以直接调用nginx命令来进行管理 添加Nginx系统服务 配置Nginx支持php解析&…

关于文件上传失败问题的排查思路

问题场景&#xff1a; 最近公司的app有很多用户反馈上传文件失败了。业务路径就是简单的app前端调用后端文件上传接口&#xff0c;所以发生上传失败的可能因素可能是&#xff1a;1、文件大小/文件类型等是否有问题&#xff0c;公司用的是七牛的文件服务器&#xff0c;对文件上…

淘宝扭蛋机源码解析:功能实现与技术细节

随着在线购物和娱乐的融合&#xff0c;淘宝扭蛋机作为一种创新的购物娱乐方式&#xff0c;受到了广大用户的喜爱。本文将深入解析淘宝扭蛋机的源码&#xff0c;探讨其功能实现与技术细节&#xff0c;以期为开发者们提供一些有价值的参考。 一、功能实现 1.用户登录与注册 淘宝…

《令狐带你阅读JDK源码之简单集合ArrayList》

文章目录 Java简单集合ArrayList继承体系源码解析 总结 大家好哈&#xff0c;欢迎来到令狐小哥本期专栏&#xff0c;这期专栏主要是带着大家阅读JDK源码&#xff0c;我会分几期篇幅来介绍这个jdk源码、会进行剖析、梳理&#xff0c;欢迎大家指正阅读。后面我会配套自己的视频进…

【C++题解】1090 - 同因查找

问题&#xff1a;1090 - 同因查找 类型&#xff1a;for循环、简单循环 题目描述&#xff1a; 求出 10 至 1000 之内能同时被2、3、7 整除的数&#xff0c;并输出。 每行一个。 输入&#xff1a; 无。 输出&#xff1a; 按要求输出满足条件的数&#xff0c;每行 1 个。 完…

TiDB-从0到1-配置篇

TiDB从0到1系列 TiDB-从0到1-体系结构TiDB-从0到1-分布式存储TiDB-从0到1-分布式事务TiDB-从0到1-MVCCTiDB-从0到1-部署篇TiDB-从0到1-配置篇 一、系统配置 TiDB的配置分为系统配置和集群配置两种。 其中系统配置对应TiDB Server&#xff08;不包含TiKV和PD的参数&#xff0…

GaussDB技术解读——GaussDB架构介绍(一)

目录 1 GaussDB 关键架构目标 2 GaussDB分布式架构 2.1 GaussDB 分布式关键技术架构 3 数据计算路由层&#xff08;Coordinator&#xff09;关键技术方案 3.1 分布式优化器 3.2 分布式执行框架 GaussDB是华为自主创新研发的关系型数据库&#xff0c;基于华为在数据库领域…

vivo手机如何改ip地址

在数字化时代&#xff0c;网络已成为我们日常生活和工作中不可或缺的一部分。而IP地址&#xff0c;作为网络连接的唯一标识&#xff0c;有时出于安全或隐私的需要&#xff0c;我们可能希望对其进行更改。对于使用vivo手机的用户来说&#xff0c;如何轻松修改IP地址可能是一个令…

2024后端服务架构升级

文章目录 背景改造方案新架构图技术选型思考 服务拆分公共组件设计自部署算法服务排期计划 全球多活改造背景架构图分布式ID大表分区处理范围使用用途改造方案排期计划升级预算 背景 1、xx业务经过多轮的业务决策和调整&#xff0c;存在非常多技术包袱&#xff0c;带了不好的用…

Nvidia Jetson/Orin +FPGA+AI大算力边缘计算盒子:轨道交通监控系统

株洲中车时代电气股份有限公司&#xff08;下称中车时代电气&#xff09;是中国中车旗下股份制企业&#xff0c;其前身及母公司——中车株洲电力机车研究所有限公司创立于1959年。中车时代电气扎根株洲&#xff0c;走好两条钢轨&#xff0c;走出两条钢轨。中车时代电气秉承“双…

Flink系列三:Flink架构、独立集群搭建及Flink on YARN模式详解

一、Flink架构 Flink 是一个分布式系统&#xff0c;需要有效分配和管理计算资源才能执行流应用程序。它集成了所有常见的集群资源管理器&#xff0c;例如Hadoop yarn&#xff0c;但也可以设置作为独立集群甚至库运行。 Flink 集群剖析 Flink 运行时由两种类型的进程组成&…

【自然语言处理】【Scaling Law】Observational Scaling Laws:跨不同模型构建Scaling Law

相关博客 【自然语言处理】【Scaling Law】Observational Scaling Laws&#xff1a;跨不同模型构建Scaling Law 【自然语言处理】【Scaling Law】语言模型物理学 第3.3部分&#xff1a;知识容量Scaling Laws 【自然语言处理】Transformer中的一种线性特征 【自然语言处理】【大…

Innodb Buffer Pool缓存机制(三)Innodb Buffer Pool内部组成

一、控制块缓存页 Buffer Pool中默认的缓存页大小和在磁盘上默认的页大小是一样的&#xff0c;都是16KB。为了更好的管理这些在Buffer Pool中的缓存页&#xff0c;InnoDB为每一个缓存页都创建了一些所谓的控制信息&#xff0c;这些控制信息包括该页所属的表空间编号、页号、缓存…

自动控制:自治系统与非自治系统的稳定性分析

自动控制&#xff1a;自治系统与非自治系统的稳定性分析 在自动控制领域&#xff0c;理解自治系统和非自治系统的区别对于分析系统稳定性至关重要。自治系统的运动方程只与系统的状态有关&#xff0c;而非自治系统的运动方程则与系统的状态和时间都有关系。本文将探讨非自治系…