文章

C++信号处理

C++信号处理

C++信号处理

C++ 中的信号处理(signal handling)指的是程序在运行过程中响应特定异步事件(通常由操作系统发送的信号)的能力。信号机制在 UNIX/Linux 系统中较常见,主要用于处理诸如中断、终止、算术错误、非法访问等异常事件。

信号的基本概念

信号是一种异步通信机制,由操作系统发送给进程,以通知发生了某种事件。每种信号都有一个编号和名称,例如:

信号名称编号含义
SIGINT2中断信号(如 Ctrl+C)
SIGTERM15请求终止进程
SIGSEGV11段错误,非法内存访问
SIGFPE8浮点异常(如除0)
SIGKILL9强制终止进程(不可捕获)
SIGABRT6程序异常终止(abort)

C++ 中的信号处理 API

C++ 使用 C 标准库中的 <csignal>(C 中为 <signal.h>)来处理信号。

基本函数:signal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <csignal>
#include <iostream>

void signalHandler(int signal) {
    std::cout << "Caught signal " << signal << std::endl;
    exit(signal);
}

int main() {
    signal(SIGINT, signalHandler);  // 捕获 Ctrl+C
    while (true) {
        std::cout << "Running...\n";
        sleep(1);
    }
}

函数签名

1
2
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

特殊处理器

  • SIG_DFL:默认处理方式。
  • SIG_IGN:忽略信号。

例如:

1
signal(SIGINT, SIG_IGN); // 忽略 Ctrl+C

信号处理函数注意事项

在信号处理函数中必须非常小心,因为它是异步调用的,所以:

可以做的事情:

  • 设置全局变量
  • 调用 write_exit异步信号安全(async-signal-safe)的函数

不安全的操作:

  • 使用 printf(可能是线程不安全的)
  • 使用 malloc、new、文件 I/O、lock、其他可能阻塞的系统调用

因此,更安全的做法是:

  • 设置标志变量,在主循环中检查它并做后续处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <csignal>
#include <atomic>
#include <iostream>
#include <unistd.h>

std::atomic<bool> stopFlag(false);

void handler(int signum) {
    stopFlag = true;
}

int main() {
    signal(SIGINT, handler);
    while (!stopFlag) {
        std::cout << "Working...\n";
        sleep(1);
    }
    std::cout << "Exiting gracefully\n";
}

更强大的 signal 替代:sigaction

signal 有实现差异且不支持重入保护等特性,更推荐使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <csignal>
#include <iostream>
#include <unistd.h>

void handler(int signo) {
    std::cout << "Signal " << signo << " caught\n";
}

int main() {
    struct sigaction sa;
    sa.sa_handler = handler;
    sigemptyset(&sa.sa_mask); // 处理期间不屏蔽信号
    sa.sa_flags = 0;          // 默认行为

    sigaction(SIGINT, &sa, nullptr);
    while (true) {
        std::cout << "Running...\n";
        sleep(1);
    }
}

对比:

特性signalsigaction
灵活性功能有限,只能简单注册处理函数可以精细控制信号行为(信号屏蔽、标志等)
行为一致性不同系统/编译器实现可能不完全一致标准化、跨平台行为更一致
信号屏蔽不能控制信号屏蔽可以设置处理信号时临时屏蔽其他信号
重新安装某些系统中处理函数会被恢复默认(信号处理仅一次)处理函数安装后不会被重置
支持的标志可以设置多个标志(例如 SA_RESTART 自动重启系统调用)
安全性有些实现中信号处理期间可能被其他信号打断支持信号掩码,处理过程更安全

常见用途

  • 优雅退出(处理 SIGINT
  • 热重启配置(例如处理 SIGHUP
  • 崩溃调试(处理 SIGSEGV,记录日志)
  • 定时器机制(如 SIGALRM

信号 vs 异常 vs 中断

项目信号C++ 异常硬件中断
类型OS 级事件编译期语言特性CPU 层面
来源内核/外部事件程序代码硬件设备
响应方式异步触发同步抛出/捕获异步中断处理
例子SIGSEGVtry-catch鼠标点击

补充:线程与信号

  • 默认情况下,信号发送给进程,内核会选择一个线程来处理。
  • 使用 pthread_sigmask 可设置线程屏蔽信号。
  • 使用 sigwait() 系列函数可同步等待信号(适合多线程)。
本文由作者按照 CC BY 4.0 进行授权