sa_mask需先调用sigemptyset(&sa.sa_mask)清空,再用sigaddset显式添加要屏蔽的信号(如SIGUSR1),才能在信号处理函数执行期间阻塞指定信号;未初始化或漏调sigaddset将导致行为不可预测。

sigaction 里 sa_mask 怎么设才能屏蔽其他信号
在 sigaction 处理函数执行期间,你想让某些信号(比如 SIGUSR1)不递达,就得靠 sa_mask。它不是“屏蔽当前信号”——那个是内核自动做的——而是额外阻塞你指定的其他信号。
常见错误是直接传个未初始化的 sigset_t,导致行为不可预测;或者只用 sigemptyset() 清空但没加需要屏蔽的信号。
-
sigemptyset(&sa.sa_mask)必须调用,否则sa_mask是垃圾值 - 如果想在处理
SIGINT时顺便屏蔽SIGUSR1和SIGUSR2,要接着调:sigaddset(&sa.sa_mask, SIGUSR1)、sigaddset(&sa.sa_mask, SIGUSR2) - 不要手动改
sa_mask的底层字节,所有操作必须通过sigemptyset/sigaddset等标准函数
为什么 handler 里不能调 printf 或 malloc
因为 printf 和 malloc 都不是可重入函数:它们内部访问全局链表、缓冲区或锁,而信号中断可能发生在这些函数执行中途。此时 handler 再调一次,大概率破坏数据结构,轻则输出乱码,重则进程崩溃。
你看到的“程序卡住”“段错误”“日志不全”,往往就出在这里。
立即学习“C++免费学习笔记(深入)”;
- 只允许在 handler 中调用异步信号安全函数,比如
write、read、sigprocmask、_exit - 像
g_stop_requested这种volatile sig_atomic_t变量赋值是安全的,但仅限于基本类型和固定大小整数 - 别试图在 handler 里
std::cout、new、std::string::append,哪怕看起来“只做了一件事”也不行
SA_NODEFER 和 SA_RESTART 对重入的影响
SA_NODEFER 控制的是“当前信号是否在 handler 执行期间被自动屏蔽”。默认不设它,内核会把正在处理的信号加入屏蔽字——这是防止 handler 被同种信号反复打断的关键机制。如果你手动加了 SA_NODEFER,又没做同步保护,就可能触发嵌套调用。
SA_RESTART 不影响重入,但它决定系统调用是否自动恢复。比如 read() 被 SIGINT 中断后,若没设 SA_RESTART,会返回 -1 并置 errno = EINTR;设了就继续读,避免你在主循环里反复检查 EINTR。
- 除非你明确需要捕获中断点(如实现超时逻辑),否则建议始终设
SA_RESTART -
SA_NODEFER一般不用,加了反而容易出问题;只有调试时需连续发多次SIGUSR1触发 handler 才考虑它 - 这两个 flag 是位或关系:
sa.sa_flags = SA_RESTART | SA_NODEFER,但别同时滥用
多线程下 sigaction 的信号屏蔽陷阱
信号是发给进程的,但最终由某个线程接收。线程创建时继承父线程的信号掩码,但 sigaction 设置的 handler 是进程级的——所有线程共用同一个处理函数。
你以为在主线程注册了 SIGINT handler,结果子线程里 read() 被中断,handler 却在主线程上下文执行,而你正在子线程里操作某个非原子对象……这就崩了。
- 生产环境推荐:主线程调用
pthread_sigmask屏蔽所有信号,再起一个专用线程调用sigsuspend等待信号 - 不要在多个线程里分别调
sigaction,它不会覆盖,但可能引发竞态 - 如果必须多线程响应信号,优先用
signalfd(Linux 特有),把信号转成文件描述符,用epoll统一管理
sigaction 的参数,而是守住 handler 的边界:它只能做最轻量的事,所有复杂逻辑必须挪到主流程中检查标志位后执行。稍一越界,问题就藏得深、复现难、定位苦。


















