使用 fork()
函数派生出多个子进程来并行执行程序的不同代码块,是一种常用的编程泛型。但是,在使用 fork()
函数时若处理不当,很容易产生僵尸进程。
fork()
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv) {
while (1) {
pid_t pid = fork();
if (pid > 0) {
// 主进程
sleep(5);
} else if (pid == 0) {
// 子进程
return 0;
} else {
fprintf(stderr, "fork error\n");
return 2;
}
}
}
该函数的返回值有三种情况,分别是:
- 大于 0,表示当前进程为父进程,返回值是子进程号;
- 等于 0,表示当前进程是子进程;
- 小于 0(确切地说是等于 -1),表示 fork() 调用失败。
示例代码每 5 秒创建一个子进程,而且没有等待回收,变成僵尸进程
$ gcc test.c -o fork
$ ./fork
kk 24336 17551 0 11:16 pts/6 00:00:00 ./fork
kk 24337 24336 0 11:16 pts/6 00:00:00 [fork] <defunct>
kk 24340 24336 0 11:16 pts/6 00:00:00 [fork] <defunct>
kk 24345 24336 0 11:16 pts/6 00:00:00 [fork] <defunct>
僵尸进程
僵尸进程是指子进程退出后,它的父进程没有“等待”该子进程,这样的子进程就会成为僵尸进程。
一般父进程需要调用 wait
或 waitpid
来等待子进程进行回收
wait
waitpid
SIGCHLD
在 SIGCHLD
中调用 wait
或 waitpid
SIG_IGN
除了在 SIGCHLD 信号处理函数中调用 wait() 来避免产生僵尸进程,我们还可以选择忽略 SIGCHLD 信号,告知操作系统父进程不关心子进程的退出状态,可以直接清理。
signal(SIGCHLD, SIG_IGN);
但需要注意的是,在部分 BSD 系统中,这种做法仍会产生僵尸进程。因此更为通用的方法还是使用 wait() 函数。
需要区分僵尸进程和孤儿进程