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

C语言中如何让同类型信号进入挂起状态?

解决同类型非实时信号无法重复挂起交付的问题

你遇到的是Linux/Unix下**非实时信号(不可靠信号)**的固有特性:像SIGUSR1、SIGUSR2这类传统信号,内核的待处理队列只能保存一个该信号的实例。当信号正在处理(已经被加入sa_mask阻塞)时,同类型的新信号到达,内核只会标记信号为pending状态,但不会把它加入队列——所以当你处理完第一个信号后,内核只会交付一次,第二个信号直接丢失,这就是你看到的输出只有一次SIGUSR1 received的原因。

实时信号(SIGRTMIN到SIGRTMAX)支持排队多个实例,但如果不想切换到实时信号,我们可以自己模拟一个信号队列,用用户态的计数来追踪待处理的同类型信号。下面是具体的实现方案:

核心思路

  • 用**volatile sig_atomic_t**类型的全局变量记录待处理的同类型信号数量(sig_atomic_t是内核保证在信号处理中可以原子操作的类型,避免竞态问题)。
  • 信号处理函数只负责累加计数,不执行实际的业务逻辑(或者只执行一次,剩下的交给主循环处理)。
  • 主程序中通过循环检查计数,当计数大于0时,执行实际的信号处理逻辑,每处理一次计数减1。

修改后的代码示例

#define MAXCHILD 1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

// 全局计数:记录待处理的SIGUSR1数量,必须是volatile sig_atomic_t保证原子性
volatile sig_atomic_t sigusr1_count = 0;

// 实际的信号处理逻辑,保留你的write函数
void handle_sigusr1() {
    write(1, "SIGUSR1 received \n", strlen("SIGUSR1 received \n"));
    sleep(5);
}

// 信号处理函数:只负责累加计数
void handler1(int signo) {
    switch(signo) {
        case SIGUSR1:
            sigusr1_count++;
            break;
        case SIGUSR2:
            write(1, "SIGUSR2 received \n", strlen("SIGUSR2 received \n"));
            break;
    }
}

int main() {
    struct sigaction action1;
    sigset_t set1, wait_set;

    // 初始化信号掩码:处理信号时阻塞SIGUSR1和SIGUSR2
    sigemptyset(&set1);
    sigaddset(&set1, SIGUSR2);
    sigaddset(&set1, SIGUSR1);

    action1.sa_handler = handler1;
    action1.sa_mask = set1;
    action1.sa_flags = 0;

    sigaction(SIGUSR1, &action1, NULL);
    sigaction(SIGUSR2, &action1, NULL);

    // 初始化等待集:只等待SIGUSR1和SIGUSR2
    sigemptyset(&wait_set);
    sigaddset(&wait_set, SIGUSR1);
    sigaddset(&wait_set, SIGUSR2);

    pid_t parent = getpid();
    pid_t pid[MAXCHILD];
    int inchild = 0;

    for (int i = 0; i < MAXCHILD; i++) {
        pid[i] = fork();
        if (pid[i] == 0) {
            inchild = 1;
            break;
        }
    }

    if (inchild == 1) {
        // 子进程发送两个SIGUSR1
        kill(parent, SIGUSR1);
        kill(parent, SIGUSR1);
        sleep(1);
        exit(0);
    }

    // 父进程主循环:处理待处理的信号计数
    while (1) {
        // 先阻塞所有我们关心的信号,避免在检查计数时信号到达
        sigprocmask(SIG_BLOCK, &wait_set, NULL);

        // 处理所有待处理的SIGUSR1
        while (sigusr1_count > 0) {
            sigusr1_count--;
            // 执行实际处理逻辑
            handle_sigusr1();
        }

        // 解除阻塞,等待信号到达
        sigprocmask(SIG_UNBLOCK, &wait_set, NULL);

        // 等待信号,避免空循环浪费CPU
        sigsuspend(&wait_set);

        // 检查是否需要退出(这里可以根据实际需求设置退出条件)
        // 比如子进程退出后,我们处理完剩余信号就退出
        int status;
        if (waitpid(pid[0], &status, WNOHANG) != 0) {
            // 处理剩余的信号
            sigprocmask(SIG_BLOCK, &wait_set, NULL);
            while (sigusr1_count > 0) {
                sigusr1_count--;
                handle_sigusr1();
            }
            break;
        }
    }

    printf("end\n");
    return 0;
}

代码说明

  1. 全局计数变量:sigusr1_count用volatile sig_atomic_t修饰,确保在信号处理函数中修改时,主循环能立刻看到最新值,且操作是原子的,不会被中断。
  2. 信号处理函数拆分:原来的handler1只负责累加计数,实际的业务逻辑放到handle_sigusr1中,这样信号处理函数执行时间极短,不会长时间阻塞其他信号。
  3. 主循环逻辑:
    • 先阻塞关心的信号,避免在检查计数时信号到达,导致计数没被及时处理。
    • 循环处理所有待处理的SIGUSR1,每处理一次计数减1。
    • 用sigsuspend等待信号,避免空循环占用CPU。
    • 子进程退出后,处理剩余的信号再退出,确保所有信号都被处理。

预期输出

运行这段代码后,你会得到期望的输出:

SIGUSR1 received 
SIGUSR1 received 
end

这样就模拟实现了同类型信号的“挂起交付”效果,即使是非实时信号,也能处理多次到达的实例。

内容的提问来源于stack exchange,提问作者milad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 07:53:53