僵尸进程
概念
在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……
僵尸进程在系统内部的存在方式
进程调用exit,内核释放资源(打开的文件、占用的内存等)但是会保留一定的信息(进程号、退出状态、运行时间等)。父进程通过wait/waitpid获得子进程的这些状态,然后释放子进程保留的资源。
所以说极端的情况下,出现了大量的僵尸进程,那么进程号会被大量的占用,当系统所有进程号都被僵尸进程占用时,就无法创建新的进程了。
产生僵尸进程
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { pid_t child; int n=0; while(n < 10) { child = fork(); if(child == 0) { printf("child process id %d\n",getpid()); exit(0); } sleep(5); n++; } return 0; }
执行程序:
XXXX> ./a.out child process id 21517 child process id 21522
查看进程状态:
XXXX> ps -aux | grep a.out USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 1000 21516 0.0 0.0 3844 308 pts/1 S+ 20:41 0:00 ./a.out 1000 21517 0.0 0.0 0 0 pts/1 Z+ 20:41 0:00 [a.out] <defunct> 1000 21522 0.0 0.0 0 0 pts/1 Z+ 20:41 0:00 [a.out] <defunct>
如何避免产生僵尸进程
1.父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { pid_t child; child = fork(); if(child == 0) { printf("child process id %d\n",getpid()); exit(0); } else if (child > 0) { sleep(10); wait(); printf("father process. wait done.\n"); sleep(10); } else printf("fork() error\n"); return 0; }
执行程序以及使用ps后的结果:
XXXX> ./a.out child process id 32541 XXXX> ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 1000 32540 0.0 0.0 3844 304 pts/2 S+ 00:17 0:00 ./a.out 1000 32541 0.0 0.0 0 0 pts/2 Z+ 00:17 0:00 [a.out] <defunct>
等待父进程执行wait后:
father process. wait done. XXXX> ps u USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND 1000 32540 0.0 0.0 3848 304 pts/2 S+ 00:17 0:00 ./a.out
父进程调用wait,替子进程收尸,处理了僵尸进程
2.如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后,父进程会收到该信号,可以在handler中调用wait回收。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> static void sig_chld(int); int main(void) { pid_t child; signal(SIGCHLD, sig_chld); child = fork(); if(child == 0) { printf("child process id %d\n",getpid()); exit(0); } else if(child < 0) printf("fork() error\n"); while(1); return 0; } void sig_chld(int signo) { printf("func sig_chld() before wait()\n"); sleep(10); wait(); printf("func sig_chld() after wait()\n"); sleep(10); }
可以分别在信号处理函数执行wait前和执行wait后使用ps查看是否产生僵尸进程
3.如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD, SIG_IGN)通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号。
4.还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收还要自己做
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) { pid_t child,grdson; if((child=fork()) == 0) { printf("child process id %d\n",getpid()); if((grdson=fork())==0) { printf("grdson process id %d\n", getpid()); sleep(10); exit(0); } else if(grdson > 0) { exit(0); } else { printf("fork grdson error.\n"); exit(0); } } else if (child > 0) { wait(); } else printf("fork() error\n"); sleep(60); return 0; }
执行以及查看结果:
XXXX> ./a.out child process id 13479 grdson process id 13480 XXXX> ps -ef | grep a.out 1000 13478 19733 0 00:41 pts/2 00:00:00 ./a.out 1000 13480 1 0 00:41 pts/2 00:00:00 ./a.out 1000 13482 25700 0 00:41 pts/0 00:00:00 grep a.out
孙子进程13480由init进程领养了