0%

回收 fork() 的子进程

使用 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>

僵尸进程

僵尸进程是指子进程退出后,它的父进程没有“等待”该子进程,这样的子进程就会成为僵尸进程。

一般父进程需要调用 waitwaitpid 来等待子进程进行回收

wait

waitpid

SIGCHLD

SIGCHLD 中调用 waitwaitpid

SIG_IGN

除了在 SIGCHLD 信号处理函数中调用 wait() 来避免产生僵尸进程,我们还可以选择忽略 SIGCHLD 信号,告知操作系统父进程不关心子进程的退出状态,可以直接清理。

signal(SIGCHLD, SIG_IGN);

但需要注意的是,在部分 BSD 系统中,这种做法仍会产生僵尸进程。因此更为通用的方法还是使用 wait() 函数。

需要区分僵尸进程和孤儿进程

Ref

  1. 异步回收 fork 出的子进程(僵尸进程)
  2. Linux 进程全解 7——父进程 wait / waitip 回收子进程
  3. 父进程非阻塞回收子进程(适用 LINUX 下 C 语言的 client-server 模型)
  4. fork() 与僵尸进程
  5. 僵尸进程的发现与解决
  6. SIGCHLD 信号与 SIG_IGN 处理的使用
  7. linux 下的僵尸进程处理 SIGCHLD 信号