C++中的操作系统级信号处理——signal与sigaction

news2025/1/14 1:45:37

在多进程的编程中,信号是一种非常重要的多进程通讯手段。而进程间的信号很大情况是和操作系统是相关的,或者说很多信号是从操作系统中过来的。

我们这一篇就来说一下操作系统的信号。

操作系统中的信号其实在操作系统中可以称作是中断,可以理解为一个循环执行的程序中突然收到一个通知,或者信号,操作系统分配一个中断处理程序来处理这个中断信号。基本上所有的操作系统都是基于这个逻辑。

而中断又可以简单的分成两个大类,硬件中断和软中断。硬件中断可以理解为在电路上就会有一个电信号来给操作系统一个中断,软中断就可以理解为一个逻辑上的通知。

一般来说,硬件中断的优先级要比软中断的优先级要高,关于中断的内容可以讲好多,具体可以参考操作系统原理,有机会再说这一块,后续看看是不是可以整理操作系统级的C++编程再说。今天先简单说一下软中断的处理。

std库的信号处理

上面提到了,信号实际上是和操作系统强绑定的,可以说是操作系统级别的内容了。而C++的std标准库在操作系统之上定义了一层,讲操作系统相关的内容封装了起来,统一对外提供了一个接口,将操作系统的一些标准信号给出了统一的定义。

这个定义在<singal.h>头文件中。

总共定义了32个软中断。

#define SIGHUP  1       /* hangup */
#define SIGINT  2       /* interrupt */
#define SIGQUIT 3       /* quit */
#define SIGILL  4       /* illegal instruction (not reset when caught) */
#define SIGTRAP 5       /* trace trap (not reset when caught) */
#define SIGABRT 6       /* abort() */
#if  (defined(_POSIX_C_SOURCE) && !defined(_DARWIN_C_SOURCE))
#define SIGPOLL 7       /* pollable event ([XSR] generated, not supported) */
#else   /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGIOT  SIGABRT /* compatibility */
#define SIGEMT  7       /* EMT instruction */
#endif  /* (!_POSIX_C_SOURCE || _DARWIN_C_SOURCE) */
#define SIGFPE  8       /* floating point exception */
#define SIGKILL 9       /* kill (cannot be caught or ignored) */
#define SIGBUS  10      /* bus error */
#define SIGSEGV 11      /* segmentation violation */
#define SIGSYS  12      /* bad argument to system call */
#define SIGPIPE 13      /* write on a pipe with no one to read it */
#define SIGALRM 14      /* alarm clock */
#define SIGTERM 15      /* software termination signal from kill */
#define SIGURG  16      /* urgent condition on IO channel */
#define SIGSTOP 17      /* sendable stop signal not from tty */
#define SIGTSTP 18      /* stop signal from tty */
#define SIGCONT 19      /* continue a stopped process */
#define SIGCHLD 20      /* to parent on child stop or exit */
#define SIGTTIN 21      /* to readers pgrp upon background tty read */
#define SIGTTOU 22      /* like TTIN for output if (tp->t_local&LTOSTOP) */
#if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGIO   23      /* input/output possible signal */
#endif
#define SIGXCPU 24      /* exceeded CPU time limit */
#define SIGXFSZ 25      /* exceeded file size limit */
#define SIGVTALRM 26    /* virtual time alarm */
#define SIGPROF 27      /* profiling time alarm */
#if  (!defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE))
#define SIGWINCH 28     /* window size changes */
#define SIGINFO 29      /* information request */
#endif
#define SIGUSR1 30      /* user defined signal 1 */
#define SIGUSR2 31      /* user defined signal 2 */

这些相当于是操作系统给出的一些信号,可以由进程自定义的来处理。

在代码里就可以通过signal函数来注册某种信号的处理函数了。

#include <signal.h>
#include <unistd.h>

void signalHandler(int signum)
{
    std::cout<<"Interrupt signal (" << signum<< ") received"<<std::endl;

    exit(signum);
}

int main() {
    signal(SIGINT, signalHandler);

    while(true)
    {
        std::cout<<"going to sleep ... " << std::endl;
        sleep(1);
    }

    return 0;
}

上面的代码就是实现了,当用户输入一个Ctrl+C的中断指令时,可以完成程序员一些自定义的处理。

使用raise函数

signal库中提供了一个raise函数用于信号处理函数的模拟测试,也就是说可以自己给自己发一个信号,看看程序是否可以运行正常。

因为是只能自己给自己发信号,所以除了测试以外,就只能是处理自定义的信号了。

上面的信号中,有两个用户自定义信号。也就是说可以通过raise来发送这两个信号,来实现进程中的消息发送(操作系统已经定义好的信号不建议改变其固有逻辑)。

个人理解是起到了一个GOTO语句的作用。

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>

// 定义一个信号处理函数
void signal_handler(int signal) {
    printf("Received signal %d\n", signal);
    exit(0); // 收到信号后退出程序
}

int main() {
    // 设置SIGUSR1信号的处理函数为signal_handler
    if (signal(SIGUSR1, signal_handler) == SIG_ERR) {
        perror("Error setting signal handler");
        return 1;
    }

    printf("Raising SIGINT signal\n");
    // 使用raise函数发送SIGINT信号给当前进程
    raise(SIGUSR1);

    // 如果没有收到信号,程序会继续执行到这里
    printf("This line will not be executed\n");

    return 0;
}

另外,这个signal和raise函数是无法正常处理32及以上的信号的。如果使用32以上的信号,信号处理函数是无法被调用的。

更牛逼的sigaction

上面的代码中,定义的这个信号处理函数只能收到一个信号,无法有更多的信息。

在std库的signal包中,还提供了一个更厉害一点的处理方法:sigaction。

在signal.h中看一下这个sigaction的结构体:

struct  sigaction {
	union __sigaction_u __sigaction_u;  /* signal handler */
	sigset_t sa_mask;               /* signal mask to apply */
	int     sa_flags;               /* see signal options below */
};
  • 第一个成员是一个union类型,这个union的类型定义为:

    /* union for signal handlers */
    union __sigaction_u {
        void    (*__sa_handler)(int);
        void    (*__sa_sigaction)(int, struct __siginfo *,
            void *);
    };
    

    也就是说,如果使用第一个__sa_handler的时候,就是和上面提到的普通的信号处理一样使用。

    这里还配套了两个宏定义:

    /* if SA_SIGINFO is set, sa_sigaction is to be used instead of sa_handler. */
    #define sa_handler      __sigaction_u.__sa_handler
    #define sa_sigaction    __sigaction_u.__sa_sigaction
    

    下面的代码一起来写这一块。

  • 如果使用另外一个union的成员的话,操作系统就会将siginfo的内容提供到信号响应函数。

  • 先看一下这个siginfo是什么东西,signal中也给出了定义:

    typedef struct __siginfo {
        int     si_signo;               /* signal number */
        int     si_errno;               /* errno association */
        int     si_code;                /* signal code */
        pid_t   si_pid;                 /* sending process */
        uid_t   si_uid;                 /* sender's ruid */
        int     si_status;              /* exit value */
        void    *si_addr;               /* faulting instruction */
        union sigval si_value;          /* signal value */
        long    si_band;                /* band event for SIGPOLL */
        unsigned long   __pad[7];       /* Reserved for Future Use */
    } siginfo_t;
    
  • 上述内容需要使用的话,需要配合第三个参数sa_flags用,将sa_flags赋值为SA_SIGINFO。

不多说,直接上代码吧:

#include <iostream>
#include <csignal>
#include <unistd.h>

// 定义一个信号处理函数
void signalHandler(int signum) {
    std::cout << "Interrupt signal (" << signum << ") received.\n";

}

void signalHandlerMore(int signum, siginfo_t *info, void *context) {
    std::cout << "Received signal " << signum << std::endl;
    if (info) {
        std::cout << "Signal originates from process: " << info->si_pid << std::endl;
    }
    
}

int main() {
    struct sigaction action;
    // 设置信号处理函数
    action.sa_handler = signalHandler;
    // 清空信号集
    sigemptyset(&action.sa_mask);
    // 不使用特殊标志
    action.sa_flags = 0;
    
    struct sigaction action_more;
    // 设置信号处理函数
    action_more.sa_sigaction = signalHandler;
    // 清空信号集
    sigemptyset(&action_more.sa_mask);
    // 不使用特殊标志
    action.sa_flags = 0;

    // 注册信号处理函数
    if (sigaction(SIGUSR1, &action, NULL) < 0) {
        perror("sigaction");
        return 1;
    }
    
    // 注册信号处理函数
    if (sigaction(SIGUSR2, &action_more, NULL) < 0) {
        perror("sigaction");
        return 1;
    }
    
    raise(SIGUSR1);
    raise(SIGUSR2);

    return 0;
}

context可以传一些参数进行,比如this指针之类,就可以在不同的函数中实现传参了,参考:

C++类中多线程的编码方式

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

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

相关文章

如何进行Appium实现移动端UI自动化测试呢?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 Appium是一个开源跨平台移动应用自动化测试框架。 既然只是想学习下Appium如何入门&#xff0c;那么我们就直奔主题。文章结构如下&#xff1a; 为什么要使用…

骨架行为识别-论文复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

Unity 设计模式-观察者模式(Observer Pattern)详解

观察者模式 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时&#xff0c;它的所有依赖者&#xff08;观察者&#xff09;都会收到通知并自动更新。这种模式用于事件处理系…

【webApp之h5端实战】首页评分组件的原生实现

关于评分组件,我们经常在现代前端框架中用到,UI美观效果丰富,使用体验是非常不错的。现在自己动手使用原生js封装下评分组件,可以用在自己的项目中。 组件实现原理 点击的❤左侧包括自己都是高亮的样式,右侧都是灰色的样式,这样就能把组件的状态区分开了。右边再加上辅…

unity与android拓展

一.AndroidStudio打包 1.通过Unity导出Android Studio能够打开的工程 步骤 1.设置导出基本信息&#xff1a;公司名、游戏名、图标、包名等关键信息 2.在File——>Build Settings中&#xff0c;勾选 Export Project 选项 3.点击Export 导出按钮 2.在Android Studio中打开Un…

几种常见的javascript设计模式

摘要 最近开发HarmonyOSApp&#xff0c;使用的Arkts语言&#xff0c;此语言类似后端C#语言风格&#xff0c;同时兼顾写后端接口的我突然想总结一下近8年前端开发中无意中使用的设计模式&#xff0c;我们用到了却不知属于哪些&#xff0c;下面和大家分享一下。 什么是前端设计…

2.4特征预处理(机器学习)

2.4特征预处理 2.4.1 什么是特征预处理 通过 一些转换函数将特征数据转换成更加适合算法模型的特征数据过程。 1 包含内容 数值型数据的无量纲化&#xff1a; 归一化 标准化 2 特征预处理API sklearn.preprocessing 为什么要进行归一化/标准化&#xff1f; 特征的单…

学习笔记052——Spring Boot 自定义 Starter

文章目录 Spring Boot 自定义 Starter1、自定义一个要装载的项目2、创建属性读取类 ServiceProperties3、创建 Service4、创建自动配置类 AutoConfigration5、创建 spring 工程文件6、将项目打成 jar 包7、jar 打包到本地仓库8、配置application.yml Spring Boot 自定义 Starte…

重学设计模式-建造者模式

本文介绍一下建造者模式&#xff0c;相对于工厂模式来说&#xff0c;建造者模式更为简单&#xff0c;且用的更少 定义 建造者模式是一种创建型设计模式&#xff0c;它使用多个简单的对象一步一步构建成一个复杂的对象。这种模式的主要目的是将一个复杂对象的构建过程与其表示…

复现SMPLify-X: Ubuntu22.04, Cuda-11.3, GPU=3090Ti

Env: 3090Ti CUDA 最低支持版本需要>cuda-11.1 Ubuntu 22.04 Installation: Installing CUDA11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run …

Milvus×OPPO:如何构建更懂你的大模型助手

01. 背景 AI业务快速增长下传统关系型数据库无法满足需求。 2024年恰逢OPPO品牌20周年&#xff0c;OPPO也宣布正式进入AI手机的时代。超千万用户开始通过例如通话摘要、新小布助手、小布照相馆等搭载在OPPO手机上的应用体验AI能力。 与传统的应用不同的是&#xff0c;在AI驱动的…

JAVA |日常开发中读写XML详解

JAVA &#xff5c;日常开发中读写XML详解 前言一、XML 简介二、在 Java 中读取 XML2.1 使用 DOM&#xff08;Document Object Model&#xff09;方式读取 XML2.2 使用 SAX&#xff08;Simple API for XML&#xff09;方式读取 XML 三、在 Java 中写入 XML3.1 使用 DOM 方式写入…

GEOBench-VLM:专为地理空间任务设计的视觉-语言模型基准测试数据集

2024-11-29 ,由穆罕默德本扎耶德人工智能大学等机构创建了GEOBench-VLM数据集&#xff0c;目的评估视觉-语言模型&#xff08;VLM&#xff09;在地理空间任务中的表现。该数据集的推出填补了现有基准测试在地理空间应用中的空白&#xff0c;提供了超过10,000个经过人工验证的指…

南昌榉之乡托养机构解读:自闭症与看电视并无必然联系

在探讨自闭症的成因时&#xff0c;有人会问&#xff1a;自闭症是多看电视引起的吗&#xff1f;今天&#xff0c;就让我们来看看南昌榉之乡托养机构对此有何见解。 榉之乡大龄自闭症托养机构在江苏、广东、江西等地都有分校&#xff0c;一直致力于为大龄自闭症患者提供专业的支持…

LabVIEW MathScript工具包对运行速度的影响及优化方法

LabVIEW 的 MathScript 工具包 在运行时可能会影响程序的运行速度&#xff0c;主要是由于以下几个原因&#xff1a; 1. 解释型语言执行方式 MathScript 使用的是类似于 MATLAB 的解释型语言&#xff0c;这意味着它不像编译型语言&#xff08;如 C、C 或 LabVIEW 本身的 VI&…

基于eFramework车控车设中间件介绍

车设的发展&#xff0c;起源于汽车工业萌芽之初&#xff0c;经历了机械式操作的原始粗犷&#xff0c;到电子式调控技术的巨大飞跃&#xff0c;到如今智能化座舱普及&#xff0c;远程车控已然成为汽车标配&#xff0c;车设功能选项也呈现出爆发式增长&#xff0c;渐趋多元繁杂。…

使用 AWR 进行 Exadata 性能诊断 - 2018版

本文和之前的使用 AWR 进行 Exadata 性能诊断是非常类似的&#xff0c;理论部分几乎一样&#xff0c;但案例部分是不同的&#xff0c;其价值也在于此。前文是基于Exadata X10&#xff0c;本文是基于Exadata X5。当然&#xff0c;型号并不重要&#xff0c;重要的是分析过程。 本…

【AI系统】计算与调度

计算与调度 上一篇文章我们了解了什么是算子&#xff0c;神经网络模型中由大量的算子来组成&#xff0c;但是算子之间是如何执行的&#xff1f;组成算子的算法逻辑跟具体的硬件指令代码之间的调度是如何配合&#xff1f;这些内容将会在本文进行深入介绍。 计算与调度 计算与…

JavaSE学习心得(APL与算法篇)

常用APL和常见算法 前言 常用APL Math System Runtime Object ​编辑浅克隆 深克隆 Objects Biginteger 构造方法 成员方法 底层存储方式 Bigdecimal 构造方法 Bigdecimal的使用 底层存储方式 ​编辑正则表达式 两个判断练习 两个爬取练习 贪婪爬取和非贪…

C++ ——— 引用的概念以及特性

目录 引用的概念 引用在实际代码中的作用 引用的特性 1. 引用在定义时必须初始化 2. 一个变量可以有多个引用 3. 可以给别名再次取别名&#xff0c;或者多次取别名 4. 引用一旦引用了实体&#xff0c;就不能再引用其他实体了 引用的概念 引用不是新定义一个变量&#x…