目录
什么是僵尸进程?
僵尸进程的目的?
如何避免僵尸进程?
总结:
提到SIGCHLD信号,就不得不先说一个有关僵尸状态的进程知识点了。
什么是僵尸进程?
首先内核会释放终止进程(调用了exit系统调用)所使用的所有存情区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息,这些信息至少包括进程ID,进程的终止状态,以及该近程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
而僵尸进程就是指:一个进程执行了exit系统调用函数后退出,而其父进程并没有为它收尸(调用wait或watpid来获得它的结束状态)的进程。任何一个子进程(init除外)在exit()后并非马上就消失,而是留下僵尸进程的数据结构,等待父进程处理,这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号,通知父进程来收尸。——这个是关键,父进程能够及时回收子进程资源全凭借子进程退出时发送的这个信号来感知!
僵尸进程的目的?
设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息,如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1 (init进程)。继承这些子进理的init进程将wait它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。
#include<signal.h>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types .h>
void Count(int cnt){ //定时器
while(cnt)fprintf("cnt: %d\r");
fflush(stdout);
cnt--;
}
void myhandler(int signo){
printf("%d号信号已被捕况到,表明子进机退出后发送了SIGCHLD信号给父进程\n",signo);
}
int main(){
printf("我是父进程,我的pid:%d\n",getpid());
//信号捕捉
signal(SIGCHLD,myhandler);
pid_t id=fork(); //创建子进程
//信号捕捉
signal(SIGCHLD,myhandler);
if(id==0)( //id==0 子进程
printf("我是子进程,我的pid:%d 我的ppid:%d\n",getpid(),getppid());
Count(5);
printf("子进程已退\n");
exit(1); //退出子进程,子进程比父进任先退出后成为信,进和,需要父进和进行回收才行
//那么父进程怎么个会知道子进程何时退出呢? 一子进程一定会发送信号给父进程,
//父进任收到该信号就会对子进程进行资源的回收
//id>0 为父进程
else{
while(1)
sleep(1);
}
return 0;
}
运行结果:
从上图结果中可知:子进程退出后发送了一个信号给父进程,而我在代码中使用signal函数对SIGCHLD信号进行捕获,发现确实捕获到了,论证了子进程退出后会发送SIGCHLD信号给父进程,父进程收到了信号,进而回收子进程资源。
如何避免僵尸进程?
1.通过signal(SIGCHLD,SIGIGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句: siqnal(SIGCHLD,SIG IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
代码如下:
如上图: signal函数中向进程发送的SIGCHLD信号后,进程会对该信号做SIG_IGN处理,说白了就是:在我们之前学习进程时,父进程创建子进程,子进程退出后若父进程不进行回收,子进程会成为僵尸进程。而且父进程不知道子进程什么时候退出,父进程可以采用阻塞等待的方式等子进程结束,也可以采用非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式);
若采用第一种方式,父进程阻塞了就不能处理自己的工作了(父进程只能干等着);而采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下,程序实现复杂。
若采用了忽略的方式去让父进程递达处理信号,那么父进程就再也不会阻塞和非阻塞的等待子进程结束,而父进程会专心做自己的事情,等到子进程退出时,会自动被清理回收掉,不会产生僵尸进程,也不会通知父进程。十分方便!
2.父进程调用wait/waitpid等函数等待了进程结束,如果尚无子进程退出wait()会导致父进程阻塞。waitpid()可以通过传递WNOHANG参数使父进程不阻塞立即返回;
3.如果父进程很忙可以用signal()信号捕捉函数,在信号处理函数调用wait()/waitpid()等待子进程退出。
注:父进程采用signal函数捕捉子进程退出的时候发出的SIGCHLD信号,捕捉到后,父进程会被通知子进程已退出,然后采用waitpid方法去回收子进程。
4.通过两次调用fork。父进程首先调用fork()创建一个子进程然后waitpid()等待子进程退出,子进程再fork()一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出,所以子进程成为一个孤儿进程,孤儿进程由i计进程接管,子进程结束后,init进程会等待回收。
总结:
使用signal信号捕捉方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧,因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可以内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。