信号的捕捉处理

news2024/9/21 15:37:54

文章目录

  • 4 信号的捕捉处理
    • 4.1 内核如何实现信号的捕捉
    • 4.2 sigaction
      • 4.2.1 使用这个函数对2号信号进行捕捉
      • 4.2.2 pending位图什么时候由1变0
      • 4.2.3 不允许信号重复发送
  • 5. 其他
    • 5.1 可重入函数
    • 5.2 volatile
    • 5.3 SIGCHLD信号
    • 5.4 信号生命周期

4 信号的捕捉处理

4.1 内核如何实现信号的捕捉

当我们的进程从内核态返回到用户态的时候进行信号的检测和处理
内核态:允许进程访问OS的代码和数据
用户态:只能访问进程自己的代码和数据

image-20240901173143702

image-20240901155127915

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。举例如下: 用户程序写了SIGQUIT信号的处理函数sighandler。 当前正在执行main函数,这时**发生中断或异常(也可能是系统调用)**切换到内核态。 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler函 数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是 两个独立的控制流程。 sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。

4.2 sigaction

#include <signal.h>
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
struct sigaction {
    void     (*sa_handler)(int);	// 自定义信号处理函数
    sigset_t   sa_mask;		// 设置block表
	// ...
};

  • sigaction函数可以读取和修改与指定信号相关联的处理动作。调用成功则返回0,出错则返回- 1。signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非 空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体
  • 将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号,赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

4.2.1 使用这个函数对2号信号进行捕捉

void MyHandler(int sigNo) 
{
    cout << "Catch a signal, signal number:" << sigNo << endl;
}

int main()
{
    // 变量定义和初始化
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(act));
    // 指定一个自定义函数
    act.sa_handler = MyHandler;
    // 调用sigaction函数
    sigaction(2, &act, &oact);
    while(true) {
        cout << "Process running!" << endl;
        sleep(1);
    }
    return 0;
}

image-20240905185341284

4.2.2 pending位图什么时候由1变0

添加如下的代码

void PrintPending()
{
    sigset_t pending;
    sigpending(&pending);
    cout << "Pending: ";
    for(size_t i = 31;i>=1;--i) {
        if(sigismember(&pending, i))    cout << "1";
        else cout << "0";
    }
    cout << endl;
}

void MyHandler(int sigNo) 
{
    PrintPending();
    cout << "Catch a signal, signal number:" << sigNo << endl;
}

image-20240905204058607

可见,在执行信号捕捉方法之前,先清0,再调用

4.2.3 不允许信号重复发送

当某个信号的处理函数被调用时, 内核自动将当前信号加入进程的信号屏蔽字 ,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止

验证1,一个信号被捕捉处理过程中,该信号会被屏蔽。

// 修改MyHandler()方法
void MyHandler(int sigNo) 
{
    cout << "Catch a signal, signal number:" << sigNo << endl;
    while(true) {
        PrintPending();
        sleep(1);
    }
}

image-20240905210215226

如果**在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,**则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

验证2,希望在捕捉处理信号过程中,同时屏蔽一些其他的信号

void MyHandler(int sigNo) 
{
    cout << "Catch a signal, signal number:" << sigNo << endl;
    while(true) {
        PrintPending();
        sleep(1);
    }
}

int main()
{
    // 变量定义和初始化
    struct sigaction act, oact;
    memset(&act, 0, sizeof(act));
    memset(&oact, 0, sizeof(act));
    // 指定一个自定义函数
    act.sa_handler = MyHandler;
    // 在屏蔽2号信号的同时屏蔽1,3,4信号
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, 1);
    sigaddset(&act.sa_mask, 3);
    sigaddset(&act.sa_mask, 4);
    // 调用sigaction函数
    sigaction(2, &act, &oact);
    while(true) {
        cout << "Process running! pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

5. 其他

5.1 可重入函数

image-20240905215628606

main函数调用insert函数向一个链表head中插入节点node1,插入操作分为两步,刚做完第一步的时候,因为硬件中断使进程切换到内核,再次回用户态之前检查到有信号待处理,于是切换到sighandler函数,sighandler也调用insert函数向同一个链表head中插入节点node2,插入操作的两步都做完之后从sighandler返回内核态,再次回到用户态就从main函数调用的insert函数中继续往下执行,先前做第一步之后被打断,现在继续做完第二步。结果是,main函数和sighandler先后向链表中插入两个节点,而最后只有一个节点真正插入链表中了

像上面这样,insert函数被不同的执行流调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。

如果一个函数符合以下条件之一则是不可重入的:

  • 调用了malloc或free,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

5.2 volatile

看下面的代码

int flag = 0;

void myHandler(int sigNo)
{
    cout << "Catch a signal, signal number:" << sigNo << endl;
    flag = 1;
}

int main()
{
    signal(2, myHandler);
    while(!flag);
    cout << "Process normal quit" << endl;
    return 0;
}

image-20240906175108826

问题:在优化条件下,flag变量可能直接优化到cpu内的寄存器中。而myHandler执行流修改的是内存的值

修改makefile

outSignal : mySignal.cc
	g++ -o $@ $^ -O1

.PHONY : clean
clean:
	rm -rf outSignal

image-20240906181149097

此时,进程就退不出来了。volatile的作用就是为了防止CPU进行过度的优化

flag变量前加上volatile后,即便g++ 设置了-O3的最高级别的优化,进程也能正常退出

5.3 SIGCHLD信号

验证:子进程在终止时会给父进程发SIGCHLD信号

void MyHandler(int sigNo)
{
    cout << "Process " << getpid() << " catch a signal, signal number:" << sigNo << endl;
}

int main()
{
    signal(SIGCHLD, MyHandler);
    pid_t n = fork();
    if(n == 0) {
        // child
        int cnt = 3;
        while(cnt--) {
            cout << "I am child process, pid: " << getpid() << " ppid: " << getppid() << endl;
            sleep(1);
        }
        cout << "Child quit!" << endl;
        exit(0);
    }
    // father
    while(true) {
        cout << "I am father process, pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

image-20240906201319608

代码:基于信号捕捉等待子进程

// 仅修改MyHandler()
void MyHandler(int sigNo)
{
    sleep(3);   // 方便观察,让子进程僵尸一会
    pid_t rid = waitpid(-1, nullptr, 0);
    cout << "Process " << getpid() << " catch a signal, signal number:" 
    << sigNo << " child quit pid: "<< rid << endl;
}

image-20240906203029783

问题:如果有多个子进程同时退出呢?这多个子进程都会向父进程发送SIGCHLD信号,当父进程处理该信号时,会阻塞屏蔽掉该信号,这会导致其它子进程发送的信号不被处理。

此时就需要修改MyHandler()的写法

void MyHandler(int sigNo)
{
    sleep(3);   // 方便观察,让子进程僵尸一会
    pid_t rid = 0;
    while(rid = waitpid(-1, nullptr, WNOHANG) > 0) {	// 设置WNOHANG的目的是当没有进程处于僵尸状态时,rid会返回0,此时就不会进循环
        cout << "Process " << getpid() << " catch a signal, signal number:" 
        << sigNo << " child quit pid: "<< rid << endl;
    }
}

int main()
{
    signal(SIGCHLD, MyHandler);
    for(int i = 0; i < 10; ++i) {
        pid_t n = fork();
        if(n == 0) {
            // child
            int cnt = 3;
            while(cnt--) {
                cout << "I am child process, pid: " << getpid() << " ppid: " << getppid() << endl;
                sleep(1);
            }
            cout << "Child quit!" << endl;
            exit(0);
        }
    }
    // father
    while(true) {
        cout << "I am father process, pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

image-20240906204530392

子进程在终止时会给父进程发SIGCHLD信号**,该信号的默认处理动作是忽略,**父进程可以自 定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。

事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不 会产生僵尸进程,也不会通知父进程。但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用

int main()
{
    signal(SIGCHLD, SIG_IGN);
    for(int i = 0; i < 10; ++i) {
        pid_t n = fork();
        if(n == 0) {
            // child
            int cnt = 3;
            while(cnt--) {
                cout << "I am child process, pid: " << getpid() << " ppid: " << getppid() << endl;
                sleep(3);
            }
            cout << "Child quit!" << endl;
            exit(0);
        }
    }
    // father
    while(true) {
        cout << "I am father process, pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

image-20240906211010604

5.4 信号生命周期

image-20240906211758859

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

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

相关文章

鸿蒙(API 12 Beta6版)图形加速【Vulkan平台】超帧功能开发

业务流程 基于Vulkan图形API平台&#xff0c;集成超帧内插模式的主要业务流程如下&#xff1a; 用户进入超帧适用的游戏场景。游戏应用调用[HMS_FG_CreateContext_VK]接口创建超帧上下文实例。游戏应用调用接口配置超帧实例属性。包括调用[HMS_FG_SetAlgorithmMode_VK]&#x…

第T11周:使用TensorFlow进行优化器对比实验

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 文章目录 一、前期工作1.设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09; 二、导入数据1、导入数据2、检查数据3、配置数据集4、数据可视化 三、…

淘宝和微信支付“好”上了,打翻了支付宝的“醋坛子”?

文&#xff1a;互联网江湖 作者&#xff1a;刘致呈 最近&#xff0c;淘宝将全面接入微信支付的消息&#xff0c;在整个互联网圈里炸开了锅。 虽说阿里系平台与腾讯之间“拆墙”的消息&#xff0c;早就不算是啥新鲜事了。而且进一步互联互通&#xff0c;无论是对广大用户&…

43. 1 ~ n 整数中 1 出现的次数【难】

comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9843.%201%EF%BD%9En%E6%95%B4%E6%95%B0%E4%B8%AD1%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0/README.md 面试题 43. 1 &#xff5e; n 整数中 1 …

(postman)接口测试进阶实战

1.内置和自定义的动态参数 内置的动态参数有哪些&#xff1f; ---{{$}}--是内置动态参数的标志 //自定义的动态参数 此处date.now()的作用就相当于上面的timestamp 2.业务闭环及文件接口测试 返回的url地址可以在网页中查询得到。 3. 常规断言&#xff0c;动态参数断言&#xf…

Linux进程初识:OS基础、fork函数创建进程、进程排队和进程状态讲解

目录 1、冯诺伊曼体系结构 问题一&#xff1a;为什么在体系结构中存在存储器&#xff08;内存&#xff09;&#xff1f; 存储单元总结&#xff1a; 问题二&#xff1a;为什么程序在运行的时候&#xff0c;必须把程序先加载到内存&#xff1f; 问题三&#xff1a;请解释&am…

爆改YOLOv8|利用yolov10的SCDown改进yolov8-下采样

1, 本文介绍 YOLOv10 的 SCDown 方法来优化 YOLOv8 的下采样过程。SCDown 通过点卷积调整通道维度&#xff0c;再通过深度卷积进行空间下采样&#xff0c;从而减少了计算成本和参数数量。这种方法不仅降低了延迟&#xff0c;还在保持下采样过程信息的同时提供了竞争性的性能。…

使用Python通过字节串或字节数组加载和保存PDF文档

处理PDF文件的可以直接读取和写入文件系统中的PDF文件&#xff0c;然而&#xff0c;通过字节串&#xff08;byte string&#xff09;或字节数组&#xff08;byte array&#xff09;来加载和保存PDF文档在某些情况下更高效。这种方法不仅可以提高数据处理的灵活性&#xff0c;允…

Mysql8客户端连接异常:Public Key Retrieval is not allowed

mysql 8.0 默认使用 caching_sha2_password 身份验证机制 &#xff08;即从原来mysql_native_password 更改为 caching_sha2_password。&#xff09; 从 5.7 升级 8.0 版本的不会改变现有用户的身份验证方法&#xff0c;但新用户会默认使用新的 caching_sha2_password 。 客户…

ISO26262和Aspice之间的关联

ASPICE 介绍&#xff1a; ASPICE&#xff08;Automotive Software Process Improvement and Capability dEtermination&#xff09;是汽车软件过程改进及能力评定的模型&#xff0c;它侧重于汽车软件的开发过程。ASPICE 定义了一系列的过程和活动&#xff0c;包括需求管理、软…

基于yolov8的抽烟检测系统python源码+onnx模型+评估指标曲线+精美GUI界面

【算法介绍】 基于YOLOv8的抽烟检测系统是一种利用先进深度学习技术实现的实时目标检测系统。该系统采用YOLOv8算法&#xff0c;该算法以其高速度和高精度在目标检测领域脱颖而出。该系统通过训练大量标注好的抽烟行为数据集&#xff0c;使模型能够自动识别和定位视频或图像中…

使用YOLOv10训练自定义数据集之二(数据集准备)

0x00 前言 经过上一篇环境部署的介绍【传送门】&#xff0c;我们已经得到了一个基本可用的YOLOv10的运行环境&#xff0c;还需要我们再准备一些数据&#xff0c;用于模型训练。 0x01 准备数据集 1. 图像标注工具 数据集是训练模型基础素材。 对于小白来说&#xff0c;一般…

如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的?

如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的&#xff1f; 目录 如何判断小程序是运行在“企业微信”中的还是运行在“微信”中的&#xff1f; 一、官方开发文档 1.1、“微信小程序”开发文档的说明 1.2、“企业微信小程序”开发文档的说明 1.3、在企业…

redis缓存预热、缓存穿透的详细教程

前言 作此篇主要在于关于redis的缓存预热、缓存雪崩、缓存击穿和缓存穿透在面试中经常遇到&#xff0c;工作中也是经常遇到。中级程序员基本上不可避免要克服的几个问题&#xff0c;希望一次性解释清楚 缓存预热 MySQL加入新增100条记录&#xff0c;一般默认以MySQL为准为底单…

5-2 检测内存容量

1 使用的是bios 中断&#xff0c; 每次进行检测都会返回一块 内容。并且标志上&#xff0c;这块内存是否可用。 接下来是代码&#xff1a; 首先是构建 一个文件夹&#xff0c; 两个文件。 types.h 的内容。 #ifndef TYPES_H #define TYPES_H// 基本整数类型&#xff0c;下面的…

C++系统教程002-数据类型(01)

一、数据类型 学习一门编程语言&#xff0c;首先要掌握它的数据类型。不同的数据类型占用的内存空间不同&#xff0c;定义数据类型合理在一定程度上可以优化程序的运行。本次主要介绍C中常见的数据类型及数据的输入与输出格式。本章知识架构及重难点如下&#xff1a; &#xf…

linux监听网速

方法一 tcpdump -i ens33 -w - | pv -bert > /dev/null方法二

问题 J: 数据结构基础33-查找二叉树

题目描述 已知一棵二叉树用邻接表结构存储&#xff0c;中序查找二叉树中值为x的结点&#xff0c;并指出是第几个结点。例&#xff1a;如图二叉树的数据文件的数据格式如下 输入 第一行n为二叉树的结点个树&#xff0c;n<100&#xff1b;第二行x表示要查找的结点的值&…

windows环境安装OceanBase数据库并创建表、插入数据

windows环境安装OceanBase数据库并创建表、插入数据 前言&#xff1a;OceanBase数据库目前不支持直接在Windows环境下安装&#xff0c;安装比较麻烦&#xff0c;记录一下安装过程 1.安装方案 根据官方文档&#xff1a;https://www.oceanbase.com/docs/common-oceanbase-databa…

实验六 异常处理

实验目的及要求 目的&#xff1a;了解异常的概念&#xff0c;掌握异常处理的方法&#xff0c;掌握throws与throw关键字的区别与联系&#xff0c;掌握自定义异常的方法及用途。 要求&#xff1a; &#xff08;1&#xff09;编写程序了解程序中可能出现的运行时异常与非运行时…