Defunct进程 僵尸进程

来源:互联网 发布:java博彦科技 编辑:程序博客网 时间:2024/05/17 22:33

在测试基于 DirectFB+Gstreamer 的视频联播系统的一个 Demo 的时候,其中大量使用 system 调用的语句,例如在 menu 代码中的 system("./play") ,而且多次执行,这种情况下,在 ps -ef 列表中出现了大量的 defunct 进程,对程序的运行时有害的。按说system的源码中应该已经包含了wait,但也不能排除开发板上这个版本的system中可能没有wait,总之,开发板上在调用system后添加wait之后,defunct进程不复存在了。

下面谈谈 defunct 进程,中文翻译叫僵尸进程。下文整理于网络以及APUE一书。


一、什么是僵尸进程


在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他,那么他将变成一个僵尸进程。当用ps命令观察进程的执行状态时,看到这些进程的状态栏为defunct。僵尸进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。


但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init进程来接管他,成为他的父进程,从而保证每个进程都会有一个父进程。而Init进程会自动wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。


二、UNIX下进程的运作方式


每个Unix进程在进程表里都有一个进入点(entry),核心进程执行该进程时使用到的一切信息都存储在进入点。当用 ps 命令察看系统中的进程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,核心进程就会在进程表中给这个新进程分配一个进入点,然后将相关信息存储在该进入点所对应的进程表内。这些信息中有一项是其父进程的识别码。


子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。那么会不会因为父进程太忙来不及 wait 子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢?


不会。因为UNIX提供了一种机制可以保证,只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是:当子进程走完了自己的生命周期后,它会执行exit()系统调用,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出码exit code,退出状态the terminationstatus of the process,运行时间the amount of CPU time taken by the process等),这些数据会一直保留到系统将它传递给它的父进程为止,直到父进程通过wait / waitpid来取时才释放。


也就是说,当一个进程死亡时,它并不是完全的消失了。进程终止,它不再运行,但是还有一些残留的数据等待父进程收回。当父进程 fork() 一个子进程后,它必须用 wait() (或者 waitpid())等待子进程退出。正是这个 wait() 动作来让子进程的残留数据消失。


三、僵尸进程的危害


如果父进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统的进程表容量是有限的,所能使用的进程号也是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。


所以,defunct进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。而且,
由于调度程序无法选中Defunct 进程,所以不能用kill命令删除Defunct 进程,惟一的方法只有重启系统。


四、僵尸进程的产生


如果子进程死亡时父进程没有 wait(),通常用 ps 可以看到它被显示为“<defunct>”,这样就产生了僵尸进程。它将永远保持这样直到父进程 wait()。


由此可见,defunct进程的出现时间是在子进程终止后,但是父进程尚未读取这些数据之前。利用这一点我们可以用下面的程序建立一个defunct 进程:

C代码  收藏代码
  1. #include <stdio.h>  
  2.   
  3. #include<sys/types.h>  
  4.   
  5. main()  
  6. {  
  7.   
  8.     if(!fork())  
  9.     {  
  10.   
  11.         printf(“child pid=%d\n”, getpid());  
  12.   
  13.         exit(0);  
  14.   
  15.     }  
  16.   
  17.     sleep(20);  
  18.   
  19.     printf(“parent pid=%d \n”, getpid());  
  20.   
  21.     exit(0);  
  22.   
  23. }  
 

当上述程序以后台的方式执行时,第17行强迫程序睡眠20秒,让用户有时间输入ps -e指令,观察进程的状态,我们看到进程表中出现了defunct进程。当父进程执行终止后,再用ps -e命令观察时,我们会发现defunct进程也随之消失。这是因为父进程终止后,init 进程会接管父进程留下的这些“孤儿进程”(orphan process),而这些“孤儿进程”执行完后,它在进程表中的进入点将被删除。如果一个程序设计上有缺陷,就可能导致某个进程的父进程一直处于睡眠状态或是陷入死循环,父进程没有wait子进程,也没有终止以使Init接管,该子进程执行结束后就变成了defunct进程,这个defunct 进程可能会一直留在系统中直到系统重新启动。

 

 

在看一个产生僵尸进程的例子。

子进程要执行的程序test_prog

 

 

C代码  收藏代码
  1. //test.c  
  2. #include <stdio.h>  
  3. int main()  
  4. {  
  5.         int i = 0;  
  6.         for (i = 0 ; i < 10; i++)  
  7.         {  
  8.                 printf ("child time %d\n", i+1);  
  9.                 sleep (1);  
  10.         }  
  11.         return 0;  
  12. }  
 

 

 

父进程father的代码father.c

 

 

C代码  收藏代码
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <sys/types.h>  
  4. #include <sys/wait.h>  
  5. int main()  
  6. {  
  7.         int pid = fork ();  
  8.         if (pid == 0)  
  9.         {  
  10.                 system ("./test_prog");  
  11.                 _exit (0);  
  12.         }else  
  13.         {  
  14.                 int i = 0;  
  15.                 /* 
  16.                                 int status = 0; 
  17.                 while (!waitpid(pid, &status, WNOHANG)) 
  18.                 { 
  19.                         printf ("father waiting%d\n", ++i); 
  20.                         sleep (1); 
  21.                 }*/  
  22.                 while (1)  
  23.                 {  
  24.                         printf ("father waiting over%d\n", ++i);  
  25.                         sleep (1);  
  26.                 }  
  27.                 return 0;  
  28.         }  
  29.   
  30. }  
 

 

 

执行./father,当子进程退出后,由于父进程没有对它的退出进行关注,会出现僵尸进程

 

 

C代码  收藏代码
  1. 20786 pts/0    00:00:00 father  
  2. 20787 pts/0    00:00:00 father <defunct>  

 

    总结:子进程成为 defunct 直到父进程 wait(),除非父进程忽略了 SIGCLD 。更进一步,父进程没有 wait() 就消亡(仍假设父进程没有忽略 SIGCLD )的子进程(活动的或者 defunct)成为 init 的子进程,init 着手处理它们。


五、如何避免僵尸进程

 

1、父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。

 

在上个例子中,如果我们略作修改,在第8行sleep()系统调用前执行wait()或waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,此时系统会立即删除该进入点。在这种情形下就不会产生defunct进程。


2. 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler。在子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。

 

3. 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCLD, SIG_IGN)或signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号 


4. fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做。 下面就是Stevens给的采用两次folk避免僵尸进程的示例:

 

 

C代码  收藏代码
  1. #include "apue.h"  
  2. #include <sys/wait.h>  
  3.   
  4. int  
  5. main(void)  
  6. ...{  
  7.      pid_t    pid;  
  8.   
  9.     if ((pid = fork()) < 0) ...{  
  10.          err_sys("fork error");  
  11.      } else if (pid == 0) ...{     /**//* first child */  
  12.         if ((pid = fork()) < 0)  
  13.              err_sys("fork error");  
  14.         else if (pid > 0)  
  15.              exit(0);    /**//* parent from second fork == first child */  
  16.         /**//* 
  17.           * We're the second child; our parent becomes init as soon 
  18.           * as our real parent calls exit() in the statement above. 
  19.           * Here's where we'd continue executing, knowing that when 
  20.           * we're done, init will reap our status. 
  21.          */  
  22.          sleep(2);  
  23.          printf("second child, parent pid = %d ", getppid());  
  24.          exit(0);  
  25.      }  
  26.       
  27.     if (waitpid(pid, NULL, 0) != pid)  /**//* wait for first child */  
  28.          err_sys("waitpid error");  
  29.   
  30.     /**//* 
  31.       * We're the parent (the original process); we continue executing, 
  32.       * knowing that we're not the parent of the second child. 
  33.      */  
  34.      exit(0);  
  35. }  
阅读全文
'); })();
0 0
原创粉丝点击
热门IT博客
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 瑞雪兆丰年上一句农谚 当家农女 谢惟爱你 谷贱伤农 谷贱伤农经济学原理 千禧农谷乐园 千禧农谷 谷什么什么农成语 淄博千禧农谷 谷贱伤农的经济学解释 谷贱伤农的供求曲线图 谷贵饿农谷贱伤农 农女财迷小当家 农财网柑桔通 山西农大计财处 农女爱财娶夫有道 守财农妃千千岁 黔农贷 旺农贷 宜农贷 江苏农贷 新农贷 翼农贷 京农贷 e农贷 农贷宝 农贷通 成都农贷通 惠农贷 小额农贷 卡农贷 惠农贷 担保 借贷的银行 额度信贷 农贸市场 新农贸市场 农贸 江村农贸综合批发市场 附近农贸市场 华东农贸市场 农贸市场做什么生意好 诸天农贸世界