【Linux C++】采用"回调链设计"实现 signal 的全局处理实现 & 多个中断信号回调

如何在Linux C++ 程序中优雅地、统一管理 Signal 呢?答案就在下方——

使用回调链的 SignalHandler方案代码

// signal_handler.h
#pragma once

#include <atomic>
#include <functional>
#include <vector>
#include <csignal>
#include <mutex>

class SignalHandler {
public:
    static SignalHandler& getInstance();
    
    // 注册信号处理回调(支持多个回调)
    void registerHandler(int signal, std::function<void(int)> handler);
    
    // 初始化信号处理(只需要调用一次)
    void initialize();
    
    // 检查是否有信号发生
    bool isSignalReceived() const { return m_signalReceived; }
    
    // 获取当前信号
    int getCurrentSignal() const { return m_currentSignal; }
    
private:
    SignalHandler() = default;
    ~SignalHandler() = default;
    
    static void globalSignalHandler(int signal);
    void executeHandlers(int signal);
    
private:
    std::atomic<bool> m_signalReceived{false};
    std::atomic<int> m_currentSignal{0};
    std::mutex m_mutex;
    std::vector<std::pair<int, std::function<void(int)>>> m_handlers;
};
cpp
// signal_handler.cpp
#include "signal_handler.h"
#include <iostream>
#include <algorithm>

SignalHandler& SignalHandler::getInstance() {
    static SignalHandler instance;
    return instance;
}

void SignalHandler::registerHandler(int signal, std::function<void(int)> handler) {
    std::lock_guard<std::mutex> lock(m_mutex);
    m_handlers.emplace_back(signal, handler);
}

void SignalHandler::initialize() {
    struct sigaction sa;
    sa.sa_handler = globalSignalHandler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    
    sigaction(SIGINT, &sa, nullptr);
    sigaction(SIGTERM, &sa, nullptr);
    // 可以添加更多信号
    
    std::cout << "[SignalHandler] Signal handlers initialized" << std::endl;
}

void SignalHandler::globalSignalHandler(int signal) {
    auto& instance = getInstance();
    instance.m_signalReceived = true;
    instance.m_currentSignal = signal;
    instance.executeHandlers(signal);
}

void SignalHandler::executeHandlers(int signal) {
    std::lock_guard<std::mutex> lock(m_mutex);
    for (const auto& handler : m_handlers) {
        if (handler.first == signal || handler.first == 0) { // 0表示所有信号
            try {
                handler.second(signal);
            } catch (const std::exception& e) {
                std::cerr << "[SignalHandler] Error in handler: " << e.what() << std::endl;
            }
        }
    }
}

1. 只在 main 中注册信号处理

// main.cpp 修改部分
#include "signal_handler.h"

// 全局退出标志
std::atomic<bool> g_shutdownRequested{false};

void globalShutdownHandler(int signal) {
    std::cout << "\nReceived signal " << signal << ", initiating shutdown..." << std::endl;
    g_shutdownRequested = true;
}

int main() {
    // 初始化信号处理
    auto& signalHandler = SignalHandler::getInstance();
    signalHandler.initialize();
    
    // 注册全局关机处理
    signalHandler.registerHandler(SIGINT, globalShutdownHandler);
    signalHandler.registerHandler(SIGTERM, globalShutdownHandler);
    
    // SystemInit 可以这样注册
    auto& insSystemInit = SystemInit::getInstance();
    signalHandler.registerHandler(SIGINT, [&insSystemInit](int sig) {
        std::cout << "SystemInit handling signal" << std::endl;
        insSystemInit.stop();
    });
    
    // STEP0. 系统初始化
    if(!insSystemInit.initialize()) {
        return -1;
    }
    
    // clsCtrl 中的注册(现在不会冲突)
    // ctrl = std::make_shared<clsCtrl>(vcm_comm);
    // ctrl 构造函数中的信号注册改为:
    signalHandler.registerHandler(SIGINT, [ctrl](int sig) {
        if (ctrl) {
            std::cout << "clsCtrl handling signal" << std::endl;
            ctrl->stop(); // 需要为 clsCtrl 添加 stop() 方法
        }
    });
    
    // 主循环
    while(!g_shutdownRequested && insSystemInit.isRunning()) {
        // 业务逻辑...
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    
    // 优雅退出
    std::cout << "Shutting down gracefully..." << std::endl;
    
    // 确保所有线程都停止
    if (ctrl) {
        ctrl->stop();
    }
    
    insSystemInit.stop();
    
    std::cout << "Clean shutdown completed" << std::endl;
    return 0;
}

总结建议

对于你的项目,我建议:

  1. 采用方案一的多回调SignalHandler,因为它最灵活

  2. 在main.cpp中统一管理所有信号注册

  3. 修改各个类,移除它们内部的信号处理逻辑

  4. 为每个需要响应信号的类提供stop()或类似的方法

 
已知:在Linux 内核, signal 注册是唯一的

追问:signal()注册是唯一的,为什么用回调链可以,而且只用初始化一次

 

这是个非常关键的问题! 让我解释清楚其中的核心原理。

核心答案:我们只注册了一个全局处理函数,但它内部调用多个用户回调

1. 实际注册的只有一个函数

// 在 initialize() 中只注册了一次
sigaction(SIGINT, &sa, nullptr);  // 这里 sa.sa_handler = globalSignalHandler

只注册了 globalSignalHandler 这一个函数给内核。

2. 回调链是用户空间实现的

 
用户按下 Ctrl+C
      ↓
内核调用 globalSignalHandler(唯一的注册函数)
      ↓
┌─────────────────────────────────────────────┐
│ globalSignalHandler(int signal) {            │
│   // 1. 设置标志                            │
│   m_signalReceived = true;                   │
│                                              │
│   // 2. 执行用户注册的所有回调               │
│   for (auto& handler : m_handlers) {         │  ← 这里遍历执行多个回调!
│     if (handler.first == signal) {           │
│       handler.second(signal);                │
│     }                                         │
│   }                                          │
│ }                                            │
└─────────────────────────────────────────────┘

可视化对比

错误理解(你以为的):

SIGINT → handler1 (内核调用)
SIGINT → handler2 (内核调用)  ❌ 不可能!每个信号只能注册一个
SIGINT → handler3 (内核调用)

正确实现(我们的):

 
SIGINT → globalSignalHandler (内核唯一调用)
          ↓
          ┌─ handler1()   ← 用户空间调用
          ├─ handler2()   ← 用户空间调用  
          └─ handler3()   ← 用户空间调用

详细工作流程

步骤1:初始化(一次)

void SignalHandler::initialize() {
    struct sigaction sa;
    sa.sa_handler = globalSignalHandler;  // 设置我们唯一的处理函数
    sigaction(SIGINT, &sa, nullptr);     // 告诉内核:"调用globalSignalHandler"
    
    // 注意:这里只注册了一个函数!
    // 内核表:SIGINT → 0x12345678 (globalSignalHandler地址)
}

步骤2:用户注册多个回调

// 这些只是往我们的vector里添加,不涉及内核
signalHandler.registerHandler(SIGINT, handler1);  // 添加到m_handlers
signalHandler.registerHandler(SIGINT, handler2);  // 再添加
signalHandler.registerHandler(SIGINT, handler3);  // 再添加

// 此时:
// - 内核只知道:SIGINT → globalSignalHandler
// - 我们知道:m_handlers = [{SIGINT,handler1}, {SIGINT,handler2}, {SIGINT,handler3}]

步骤3:信号发生时的完整流程

// 内核视角:
1. 用户按Ctrl+C → 产生SIGINT
2. 内核查找:SIGINT的处理函数是0x12345678
3. 内核调用:globalSignalHandler(2)  // 2是SIGINT

// 我们的globalSignalHandler:
void SignalHandler::globalSignalHandler(int signal) {
    // 获取单例
    auto& instance = getInstance();
    
    // 遍历执行所有用户注册的回调
    instance.executeHandlers(signal);
    // 内部会调用:handler1(2), handler2(2), handler3(2)
}

代码示例验证

让我们写个简单验证:

cpp
#include <iostream>
#include <csignal>
#include <vector>
#include <functional>

std::vector<std::function<void(int)>> userCallbacks;

// 唯一的全局处理函数
void myGlobalHandler(int sig) {
    std::cout << "Global handler called for signal " << sig << std::endl;
    
    // 执行所有用户回调
    for (auto& cb : userCallbacks) {
        std::cout << "  Calling user callback..." << std::endl;
        cb(sig);
    }
}

int main() {
    // 1. 只注册一次给内核
    signal(SIGINT, myGlobalHandler);
    std::cout << "Registered ONE handler with kernel" << std::endl;
    
    // 2. 用户注册多个回调(不影响内核)
    userCallbacks.push_back([](int sig) {
        std::cout << "    User callback 1: SIGINT received" << std::endl;
    });
    
    userCallbacks.push_back([](int sig) {
        std::cout << "    User callback 2: Doing cleanup..." << std::endl;
    });
    
    userCallbacks.push_back([](int sig) {
        std::cout << "    User callback 3: Exiting..." << std::endl;
    });
    
    std::cout << "Registered " << userCallbacks.size() 
              << " user callbacks (in user space)" << std::endl;
    std::cout << "\nPress Ctrl+C to test..." << std::endl;
    
    // 等待信号
    pause();
    
    return 0;
}

运行结果:

Registered ONE handler with kernel
Registered 3 user callbacks (in user space)

Press Ctrl+C to test...
^CGlobal handler called for signal 2
  Calling user callback...
    User callback 1: SIGINT received
  Calling user callback...
    User callback 2: Doing cleanup...
  Calling user callback...
    User callback 3: Exiting...

为什么你的原始代码会冲突?

你的原始代码问题:

 
// SystemInit.cpp
signal(SIGINT, SystemInit::signalHandler);  // 注册SystemInit的

// 然后clsCtrl.cpp又调用:
signal(SIGINT, clsCtrl::signalHandler);     // 覆盖了上面的!

// 结果:只有最后注册的生效

覆盖原理

 
// signal() 的伪实现
void signal(int signum, void (*handler)(int)) {
    // 直接替换内核表中的处理函数
    kernel_signal_table[signum] = handler;  // 直接替换!
}

SignalHandler 类的精妙之处

精妙点1:中间层设计

 
内核信号 → 全局处理函数 → 多个用户回调
    ↑           ↑              ↑
  唯一       我们的中间层     用户模块

精妙点2:控制反转

// 用户模块不再直接注册给内核
// signal(SIGINT, myHandler);  // ❌ 旧的,会冲突

// 而是注册给我们
signalHandler.registerHandler(SIGINT, myHandler);  // ✅ 新的,不会冲突

精妙点3:统一管理

// 所有信号都经过同一个入口
void globalSignalHandler(int signal) {
    // 可以在这里统一:
    // 1. 日志记录
    // 2. 统计信息  
    // 3. 异常保护
    // 4. 执行用户回调
}

在你们项目中的实际应用

在你的项目中,这样修改:

// main.cpp 中
int main() {
    // 初始化一次:告诉内核调用我们的globalSignalHandler
    SignalHandler::getInstance().initialize();
    
    // 各个模块注册(不会互相覆盖)
    SignalHandler::getInstance().registerHandler(SIGINT, 
        [](int) { SystemInit::getInstance().stop(); });
    
    SignalHandler::getInstance().registerHandler(SIGINT,
        [ctrl](int) { if (ctrl) ctrl->stop(); });
    
    // 内核只知道:SIGINT → SignalHandler::globalSignalHandler
    // 但实际会调用两个用户函数
}

总结:为什么"初始化一次+回调链"能工作

 
层次 负责方 内容 说明
内核层 操作系统 SIGINT → globalSignalHandler 只注册一次,永久有效
中间层 SignalHandler globalSignalHandler() 统一入口,管理所有回调
用户层 业务模块 handler1(), handler2(), ... 任意多个,通过register注册

关键洞察

  1. 内核只关心一个地址:你告诉它"发生信号时跳转到这个地址"

  2. 我们在那个地址的函数里做所有事情:包括调用多个用户回调

  3. 用户注册不影响内核:只是往我们的列表里添加,不会调用 signal()

这就是为什么只需要初始化一次,却能实现多回调的原因:我们在用户空间实现了自己的"微内核"来分发信号,而内核只负责触发这个分发器

posted @ 2025-12-30 23:41  FBshark  阅读(2)  评论(0)    收藏  举报