Linux 僵死进程的避免

今天读《UNIX 环境高级编程》第八章进程控制,其中讲到了僵死进程,它的定义是这样的:

在 UNIX 术语中,一个已经终止,但其父进程尚未对其进行善后处理(获取终止进程的有关信息,释放它仍占有的资源)的进程被称为僵死进程( zombie )。

如果编写一个长期运行的程序,它调用 fork 产生了很多子进程,那么除非父进程等待取得子进程的终止状态,否则这些子进程终止后就会变成僵死进程。

这使我想起了在实习期间负责的一个项目。程序使用了一大块共享内存,使用哈希表存储数据。我希望能够在运行时定期输出内存的使用率,这虽然不是一个必须的功能,但对调试和运维会有一些帮助。不过对大块内存的扫描会消耗数百毫秒的时间,这样的延迟对一个提供实时服务的程序来说是不可容忍的。

我创建了一个子进程来做这些事情,和父进程并行运行,从而不引起服务中断。代码类似于:

...
if ((pid = fork()) == 0)
{
	/* do some thing */

	exit(0);
}
...

这样子进程调用 exit() 进入终止状态后,父进程没有调用 wait() / waitpid() 函数,也没有显式忽略 SIGCHLD 信号,进程就会进入僵死状态。我在程序运行一段时间后,使用命令

ps -ef | grep a.out

果然发现了很多僵死进程。僵死进程消耗很小的计算机资源,但是 Linux 系统对运行的进程数量有限制,如果产生过多的僵尸进程占用了可用的进程号,将会导致新的进程无法生成。但程序如果使用 wait() / waitpod() 阻塞等待的话,已经失去创建子进程的意义。

解决办法一:程序在进入终止状态时会向父进程发送 SIGCHLD 信号,在 linux 上,如果在程序开始运行时显式忽略该信号可以避免僵死进程的产生,代码如下:

if (signal(SIGCHLD, SIG_IGN) == SIG_ERR)
{
	fputs("Cannot catch SIGCHLDn", stderr);
	exit(1);
}

这样做并不是一个标准的做法,因为在某些 UNIX 操作系统上,忽略该信号并不能避免僵死进程的产生。

解决办法二:更好的做法是调用 signal() 函数接管 SIGCHLD 信号,父进程收到此信号后,执行 waitpid() 函数为子进程收尸。代码如下:

static void process_wait(int sig)
{
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
}
...
if (signal(SIGCHLD, process_wait) == SIG_ERR)
{
	fputs("Cannot catch SIGCHLDn", stderr);
	exit(1);
}
...

waitpid() 函数在指定 WNOHANG 参数时,并不会阻塞进程运行。此时 waitpid() 作用为获得进程的运行信息,当子进程没有结束时返回值为 0, 在已经结束则返回值为子进程 ID.

(完)

Linux 僵死进程的避免》上有2条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注