第一部分:信号处理基础概念
1. 什么是信号?
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++头文件 <csignal> (C头文件signal.h)中。
2. 信号处理的应用场景
-
优雅处理程序终止请求(如保存数据后退出)
-
捕获运行时错误(如段错误、浮点异常)
-
实现自定义调试机制(如输出堆栈跟踪)
-
资源清理和状态保存(如关闭文件、释放内存)
第二部分:C++信号处理核心API
1. 关键函数
-
signal(int sig, void (*handler)(int))
注册信号处理函数,参数为信号值和回调函数指针。 -
raise(int sig)
向当前进程发送指定信号。 -
kill(pid_t pid, int sig)
向指定进程发送信号(需包含<unistd.h>
)。
2. 预定义信号常量
信号值 | 宏定义 | 触发场景 |
---|---|---|
1 | SIGHUP | 终端连接断开 |
2 | SIGINT | 键盘中断(Ctrl+C) |
6 | SIGABRT | 调用 abort() 触发 |
8 | SIGFPE | 算术运算错误 |
11 | SIGSEGV | 无效内存访问 |
15 | SIGTERM | 终止请求 |
第三部分:基础用法示例
示例1:捕获 Ctrl+C 信号
#include <csignal>
#include <iostream>
volatile sig_atomic_t g_exit_flag = 0;
void signal_handler(int signum) {
std::cout << "捕获到信号: " << signum << std::endl;
g_exit_flag = 1;
}
int main() {
signal(SIGINT, signal_handler); // 注册 SIGINT 处理函数
while (!g_exit_flag) {
// 主循环逻辑
}
std::cout << "程序正常退出" << std::endl;
return 0;
}
示例2:处理段错误
#include <csignal>
#include <cstdlib>
void segv_handler(int signum) {
std::cerr << "发生段错误!保存日志..." << std::endl;
exit(EXIT_FAILURE);
}
int main() {
signal(SIGSEGV, segv_handler);
int* ptr = nullptr;
*ptr = 42; // 故意触发段错误
return 0;
}
第四部分:高级应用技巧
1. 多线程信号处理
#include <csignal>
#include <pthread.h>
void init_signal_handler() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
pthread_sigmask(SIG_BLOCK, &set, nullptr); // 在主线程阻塞信号
// 创建专用信号处理线程
std::thread([] {
sigset_t wait_set;
sigemptyset(&wait_set);
sigaddset(&wait_set, SIGINT);
int sig;
while (true) {
sigwait(&wait_set, &sig); // 安全等待信号
std::cout << "线程处理信号: " << sig << std::endl;
}
}).detach();
}
2. 信号驱动异步I/O
#include <csignal>
#include <fcntl.h>
#include <unistd.h>
void io_handler(int) {
char buf[256];
int len = read(STDIN_FILENO, buf, sizeof(buf));
std::cout << "输入数据: " << std::string(buf, len) << std::endl;
}
int main() {
signal(SIGIO, io_handler);
fcntl(STDIN_FILENO, F_SETOWN, getpid());
fcntl(STDIN_FILENO, F_SETFL, O_ASYNC | O_NONBLOCK);
while (true) {
// 主程序逻辑
}
}
第五部分:现代C++最佳实践
1. 使用RAII封装信号处理
class SignalGuard {
public:
SignalGuard(int sig, void (*handler)(int))
: m_sig(sig), m_old_handler(signal(sig, handler)) {}
~SignalGuard() { signal(m_sig, m_old_handler); }
private:
int m_sig;
void (*m_old_handler)(int);
};
// 使用示例
{
SignalGuard guard(SIGINT, [](int) {
std::cout << "临时处理Ctrl+C" << std::endl;
});
// 在此作用域内使用自定义处理
}
2. 原子操作替代全局变量
#include <atomic>
std::atomic<bool> g_exit_flag(false);
void handler(int) {
g_exit_flag.store(true);
}
int main() {
signal(SIGTERM, handler);
while (!g_exit_flag.load()) {
// 主循环逻辑
}
}
第六部分:注意事项与常见陷阱
-
异步安全函数
信号处理函数中只能调用异步安全函数(如write()
),避免使用printf
、malloc
等非安全函数。 -
信号丢失问题
连续快速发送相同信号可能导致信号丢失,需通过信号队列或标志位解决。 -
多线程同步
使用pthread_sigmask
控制信号接收线程,避免竞态条件。 -
可重入性
避免在信号处理函数中修改全局状态,确保代码可重入。