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

news/2026/1/16 22:48:34/文章来源:https://www.cnblogs.com/FBsharl/p/19423655

如何在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 2Calling user callback...User callback 1: SIGINT receivedCalling 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() {// 初始化一次:告诉内核调用我们的globalSignalHandlerSignalHandler::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()

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1098186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

挠弹记录

记一个唐诗UB 看如下两份代码: har *big_multiply_fft(char *sa,char *sb){if (la<=0||lb<=0){char s[2]={0,\0};return s;} }har *big_multiply_fft(char *sa,char *sb){if (la<=0||lb<=0){char *s=mall…

UDP-酮半乳糖二钠盐——赋能前沿糖基化研究的核心工具分子

UDP-酮半乳糖二钠盐 (UDP-ketogalactose disodium salt) 是糖核苷酸家族中一个关键且具有独特反应活性的成员。作为细胞糖基化生物合成途径中的重要代谢中间体&#xff0c;它不仅是基础糖生物学研究的探针&#xff0c;更是探索新型生物合成路径与开发糖类衍生疗法的潜力分子。化…

把织物上的“轻微脏污”抓出来:客户下单50台

我们做了一套织物脏污检测方案,目标不是“实验室的算法”,而是​能在强纹理、曲面反光、褶皱阴影这些产线常态里稳定工作​。近期在外贸头部内衣胸杯客户现场验证跑通,客户已​预定数十台全检设备​,用于从抽检走向…

UDP-2-F-D-葡萄糖胺二钠盐—糖生物学研究与药物开发的关键工具分子

糖核苷酸是生命体内糖基化修饰的活化供体&#xff0c;直接参与多糖合成、蛋白质糖基化以及多种天然产物生物合成等核心生物过程&#xff0c;是连接碳水化合物代谢与复杂生命活动的重要桥梁。UDP-2-F-D-葡萄糖胺二钠盐作为一种经过特异性修饰的糖核苷酸类似物&#xff0c;为深入…

特斯拉为何死磕纯视觉?成本、数据与安全冗余的自动驾驶路线之争

在自动驾驶感知技术路线中&#xff0c;以激光雷达为核心的多传感器融合方案已成为行业主流。然而&#xff0c;特斯拉却始终坚持仅使用摄像头的纯视觉方案。这一选择背后&#xff0c;是成本控制、数据驱动与第一性原理思维的综合考量。本文将剖析特斯拉坚持纯视觉方案的核心逻辑…

论文AI率超标自救:五佳降AI工具合集

又到毕业季&#xff0c;毕业论文除了查重外&#xff0c;真的别忘记查ai率。 今天就告诉大家高重复率以及高ai率应该如何快速降低&#xff01;实测全网产品&#xff0c;无广&#xff01;&#xff01; 如果这篇整理能帮你少走点弯路&#xff0c;那就值了。 1、嘎嘎降AI 官网&…

摸鱼没翻车,全靠这套 Chrome 快捷键组合

摸鱼没翻车,全靠这套 Chrome 快捷键 我自己基本每天都在用,熟到手指都不用过脑子。 一、老板靠近时的「三连保命组合」 这三招,是底层逻辑,先记死。 1️⃣ 一键关掉当前摸鱼页 Ctrl + W 别犹豫,看到人影直接按。 …

【毕业设计】基于SpringBoot社区住户信息管理系统(源码+文档+远程调试,全bao定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

DaBai Max Pro 与 DaBai DCW2-DW2 深度相机坐标系解析

DaBai Max Pro 与 DaBai DCW2-DW2 深度相机坐标系解析DaBai Max Pro与DaBai DCW2-DW2&#xff08;奥比中光DaBai系列&#xff09;深度相机采用标准右手坐标系&#xff0c;原点位于相机焦点&#xff0c;单位为毫米&#xff0c;核心方向与转换规则如下&#xff1a;核心坐标系定义…

102301338郭砚康的软件工程课程总结 - Nicholas

102301338郭砚康的软件工程课程总结 一、学期回顾 1.1 回顾你对于软件工程课程的想象 在课程开始前,我心目中的软件工程主要是一门关于“如何更好地写代码”的课程,期望能学习系统的开发流程、团队协作模式以及项目管…

Java毕设项目:基于SpringBoot+Vue的二手数码产品交易平台的开发与实现(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

高达一亿港币人工智能创投基金,亚洲人工智能初创大赛上海站招募丨社区伙伴活动推荐

阅读更多 Voice Agent 学习笔记:了解最懂 AI 语音的头脑都在思考什么

论文被判AI生成?五佳降AI工具避坑分享

又到毕业季&#xff0c;毕业论文除了查重外&#xff0c;真的别忘记查ai率。 今天就告诉大家高重复率以及高ai率应该如何快速降低&#xff01;实测全网产品&#xff0c;无广&#xff01;&#xff01; 如果这篇整理能帮你少走点弯路&#xff0c;那就值了。 1、嘎嘎降AI 官网&…

Java计算机毕设之基于SpringBoot+Vue的二手数码产品交易平台的开发与实现(完整前后端代码+说明文档+LW,调试定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

通信原理篇---误码率

第一部分&#xff1a;什么是误码率&#xff1f;——“游戏的总出错率”想象你和朋友隔着一个嘈杂的教室玩传话游戏&#xff0c;你说一句话&#xff0c;他写下来。误码率 就是&#xff1a;他写错的字数&#xff0c;除以你总共说的字数&#xff0c;得到的比例。公式&#xff1a;误…

【课程设计/毕业设计】基于SpringBoot+Vue的二手电子产品交易平台二手数码产品交易平台的开发与实现【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

重复率过了AI率却超?这五佳降AI工具能用

又到毕业季&#xff0c;毕业论文除了查重外&#xff0c;真的别忘记查ai率。 今天就告诉大家高重复率以及高ai率应该如何快速降低&#xff01;实测全网产品&#xff0c;无广&#xff01;&#xff01; 如果这篇整理能帮你少走点弯路&#xff0c;那就值了。 1、嘎嘎降AI 官网&…

通信原理篇---可靠性和有效性

第一部分&#xff1a;两个核心目标——别送错 & 快点送你开了一家外卖店&#xff0c;有两个最基本的要求&#xff1a;可靠性&#xff1a;别送错地方&#xff0c;别洒了汤&#xff0c;别送晚了导致饭菜馊了。通信系统的可靠性&#xff1a;信息准确无误、及时地送到对方手里。…

Amaze File Manager:一款基于 Material Design 的开源安卓文件管理器

Amaze File Manager是一款轻量、快速、遵循Material Design设计规范的开源安卓文件管理应用,支持本地、云端、FTP/SFTP、SMB等多种存储方式,并提供文件加密、压缩包处理等高级功能。Amaze File Manager Amaze File M…

LLM解析脑电波,中风康复预测提前1月

&#x1f4dd; 博客主页&#xff1a;Jax的CSDN主页 目录超越症状&#xff1a;AI如何识别医疗决策中的“沉默成本” 一、沉默成本&#xff1a;医疗决策的隐形黑洞 二、大模型如何破译“沉默成本”&#xff1f; 1. 从医患对话中提取情感线索 2. 社交网络数据的伦理化挖掘 3. 多模…