106.进程控制(结束、孤儿、僵尸进程)以及进程回收

目录

结束进程

孤儿进程 

僵尸进程 

进程回收 

wait()

waitpid


        进程控制是指在操作系统中对进程进行创建、终止、挂起、唤醒以及进程之间的同步、通信等操作的管理。

结束进程

exit()_exit() 函数都用于终止一个进程,但它们之间有一些重要的区别:

exit() 函数:

  • 头文件: #include <stdlib.h>
  • 功能: exit() 函数用于正常终止程序的执行。它执行一系列清理操作,包括调用通过 atexit() 注册的终止处理程序,并将程序的返回状态传递给操作系统。
  • 清理操作: 会调用通过 atexit() 注册的终止处理程序,关闭文件流(通过 fclose()),刷新缓冲区等。
  • 使用范例:
    #include <stdlib.h>
    
    int main() {
        // 一些代码
    
        // 正常退出,返回状态码 0
        exit(0);
    }
    

    _exit() 函数:

  • 头文件: #include <unistd.h>
  • 功能: _exit() 函数也用于终止程序,但它是一个较低级别的函数。它不会执行 exit() 做的清理工作,包括不会调用通过 atexit() 注册的函数,不会刷新 I/O 缓冲区等。
  • 适用场景: _exit() 主要用于在子进程中立即终止程序而无需执行清理操作。在这种情况下,使用 _exit() 可以避免执行 exit() 中的一些不必要的操作。
  • 使用范例:
    #include <unistd.h>
    
    int main() {
        // 一些代码
    
        // 立即退出,不执行清理操作
        _exit(0);
    }
    

    在大多数情况下,如果你需要正常终止程序并执行清理操作,应该使用 exit() 函数。如果你希望在子进程中立即退出,可以使用 _exit()

孤儿进程 

        孤儿进程是指在其父进程结束或者被终止后,仍然在系统中运行的子进程。孤儿进程会被操作系统的 init 进程(进程号为1)接管,并由 init 进程负责回收。这确保了孤儿进程不会成为系统资源的泄漏。

        当一个进程创建了子进程,而父进程先于子进程结束,那么子进程就会变成孤儿进程。这通常发生在父进程创建子进程后,父进程先于子进程调用 exit() 终止,或者父进程意外崩溃的情况。

        init 进程会定期检查是否有孤儿进程,如果发现孤儿进程,就会成为孤儿进程的新的父进程,并负责回收它的资源。这种处理方式确保了操作系统的稳定性和资源管理的有效性。

下面是一个产生孤儿进程的简单示例:

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

int main()
{
    pid_t child_pid = fork();

    if (child_pid < 0)
    {
        // 错误处理
        perror("fork");
        exit(0);
    }

    if (child_pid > 0)
    {
        // 父进程
        printf("我是父进程:pid=%d\n", getpid());
        exit(0);
    }
    else if (child_pid == 0)
    {
        // 子进程
        sleep(2);
        printf("我是子进程:pid=%d,父进程:ID=%d\n", getpid(), getppid());
    }
    return 0;
}

        在 Linux 中,如果一个进程的父进程终止,而它还没有被领养,那么它会被 init 进程(PID=1)领养。这确保了孤儿进程总是有一个父进程。init 进程在系统启动时由内核启动,是所有进程的祖先。

        对于没有桌面环境的系统,确实是 init 进程接管孤儿进程。但是,对于有桌面环境的系统,具体情况可能有所不同,因为桌面环境通常有自己的进程管理机制。

僵尸进程 

        僵尸进程是已经结束执行的进程,但其在进程表中仍然保留着一定的信息,包括进程号(PID)和退出状态等。这样的进程称为僵尸进程。僵尸进程的存在可能导致系统中的进程表被占用,过多的僵尸进程可能影响系统的正常运行。

        通常,一个进程在结束时会向其父进程发送一个信号,告诉父进程它已经结束。父进程接收到这个信号后,应该调用 waitwaitpid 系统调用来获取子进程的退出状态,释放子进程的资源。如果父进程没有及时处理,子进程就会变成僵尸进程。

运行下面的代码就可以得到一个僵尸进程了:

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

int main()
{
    pid_t pid;
    // 创建子进程
    for (int i = 0; i < 5; ++i)
    {
        pid = fork();
        if (pid == 0)
        {
            break;
        }
    }

    // 父进程
    if (pid > 0)
    {
        // 需要保证父进程一直在运行
        // 一直运行不退出, 并且也做回收, 就会出现僵尸进程
        while (1)
        {
            printf("我是父进程, pid=%d\n", getpid());
            sleep(1);
        }
    }
    else if (pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}

        这段代码创建了一个父进程和5个子进程。子进程在输出一行信息后立即退出,而父进程则进入一个无限循环,在每次循环中输出一行信息。

        在这个场景中,由于父进程一直在运行,而子进程在输出信息后就退出了,子进程就有可能成为僵尸进程。父进程没有调用 waitwaitpid 函数来回收子进程,因此子进程退出后,其退出状态信息会一直保留在进程表中,形成僵尸进程。

进程回收 

        为了避免僵尸进程的产生,一般我们会在父进程中进行子进程的资源回收,回收方式有两种,一种是阻塞方式wait(),一种是非阻塞方式waitpid()。

wait()

        这是个阻塞函数,如果没有子进程退出, 函数会一直阻塞等待, 当检测到子进程退出了, 该函数阻塞解除回收子进程资源。这个函数被调用一次, 只能回收一个子进程的资源,如果有多个子进程需要资源回收, 函数需要被调用多次。

函数原型如下:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

返回值:

  • 成功:返回被回收的子进程的进程ID
  • 失败: -1
  • 没有子进程资源可以回收了, 函数的阻塞会自动解除, 返回-1
  • 回收子进程资源的时候出现了异常
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

// wait 函数回收子进程资源
int main()
{
    pid_t pid;
    // 创建子进程
    for (int i = 0; i < 5; ++i)
    {
        pid = fork();
        if (pid == 0)
        {
            break;
        }
    }

    // 父进程
    if (pid > 0)
    {
        // 需要保证父进程一直在运行
        while (1)
        {
            // 回收子进程的资源
            // 子进程由多个, 需要循环回收子进程资源
            pid_t ret = wait(NULL);
            if (ret > 0)
            {
                printf("成功回收了子进程资源, 子进程PID: %d\n", ret);
            }
            else
            {
                printf("回收失败, 或者是已经没有子进程了...\n");
                break;
            }
            printf("我是父进程, pid=%d\n", getpid());
        }
    }
    else if (pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}

waitpid

        waitpid() 函数可以看做是 wait() 函数的升级版,通过该函数可以控制回收子进程资源的方式是阻塞还是非阻塞,另外还可以通过该函数进行精准打击,可以精确指定回收某个或者某一类或者是全部子进程资源。

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);
  • pid 参数指定要等待的子进程:

    • 如果 pid > 0,则等待进程ID等于 pid 的子进程。
    • 如果 pid == 0,则等待与调用进程在同一进程组的任一子进程。
    • 如果 pid == -1,则等待任一子进程,与 wait 效果相同。
    • 如果 pid < -1,则等待进程组ID等于 pid 绝对值的任一子进程。
  • status 是一个指向整型的指针,用于保存子进程的退出状态。

  • options 是一组位掩码,可以通过按位或运算来组合。常用的选项有:

    • WNOHANG:以非阻塞方式等待,即使没有子进程退出也立即返回。

返回值:

  • 如果函数是非阻塞的, 并且子进程还在运行, 返回0
  • 成功: 得到子进程的进程ID
  • 失败: -1
  • 没有子进程资源可以回收了, 函数如果是阻塞的, 阻塞会解除, 直接返回-1
  • 回收子进程资源的时候出现了异常
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

// 和wait() 行为一样, 阻塞
int main()
{
    pid_t pid;
    // 创建子进程
    for (int i = 0; i < 5; ++i)
    {
        pid = fork();
        if (pid == 0)
        {
            break;
        }
    }

    // 父进程
    if (pid > 0)
    {
        // 需要保证父进程一直在运行
        while (1)
        {
            // 回收子进程的资源
            // 子进程由多个, 需要循环回收子进程资源
            int status;
            pid_t ret = waitpid(-1, &status, 0); // == wait(NULL);
            if (ret > 0)
            {
                printf("成功回收了子进程资源, 子进程PID: %d\n", ret);
                // 判断进程是不是正常退出
                if (WIFEXITED(status))
                {
                    printf("子进程退出时候的状态码: %d\n", WEXITSTATUS(status));
                }
                if (WIFSIGNALED(status))
                {
                    printf("子进程是被这个信号杀死的: %d\n", WTERMSIG(status));
                }
            }
            else
            {
                printf("回收失败, 或者是已经没有子进程了...\n");
                break;
            }
            printf("我是父进程, pid=%d\n", getpid());
        }
    }
    else if (pid == 0)
    {
        // 子进程, 执行这句代码之后, 子进程退出了
        printf("===我是子进程, pid=%d, 父进程ID: %d\n", getpid(), getppid());
    }
    return 0;
}

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

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

相关文章

区分JAVA项目中的ENTITY,VO,DTO,BO

目录 前言1. ENTITY2. VO3. DTO4. BO5. 总结 前言 在Java项目中&#xff0c;ENTITY、VO、DTO和BO是常见的设计模式或者概念&#xff0c;用于表示不同的数据层次和对象之间的关系。 了解这些概念助于在项目中分离关注点&#xff0c;提高代码的可维护性和可扩展性。 ENTITY用于…

Theamleaf导出pdf模版编写(原始th/td编写表格)

需求&#xff1a;简单的theamleaf编写表格就是简单的th/td&#xff0c;新需求是导出的模版是学员table表&#xff0c;每个项目的学员数量是不定的&#xff0c;所以用到 <tr th:each"item,start:${studentList}"> 所有代码&#xff1a; <!DOCTYPE html>…

harmonyOS学习笔记之@Provide装饰器和@Consume装饰器

Provide和Consume&#xff0c;应用于与后代组件的双向数据同步&#xff0c;应用于状态数据在多个层级之间传递的场景。不同于State/Link装饰器修饰的 父子组件之间通过命名参数机制传递&#xff0c;Provide和Consume摆脱参数传递机制的束缚&#xff0c;实现跨层级传递。 其中Pr…

全景万店通打造掌上智慧生活助手,助力店铺全景引流

随着网络经济的崛起&#xff0c;新一代的消费群体的消费习惯逐渐变得富有个性化&#xff0c;因此他们对于传统的营销方式具有视觉疲劳&#xff0c;传统广告的效果也越发微小&#xff0c;但是请明显来代言&#xff0c;成本又十分高昂&#xff0c;那么还有什么引流好方法呢&#…

Web信息收集,互联网上的裸奔者

Web信息收集&#xff0c;互联网上的裸奔者 1.资产信息收集2.域名信息收集3.子域名收集4.单点初步信息收集网站指纹识别服务器类型(Linux/Windows)网站容器(Apache/Nginx/Tomcat/IIS)脚本类型(PHP/JSP/ASP/ASPX)数据库类型(MySQL/Oracle/Accees/SqlServer) 5.单点深入信息收集截…

基于python+unittest实现接口自动化测试

简介 本文通过从Postman获取基本的接口测试Code简单的接口测试入手&#xff0c;一步步调整优化接口调用&#xff0c;以及增加基本的结果判断&#xff0c;讲解Python自带的Unittest框架调用&#xff0c;期望各位可以通过本文对接口自动化测试有一个大致的了解。 为什么要做接口…

React 中虚拟DOM是什么,为什么需要它?

注意&#xff1a;本节主要讲React中的虚拟DOM&#xff0c;但是虚拟DOM并不是React中特有的内容。 1. React 中虚拟 DOM是什么&#xff1f; 虚拟DOM是对真实DOM的描述&#xff0c;虚拟DOM是JS对象&#xff0c;实际上就是 JSX 通过 babel 转换成 React.createElement()&#xff…

中缀表达式转后缀表达式与后缀表达式计算(详解)

**中缀表达式转后缀表达式的一般步骤如下&#xff1a; 1&#xff1a;创建一个空的栈和一个空的输出列表。 2&#xff1a;从左到右扫描中缀表达式的每个字符。 3&#xff1a;如果当前字符是操作数&#xff0c;则直接将其加入到输出列表中。 4&#xff1a;如果当前字符是运算符&a…

你是外包,麻烦不要偷吃零食,注意素质..

我自己没经历过外包&#xff0c;靠自己的所见所闻可能写出来的东西会很主观&#xff0c;所幸我有不少外包的读者&#xff0c;还有几个在外包工作或工作过的朋友&#xff0c;在跟她们深度交流之后&#xff0c;这这里聊一下我自己的一些看法。 注&#xff1a;本文不代表所有外包公…

Python接口自动化测试数据和代码分离解析

common中存放的是整个项目中公共使用的封装方法 从工程目录上可以看到区分 datas中专门存放测试数据(yml文件) cases中专门集中存放测试用例 ... 数据分离的第一步先找到工程项目路径 1 2 3 4 5 6 7 8 9 10 11 12 # -*- encoding: utf-8 -*- """ __Software…

智能运维相关算法总结

概念&#xff1a; 智能运维&#xff08;AIOps&#xff09;是基于已有的运维数据&#xff08;日志、监控信息 、应用信息&#xff09;并通过机器学习的方法来进一步解决自动化运维没办法解决的问题&#xff0c;其核心是机器学习和大数据平台。 目标&#xff1a; 事前预警&…

C++ day59 下一个更大元素Ⅱ 接雨水

题目1&#xff1a;503 下一个更大元素Ⅰ 题目链接&#xff1a;下一个更大元素Ⅱ 对题目的理解 返回循环数组中每个元素的下一个更大元素&#xff0c; 数字x的下一个更大元素是循环等的搜索它的最近的下一个更大的数 数组的中至少有一个元素 本题难点在于循环遍历这里&…

新手搭建知识付费平台必备攻略:如何以低成本实现高转化?

我有才知识付费平台 一、引言 随着知识经济的崛起&#xff0c;越来越多的知识提供者希望搭建自己的知识付费平台。然而&#xff0c;对于新手来说&#xff0c;如何以低成本、高效率地实现这一目标&#xff0c;同时满足自身需求并提高客户转化率&#xff0c;是一大挑战。本文将…

【面试经典150 | 二叉树】从前序与中序遍历序列构造二叉树

文章目录 写在前面Tag题目来源题目解读解题思路方法一&#xff1a;递归 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并附带一些对于本题涉及到的数据结构等内容…

Java数字化健康卫生智慧云HIS系统源码

基于云计算技术的B/S架构云HIS集挂号、处方、收费、取药、病历于一体,完全适配各类中小型医院、诊所。 一、云 HIS定义 1、云 HIS 系统是运用云计算、大数据、物联网等新兴信息技术&#xff0c;按照现代医疗卫生管理要求&#xff0c;在一定区域范围内以数字化形式提供医疗卫生…

软文营销全过程分享,助力企业提高宣传效率

数字化时代下&#xff0c;软文营销已经成为许多企业推广品牌的手段&#xff0c;但是在营销过程中大部分企业认为只需要写好文章进行发布就够了&#xff0c;这其实是错误的&#xff0c;只会浪费企业的时间精力。今天媒介盒子就分享软文营销全过程&#xff0c;助力企业提高宣传效…

cpu 300% 爆满 内存占用不高 排查

top查询 cpu最高的PID ps -ef | grep PID 查看具体哪一个jar服务 jstack -l PID > ./jstack.log 下载/打印进程的线程栈信息 可以加信息简单分析 或进一步 查看堆内存使用情况 jmap -heap Java进程id jstack.log 信息示例 Full thread dump Java HotSpot(TM) 64-Bit Se…

SpringMVC 案例

文章目录 前言1. 计算器1.1 准备前端代码1.2 测试前端代码1.3 完成后端代码1.4 验证程序 2. 留言板2.1 前端代码准备2.2 测试前端代码2.3 完成前后端交互代码2.4 完成后端代码2.5 案例测试2.6 完善前后端交互2.7 完善后端代码2.8 完整功能测试 lombok简单的方式添加Lombok工具3…

小视频怎么做成二维码?视频二维码3步生成

在日常工作和生活中经常会看到各种类型的小视频、短视频&#xff0c;比如网页、抖音等等的视频都是可以下载查看的。当我们想要将下载视频分享给多个人看时&#xff0c;生成二维码的方式会更加的方便&#xff0c;那么视频如何生成二维码呢&#xff1f;下面就将快捷生成二维码的…

spring boot 3.2 整合 keycloak

背景 项目中用到 keycloak&#xff0c;因此其他所有管理页面要集成 keycloak 做统一登录认证。 Keycloak 侧配置 容器方式启动 keycloak 服务端 docker run -d --name mykeycloak -p 8080:8080 -e KEYCLOAK_ADMINadmin -e KEYCLOAK_ADMIN_PASSWORDadmin ke…