C++怎么使用信号处理_C++sigaction注册回调【系统】

2次阅读

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

C++怎么使用信号处理_C++sigaction注册回调【系统】

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()newmallocprintf —— 这些都不是 async-signal-safe
  • 如果要用 C++ 对象状态,推荐用 std::atomic<int></int> 标记事件,主循环轮询,而不是在 handler 里修改复杂对象

信号机制本身很简单,但和线程、标准库、系统调用交织后,边界条件极多。别试图“优雅地”在 handler 里做业务逻辑,能用原子变量+轮询解决的,就别碰 sigaction 的深水区。

text=ZqhQzanResources