【Linux】:信号的保存和信号处理

news2024/11/24 3:35:36

朋友们、伙计们,我们又见面了,本期来给大家带来信号的保存和信号处理相关代码和知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

​ 

目录

1. 信号的保存

1.1 信号相关概念

1.2 信号的保存 

1.3 处理位图的接口 

2. 信号的处理 

2.1 状态的切换

2.2 信号的处理

2.3 sigaction函数 

3. 信号的其他补充 

3.1 可重入函数

3.2 SIGCHLD信号 


1. 信号的保存

1.1 信号相关概念

  • 实际执行信号的处理动作称为信号递达(Delivery)。

信号递达的方式有三种:

  • ① 信号的默认处理
  • ② 信号的忽略
  • ③ 信号的自定义捕捉

当我们自定义捕捉信号的时候使用的signal接口就是对指定信号进行捕捉,然后去执行我们自定义的方法,下面再来介绍一下两种用法:

  • ① signal(signo, SIG_DFL):对指定信号恢复默认操作;
  • ② signal(signo, SIG_IGN):对指定信号进行忽略(忽略也算做对信号进行处理)。
  • 信号从产生到递达之间的状态,称为信号未决(Pending)。
  • 进程可以选择阻塞 (Block )某个信号。
  • 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
  • 注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

1.2 信号的保存 

当信号产生时,我们不一定要立即对信号进行递达,而是在合适的时候进行递达,那么在信号未决时期,我们要有能力将信号保存,所以在进程PCB中会存在三张位图表,用于保存信号:

信号屏蔽字(block表):比特位的位置表示信号的编号、比特位的内容表示是否对特定信号进行屏蔽(阻塞)。

未决位图表(pending表):比特位的位置表示信号编号、比特位的内容表示特定的信号时候被递达。

handler表(函数指针数组):比特位的位置表示信号编号、比特位的内容是一个函数指针,指向该信号的处理方法。

注意:常规信号在递达之前产生多次只记一次!

1.3 处理位图的接口 

sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset (sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
  • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
  • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系统支持的所有信号。
  • 注意,在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
  • 函数sigaddset用于向指定的信号集添加某种信号。
  • 函数sigdelset用于删除指定信号集中的某种信号。
  • 函数sigismember用于判断指定信号在指定信号集是否存在。

对block表进行操作:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:

① set:将要设置的新的信号屏蔽字

② oldset:获取旧的信号屏蔽字

③ how:修改block表的选项

SIG_BLOCKset包含了我们希望添加到当前信号屏蔽字的信号,相当于mask = mask l set
SIG_UNBLOCK

set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask = mask & ~set

SIG_SETMASK

设置当前信号屏蔽字为set所指向的值,相当于 

mask = set

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
 
对pengding表操作:
#include <signal.h>
int sigpending(sigset_t *set);

参数:

① set:获取当前进程的信号未决表

返回值:

成功返回0,出错返回-1

接下来通过这些接口我们可以实现一个动态的打印pending表的一个代码:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
using namespace std;

// 打印pending表
void PrintPending(const sigset_t &pending)
{
    for (int signo = 31; signo > 0; signo--)
    {
        if (sigismember(&pending, signo))
        {
            std::cout << "1";
        }
        else
        {
            std::cout << "0";
        }
    }
    std::cout << "\n";
}

int main()
{
    // 1. 屏蔽2号信号
    sigset_t set, oset;
    // 1.1 初始化信号集
    sigemptyset(&set);
    sigemptyset(&oset);
    // 1.2 添加信号
    sigaddset(&set, 2);
    // 1.3 修改信号集
    sigprocmask(SIG_BLOCK, &set, &oset);

    // 2. 让进程不断获取当前进程的pending
    int cnt = 0;
    sigset_t pending;
    while (true)
    {
        // 2.1 获取pending表
        sigpending(&pending);
        // 2.2 打印
        PrintPending(pending);

        sleep(1);

        cnt++;

        if (cnt == 10)
        {
            std::cout << "解除对2号信号的屏蔽, 2号信号准备递达" << std::endl;
            // 2.3 恢复原pending表
            sigprocmask(SIG_SETMASK, &oset, nullptr);
        }
    }
    return 0;
}

2. 信号的处理 

2.1 状态的切换

进程会在合适的时候处理信号,那么这个合适的时候是指什么时候呢?

进程从内核态返回到用户态时,进行信号的检测和处理。

  • 用户态:一种受控的状态,能够访问的资源是有限的。
  • 内核态:操作系统的工作状态,能访问大部分的系统资源,并且可以让用户以操作系统的身份访问内核空间。
  • ① 用户是无法直接访问OS底层资源,只能通过系统调用间接访问,所以用户调用系统调用,必然包含了身份的变化;
  • ② 进程要被调度首先得加载到内存然后通过页表映射到物理内存,那么操作系统也是需要被映射到物理内存的;
  • ③ 用户空间由用户级页表映射到物理内存;
  • ④ 内核空间由内核级页表映射到物理内存;
  • 所以在调用系统调用时访问OS直接在进程地址空间内进行跳转,就如同函数调用一样,调用系统调用接口也是在进程地址空间内进行的。

① 操作系统的代码、系统调用、数据结构、数据在整个系统中只有一份,所以内核级页表只需要有一张即可;

② 如果有多个进程,只需将内核空间通过内核级页表映射到物理内存,尽管有多个进程,使用的也是同一份系统调用接口;

③ 无论进程如何调度,CPU都可以直接找到操作系统!

④ 我们进程所有代码的执行,都可以在自己的进程地址空间内通过跳转的方式,进行调用和返回。

那么如何区分内核态和用户态呢?

CPU内存在的寄存器CS寄存器,CS寄存器用来保存代码段的,其中有两个比特位01表示内核态(1)、11表示用户态(3);切换用户的状态其实就是修改CS寄存器中对应的比特位。

CPU内部还存在一些CR寄存器:

CR3寄存器用于保存当前运行进程的用户级页表的物理地址;

CR1寄存器用于保存上一次引发缺页中断的虚拟地址。

2.2 信号的处理

用户在调用系统调用之后,在要完成调用任务时,会从用户态切换至内核态完成对应的任务,此时并不是直接切换回用户态,而是先要检测信号,如果有需要处理的信号,根据对信号的处理方法,如果是默认动作、忽略就直接处理,如果是用户自定义方法,那么此时不能在内核态处理,而是要返回用户态去执行用户自定义方法,在执行完之后,不能直接跳转到用户代码处,而是要再次返回内核态,再从内核态返回进入内核态的用户代码处。

简化的图就是一个♾️

在信号捕捉中,一共会涉及到4次状态的切换!

上述情况是只有一个信号需要被处理,那么如果存在多个需要处理的信号,那么在处理完一个信号之后会轮训式的检测需要处理的信号,在所有信号处理完之后再切换为用户态。

2.3 sigaction函数 

该函数是一个检测信号并改变处理动作的函数:

参数:

signum:要改变的信号的编号;

sigaction是一个结构体:

其中我们只需要关注sa_handler和sa_mask

sa_handler是要指定的处理动作;

sa_mask是要额外屏蔽的信号集。

act:表示要改变的新的处理方法;

oldact:表示被改变之前的处理方法。

Linux是不允许同一个信号已经在被处理的过程中,再次进行嵌套处理的,所以当某一个信号在被处理的过程中,内核会自动将该信号加入到信号屏蔽字中,当处理的函数返回之后,会对该信号进行恢复,除了当前处理的信号被屏蔽外,我们也可以通过sa_mask(信号集)添加一些额外的信号进行屏蔽。 

代码演示:

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

void Print(const sigset_t &pending);

void handler(int signo)
{
    std::cout << "get a sig: " << signo << std::endl;
    sleep(1);
    while (true)
    {
        sigset_t pending;
        sigpending(&pending);
        Print(pending);
        sleep(1);
    }
}

// 打印pending表
void Print(const sigset_t &pending)
{
    for (int signo = 31; signo > 0; signo--)
    {
        if (sigismember(&pending, signo))
        {
            std::cout << "1";
        }
        else
        {
            std::cout << "0";
        }
    }
    std::cout << std::endl;
}
int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    struct sigaction act, oact;
    // 自定义处理方法
    act.sa_handler = handler;
    // 初始化信号集
    sigemptyset(&act.sa_mask);
    // 添加3号信号
    sigaddset(&act.sa_mask, 3);
    // 自定义捕捉2号信号
    sigaction(2, &act, &oact);
    while (1)
        sleep(1);
    return 0;
}

3. 信号的其他补充 

3.1 可重入函数

将函数和信号结合起来研究:

在链表阶段我们实现了一个头插的函数接口,头插的阶段分为两步,做完第一步的时候由于某些硬件中断使进程回到了内核态,再次返回用户态时需要进行信号的检测与处理,如果此时的信号自定义方法中也调用了头插的函数,在做完头插的两步之后又重新返回用户态的代码处继续向下执行,那么在自定义方法中插入的头节点就会被丢失掉,此时这个头插的这个函数就是一个不可重入函数。

像上例这样,insert函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入(Reentrant) 函数。
如果一个函数符合以下条件之一则是不可重入的 :
  • 调用了mallocfree,因为malloc也是用全局链表来管理堆的。
  • 调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。

3.2 SIGCHLD信号 

在进程等待的章节说到过,子进程退出时父进程必须进行等待(waitpid()),否则会造成僵尸问题,并且我们有时还需要知道子进程的退出信息,另外在子进程退出的时候回向父进程发送SIGCHLD信号。

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

void handler(int signo)
{
    std::cout << "get a sig: " << signo << std::endl;
}

int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    // 自定义捕捉信号
    signal(SIGCHLD, handler);
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "child is running" << std::endl;
        sleep(5);
        exit(10);
    }
    while (true)
        sleep(1);
    return 0;
}

父进程自定义捕捉SIGCHLD信号,子进程在运行5秒后退出,可以看到果然子进程给父进程发送了SIGCHLD信号。

所以我们就可以基于信号来对子进程进行回收等待了:

#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

void handler(int signo)
{
    std::cout << "get a sig: " << signo << std::endl;
    // 等待任意进程
    waitpid(-1, nullptr, 0);
}

int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    // 自定义捕捉信号
    signal(SIGCHLD, handler);
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "child is running" << std::endl;
        sleep(5);
        exit(10);
    }
    while (true)
        sleep(1);
    return 0;
}

Linux支持手动忽略SIGCHLD,如果对其进行忽略,那么所有的子进程都不要父进程进行等待了,子进程会在终止时自动的清理。

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

int main()
{
    std::cout << "pid: " << getpid() << std::endl;
    // 手动忽略SIGCHLD
    signal(SIGCHLD, SIG_IGN);
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "child is running" << std::endl;
        sleep(5);
        exit(10);
    }
    return 0;
}

 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!   

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

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

相关文章

SpringBoot + MySQL + MyBatis 实操示例教学

一、准备工作 1.导入相关依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><…

网络安全实训十(Windows提权、UAC绕过、Linux利用suid提权)

一、Windows提权 1 手动查找系统存在漏洞 使用命令查看安装的补丁信息 systeminfo wmic qfe get caption,description,hotfixid,installedon 2 自动查找系统存在漏洞 2.1 Windows Exploit Suggester 2.1.1 下载脚本 下载地址&#xff1a;https://github.com/AonCyberLabs/Wi…

【有啥问啥】深入解析3A算法:自动对焦、自动曝光与自动白平衡的原理、实现与应用

深入解析3A算法&#xff1a;自动对焦、自动曝光与自动白平衡的原理、实现与应用 在现代图像处理技术中&#xff0c;3A算法&#xff08;自动对焦、自动曝光、自动白平衡&#xff09;是数码摄像设备核心的成像控制系统&#xff0c;负责调节图像的清晰度、亮度和色彩平衡。这些算…

《深度学习》—— 神经网络基本结构

前言 深度学习是一种基于神经网络的机器学习算法&#xff0c;其核心在于构建由多层神经元组成的人工神经网络&#xff0c;这些层次能够捕捉数据中的复杂结构和抽象特征。神经网络通过调整连接各层的权重&#xff0c;从大量数据中自动学习并提取特征&#xff0c;进而实现预测或…

Aigtek功率放大器的工作状态和技术指标有哪些

功率放大器是电子电路中的重要组成部分&#xff0c;用于放大电信号的功率&#xff0c;以便驱动负载&#xff0c;如扬声器、天线或电动机。它在各种应用中都起到至关重要的作用&#xff0c;从音响系统到通信设备&#xff0c;以下是功率放大器的工作状态和技术指标的详细介绍。 工…

利用zabbix监控ogg进程(Windows平台)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

Boost.pyhon 使用方法

哈哈,又是一个相当nice的技巧 boost 在使用时定义使用静态库 很完整的功能 方法实现如上,很多时候写python脚本直接执行还是最容易的,编译打包还是比较麻烦,内置解释器到QT C的代码中 加载python脚本时,从python 脚本中获取值&#xff0c;在C 中进行计算使用 在python 和 C 的交…

OLED显示屏应用(STM32)

一、接线 OLED的四针脚对应接法如下图 GND——GND 3.3V——3.3V SCL——PB8 SDA——PB9 二、OLED.c代码介绍 #include "stm32f10x.h" #include "OLED_Font.h"/*引脚配置*/ //OLED时钟线 //GPIOB8接时钟线接口 //定义一个函数&#xff0c;函数的参数为…

【JavaScript】LeetCode:31-35

文章目录 31 反转链表32 回文链表33 环形链表34 环形链表Ⅱ35 合并两个有序链表 31 反转链表 初始化&#xff1a;cur head&#xff0c;pre null。pre和cur一起向前移。由于反转链表时&#xff0c;cur.next指向pre&#xff0c;导致cur在下次循环中就找不到了原来的cur.next&am…

牛客思维题———进制(简单)

C-小红的双好数&#xff08;easy&#xff09;_牛客周赛 Round 57 (nowcoder.com) 思路&#xff1a; 任何一个数n可以表示为n进制&#xff0c;且值为1 特判1 2 即可 代码&#xff1a; #include<bits/stdc.h> #define int long long using namespace std;#define IOS i…

Java设计模式—面向对象设计原则(一) ----->开闭原则OCP(完整详解,附有代码+案例)

3.1开闭原则 对扩展开放&#xff0c;对修改关闭。在程序需要进行拓展的时候&#xff0c;不能去修改原有的代码&#xff0c;实现一个热插拔的效果。简言之&#xff0c;是为了使程序的扩展性好&#xff0c;易于维护和升级。想要达到这样的效果&#xff0c;我们需要使用接口和抽象…

【黑金系】金融UI/UX体验设计师面试作品集 Figma源文件分享

在数字金融时代&#xff0c;UI/UX体验设计师扮演着至关重要的角色。他们不仅塑造着产品的界面&#xff0c;更引领着用户的使用体验。我们的面试作品集&#xff0c;正是这样一部展现金融UI/UX设计魅力的宝典。 这套作品集汇聚了众多经典案例&#xff0c;每一处设计都经过精心雕…

docker部署bind9

一、部署 ## docker 部署bind9# docker run -d --name bind9 --restartalways --publish 53:53/tcp --publish 53:53/udp --publish 10000:10000/tcp --volume /data/docker/dns-server:/data --env ROOT_PASSWORDroot dhub.kubesre.xyz/sameersbn/bind:9.16.1-20200524# 建数…

高等数学精解【13】

文章目录 简化二次方程轴平移轴平移是一种简化二次方程图形表示的有用技巧一元二次方程的轴平移二元二次方程的轴平移轴平移简化二次方程定义性质计算例子一元二次方程的例子二元二次方程的例子&#xff08;圆&#xff09; 例题 轴旋转简化二次方程轴旋转的定义轴旋转的性质例题…

持续集成与持续交付CI/CD

CI/CD 是指持续集成&#xff08;Continuous Integration&#xff09;和持续部署&#xff08;Continuous Deployment&#xff09;或持续交付&#xff08;Continuous Delivery&#xff09; 持续集成&#xff08;Continuous Integration&#xff09; 持续集成是一种软件开发实践&…

plt.imshow(img_show)有什么作用

加plt.imshow(img_show) 不加plt.imshow(img_show)

pytest 接口测试

pytest 是什么 .py文件名 用 test_开头&#xff0c;函数用 test_开头&#xff0c;运行的时候,pycharm会自动用pytest模式去执行代码! 处理excel 文件 点击获取excel 文件 核心思想&#xff1a; 把excel 文件的内容转化为 python的字典数组 可以先阅读 python函数 函数 注意&…

C sharp 学习 笔记

介绍 这篇文章是我学习C#语言的笔记 学的是哔哩哔哩刘铁锰老师2014年的课程 在学习C#之前已经学习过C语言了。看的是哔哩哔哩比特鹏哥的课程。他们讲的都很不错 正在更新&#xff0c; 大家可以在我的gitee仓库中下载笔记源文件、项目资料等 笔记源文件可以在Notion中导入…

pikachu下

CSRF(跨站请求伪造) CSRF(get) url变成了这样了&#xff0c;我们就可以新开个页面直接拿url去修改密码 http://pikachu-master/vul/csrf/csrfget/csrf_get_login.php?username1&password2&submitLogin CSRF(post&#xff09; 这里只是请求的方式不同&#xff0c;…

【解决】AnimationCurve 运行时丢失数据问题

开发平台&#xff1a;Unity 2022 编程平台&#xff1a;Visual Studio 编程语言&#xff1a;CSharp   一、问题背景 如上图所示的 GracityComponent 组件中&#xff0c;引用 AnimationCurve 作为可调属性。但在实际使用中出现数据丢失问题。大致为以下两种情况&#xff1a; 运…