什么是僵尸进程?
当一个子进程终止的时候,父进程存在但是没有调用wait或这waitpid获取子进程的退出状态。这样,这个子进程就不能完全从内存中清楚,从而变成僵尸状态。
子进程自己为什么不完全退出呢?
因为子进程结束时,父进程可能需要获取子进程的退出状态,进程id等信息,这些信息被保存在一个结构体中,父进程通过调用wait或者waitpid可以获取这些信息,同时把子进程从内存中完全清除。
僵尸进程产生的必要条件?
子进程比父进程更早结束,同时父进程没有调用wait或者waitpid。
因为,如果父进程比子进程先结束的话,那么子进程就变成了“孤儿进程”,然后,孤儿进程(子进程)会被过继给init进程(1号进程),init进程在系统启动时会被自动建立,这个init进程的一项功能就是清除过继给自己的僵尸进程。
产生僵尸进程的例子:
提醒:千万不要运行此程序!!!
#include <unistd.h>
#include <stdlib.h>
int main(void){
int i = 0;
for(;i<100;i++) {
if(fork()==0){
}
}
while(1);
return 0;
}
由于本人一时激动,在子进程中没有调用exit(0)清楚自己,所以造成了大量开辟进程的情况,因为子进程并没有返回,反而也在执行fork(),所以本人很不幸的强制关机。
下面这个才可以测试运行:
#include <unistd.h>
#include <stdlib.h>
int main(void){
int i = 0;
for(;i<100;i++) {
if(fork()==0){
exit(0);
}
}
while(1);
return 0;
}
编译运行,ps -e查看,发现产生了100个僵尸进程。
root@chenjingui-pc:/home/workspace/unp/echo# ps -e | grep 'zoom <defunct>' | cat -n
1 2453 pts/2 00:00:00 zoom <defunct>
2 2454 pts/2 00:00:00 zoom <defunct>
此处省掉36个僵尸。。。
99 2551 pts/2 00:00:00 zoom <defunct>
100 2552 pts/2 00:00:00 zoom <defunct>
如何杀掉已经存在的僵尸进程?
1.不要试图手动找到这些僵尸进程的id一个一个杀掉。因为这是杀不掉的。
2。杀掉僵尸进程的父进程,让这些僵尸进程变成“孤儿进程”,从而过继给init进程(1号进程),init会清理这些僵尸进程。
如何防止产生僵尸进程?
1.父进程调用wait或者waitpid
因为子进程结束的时候,会向父进程发送SIGCHLD信号,只需要捕捉此信号然后调用wait或waitpid即可。
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handle_signal(int sig)
{
if(sig==SIGCHLD){
wait(NULL);
}
}
int main(void){
int i = 0;
signal(SIGCHLD,handle_signal);
for(;i<100;i++) {
if(fork()==0){
exit(0);
}
}
while(1);
return 0;
}
理论上,这样本应该不会产生僵尸进程了,但是ps -e查看,还有几个僵尸进程?why???
因为unix信号一般是不排队的,这个程序开了100个子进程,而几乎是在同一时间退出,所以,程序在发生SIGCHLD信号中断,进而执行handle_signal的时候,如果再有N个(N>1)SIGCHLD信号达到,那么将会有N-1个SIGCHLD就被抛弃了,也就接下来到来的N个sigchld只按1个算。所以一个SIGCHLD只能激发一次handle_signal,但是N个SIGCHLD信号并不能保证一定激发N次handle_signal(因为同时到达的SIGCHLD被抛弃了)。
这时候,waitpid隆重登场了。先see代码
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
void handle_signal(int sig)
{
if(sig==SIGCHLD){
while(waitpid(-1,NULL,WNOHANG)>0);
}
}
int main(void){
int i = 0;
signal(SIGCHLD,handle_signal);
for(;i<100;i++) {
if(fork()==0){
exit(0);
}
}
while(1);
return 0;
}
waitpid原型:
pid_t waitpid(pid_t pid, int *status, int options);
第一个参数pid可以指定获取哪一个进程的退出,第二参数status报错退出状态,第三个参数指定选项(例如,非阻塞)
当pid为-1时,那么这次等待就是只要有子进程退出,此函数就返回。第二个参数传NULL,表示并不获取退出状态,第三个三处指明如果没有结束了的子进程的话,立即返回。
而该函数的返回值表示退出的进程id。返回值=0表示没有退出的子进程,=-1表示出错。
我们接收到一次SIGCHLD信号的时候,将会激发handle_signal函数,如果此时又有一个SIGCHLD到达,那么肯定说明又一个子进程退出了,而这次在handle_signal函数中循环获取退出了的子进程的状态。
例如:有三个子进程A,B,C
首先A进程退出,发出SIGCHLD信号,handle_signal函数被激发。这时可以清理的子进程有A
刚要执行handle_signal函数的时候,B,C进程同时退出了,然后B,C进程也发出SIGCHLD信号,但是信号一般时不排队的,所以B,C的信号只记一次。
总之,虽然B,C进程发出了两次SIGCHLD信号,但是却只激发了一次handle_signal函数的执行,但是handle_signal却通过循环清楚了所有可以清楚的子进程。