linux系统中僵尸进程和现实中僵尸类似,虽然已经死了,但是由于没人给他们收尸,还能四处走动,僵尸进程指的是那些虽然已经终止的进程,但仍保留一些信息,等待其父进程为其收尸;
僵尸进程如何产生的?
如果一个进程在其终止的时候,自己就回收所有分配给他的资源,系统就不会产生所谓的僵尸进程了,那么我们说一个进程终止之后,还保留那些信息?为什么终止之后还需要保留这些信息呢?
一个进程终止的方式有很多,进程终止之后有些信息对于父进程和内核还是很有用的,例如进程的ID号,进程的退出状态,进程运行的CPU时间等,因此进程在终止时,回收所有内核分配给他的内存,关闭它打开的所有文件等等,单是还会保留以上极少的信息,以供父进程使用,父进程可以使用wait/waitpid等系统调用来为子进程收拾,做一些收尾的工作。
因此,一个僵尸进程产生的过程是:父进程调用fork创建子进程后,子进程运行直至其终止,他立即从内存中移动,但进程描述符 仍然保留在内存中(进程描述符占有极少的内存空间),子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD信号,父进程此时应该调用wait()系统调用来获取子进程的退出状态以及其他的信息,在wait调用之后,僵尸进程就会完全从内存中移除,因此一个僵尸存在于其终止到父进程调用wait等函数这个时间的间隙,一般很快就会消失,但如果编程不合理,父进程从不调用wait等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。
在linux下,我们可以使用ps等命令查看系统中僵尸进程,僵尸进程的状态记为'Z':
ps -aux | grep -w "Z"
根据下面的描述,我们很容易去写一个程序来产生僵尸进程,如下代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
int main()
{
//fork a child process
pid_t pid=fork();
if(pid>0)//parent process
{
printf("in parent process.sleep for one miniute..zzzzz\n");
sleep(60);
printf("after sleeping,and exit!\n");
}
else if(pid==0)
{
//child process exit,and to be a zombie process
printf("in child process,and exit!\n");
exit(0);
}
return 0;
}
父进程并没有写wait等系统调用函数,因此在子进程退出之后变成僵尸进程,父进程并没有为其收尸,我们使用下面命令编译运行该进程,然后查看系统中进程状态:
gcc zombie.c -o zombie
grep -aux | grep -w "Z"
系统中多了一个僵尸进程,但如果等父进程睡眠醒来退出之后,我们再次查看系统进程信息,发现刚才的僵尸进程不见了。
这是为什么呢?父进程到死都没有为其子进程收尸啊,怎么父进程退出后,那个僵尸进程就会消失呢?难道父进程在退出时会为子进程收拾吗?其实不然,真正的原因是,父进程死掉后,其子进程过继给init进程,Init进程称为该僵尸进程的父进程,init进程会周期性地去调用wait系统调用来清除它的僵尸孩子,因此,你会发现上面例子中父进程死掉后,僵尸进程也跟着消失,其实是Init进程为其收尸的。
怎么避免僵尸进程的产生?
不能使用kill后接SIGKILL信号这样的命令像杀死普通进程一样杀死僵尸进程,因为僵尸进程是已经死掉的进程,它不能再接受任何信号,事实上,如果系统中僵尸进程并不多的话,我们也无需去消除他们,少数的僵尸进程并不会对系统的性能有什么影响。
那么在编程时,如何避免系统中产生大量的僵尸进程呢?根据上面描述的,子进程在终止时会向父进程发SIGCHLD信号,linux默认是忽略该信号的,我们可以显示安装该信号,在信号处理函数中调用wait等函数来为其收尸,这样就能避免僵尸进程长期存在于系统中。代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <sys/wait.h>
sig_atomic_t child_exit_status;
clean_up_child_process(int signal_num)
{
//clean up child process
int status;
wait(&status);
//store its exit status in a global variable
child_exit_status=status;
}
int main()
{
//handle SIGCHLD by calling clean_up_child_process
struct sigaction sigchild_action;
memset(&sigchild_action,0,sizeof(sigchild_action));
sigchild_action.sa_handler=&clean_up_child_process;
sigaction(SIGCHLD,&sigchild_action,NULL);
//fork a child process
pid_t pid=fork();
if(pid>0)//parent process
{
printf("in parent process.sleep for one miniute..zzzzz\n");
sleep(60);
// printf("after sleeping,and exit!\n");
}
else if(pid==0)
{
//child process exit,and to be a zombie process
printf("in child process,and exit!\n");
exit(0);
}
else
{
printf("fork failed\n");
}
return 0;
}