• Linux 下孤儿进程与僵尸进程详解


    引言:前面的系列文章介绍了进程的基础概念相关常用的 API,本文将介绍两种比较特殊进程:孤儿进程与僵尸进程,进一步加深对进程的了解,避免进程使用过程中的一些坑点。

    孤儿进程

    什么是孤儿进程?

    父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程(Orphan Process)

    每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。 因此孤儿进程并不会有什么危害。

    总之:孤儿进程就是父进程退出了,但子进程还在执行。

    示例

    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
       pid_t pid = -1;
       
       // 创建子进程
       pid = fork();
       if (pid < 0)  // 没有创建成功  
       {   
           perror("fork");
           return 1;
       }
       
       // 父进程
       if (pid > 0)
       {
           printf("父进程休息3秒后退出。。。\n");
           printf("父进程: pid:%d\n", getpid());
           sleep(1);
           printf("父进程等太累了,现退出了。。。\n");
           exit(0);
       }
       
       while (1)
       {
           printf("子进程不停的工作,子进程:pid:%d,父进程:ppid:%d\n", getpid(), getppid());
           sleep(1);
       }
       
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    运行结果

    yxm@192:~$ gcc test.c -o test
    yxm@192:~$ ./test
    父进程休息3秒后退出。。。
    父进程: pid:32075
    子进程不停的工作,子进程:pid:32076,父进程:ppid:32075
    父进程等太累了,现退出了。。。
    子进程不停的工作,子进程:pid:32076,父进程:ppid:32075
    yxm@192:~$ 子进程不停的工作,子进程:pid:32076,父进程:ppid:1 # 终端可以输入,同时有数据在输出
    子进程不停的工作,子进程:pid:32076,父进程:ppid:1
    子进程不停的工作,子进程:pid:32076,父进程:ppid:1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 一般情况下,运行一个程序时,默认会切换到后台运行,当有输出的时候再切换到前台。

      如上面的运行结果所示:创建子进程后,子进程复制了父进程内核部分的某些数据(比如标准输入、标准输出、标准错误),所以父进程和子进程的标准输出都是当前终端。又因为父进程是前台进程,所以会占用当前终端,但是父进程死亡后,终端占用被解除,但是子进程(变成孤儿进程)没有死亡,其标准输出依旧是当前终端。最终形成了,终端可以输入,同时有数据在输出的特殊情况。

    • 【注意】ubuntu 系统中,字节界面中,产生的孤儿进程会被 1 号( 即 init 进程)进程收养,但是在图形界面中孤儿进程会被非1号进程收养。

    僵尸进程

    僵尸进程介绍

    每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉(子进程残留资源(PCB)存放于内核中),需要父进程去释放,进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。

    僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait()waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。

    总之:僵尸进程就是子进程结束了,但父进程没有回收其资源。

    #include 
    #include 
    #include 
    #include 
    
    int main()
    {
       int i = 0;
       pid_t pid = -1;
       
       // 创建子进程
       pid = fork();
       if (-1 == pid)  // 没有创建成功  
       {   
           perror("fork");
           return 1;
       }
       
       // 子进程
       if (0 == pid)
       {
           for (int i = 0; i < 5; i++)
           {
               printf("子进程做事%d\n", i);
               sleep(1);
           }
           printf("子进程想不开,结束了自己。。。。\n");
           exit(0);
       }
       else if (pid > 0) 
       {
           while(1) 
           {
               printf("父进程休眠了, pid : %d, ppid : %d\n", getpid(), getppid());
               sleep(1);
           }
    
       }
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    yxm@192:~$ gcc test.c -o test
    yxm@192:~$ ./test 
    父进程休眠了, pid : 33087, ppid : 30344
    子进程做事0
    父进程休眠了, pid : 33087, ppid : 30344
    子进程做事1
    子进程做事2
    父进程休眠了, pid : 33087, ppid : 30344
    父进程休眠了, pid : 33087, ppid : 30344
    子进程做事3
    子进程做事4
    父进程休眠了, pid : 33087, ppid : 30344
    父进程休眠了, pid : 33087, ppid : 30344
    子进程想不开,结束了自己。。。。
    父进程休眠了, pid : 33087, ppid : 30344
    ^C
    yxm@192:~$ ps -aux
    ...
    ...
    yxm       33087  0.0  0.0   4516   756 pts/0    S+   00:10   0:00 ./test
    yxm       33088  0.0  0.0      0     0 pts/0    Z+   00:10   0:00 [test] <defunct>#僵尸进程
    yxm       33125  0.0  0.0   7476   832 ?        S    00:10   0:00 sleep 180
    yxm       33180  0.0  0.1  37860  3420 pts/1    R+   00:10   0:00 ps -aux
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    僵尸进程解决办法

    方式一:僵尸进程的产生是因为父进程没有 wait() 子进程。所以如果我们自己写程序的话一定要,最好在父进程中通过 wait()waitpid() 来避免僵尸进程的产生

    方式二:当系统中出现了僵尸进程时,我们是无法通过 kill 命令把它清除掉的。但是我们可以杀死它的父进程,让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并清理。具体步骤如下:

    1. 首先,需要确定僵尸进程的相关信息,比如父进程 ppid、僵尸进程的 pid 以及命令行等信息。可以执行如下命令:

      ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]'
      
      • 1

      参数说明:

      • -e:参数用于列出所有的进程;
      • -o:参数用于设定输出格式,这里只输出进程的stat(状态信息)、ppid(父进程pid)、pid(当前进程的pid),cmd(进程的可执行文件);
      • egrep:是linux下的正则表达式工具:
        • ‘^’:这是正则表达式,表示第一个字符的位置
        • [Zz],表示 z 或者大写的 Z 字母,即表示第一个字符为 Z 或者 z 开头的进程数据,因为僵尸进程的状态信息以 Z 或者 z 字母开头。
    2. 然后,可以 kill -9 父进程 pid。kill 之后,僵尸进程将被 init 进程收养并清理

    【补充】现在大多数 linux 系统,会将僵尸进程标识为 defunct,所以也可以通过如下命令来获取僵尸进程信息:

    ps -ef | grep "defunct"
    
    • 1

    推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
    分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
    fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
    TCP/IP,协程,DPDK等技术内容,点击立即学习:服务器课程

    总结

    孤儿进程与僵尸进程是两种特殊的进程,一种是父进程先退出,子进程变成孤儿,这种进程没有危害;一种是子进程先退出,父进程没有回收资源导致子进程变成僵尸,会占用系统资源。他们都发生过在父子进程之间。

  • 相关阅读:
    Twincat Scope 使用经验总结
    美国政府备忘录中的软件自我认证
    Servlet
    视频批量剪辑工具,自定义视频速率,批量剪辑工具助力创意无限”
    Web服务器实战
    Web前端:选择ReactJS的7个理由
    创作没灵感?可视化图谱+搜索引擎助你无障碍生成内容 #ATLAS + Stable Diffusion
    怎么提高外贸开发信的回复率?
    数据结构的结构复杂度你了解么
    记一次 .NET 某工控数据采集平台 线程数 爆高分析
  • 原文地址:https://blog.csdn.net/weixin_45004203/article/details/126057449