如何避免快速连续发送的SIGUSR1信号被操作系统忽略?
解决重复SIGUSR1信号被合并丢失的问题
这个问题我之前调试代码时也碰到过,核心根源很好理解:你用的SIGUSR1属于不可靠信号(POSIX标准里的非实时信号),这类信号的特性是操作系统不会为它们排队——如果多个相同信号在父进程还没处理完前一个的间隙到达,系统只会保留一个,剩下的直接丢弃,这就是为什么你偶尔只会看到一次handler触发。
现在要解决这个问题,又不想换不同信号、也不想给子进程加延迟,有两个非常靠谱的方案:
方案1:改用实时信号(最推荐)
POSIX定义的实时信号(范围是SIGRTMIN到SIGRTMAX)是支持排队的——每一个发送的实时信号都会被操作系统保存下来,直到进程处理它们,绝对不会被合并。你只需要把代码里的SIGUSR1替换成某个实时信号就行,比如最常用的SIGRTMIN:
修改后的关键代码片段
// 替换SIGUSR1为实时信号SIGRTMIN sigaction(SIGRTMIN, &sigact, NULL); // 子进程发送信号时也对应改成SIGRTMIN sigqueue(getppid(), SIGRTMIN, signalValueInt);
这样不管两个子进程发送信号的间隔有多短,父进程的handler都会被触发两次,因为每个实时信号都会被系统排队处理,不会丢失。
方案2:用sigwaitinfo同步等待信号
如果你不想改用实时信号,还可以让父进程主动等待信号,而不是依赖异步的信号handler。这种方式能逐个接收pending的信号,完全不会错过。具体做法如下:
修改后的main函数核心逻辑
int main() { pid_t ambulance1 = 0; pid_t ambulance2 = 0; sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGUSR1); // 先阻塞SIGUSR1,让信号进入pending状态,不会触发异步handler sigprocmask(SIG_BLOCK, &sigset, NULL); ambulance1 = fork(); if(ambulance1 > 0) { ambulance2 = fork(); if(ambulance2 > 0) { // 父进程:主动等待两个信号,确保都收到 struct siginfo info; for(int i=0; i<2; i++){ sigwaitinfo(&sigset, &info); // 这里直接执行原来handler里的逻辑,比如打印你需要的<Text> printf("Received signal from ambulance %d\n", info.si_pid); } int status; waitpid(ambulance1, &status, 0); waitpid(ambulance2, &status, 0); printf("HQ went home!\n"); } } if(ambulance1 == 0 || ambulance2 == 0) { union sigval signalValueInt; signalValueInt.sival_int = SIG_INT; sigqueue(getppid(), SIGUSR1, signalValueInt); printf("Ambulance[%d] ended.\n", getpid()); } return 0; }
这个方案的原理是:父进程先把SIGUSR1阻塞,这样子进程发送的信号会被标记为pending(未处理),不会被系统合并。然后父进程用sigwaitinfo主动等待信号,每次调用都会返回一个pending的信号,循环两次就能确保收到两个子进程的信号,完美解决丢失问题。
内容的提问来源于stack exchange,提问作者vapandris




