尝试复现ASAN假阳性时发现连续调用alarm()第二个信号未触发,求原因
为什么第二次alarm()的信号从未触发?
这个问题的核心坑点在于**longjmp()不会自动恢复信号掩码**——咱们一步步拆解来理清楚:
问题的来龙去脉
当SIGALRM信号触发并进入处理函数handle_timeout时,系统会自动把SIGALRM加入进程的信号掩码(也就是暂时阻塞这个信号),这是系统的默认行为,目的是防止处理过程中再次收到同信号导致嵌套调用,和你看到的GNU文档描述一致。
但你用longjmp(jump_buffer, 1)直接跳回了test()里的setjmp返回点,这就绕过了信号处理函数的正常返回流程:
- 正常情况下,信号处理函数执行完毕返回时,系统会自动恢复之前的信号掩码(解除
SIGALRM的阻塞); - 但
longjmp是“强行跳转”,完全跳过了这个恢复步骤,导致SIGALRM一直处于被阻塞的状态!
所以第二次调用alarm(2)确实会在2秒后发送SIGALRM信号,但进程的信号掩码还挡着它,信号被挂起无法触发处理函数,你的程序就卡在f()的死循环里永远跑下去了。
修复方案
有两种可靠的解决方式,推荐第一种:
方案1:使用sigsetjmp和siglongjmp(推荐)
这对函数是专门为信号场景设计的,会自动保存和恢复信号掩码。只需要把原来的jmp_buf、setjmp、longjmp替换成对应的信号版本即可:
#include <setjmp.h> #include <signal.h> #include <unistd.h> static sigjmp_buf jump_buffer; // 替换为sigjmp_buf void f() { while(1) {}; } void handle_timeout(int) { siglongjmp(jump_buffer, 1); // 替换为siglongjmp } void test() { // sigsetjmp第二个参数传1,表示保存当前信号掩码 if (sigsetjmp(jump_buffer, 1) == 0) { f(); } } int main() { signal(SIGALRM, handle_timeout); alarm(2); test(); signal(SIGALRM, handle_timeout); alarm(2); test(); return 0; }
方案2:手动恢复信号掩码
如果坚持用longjmp,可以在跳转前手动解除SIGALRM的阻塞:
void handle_timeout(int sig) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGALRM); // 手动解除SIGALRM的阻塞 sigprocmask(SIG_UNBLOCK, &mask, NULL); longjmp(jump_buffer, 1); }
不过这种方式需要你自己处理信号掩码的细节,不如第一种方案省心可靠。
内容的提问来源于stack exchange,提问作者choeger
相关产品推荐
相关产品推荐

