文章目录
1. 僵尸进程的产生原因
1.1 僵尸状态
在UNIX中使用 fork() 系统调用创建进程时,将复制父进程的地址空间。如果父进程调用 wait() 系统调用,则父进程的执行将暂停,直到子进程终止。在子进程终止时,会生成“ SIGCHLD”信号,该信号会由内核传递给父进程。父进程在收到“SIGCHLD”时从进程表中获取子进程的状态。当子进程被终止,进程表中有一个条目对应存储状态的子进程。父进程收集状态后,此条目将被删除。至此,子进程的所有痕迹都将从系统中删除。如果父进程决定不等待子进程的终止就继续执行其后续任务,则在子进程终止时,退出状态不会被读取。因此,即使在终止子进程之后,进程表中仍保留一个条目。子进程的这种状态称为僵尸状态。
// A C program to demonstrate working of
// fork() and process table entries.
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
int i;
int pid = fork();
if (pid == 0)
{
for (i=0; i<20; i++)
printf("I am Child\n");
}
else
{
printf("I am Parent\n");
while(1);
}
}
输出:
# gcc test1.c -o test1
# ./test1
I am Parent
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
I am Child
现在使用终端中的以下命令检查进程表:
$ ps -eaf
或
# ps aux|grep test
root 2746 102 0.0 4216 352 pts/1 R+ 09:42 0:19 ./test1
root 2747 0.0 0.0 0 0 pts/1 Z+ 09:42 0:00 [test1] <defunct>
root 2749 0.0 0.0 112808 968 pts/2 S+ 09:42 0:00 grep --color=auto test
这里,[test1] <defunct> 显示了僵尸进程。
1.2 为什么我们需要避免创建僵尸进程?
每个系统有一个进程表。进程表的大小是有限的。如果生成的僵尸进程太多,则进程表会被填满。也就是说,系统将无法生成任何新的进程,然后系统将陷于停顿。因此,我们需要防止僵尸进程的创建。
2. 防止僵尸进程的不同方法
2.1 使用 wait() 系统调用
当父进程创建子进程,并调用 wait() 时,就表明父进程将等待子进程完成,并获得子进程的退出状态。父进程被挂起(在等待队列中等待),直到子进程终止。必须理解,在此期间,父进程什么都不做,只是等待。
// A C program to demonstrate working of
// fork()/wait() and Zombie processes
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
int i;
int pid = fork();
if (pid==0)
{
for (i=0; i<20; i++)
printf("I am Child\n");
}
else
{
wait(NULL);
printf("I am Parent\n");
while(1);
}
}
2.2 忽略SIGCHLD信号
当一个子进程终止时,一个相应的SIGCHLD信号被传递给父进程,如果我们调用 ‘signal(SIGCHLD,SIGIGN)’,那么系统将忽略SIGCHLD信号,并且将从进程表中删除子进程条目。因此,不会创建僵尸。但是,在这种情况下,父进程不能知道子进程的退出状态。
// A C program to demonstrate ignoring
// SIGCHLD signal to prevent Zombie processes
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
int main()
{
int i;
int pid = fork();
if (pid == 0)
for (i=0; i<20; i++)
printf("I am Child\n");
else
{
signal(SIGCHLD,SIG_IGN);
printf("I am Parent\n");
while(1);
}
}
2.3 使用 signal 句柄
父进程为SIGCHLD信号创建一个 signal 句柄。 signal 句柄在其中调用 wait() 系统调用。在这种情况下,当子进程终止时,SIGCHLD将传递给父进程。在收到SIGCHLD时,相应的处理程序被激活,它依次调用 wait() 系统调用。所以,父进程几乎立即收集exit状态,并且清除进程表中的子进程。这样就不会产生僵尸进程。
// A C program to demonstrate handling of
// SIGCHLD signal to prevent Zombie processes.
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
void func(int signum)
{
wait(NULL);
}
int main()
{
int i;
int pid = fork();
if (pid == 0)
for (i=0; i<20; i++)
printf("I am Child\n");
else
{
signal(SIGCHLD, func);
printf("I am Parent\n");
while(1);
}
}
输出没有任何 [a.out] defunct,即没有任何僵尸进程被创建。
参考文档
[1]Kishlay Verma.Zombie Processes and their Prevention[EB/OL].https://www.geeksforgeeks.org/zombie-processes-prevention/,2019-08-02.