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; }
代码说明
- 全局计数变量:
sigusr1_count用volatile sig_atomic_t修饰,确保在信号处理函数中修改时,主循环能立刻看到最新值,且操作是原子的,不会被中断。 - 信号处理函数拆分:原来的
handler1只负责累加计数,实际的业务逻辑放到handle_sigusr1中,这样信号处理函数执行时间极短,不会长时间阻塞其他信号。 - 主循环逻辑:
- 先阻塞关心的信号,避免在检查计数时信号到达,导致计数没被及时处理。
- 循环处理所有待处理的SIGUSR1,每处理一次计数减1。
- 用
sigsuspend等待信号,避免空循环占用CPU。 - 子进程退出后,处理剩余的信号再退出,确保所有信号都被处理。
预期输出
运行这段代码后,你会得到期望的输出:
SIGUSR1 received SIGUSR1 received end
这样就模拟实现了同类型信号的“挂起交付”效果,即使是非实时信号,也能处理多次到达的实例。
内容的提问来源于stack exchange,提问作者milad
相关产品推荐
相关产品推荐

