sigaction() 更安全,因其显式控制信号掩码、SA_RESTART 和 SA_NODEFER 等行为,POSIX 保证其可移植可靠;signal() 在不同系统行为不一致,可能重置为默认处理,导致二次信号终止进程。

signal() 和 sigaction() 哪个更安全?
直接用 signal() 注册信号处理函数是不安全的,尤其在多线程或需要可靠语义的场景下。它在不同系统(如 Linux vs macOS)行为不一致:可能重置信号处理为默认、不阻塞同类信号、无法指定标志位。生产环境必须用 sigaction()。
-
sigaction()显式控制信号掩码、是否重启被中断系统调用(SA_RESTART)、是否自动阻塞当前信号(SA_NODEFER可关) - 调用
signal()后,某些系统会将该信号 handler 重置为SIG_DFL,导致第二次信号直接终止进程 - POSIX 明确只保证
sigaction()是可移植且可靠的接口
信号处理函数里能调用哪些函数?
只有异步信号安全(async-signal-safe)函数才能在 signal handler 中安全调用。绝大多数 C++ 标准库函数(包括 std::cout、malloc、printf、std::string::append)都不在此列 —— 它们内部可能锁全局资源或调用非安全函数,引发死锁或崩溃。
- 可用函数极有限:
write()、read()、sigprocmask()、raise()、_exit()(不是exit())等,完整列表见man 7 signal-safety - 常见误用:
std::cerr → 未定义行为,可能卡死或破坏堆 - 正确做法:handler 中仅写入一个字节到自管道(self-pipe)或原子变量(如
std::atomic),主循环检测并处理
如何安全地把信号转成程序可控事件?
推荐使用 self-pipe 技巧或 signalfd()(Linux only)。前者兼容性好,后者更简洁但限于 Linux。
- self-pipe:创建一对
pipe(),handler 中用write()往写端发 1 字节;主循环用select()或epoll_wait()监听读端 fd -
signalfd():用sigprocmask()屏蔽目标信号,再用signalfd()创建专用 fd,之后像读普通 fd 一样read()获取struct signalfd_siginfo - 避免轮询全局
std::atomic:虽比printf安全,但无法传递信号编号或附加信息,且主循环需频繁检查,实时性差
int pipefd[2];
pipe(pipefd);
// 在 sigaction handler 中:
write(pipefd[1], "x", 1); // 安全
// 主循环中:
char buf[1];
if (read(pipefd[0], buf, 1) == 1) {
// 处理信号逻辑,此时可自由调用任何函数
}
多线程下信号处理要特别注意什么?
信号是发给「进程」或「线程」的,但默认由任意一个未屏蔽该信号的线程接收。若多个线程都未屏蔽 SIGUSR1,谁收到是不确定的 —— 这会导致 handler 在错误线程上下文中执行,破坏 TLS 或栈状态。
立即学习“C++免费学习笔记(深入)”;
- 务必用
pthread_sigmask()在主线程初始化后,显式屏蔽所有信号;再用sigwait()或signalfd()在**单个专用线程**中同步等待信号 - 不要在多个线程里分别调用
sigaction();每个线程的 signal mask 独立,但 handler 是进程级的,混用极易出错 -
raise()发送给调用线程,kill(getpid(), ...)发送给进程 —— 注意目标粒度
std::cout,都可能让程序在高负载时随机崩掉,而且很难复现。









