Linux系统编程——特殊进程之僵尸进程

来源:互联网 发布:拉里·伯德数据 编辑:程序博客网 时间:2024/05/17 04:56
Linux多任务编程系统编程僵尸进程

目录(?)[+]

僵尸进程(Zombie Process)

进程已运行结束,但进程的占用的资源未被回收,这样的进程称为僵尸进程。


在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。 但是仍然为其保留一定的信息,这些信息主要主要指进程控制块的信息(包括进程号、退出状态、运行时间等)。直到父进程通过 wait() 或 waitpid() 来获取其状态并释放(具体用法,请看《等待进程结束》)。 这样就会导致一个问题,如果进程不调用wait() 或 waitpid() 的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程.此即为僵尸进程的危害,应当避免


子进程已运行结束,父进程未调用 wait() 或 waitpid() 函数回收子进程的资源是子进程变为僵尸进程的原因


僵尸进程测试程序如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <errno.h>  
  4. #include <stdlib.h>  
  5.   
  6. int main(int argc, char *argv[])  
  7. {  
  8.     pid_t pid;  
  9.     pid = fork();   //创建进程  
  10.       
  11.     if( pid < 0 ){ // 出错  
  12.         perror("fork error:");  
  13.         exit(1);  
  14.     }else if( 0 == pid ){ // 子进程  
  15.       
  16.         printf("I am child process.I am exiting.\n");  
  17.         printf("[son id]: %d\n", getpid() );  
  18.           
  19.         exit(0);  
  20.     }else if( pid > 0){ // 父进程  
  21.         // 父进程没有调用 wati() 或 watipid()   
  22.         sleep(1); // 保证子进程先运行  
  23.         printf("I am father process.I will sleep two seconds\n");  
  24.         printf("[father id]: %d\n", getpid() );  
  25.           
  26.         while(1); // 不让父进程退出  
  27.     }  
  28.       
  29.     return 0;  
  30. }  

我们在一个终端运行以上程序:


在终端敲:ps -ef | grep defunct ,后面尖括号里是 defunct 的都是僵尸进程。


我们另启一个终端,查看进程的状态,有哪些是僵尸进程:



或者



如何避免僵尸进程?

1)最简单的方法,父进程通过 wait() 和 waitpid() 等函数等待子进程结束,但是,这会导致父进程挂起。具体用法,请看《进程的控制:结束进程、等待进程结束》。


2)如果父进程要处理的事情很多,不能够挂起,通过 signal()  函数人为处理信号 SIGCHLD , 只要有子进程退出自动调用指定好的回调函数,因为子进程结束后, 父进程会收到该信号 SIGCHLD ,可以在其回调函数里调用 wait() 或 waitpid() 回收。关于信号的更详细用法,请看《信号中断处理》。


测试代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <errno.h>  
  4. #include <stdlib.h>  
  5. #include <signal.h>  
  6.   
  7. void sig_child(int signo)  
  8. {  
  9.     pid_t  pid;    
  10.        
  11.     //处理僵尸进程, -1 代表等待任意一个子进程, WNOHANG代表不阻塞  
  12.     while( (pid = waitpid(-1, NULL, WNOHANG)) > 0 ){  
  13.         printf("child %d terminated.\n", pid);  
  14.     }  
  15. }  
  16.   
  17. int main()  
  18. {  
  19.     pid_t pid;  
  20.       
  21.     // 创建捕捉子进程退出信号  
  22.     // 只要子进程退出,触发SIGCHLD,自动调用sig_child()  
  23.     signal(SIGCHLD, sig_child);  
  24.       
  25.     pid = fork();   // 创建进程  
  26.       
  27.     if (pid < 0){ // 出错  
  28.         perror("fork error:");  
  29.         exit(1);  
  30.     }else if(pid == 0){ // 子进程  
  31.         printf("I am child process,pid id %d.I am exiting.\n",getpid());  
  32.         exit(0);  
  33.           
  34.     }else if(pid > 0){ // 父进程  
  35.         sleep(2);   // 保证子进程先运行  
  36.         printf("I am father, i am exited\n\n");  
  37.         system("ps -ef | grep defunct"); // 查看有没有僵尸进程  
  38.       
  39.     }  
  40.       
  41.     return 0;  
  42. }  

运行结果如下:



3)如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,父进程忽略此信号,那么子进程结束后,内核会回收, 并不再给父进程发送信号。关于信号的更详细用法,请看《信号中断处理》。


测试代码如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. #include <unistd.h>  
  3. #include <errno.h>  
  4. #include <stdlib.h>  
  5. #include <signal.h>  
  6.   
  7. int main()  
  8. {  
  9.     pid_t pid;  
  10.       
  11.     // 忽略子进程退出信号的信号  
  12.     // 那么子进程结束后,内核会回收, 并不再给父进程发送信号  
  13.     signal(SIGCHLD, SIG_IGN);  
  14.       
  15.     pid = fork();   // 创建进程  
  16.       
  17.     if (pid < 0){ // 出错  
  18.         perror("fork error:");  
  19.         exit(1);  
  20.     }else if(pid == 0){ // 子进程  
  21.         printf("I am child process,pid id %d.I am exiting.\n",getpid());  
  22.         exit(0);  
  23.           
  24.     }else if(pid > 0){ // 父进程  
  25.         sleep(2);   // 保证子进程先运行  
  26.         printf("I am father, i am exited\n\n");  
  27.         system("ps -ef | grep defunct"); // 查看有没有僵尸进程  
  28.       
  29.     }  
  30.       
  31.     return 0;  
  32. }  


运行结果如下:



4)还有一些技巧,就是 fork()  两次,父进程 fork() 一个子进程,然后继续工作,子进程 fork() 一 个孙进程后退出,那么孙进程被 init 接管,孙进程结束后,init (1 号进程)会回收。不过子进程的回收还要自己做。《UNIX环境高级编程》8.6节说的非常详细。原理是将子进程成为孤儿进程,从而其的父进程变为 init 进程(1 号进程),通过 init 进程(1 号进程)可以处理僵尸进程。更多详情,请看《特殊进程之孤儿进程》。


测试程序如下所示:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <unistd.h>  
  4. #include <errno.h>  
  5.   
  6. int main()  
  7. {  
  8.     pid_t  pid;  
  9.     //创建第一个子进程  
  10.     pid = fork();  
  11.       
  12.     if (pid < 0){ // 出错  
  13.         perror("fork error:");  
  14.         exit(1);  
  15.     }else if (pid == 0){//子进程  
  16.       
  17.         //子进程再创建子进程  
  18.         printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());  
  19.         pid = fork();  
  20.         if (pid < 0){  
  21.             perror("fork error:");  
  22.             exit(1);  
  23.         }else if(pid == 0){ // 子进程  
  24.             //睡眠3s保证下面的父进程退出,这样当前子进程的父亲就是 init 进程  
  25.             sleep(3);  
  26.             printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());  
  27.             exit(0);  
  28.               
  29.         }else if (pid >0){ //父进程退出  
  30.             printf("first procee is exited.\n");  
  31.             exit(0);  
  32.         }  
  33.           
  34.     }else if(pid > 0){ // 父进程  
  35.       
  36.         // 父进程处理第一个子进程退出,回收其资源  
  37.         if (waitpid(pid, NULL, 0) != pid){  
  38.             perror("waitepid error:");  
  39.             exit(1);  
  40.         }  
  41.           
  42.         exit(0);  
  43.     }  
  44.       
  45.     return 0;  
  46. }  

运行结果如下:



本教程测试代码下载请点此处。


参考资料:百度百科


转自:http://blog.csdn.net/tennysonsky/article/details/45966571

1 0
原创粉丝点击