C++ | 信号处理

第一部分:信号处理基础概念

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. 预定义信号常量

信号值宏定义触发场景
1SIGHUP终端连接断开
2SIGINT键盘中断(Ctrl+C)
6SIGABRT调用 abort() 触发
8SIGFPE算术运算错误
11SIGSEGV无效内存访问
15SIGTERM终止请求

第三部分:基础用法示例

示例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()) {  
        // 主循环逻辑  
    }  
}  

第六部分:注意事项与常见陷阱

  1. 异步安全函数
    信号处理函数中只能调用异步安全函数(如 write()),避免使用 printfmalloc 等非安全函数。

  2. 信号丢失问题
    连续快速发送相同信号可能导致信号丢失,需通过信号队列或标志位解决。

  3. 多线程同步
    使用 pthread_sigmask 控制信号接收线程,避免竞态条件。

  4. 可重入性
    避免在信号处理函数中修改全局状态,确保代码可重入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值