僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为[僵死进程][3]。
僵尸进程产生原因
在unix/linux中,父进程调用fork创建子进程。子进程的结束和父进程的运行是一个异步过程,即父进程无法预测子进程什么时候结束。
为此unix提供了一种机制可以保证父进程知道子进程结束时的状态信息。这种机制是在每个子进程退出时,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号 process ID,退出状态 termination status of the process, 运行时间 the amount of CPU time taken by the process等)。
子进程运行直至其终止,它立即从内存中移除,但进程描述符仍然保留在内存中(进程描述符占有极少的内存空间)。子进程的状态变成EXIT_ZOMBIE,并且向父进程发送SIGCHLD 信号,父进程此时应该调用 wait() 或者waitpid()系统调用来获取子进程的退出状态以及其它的信息。直到父进程通过wait / waitpid来取时才释放保存的内容。在 wait 调用之后,僵尸进程就完全从内存中移除。
因此一个僵尸存在于其终止到父进程调用 wait 等函数这个时间的间隙,一般很快就消失,但如果编程不合理,父进程从不调用 wait 等系统调用来收集僵尸进程,那么这些进程会一直存在内存中。
僵尸进程的危害
但这样就导致了问题,如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的(为了兼容16位linux系统,最多的进程号数量是32767),大量的产生僵死进程会造成因为没有可用的进程号而导致系统不能产生新的进程。
僵尸进程程序示例
- 僵尸程序的产生
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
int main()
{
pid_t pid;
pid = fork(); //创建一个子进程
if (pid < 0) //子进程创建失败
{
perror("fork error:");
exit(1);
}
else if (pid == 0) //子进程创建成功
{
printf("I am child process.I am exiting.\n");
exit(0); //子进程终止
}
printf("I am father process.I will sleep two seconds\n");
sleep(2); //父进程睡眠,等待子进程先退出
system("ps -o pid,ppid,state,tty,command"); //输出进程信息
// printf("father process is exiting.\n");
return 0;
}
由于父进程没有写 wait 等系统调用函数,因此在子进程退出之后变成僵尸进程,父进程没有为孩子收尸。但如果等父进程睡眠醒来退出之后,我们再次查看系统进程信息,发现刚才的僵尸进程不见了。原因是:父进程死掉后,它的所有子进程会变成孤儿进程,并被 init 进程回收,init 进程会周期性地调用 wait 系统调用来清除它的僵尸孩子。因此,父进程死掉之后,它的孩子僵尸进程也跟着消失,其实是 init 进程为其收尸的。
在 Linux 下,我们可以使用 ps 等命令查看系统中僵尸进程,僵尸进程的状态标记为‘Z’
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
pid_t pid;
//循环创建多个子进程
while(1)
{
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am a child process.\nI am exiting.\n");
exit(0); //子进程退出,成为僵尸进程
}
else
{
sleep(20);
continue; //父进程休眠20s继续创建子进程
}
}
return 0;
}
清除僵尸进程
不能使用 kill 后接 SIGKILL 信号这样的命令像杀死普通进程一样杀死僵尸进程,因为僵尸进程是已经死掉的进程,它不能再接收任何信号。
使用信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
int main()
{
pid_t pid;
//创建捕捉子进程退出信号
signal(SIGCHLD,sig_child);
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process,pid id %d.I am exiting.\n",getpid());
exit(0);
}
printf("I am father process.I will sleep two seconds\n");
//等待子进程先退出
sleep(2);
//输出进程信息
system("ps -o pid,ppid,state,tty,command");
printf("father process is exiting.\n");
return 0;
}
static void sig_child(int signo)
{
pid_t pid;
int stat;
//处理僵尸进程
while ((pid = waitpid(-1, &stat, WNOHANG)) >0)
printf("child %d terminated.\n", pid);
}
两次调用fork函数创建进程
原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
pid_t pid;
//创建第一个子进程
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一个子进程
else if (pid == 0)
{
//子进程再创建子进程
printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid());
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一个子进程退出
else if (pid >0)
{
printf("first procee is exited.\n");
exit(0);
}
//第二个子进程
//睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}
//父进程处理第一个子进程退出
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
exit(0);
return 0;
}
http://www.cnblogs.com/Anker/p/3271773.html
http://www.cnblogs.com/hazir/p/zombie_process.html