僵尸进程

什么是僵尸进程?

当一个子进程终止的时候,父进程存在但是没有调用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却通过循环清楚了所有可以清楚的子进程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值