You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何父进程发送的信号无法被子进程捕获?C语言信号问题

问题分析与修复

我帮你梳理下代码里的几个关键问题,这就是信号处理函数没触发的核心原因:

核心错误点

  • 信号发错了目标:父进程里的kill(getppid(), SIGTERM)是把信号发给了父进程的父进程(也就是启动你程序的shell),而不是子进程!应该用kill(child, SIGTERM),child变量保存的正是子进程的PID。
  • 子进程逻辑混乱:子进程里的wait(NULL)完全没必要——子进程没有自己的子进程,这个调用会直接返回错误;而且你的逻辑顺序错了,子进程应该先准备好信号处理,再等待信号,而不是先执行无意义的wait。
  • 信号阻塞的继承问题:fork后子进程会继承父进程的信号掩码,所以子进程一开始也是阻塞SIGTERM的,不过这部分你后面用sigprocmask(SIG_UNBLOCK)解除了,这个是对的,但前提是信号真的发过来了。

修正后的基础代码

先把这些错误改掉,让信号处理函数能正常触发:

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

void handler(int signumber){
    // 先用来验证功能,后面再替换成信号安全的写法
    printf("Signal with number %i has arrived\n", signumber);
}

int main(){
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGTERM);
    // 父进程先阻塞SIGTERM,子进程会继承这个掩码
    sigprocmask(SIG_BLOCK, &sigset, NULL);

    // 先注册信号处理函数,再fork,避免子进程错过注册时机
    signal(SIGTERM, handler);

    pid_t child = fork();
    if (child > 0) {
        printf("I'm parent (PID: %d)\n", getpid());
        printf("Waits 2 seconds, then send a SIGTERM %d signal to child (PID: %d)\n", SIGTERM, child);
        sleep(2);
        // 发给子进程!
        kill(child, SIGTERM);
        printf("I sent it.\n");
        // 父进程等待子进程结束
        wait(NULL);
    } else if (child == 0) {
        printf("I'm the child (PID: %d), waiting for signal.\n", getpid());
        // 解除SIGTERM的阻塞,让信号能被处理
        sigprocmask(SIG_UNBLOCK, &sigset, NULL);
        // 子进程等待信号到来,这里用sleep只是临时方案,后面换sigsuspend
        sleep(5);
        printf("Child process ended\n");
    } else {
        perror("fork failed");
        exit(1);
    }
    return 0;
}

运行这个修正后的代码,你就能看到信号处理函数的输出了。

用sigsuspend替代不安全的sleep和裸解除阻塞

你提到printf在信号处理函数里不安全,而且用sigsuspend可以避免竞态条件(比如在解除阻塞和sleep之间,信号可能已经到达,导致sleep不会被打断)。sigsuspend的作用是:临时替换当前的信号掩码,然后进入休眠,直到收到一个信号并处理,之后恢复原来的信号掩码。

我们可以修改子进程的逻辑,用sigsuspend来安全等待信号:

优化后的代码(使用sigsuspend)

#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>

// 改用write,因为write是异步信号安全的函数
void handler(int signumber){
    char msg[64];
    int len = snprintf(msg, sizeof(msg), "Signal with number %i has arrived\n", signumber);
    // write是异步信号安全的,不会和主程序的IO操作冲突
    write(STDOUT_FILENO, msg, len);
}

int main(){
    sigset_t sig_block, sig_wait;
    sigemptyset(&sig_block);
    sigaddset(&sig_block, SIGTERM);
    // 父进程阻塞SIGTERM,子进程继承
    sigprocmask(SIG_BLOCK, &sig_block, NULL);

    signal(SIGTERM, handler);

    pid_t child = fork();
    if (child > 0) {
        printf("I'm parent (PID: %d)\n", getpid());
        printf("Waits 2 seconds, then send a SIGTERM %d signal to child (PID: %d)\n", SIGTERM, child);
        sleep(2);
        kill(child, SIGTERM);
        printf("I sent it.\n");
        wait(NULL);
    } else if (child == 0) {
        printf("I'm the child (PID: %d), waiting for signal.\n", getpid());
        // 准备一个空的信号集:sigsuspend会临时用这个集替换当前掩码,也就是解除SIGTERM的阻塞
        sigemptyset(&sig_wait);
        // sigsuspend会一直休眠,直到收到一个能触发处理函数的信号
        // 这里收到SIGTERM后,会先执行handler,然后sigsuspend返回
        sigsuspend(&sig_wait);
        printf("Child process ended\n");
    } else {
        perror("fork failed");
        exit(1);
    }
    return 0;
}

为什么用sigsuspend更安全?

  • 避免竞态:如果先调用sigprocmask(SIG_UNBLOCK)再sleep,有可能在解除阻塞后、sleep前,信号就已经到达并被处理,导致sleep会完整睡够时间,而不是被信号打断。sigsuspend把“解除阻塞+等待信号”做成了原子操作,不会出现这个问题。
  • 信号安全:我们把printf换成了write,因为write属于POSIX定义的异步信号安全函数,可以在信号处理函数里安全调用,而printf不是(它会操作全局缓冲区,可能导致数据混乱)。

内容的提问来源于stack exchange,提问作者Gábor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:02:50