Linux多进程开发2 - 孤儿、僵尸进程

参考学习:彻底搞懂孤儿/僵尸/守护进程

一、孤儿进程(Orphan Process)

  • 父进程运行结束,但子进程还在运行,这样的子进程就称为孤儿进程
  • 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init (即养父)
  • Init 会等待被收养的子进程终止
  • 孤儿进程的 getppid() 返回的是 init 的 Pid ,该值一般为1
  • 孤儿进程并不会造成什么危害

实例

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main() {

    // 创建子进程
    __pid_t pid = fork();

    if(pid > 0) {
        printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
    } 
    else if(pid == 0) {

        // 先让子进程休眠1s,此过程中父进程已运行结束
        sleep(1);
        printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
    }

    // for循环
    for(int i = 0; i < 3; i++) {
        printf("i : %d , pid : %d\n", i , getpid());
    }
    return 0;
} 

运行结果:孤儿进程被 init 收养了

二、僵尸进程(Zombie Process)

        一个进程在调用 exit 命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构

        注意系统调用 exit,它的作用是使进程退出,但也仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
        在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,它的残留资源(PCB)仍然存放于内核中。除此之外,僵尸进程不再占有任何内存空间,它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD 信号处理函数调用 wait() 或 waitpid() 等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态。

        如果这时父进程结束了, 那么 init 进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果父进程是一个循环,那么子进程就会一直保持僵尸状态,从而占用大量的进程号导致系统不能产生新的进程,危害极大,需要避免。*僵尸进程将会导致资源浪费,而孤儿则不会。


实例

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main() {
    __pid_t pid = fork();

    if(pid > 0) {
        while(1){
            printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
            sleep(1);
        }
        
    } else if(pid == 0) {
        printf("i am child process, pid : %d, ppid : %d\n", getpid(),getppid());
    }
    
    for(int i = 0; i < 3; i++) {
        printf("i : %d , pid : %d\n", i , getpid());
    }
    return 0;
} 

运行结果:让父进程一直循环打印一句话,永不结束,而子进程退出,变为僵尸进程。可以看到有标记为 的进程就是僵尸进程

        ID 为 24597 的进程就是已经变成僵尸进程的子进程了,而在这种情况下可以使用简单的 kill -9 ID 指令来杀死父进程,从而让 init 进程接手,init 始终会负责清理掉僵尸进程。

        但是在真实的开发环境中,当程序已经跑起来时,我们不可能使用 kill 指令来杀死父进程,因此需要引入wait 和 waitpid 函数。

三、进程回收

        在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块 PCB 的信息,包括进程号、退出状态、运行时间等

        父进程可以通过调用 wait 或 waitpid 得到子进程的退出状态同时彻底清除掉它

       wait() 和 waitpid() 函数的功能一样,区别在于 wait()函数会阻塞,waitpid()可以设置不阻塞,waitpid() 还可以指定等待哪个子进程结束。

        注意:1 次 wait 或 waitpid 调用只能清理一个子进程,清理多个子进程应使用循环。

1、wait() 函数

pid_t wait(int *wstatus)

  • 功能:等待任意一个子进程结束,如果任意一个子进程结束了,回收子进程的资源
  • 参数int *wstatus

            进程退出时的状态信息,传入的是一个 int 类型的地址,传出参数。

  • 返回值

            - 成功:返回被回收的子进程的 id

            - 失败:-1 (所有的子进程都已经结束了,调用函数失败)

        调用 wait 函数的父进程会被挂起(阻塞),直到它的一个子进程退出或者收到一个不能被忽略的信号时才被唤醒(继续往下执行)

        如果没有子进程了,函数立刻返回,返回-1         

        如果子进程都已经结束了,也会立即返回,返回-1


实例

#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>


int main() {

    // 有一个父进程,创建5个子进程(兄弟关系)
    pid_t pid;

    // 创建5个子进程
    for(int i = 0; i < 5; i++) {
        pid = fork();
        if(pid == 0) {
            // 如果是子进程,为避免它创建自己的子进程,在此 break 跳出循环
            break;
        }
    }

    if(pid > 0) {
        // 父进程
        while(1) {
            printf("parent, pid = %d\n", getpid());

            int st;
            int ret = wait(&st);  // 传入st的地址

            if(ret == -1) {
                break;
            }

            if(WIFEXITED(st)) {
                // 是不是正常退出
                printf("退出的状态码:%d\n", WEXITSTATUS(st));
            }
            if(WIFSIGNALED(st)) {
                // 是不是异常终止
                printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
            }

            printf("child die, pid = %d\n", ret);

            sleep(1);
        }

    } else if (pid == 0){
            printf("child, pid = %d\n",getpid());    
            sleep(1);       
        exit(0);
    }

    return 0;
}

        *使用 fork 创建子线程时,比如:

pid = fork();

pid = fork();

        当前父进程连续创建2个子线程时,现在系统中有几个线程?3个吗?

        其实当第一个子线程被创建时,再执行一次 fork 操作,这个子线程也会创建自己的子线程,父线程超级加辈变成了祖父进程(勇者辛梅尔 ~ 老年辛梅尔),放任不管的话,会产生无穷无尽的进程,所以应当使用 break 阻止这种情况发生。

运行结果:

使用 kill -9 指令杀死子线程时,会触发 WIFSIGNALED 异常终止情况并返回被信号 9 干掉的信息

2、waitpid()函数

pid_t waitpid(pid_t pid, int *wstatus, int options)

  • 功能:回收指定进程号的子进程,可以设置是否阻塞。
  • 参数:- pid(参考详解wait、waitpid)

                pid > 0 : 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去

                pid = 0 : 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬

                pid = -1 : 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样(最常用)

                pid < -1 : 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值

                 - options:设置阻塞或者非阻塞

                0 : 阻塞

                WNOHANG : 非阻塞

  • 返回值

                > 0 : 返回子进程的id

                = 0 : options=WNOHANG, 表示还有子进程活着

                = -1 :错误,或者没有子进程了

        从本质上讲,系统调用 waitpid 和 wait 的作用是完全相同的,但waitpid多出了两个可由用户控制的参数 pidoptions,从而为我们编程提供了另一种更灵活的方式。

        目前在Linux中只支持 WNOHANGWUNTRACED 两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

ret=waitpid(-1,NULL,WNOHANG | WUNTRACED)

        如果我们不想使用它们,也可以把options设为0,如:

ret=waitpid(-1,NULL,0);

        如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,父进程可以去做自己的事情,不会像在 wait 情况下那样永远等下去。


实例

        与 wait 函数的代码相似,为 waitpid 设置参数 WNOHANG ,表示设置调用 waitpid函数 的父进程为非阻塞状态,父子进程都有工作要做,使用 kill -9 ID 指令杀死子进程,而父进程察看到子进程退出后就收集它的状态信息。

while(1) {
            printf("parent, pid = %d\n", getpid());
            sleep(1);

            int st;
            // int ret = waitpid(-1, &st, 0);
            int ret = waitpid(-1, &st, WNOHANG);

            if(ret == -1) {
                break;
            } else if(ret == 0) {
                // 说明还有子进程存在
                continue;
            } else if(ret > 0) {

                if(WIFEXITED(st)) {
                    // 是不是正常退出
                    printf("退出的状态码:%d\n", WEXITSTATUS(st));
                }
                if(WIFSIGNALED(st)) {
                    // 是不是异常终止
                    printf("被哪个信号干掉了:%d\n", WTERMSIG(st));
                }

                printf("child die, pid = %d\n", ret);
            }
        }

运行结果:  可以看出父进程并没有被阻塞

​​​​​​​

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云实时计算Flink的产品化思考与实践【下】

摘要&#xff1a;本文整理自阿里云高级产品专家黄鹏程和阿里云技术专家陈婧敏在 FFA 2023 平台建设专场中的分享。内容主要为以下五部分&#xff1a; 阿里云实时计算 Flink 产品化思考 产品化实践 SQL 产品化思考及实践 展望 接上篇&#xff1a;阿里云实时计算Flink的产品…

Centos服务器Open Gauss 部署

近期很多的项目由于信创要求使用一些国产的数据库&#xff0c;比如OpenGauss。OpenGuass是华为高斯DB的开源版&#xff0c;内核还是PostgreSQL&#xff0c;商业版是收费的。这里记录一下是如何安装部署 的。 官方中文文档 官方下载地址 部署要求 操作系统要求 ARM&#xff…

《数据结构学习笔记---第六篇》---栈和队列的实现

目录 1.栈 1.1栈的概念及结构 1.2栈的实现 2.队列 2.1队列的概念及结构 ​2.2队列的实现 3.顺序栈的具体实现 3.1建头文Stack.h” 3.2创建具体接口实现文件Stack.c 3.2.1初始化 3.2.2入栈出栈 3.2.4判空 3.2.5栈的大小 3.2.6销毁栈 3.3主函数的实现 4.链队的具体实现…

iOS UIFont-真香警告之字体管理类

UIFont 系列传送门 第一弹加载本地字体:iOS UIFont-新增第三方字体 第二弹加载线上字体:iOS UIFont-实现三方字体的下载和使用 第三弹搭建字体管理类:iOS UIFont-真香警告之字体管理类 前言 不知道友们是否有过这种经历,项目已经迭代了很多版本,项目中的文件已经上千个了…

钉钉服务端API报错 错误描述: robot 不存在;解决方案:请确认 robotCode 是否正确

problem 调用钉钉服务端API&#xff0c;机器人发送群聊消息&#xff0c;后台返回报错信息: 钉钉服务端API报错 错误描述: robot 不存在&#xff1b;解决方案:请确认 robotCode 是否正确&#xff1b; reason 定位: 登录后台&#xff0c;查看机器人是存在查看机器人调用权限接…

DARTS-PT: RETHINKING ARCHITECTURE SELECTION IN DIFFERENTIABLE NAS

Rethinking Architecture Selection in Differentiable NAS 论文链接&#xff1a;https://arxiv.org/abs/2108.04392v1 项目链接&#xff1a;https://github.com/ruocwang/darts-pt ABSTRACT 可微架构搜索(Differentiable Neural Architecture Search, NAS)是目前最流行的网…

上位机图像处理和嵌入式模块部署(qmacvisual透视变换)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 说到透视变换&#xff0c;以前我也不明白为什么有这样一个需求。后来在tier1做车道线检测的时候&#xff0c;才知道如果把camera拍摄到的图像做一次…

FebHost:意大利.IT域名一张意大利网络名片

.IT域名是意大利的国家顶级域名&#xff0c;对于意大利企业和个人而言,拥有一个属于自己的”.IT”域名无疑是件令人自豪的事。这个被誉为意大利互联网标志性代表的域名,不仅隐含着浓厚的意大利文化特色,还为使用者在当地市场的推广铺平了道路。 对于那些希望在意大利市场建立强…

HEVC的Profile和Level介绍

文章目录 HEVCProfile&#xff08;配置&#xff09;&#xff1a;Level&#xff08;级别&#xff09;&#xff1a;划分标准 HEVC HEVC&#xff08;High Efficiency Video Coding&#xff09;&#xff0c;也称为H.265&#xff0c;是一种视频压缩标准&#xff0c;旨在提供比先前的…

Linux(CentOS7.5) 安装部署 Python3.6(超详细!包含 Yum 源配置!)

文章目录 1.配置 Yum 源2.下载 Python3 包3. 解压4.安装依赖环境5.安装出错场景 6.创建软链接7.配置 Python3 的环境变量8.验证补充&#xff1a;安装 openssl-devel补充&#xff1a;pip3 源配置 1.配置 Yum 源 # 注意&#xff01;&#xff01;&#xff01;请先切换到 root 账号…

stable diffusion如何下载预处理器?

如何下载预处理器&#xff1f; 具体位置:SD文件>extensions>sd-webui-controlnet>annotator” 把整个文件夹复制到SD的文件夹里面 里面有一个“downloads”文件夹 把这些模型复制到“downloads”文件夹里

【QT学习】1.qt初识,创建qt工程,使用按钮,第一个交互按钮

1.初识qt--》qt是个框架&#xff0c;不是语言 1.学习路径 一 QT简介 &#xff0c;QTCreator &#xff0c;QT工程 &#xff0c;QT的第一个程序&#xff0c;类&#xff0c;组件 二 信号与槽 三 对话框 四 QT Desiner 控件 布局 样式 五 事件 六 GUI绘图 七 文件 八 …

constexpr与std::is_same_v碰撞会产生什么火花?

1. 只编译会用到的if分支 示例代码一中&#xff0c;checkType_v1和checkType_v2两个函数的区别就是if的条件里一个加了constexpr一个没加&#xff0c;加与不加从结果来看都一样&#xff0c;那在编译时和运行时各有什么区别呢&#xff1f; 示例代码一&#xff0c;test_01.cpp&…

数字孪生项目的开发工具

数字孪生项目的开发工具是实现数字孪生技术应用的关键。它们使得开发者能够创建、管理和优化数字孪生模型&#xff0c;以及与真实世界的实体进行交互。以下是一些数字孪生项目开发中常用的工具&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件…

软件工程学习笔记14——案例解析篇

一、大型开源项目对软件工程的应用 以VS Code为例&#xff0c;看大型开源项目是如何应用软件工程的。 软件工程的核心&#xff0c;就是围绕软件项目开发&#xff0c;对开发过程的组织&#xff0c;对方法的运用&#xff0c;对工具的使用。 所以当我们去观察一个软件项目&#…

主流好用的 Markdown 编辑器介绍

在当今程序员的日常工作中&#xff0c;Markdown 已经成为了一种常用的文本标记语言&#xff0c;它简洁、易读、易写&#xff0c;被广泛应用于写作、文档编写、博客撰写等场景。为了更高效地编辑和管理 Markdown 格式的文档&#xff0c;选择一款功能强大、易用的 Markdown 编辑器…

基于jdbc+mysql+java的简单货物管理系统

智能软件&#xff08;后端测试&#xff09; 路线篇 第一阶段&#xff1a; javamysqljdbc综合实战应用ai企业级框架工具&#xff1a;eclipse 第二阶段 seevletssm框架vueai综合实战serclet工具&#xff1a; ideaiflycode大模型gitmaven 组内任务 项目开发答辩 第三阶段 基…

GEE实践应用|热岛效应(一)地表温度计算

目录 1.学习目标 2.理论介绍 3.从MODIS获得地表温度 4.从Landsat卫星获得地表温度 1.学习目标 ①了解如何使用GEE计算地表温度 2.理论介绍 城市化涉及用建筑物、道路和停车场等建筑结构取代自然景观。这种土地覆盖的改变也改变了土地表面的特性。这些变化的范围从表面反射和…

【ERP原理与应用】作业·思考题三、四

思考题三 P77第四章3&#xff0c; 6&#xff0c;8 3.生产规划的基本内容是什么&#xff1f; 生产规划是根据企业未来一段时间内预计资源可用量和市场需求量之间的平衡所制定的概括性设想是根据企业所拥有的生产能力和需求预测&#xff0c;对企业未来较长一段时间内的产品、产…

基于springboot和vue的在线图书管理系统

目 录 摘要…………………………………………………………………………………………………………1 引言/引论 …………………………………………………………………………………………………2 1.绪论……………………………………………………………………………………3 1.1 背…