C++学习---信号处理机制、中断、异步环境

news/2024/11/19 15:25:59/

文章目录

  • 前言
  • 信号处理
    • signal()函数
      • 关于异步环境
    • 信号处理函数示例
    • raise()函数

前言


信号处理

关于信号,信号是一种进程间通信的机制,用于在程序执行过程中通知进程发生了一些事件。在Unix和类Unix系统中,信号是一种异步通知机制,通过发送信号,一个进程可以通知另一个进程发生了某个事件,如按下 Ctrl+C、除零错误等。

在C++中,可以使用 头文件提供的信号处理机制来捕获和处理信号。

信号的基本概念:

  • 信号编号:每个信号都有一个唯一的编号,用来标识不同的事件。例如,SIGINT 是表示中断的信号。
  • 信号处理器: 信号处理器是一个函数,用于处理接收到的信号。你可以为每种信号指定一个处理函数。

常见的信号

  • SIGINT:中断信号,通常由用户按下 Ctrl+C 生成。
  • SIGSEGV:段错误信号,表示非法内存访问。
  • SIGTERM:终止信号,表示进程被要求终止。
  • SIGKILL:强制终止信号,表示进程被强制终止。

signal()函数

C++使用 signal 函数可以为特定的信号注册信号处理函数

语法使用:

void (*signal(int signum, void (*handler)(int)))(int);

也可以写成:

signal(SIGINT, signalHandler);

在上述代码中,

  • signum(SIGINT):要注册的信号的编号。
  • handler(signalHandler):要注册的信号处理函数的指针。

信号处理函数的声明:

void handlerFunction(int signum)

关于信号处理函数的定义应该尽量简单,因为它在异步环境中执行,同时有一些函数(例如‘printf’,‘malloc’)不是异步安全的,所以尽量不要在信号处理函数中使用它们。

关于异步环境

异步环境是指程序执行时存在多个同时运行的线程或进程,这些线程或进程在执行过程中可能会相互干扰,因为它们共享某些资源(如内存、文件描述符等)。在异步环境中,执行顺序是不确定的,因此程序的行为可能受到非常复杂的影响。

printfmalloc 不是异步安全的主要是因为它们在执行时可能涉及到对共享资源的访问,而这样的访问在异步环境中是不安全的。

  1. printf:
    • print函数通常会使用标准输出(stdout),而在异步环境中,多个线程或进程可能会同时尝试写入标准输出,导致输出内容混乱。
    • 在标准库中的输出函数(如 printf)通常使用全局锁(mutex)来保护对输出流的访问,但这并不能解决所有的异步安全问题。在信号处理函数中使用 printf 可能导致死锁或其他竞态条件。
  2. malloc:
    • malloc 函数用于动态分配内存,而在异步环境中,多个线程或进程可能同时尝试分配或释放内存,这可能导致内存管理错误。

因此,在异步环境中,为了确保代码的正确性,应该尽量避免在信号处理函数或多线程环境中使用不可重入(non-reentrant)的函数。不可重入函数是指在执行过程中依赖于全局状态静态变量的函数,而这在异步环境中可能导致不确定的结果。

为了在异步环境中安全使用输出函数和内存分配函数,通常建议使用异步安全的替代版本。例如,在信号处理函数中,可以使用 write 函数代替 printf,而在多线程环境中,可以使用 pthread 库提供的线程安全的输出函数和内存分配函数。


信号处理函数示例

#include <iostream>
#include <csignal>// 信号处理函数
void signalHandler(int signum) {std::cout << "Received signal: " << signum << std::endl;// 自定义处理逻辑可以在这里添加// ...// 恢复对 SIGINT 的默认处理signal(SIGINT, SIG_DFL);
}int main() {// 注册信号处理函数signal(SIGINT, signalHandler);std::cout << "Press Ctrl+C to trigger the signal." << std::endl;// 一个简单的循环,使程序保持运行while (true) {// 等待信号的到来}return 0;
}

在上述代码中,声明了一个自定义的信号处理函数signalHandler;之后的main函数中,用signal注册信号处理函数,来处理SIGINT信号。signal函数的第一个参数就是要识别的信号的编号,第二个参数就是指向信号处理函数的指针。

而在信号处理函数signalHandler中,可以添加自定义的处理逻辑。

上述代码运行后,因为while(ture),程序会一直保持运行,直到我们按下Ctrl+C发生中断后,signal函数在捕获信号后,信号处理函数发挥作用,打印了Received signal: 2;

因为SIGINT 的信号编号是 2,所以signum的值是2;

如果想要在信号处理完成后恢复对该信号的默认处理,可以使用 signal(SIGINT, SIG_DFL)。

忽略和恢复信号:

  • 使用 signal(SIGINT, SIG_IGN) 可以忽略 SIGINT 信号。
  • 使用 signal(SIGINT, SIG_DFL) 可以恢复对 SIGINT 的默认处理。

关于恢复信号,当你按下 Ctrl+C 触发 SIGINT 信号时,如果没有 signal(SIGINT, SIG_DFL); 这一行,那么程序将继续执行 signalHandler 函数,但不会将 SIGINT 的处理方式恢复为默认。这意味着如果再次按下 Ctrl+C,signalHandler 函数将再次被调用,而不会终止程序。

实际上,如果不将 SIGINT 恢复为默认处理方式,程序可能会对多次 Ctrl+C 信号作出相应,而不是默认的行为(终止程序)。


raise()函数

raise 函数是用于在程序中手动触发一个信号的函数。

声明:

int raise(int sig);
  • sig:要触发的信号的编号。

raise 函数返回一个整数值,表示函数调用的结果。如果成功发送信号,返回 0;如果失败,返回非零值。

示例:

#include <csignal>
#include <iostream>// 信号处理函数
void signalHandler(int signum) {std::cout << "Received signal: " << signum << std::endl;
}int main() {// 注册信号处理函数signal(SIGINT, signalHandler);std::cout << "Press Ctrl+C to trigger the signal." << std::endl;// 模拟其他程序逻辑int count = 0;while (true) {// 模拟其他程序逻辑std::cout << "Working... (" << count << ")" << std::endl;// 在某个条件下手动触发 SIGINT 信号if (count > 500) {std::cout << "Manually triggering SIGINT..." << std::endl;raise(SIGINT);}// 模拟其他程序逻辑// ...// 增加计数count++;}return 0;
}

上述代码的运行结果:

在这里插入图片描述

可以看到在进行俩次的模拟生成信号后,程序就停止了,这是因为没有重新注册 SIGINT 的处理函数,程序将使用默认的信号处理方式,即终止程序。

如果想要第一次信号处理后继续运行,可以重新注册 SIGINT 的处理函数。
在signalHandler函数中添加:

// 重新注册信号处理函数signal(SIGINT, signalHandler);

http://www.ppmy.cn/news/1211592.html

相关文章

Shell编程入门--概念、特性、bash配置文件

目录 一、Shell概念1.定义2.分类和使用场景2.1.分类和切换2.2.使用场景 3.特性3.1.文件描述符与输出重定向3.2.历史命令---history3.3.别名 --alias3.4.命令排序执行3.5.部分快捷键3.6.通配符置换 4.脚本规范5.脚本运行方式5.1.bash脚本执行5.2.bash脚本测试 二、bash配置文件1…

36 Gateway网关 快速入门

3.Gateway服务网关 Spring Cloud Gateway 是 Spring Cloud 的一个全新项目&#xff0c;该项目是基于 Spring 5.0&#xff0c;Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关&#xff0c;它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式…

HoudiniVex笔记_P0_Houdini中文文档与翻译

1、19.0版本中文说明文档 链接&#xff1a;https://pan.baidu.com/s/1oJcX5pdnBZ_YWWwOSnFB5g?pwdz3tw 提取码&#xff1a;z3tw 2、翻译插件 有上网条件的同学可以试试这个翻译插件&#xff1a;双语网页翻译 - 电子书翻译 - PDF翻译 - 字幕文件翻译浏览器扩展 | 沉浸式翻译…

二十三种设计模式全面解析-解密迭代器模式:探索遍历之道

在软件开发中&#xff0c;遍历数据集合是一个非常常见的需求。但是&#xff0c;如何以一种优雅、灵活的方式遍历集合&#xff0c;并且能够适应各种不同的数据结构和迭代方式&#xff0c;一直是开发者们面临的挑战。今天&#xff0c;我将带你深入探索迭代器模式&#xff08;Iter…

C++ RBTree 理论

目录 这个性质可以总结为 红黑树的最短最长路径 红黑树的路径范围 code 结构 搞颜色 类 插入 插入逻辑 新插入节点 思考&#xff1a;2. 检测新节点插入后&#xff0c;红黑树的性质是否造到破坏&#xff1f; 解决方法 变色 旋转变色 第三种情况&#xff0c;如果根…

【开源】基于Vue.js的智能停车场管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容A. 车主端功能B. 停车工作人员功能C. 系统管理员功能1. 停车位模块2. 车辆模块3. 停车记录模块4. IC卡模块5. IC卡挂失模块 三、界面展示3.1 登录注册3.2 车辆模块3.3 停车位模块3.4 停车数据模块3.5 IC卡档案模块3.6 IC卡挂…

【探索Linux】—— 强大的命令行工具 P.14(进程间通信 | 匿名管道 | |进程池 | pipe() 函数 | mkfifo() 函数)

阅读导航 引言一、进程间通信概念二、进程间通信目的三、进程间通信分类四、管道1. 什么是管道2. 匿名管道&#xff08;1&#xff09;创建和关闭⭕pipe() 函数⭕创建匿名管道⭕关闭匿名管道 &#xff08;2&#xff09;通信方式&#xff08;3&#xff09;用法示例&#xff08;4&…

github上下载的深度学习代码,为快速在jupyter notebook运行替换ArgumentParser定义的参数

&#x1f525; &#x1f525; &#x1f525; github上的参数代码一般是如下格式&#xff1a; if __name__ __main__:parser ArgumentParser()parser.add_argument("--test_dir", typestr, default"test", helpDirectory containing the test data (must…