僵尸进程
在类UNIX系统中,僵尸进程是指完成执行(通过exit
系统调用,或运行时发生致命错误或收到终止信号所致),但在操作系统的进程表中仍然存在其进程控制块,处于"终止状态"的进程。这发生于子进程需要保留表项以允许其父进程读取子进程的退出狀態:一旦退出态通过wait
系统调用读取,僵尸进程条目就从进程表中删除,称之为"回收"(reaped)。正常情况下,进程直接被其父进程wait
并由系统回收。进程长时间保持僵尸状态一般是错误的并导致资源泄漏。
「僵尸进程」的各地常用名稱 | |
---|---|
中国大陸 | 僵尸进程 |
臺灣 | 殭屍行程 |
港澳 | 殭屍行程 |
英文术语zombie process源自喪屍——不死之人,隐喻子进程已死但仍然没有被回收。与正常进程不同,kill
命令对僵尸进程无效。孤儿进程不同于僵尸进程,其父进程已经死掉,但孤儿进程仍能正常执行,但并不会变为僵尸进程,因为被init
(进程ID号为1)收养并wait
其退出。
子进程死后,系统会发送SIGCHLD信号给父进程,父进程对其默认处理是忽略。如果想响应这个消息,父进程通常在信号事件处理程序中,使用wait
系统调用来响应子进程的终止。
僵尸进程被回收后,其进程号与在进程表中的表项都可以被系统重用。但如果父进程没有调用wait
,僵尸进程将保留进程表中的表项,导致了资源泄漏。某些情况下这反倒是期望的:父进程创建了另外一个子进程,并希望具有不同的进程号。如果父进程通过设置事件处理函数为SIG_IGN
显式忽略SIGCHLD信号,而不是隐式默认忽略该信号,或者具有SA_NOCLDWAIT
标志,所有子进程的退出状态信息将被抛弃并且直接被系统回收。
UNIX命令ps
列出的进程的状态("STAT")栏标示为 "Z
"则为僵尸进程。[1]
收割僵尸进程的方法是通过kill
命令手工向其父进程发送SIGCHLD信号。如果其父进程仍然拒绝收割僵尸进程,则终止父进程,使得init
进程收养僵尸进程。init
进程周期执行wait
系统调用收割其收养的所有僵尸进程。
为避免产生僵尸进程,实际应用中一般采取的方式是:
- 将父进程中对SIGCHLD信号的处理函数设为SIG_IGN(忽略信号);
- fork两次并杀死一级子进程,令二级子进程成为孤儿进程而被init所“收养”、清理[2]。
例子
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pids[10];
int i;
for (i = 9; i >= 0; --i) {
pids[i] = fork();
if (pids[i] == 0) {
sleep(i+1);
_exit(0);
}
}
for (i = 9; i >= 0; --i)
waitpid(pids[i], NULL, 0);
return 0;
}
参见
参考文献
- ^ Zombies(5) - UNIX System V (Concepts). The Collider Detector at Fermilab. [2014-05-24]. (原始内容存档于2020-11-09).
- ^ UNIX环境高级编程(Advanced Programming in the UNIX Environment),理查德·史蒂文斯著,1992,ISBN 0-201-56317-7
- UNIX man pages : ps (). UNIXhelp for Users. [2014-05-24]. (原始内容存档于2013-03-08).