Linux:对有关 SIGCHLD 的深层理解

从 signal(SIGCHLD, SIG_IGN) 说起:默认忽略为什么不等于自动回收子进程?

很多人第一次看到这行代码都会疑惑:

c 复制代码
signal(SIGCHLD, SIG_IGN);

然后问:

SIGCHLD 的默认动作不就是忽略吗?那这行是不是脱裤子放屁?

答案很关键:

SIGCHLD 默认动作是"忽略信号",但默认忽略并不会自动回收子进程。显式设置 SIG_IGN 或 SA_NOCLDWAIT,才会让内核自动回收子进程,避免僵尸。


一、前置知识:子进程退出后发生了什么?

在 Linux 中,父进程 fork() 出子进程。子进程退出后,并不是立刻从系统里彻底消失。

子进程退出时,内核会:

  1. 释放子进程的大部分资源,比如内存、文件描述符等;
  2. 保留一小部分信息,比如退出状态、PID、资源使用统计;
  3. 等待父进程调用 wait() 或 waitpid() 来读取这些信息。

在父进程调用 wait 之前,这个已经退出但还没被回收的子进程,就是僵尸进程。

用 ps 看,它的状态通常是 Z:

text 复制代码
PID   PPID  STAT  CMD
1234  1000  Z     [child] <defunct>

僵尸进程不占内存,但占 PID 和内核进程表项。如果大量产生又不回收,可能耗尽系统资源。

如果父进程先退出,子进程会被 init 或 systemd 收养,之后由它们负责回收。


二、SIGCHLD 是什么?

SIGCHLD 是父进程收到的一种信号,用来通知:

你的某个子进程终止了、停止了,或者继续运行了。

它的默认动作是 Ign,也就是忽略。

但这里的"忽略"很容易被误解。

默认忽略的意思是:

如果没有特别设置,内核不会因为子进程退出而给父进程执行什么默认处理动作。

它不表示:

子进程退出后内核自动回收,不产生僵尸。

这是核心区别。


三、关键区别:默认 Ign 与显式 SIG_IGN

对比一下:

行为 SIGCHLD 默认动作 显式 signal(SIGCHLD, SIG_IGN) 或 SA_NOCLDWAIT
子进程退出后 变成僵尸,等待父进程 wait 内核自动回收,不产生僵尸
父进程 wait 可以拿到退出状态 通常返回 -1,errno = ECHILD
主要用途 默认行为 明确表示:我不关心子进程退出状态

所以:

c 复制代码
signal(SIGCHLD, SIG_IGN);

不是简单地"忽略一个本来就忽略的信号",而是告诉内核:

这些子进程退出后不用保留状态了,直接回收,别留僵尸。

POSIX 对这一点有规定:如果 SIGCHLD 被设置为 SIG_IGN,或者通过 sigaction 设置了 SA_NOCLDWAIT,子进程退出时不会变成僵尸,wait 会失败并返回 ECHILD。


四、实验:不回收 和 显式忽略

1. 不调用 wait,产生僵尸

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        puts("child exiting");
        _exit(0);
    }

    printf("parent sleeping, child pid = %d\n", pid);
    sleep(30);
    return 0;
}

运行后,另开终端查看:

bash 复制代码
ps -o pid,ppid,stat,cmd

你会看到子进程状态是 Z,也就是僵尸。

2. 显式设置 SIG_IGN,自动回收

c 复制代码
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
    signal(SIGCHLD, SIG_IGN);

    pid_t pid = fork();

    if (pid < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        _exit(0);
    }

    printf("parent sleeping, child pid = %d\n", pid);
    sleep(30);
    return 0;
}

这次子进程退出后不会留下僵尸。


五、那如果我关心子进程退出状态怎么办

如果只是不想要僵尸,SIG_IGN 很省事。

但如果你需要知道:

  • 子进程是正常退出还是被信号杀死;
  • 退出码是多少;
  • 哪个子进程退出了;

那就不能简单 SIG_IGN,因为退出状态会被丢弃。

正确做法通常是:注册 SIGCHLD 处理函数,在 handler 里循环调用 waitpid 回收。

c 复制代码
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

static void sigchld_handler(int sig) {
    (void)sig;

    int saved_errno = errno;
    pid_t pid;
    int status;

    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {
            printf("child %d exited, code = %d\n",
                   (int)pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("child %d killed by signal %d\n",
                   (int)pid, WTERMSIG(status));
        }
    }

    errno = saved_errno;
}

int main(void) {
    struct sigaction sa;

    sa.sa_handler = sigchld_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;

    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    for (int i = 0; i < 3; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            sleep(i + 1);
            _exit(i);
        }
    }

    sleep(10);
    return 0;
}
相关推荐
aichitang20241 小时前
QCU 2.0.0:补齐 Windows、Linux 与 GNOME Wayland
linux·运维·windows·ai·agent·ai编程
Lv_沐曦1 小时前
Linux 上的Web文件管理器-filebrowser
linux·运维·服务器
NGINX开源社区1 小时前
与我们交流:NGINX Gateway Fabric 与 NGINX Ingress Controller 社区会议
java·服务器·前端
闲云自留地1 小时前
CentOS 搬 Ubuntu 24.04:yum 变 apt、防火墙、SELinux、root 全差异避坑
linux·ubuntu·centos
科技每日热闻2 小时前
电气设备综合维保服务商怎么选?从预防性维护到数字化运维,看懂五个关键能力——企业选择维保合作伙伴,不能只看“坏了能不能修”,更要看能否覆盖资产与系统的长期运营
大数据·运维·人工智能
最强小杰2 小时前
Claude Opus 4.8 调用报 529 怎么办?并发限制触发过载——退避策略与降级方案完整指南
运维·网络·python·ai
我科绝伦(Huanhuan Zhou)2 小时前
磐维数据库(PanWeiDB)日常运维命令总结
运维·数据库
深盾科技_Virbox3 小时前
软许可、加密锁和云许可混合使用时,授权接口应该怎么统一?
运维·网络·安全
逐流人3 小时前
Redis内存数据库:从数据类型到集群部署
linux·运维·数据库·redis·缓存·云计算