sigaction 比 signal 更可靠,因其显式控制信号屏蔽、系统调用重启及处理函数重置行为,避免了 signal 在不同系统上的未定义行为和安全隐患。

sigaction 为什么比 signal 更可靠
因为 signal 在不同系统上行为不一致:glibc 默认用 SIG_DFL 重置信号处理,且不保证信号屏蔽、不可重入函数调用安全;sigaction 显式控制阻塞掩码、是否重启被中断的系统调用、是否自动重置处理函数——这才是生产环境该用的接口。
常见错误现象:signal(SIGint, handler) 注册后,按 Ctrl+C 有时触发一次就恢复默认(终止进程),有时在 handler 里调用 printf 导致崩溃——这些都不是 bug,是 signal 的未定义行为。
- 必须用
sigemptyset(&sa.sa_mask)初始化掩码,否则sa_mask是垃圾值,可能意外阻塞其他信号 - 设
sa.sa_flags = SA_RESTART可避免read()、accept()等调用被信号中断后返回 -1 +EINTR - 若 handler 中需异步信号安全操作(如写日志),只能调用 async-signal-safe 函数,
write()可以,std::cout不行
注册 SIGUSR1 回调的最小安全写法
注册自定义信号(如 SIGUSR1)时,不能只填 sa_handler,还要显式清空掩码、禁用自动重置、指定 flags。
struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_handler = [](int) { write(2, "USR1 receivedn", 16); }; sa.sa_flags = SA_NODEFER; // 防止 handler 执行期间再次屏蔽该信号 if (sigaction(SIGUSR1, &sa, nullptr) == -1) { perror("sigaction"); }
-
SA_NODEFER不是必须,但若 handler 内部可能再次发SIGUSR1(比如调试触发),不加它会导致信号被临时阻塞,收不到第二次 - Lambda 作为 handler 是 c++11 起支持的,但必须是无捕获的(即
[],不能有[&]或[x]),否则地址无效 - handler 参数类型必须是
void(int),不能是void(int, siginfo_t*, void*)—— 后者需要设SA_SIGINFO且用sa_sigaction
带 siginfo_t 的高级回调怎么写
当需要知道谁发的信号(比如哪个进程用 kill() 发的)、带什么值(sigqueue() 的 si_value),就得启用 SA_SIGINFO 并用 sa_sigaction。
立即学习“C++免费学习笔记(深入)”;
- 必须把
sa_handler设为nullptr,否则sa_sigaction不生效 -
sa_flags必须包含SA_SIGINFO,否则内核不填充siginfo_t* -
si_code字段区分来源:SI_USER表示来自kill(),SI_QUEUE来自sigqueue(),SI_TKILL来自tkill() - 不要在 handler 里访问
si_value.sival_ptr指向的堆内存——发送方可能已释放,只建议传整数或文件描述符
示例中获取发送方 pid:
void sigusr1_handler(int sig, siginfo_t* info, void*) { if (sig == SIGUSR1 && info->si_code == SI_USER) { char buf[32]; int n = snprintf(buf, sizeof(buf), "from pid %dn", info->si_pid); write(2, buf, n); } } // 注册时: sa.sa_sigaction = sigusr1_handler; sa.sa_flags = SA_SIGINFO;
信号处理和多线程的坑
linux 下信号是发给整个进程的,但只有“未阻塞该信号”的线程可能收到——不是所有线程都平等地响应 sigaction 设置。
- 主线程调用
sigaction后,新创建的线程默认继承信号掩码(即该信号未被阻塞),所以也可能收到信号,行为不可控 - 正确做法:主线程一开始就用
pthread_sigmask(SIG_BLOCK, &set, nullptr)把所有信号全阻塞,然后单独起一个线程用sigsuspend()等待信号 - 绝对不要在 signal handler 里调用
std::mutex::lock()、new、malloc、printf—— 这些都不是 async-signal-safe - 如果要用 C++ 对象状态,推荐用
std::atomic<int></int>标记事件,主循环轮询,而不是在 handler 里修改复杂对象
信号机制本身很简单,但和线程、标准库、系统调用交织后,边界条件极多。别试图“优雅地”在 handler 里做业务逻辑,能用原子变量+轮询解决的,就别碰 sigaction 的深水区。