【Linux】进程信号 --- 信号保存

在这里插入图片描述

👦个人主页:Weraphael
✍🏻作者简介:目前正在学习c++和算法
✈️专栏:Linux
🐋 希望大家多多支持,咱一起进步!😁
如果文章有啥瑕疵,希望大佬指点一二
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注😍


目录

  • 一、再次认识信号
      • 1.1 为什么要进行信号保存
      • 1.2 信号如何被保存
      • 1.3 信号其他相关常见概念
  • 二、在内核中的表示
  • 三、操作系统中位图的数据类型
      • 3.1 sigset_t --- 信号集类型
      • 3.2 信号集操作函数
  • 四、系统调用接口 --- sigprocmask
  • 五、系统调用接口 --- sigpending
  • 六、总结
  • 七、相关代码

一、再次认识信号

1.1 为什么要进行信号保存

在入门篇提到过:进程收到信号之后,可能不会立即被处理,因为进程可能正在做重要的事,需要等到合适的时间再处理。就比方说:你收到外卖小哥的外卖到了的提醒消息(进程收到信号),但你正在打LOL(更重要的事),晚点下去拿(保存信号),当打完之后再下去(处理信号)。因此,从信号产生到信号处理这段期间,需要将信号保存起来,那如何保存呢?位图!(具体看1.2

  • 对于普通信号,它用的是位图,只要收到就会先保存,但是如果这个信号还没处理,又来个信号,那么就只记得最近一次的信号
  • 而对于实时信号。只要发送了,就要立即处理,哪怕此时进程在忙。它的用的是队列。
  • 关于进程信号,我们重点关心普通信号即可。

1.2 信号如何被保存

信号列表

对应的1~31号信号我们称为普通信号,是不是一个 int整型(32bit 就足以表示所有普通信号的产生信息了。

对于普通的信号处理而言,进程主要关心自己是否有信号以及收到了哪个具体的信号。并且,这个信号是由操作系统发送给进程的进程控制块(task_struct。所以结构体task_struct内一定维护类似于int signal字段。

如果给进程发的是一号信号,那么则将bit位的第一位给置为1(注意这里有第0位,表示没有收到信号),后面以此类推。所以描述一个信号,用比特位的位置来表示,即 普通信号是用位图来管理信号

总结:

  1. 比特位的内容是0还是1,表明是否收到信号。
  2. 比特位的位置(第几个),表示信号的编号。
  3. 所谓的“发信号”,本质就是操作系统(管理者)去修改task_struct的信号位图对应的比特位。也就是写信号

说明:在后面我们会说,信号是被保存在pending表中的

1.3 信号其他相关常见概念

在这里插入图片描述

  • 信号产生(Produce:由四种不同的方式发出信号。(详情见)
  • 信号未决(Pending:信号从产生到处理的中间状态。(信号保存)
  • 信号递达(Delivery:进程收到信号后,对信号的处理动作。(信号处理)

除此之外,进程还允许阻塞(屏蔽)某些信号,我们称之为 信号阻塞这意味着进程暂时不接收阻塞的信号,使其保持在未决信号集合中,只有在解除阻塞后,信号才会被递送到信号处理程序中。注意:信号阻塞是一种手段,可以发生在 信号处理 前的任意时段

注意区分阻塞和忽略(信号处理动作):忽略是真的什么都不做,相当于“已读不回”;而阻塞是信号还没到处理阶段,它可能会被处理,可能不会被处理,相当于“未读”,是一种状态。

二、在内核中的表示

在这里插入图片描述

在操作系统中,有三张表分别是block表、pending表、handler表。共同构成了操作系统内核管理信号的机制。

  • block表:也称信号阻塞表(位图)。它主要用于记录信号有没有被阻塞。如果某信号(比特位的位置)被设置成1,表示信号被阻塞;如果某信号被设置成0,表示信号没有被阻塞。
  • pending表:也称未决信号表(位图)。它主要用于记录已经向进程发送但尚未被处理的信号(信号保存)。如果信号(比特位的位置)对应的比特位是1,则表示该信号是pending的,即信号产生但还未被处理。当进程的信号处理函数还没有准备好处理信号时,信号会保持在pending表中。一旦信号的处理函数准备好,内核会从pending表中选择一个信号交付给进程。注意:如果这个信号还没处理,又来个信号,那么就只记得最近一次的信号
  • handler表:也称信号处理程序表(函数指针数组)该表存储着信号[1,31]的系统默认处理动作的函数指针(函数地址);如果用户自定设定了方法(如singal函数自定义处理方式),则会将该方法的地址填入到信号处理程序表中。当进程接收到一个信号时,内核会查找该信号对应的处理函数,并执行该处理函数来响应信号事件。
  • 信号它的一切操作都是围绕这三张表!!!

处理信号有三种方式:忽略SIG_IGN、系统默认动作SIG_DEL、用户自定义。用户自定义在【信号产生】已经用很多次了,这次来见见忽略和系统默认动作。

  • Linux操作系统对于忽略和默认动作的定义

在这里插入图片描述

默认动作就是将0强转为函数指针类型,忽略动作则是将1强转为函数指针类型,分别对应handler 表中的01下标位置

我们可以使用代码来看看效果

  • 忽略动作SIG_IGN

在这里插入图片描述

【程序结果】

在这里插入图片描述

  • 系统默认动作SIG_DEL

在这里插入图片描述

【程序结果】

在这里插入图片描述

三、操作系统中位图的数据类型

3.1 sigset_t — 信号集类型

无论是block表还是pending表,它们都是位图结构,同时也是内核的数据结构。由于操作系统不相信任何用户,它不允许用户直接修改这两张表。所以要修改这些表,操作系统一定提供了一系列的系统调用接口。

在内核中,操作系统将信号操作所需要的位图结构封装成了一个结构体类型__sigset_t,在用户层,我们可以直接使用sigset_t类型。

在这里插入图片描述

sigset_t称为信号集类型,这个类型可以表示每个信号的状态。

  • 在阻塞信号集block表中的含义是该信号是否被阻塞。
  • 而在未决信号集pending表中的含义是该信号是否处于未决状态。

至于这个类型内部如何存储这些比特位则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量,而不应该对它的内部数据做任何解释,比如直接打印sigset_t变量是没有意义的!

3.2 信号集操作函数

#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所指向的信号集,将所有信号的对应比特位清零,表示该信号集不包含任何有效信号。
  • sigfillset函数:初始化set所指向的信号集,将所有信号的对应bit设置成1,表示该信号集的有效信号包括系统支持的所有信号。
  • sigaddset函数:向指定的信号集中添加特定的信号,设置1
  • sigdelset函数,向指定的信号集中去掉特定的信号,设置0
  • 以上四个函数都是成功返回0,出错返回-1
  • sigismember函数:判断一个信号集的有效信号中是否包含某种信号。若包含则返回1,不包含则返回0

注意:在使用sigset_ t类型的变量之前,一定要调用sigemptysetsigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddsetsigdelset在该信号集中添加或删除某种有效信号。

四、系统调用接口 — sigprocmask

sigprocmask函数用来对block表进行操作(阻塞信号)。函数原型如下:

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

参数解释:

  • how参数:指定操作的类型,可以是以下值之一:

    • SIG_BLOCK:将 set中的信号添加到当前进程的block表中(mask|=set)。
    • SIG_UNBLOCK:从当前进程的block表中移除set中的信号(mask&=~set)。
    • SIG_SETMASK:设置当前进程的block表为 set 中的值(mask==set)。
  • set 参数:就是一个信号集,主要从此信号集中获取屏蔽信号信息

  • oldset 参数:指向 sigset_t 类型的指针,用于存储之前的block表。如果不需要获取旧的block表,可以将 oldset 设为 nullptr

  • 返回值:若成功则为0,若出错则为-1

五、系统调用接口 — sigpending

sigpending函数用来获取当前进程中的未决信号集pengding表,为了做检查。函数原型如下:

#include <signal.h>
int sigpending(sigset_t *set);

参数说明:

  • 参数:待获取的未决信号集
  • 返回值:成功返回0,失败返回 -1并将错误码设置

如何根据打印 pending

  1. 使用函数sigismember判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0
  2. 如此重复,将 31 个信号全部判断打印输出即可

【代码】

代码逻辑:循环打印pending表,阻塞2号信号。若进程没有收到2号信号,那么位图一定是全0;当进程收到2号信号时,位图的低2位比特位由01

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;int main()
{// 在用户层定义sigset_t变量sigset_t sset;// 初始化信号集,将所有信号的对应比特位清零sigemptyset(&sset);// 将信号集sset添加特定的信号,设置1sigaddset(&sset, 2);// sigprocmask函数用来对block表进行操作(阻塞信号)sigset_t oldset;sigemptyset(&oldset);sigprocmask(SIG_SETMASK, &sset, &oldset);// 重复打印当前进程的pending表。// 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0sigset_t pending_t; // 获取pending表while (true){// sigpending函数用来获取当前进程中的未决信号集pending表int n = sigpending(&pending_t);if (n < 0) continue; // 打印// 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0for (int i = 31; i >= 1; i--){if (sigismember(&pending_t, i)){cout << "1";}else{cout << "0";}}cout << endl << endl;sleep(1);}return 0;
}

【程序结果】

在这里插入图片描述

我们再看下面的代码,这段代码在上面的基础上加了解除2号信号阻塞,那么当收到2号信号后,由于解除了阻塞,对应的比特位由1变为0

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;int main()
{// 在用户层定义sigset_t变量sigset_t sset;// 初始化信号集,将所有信号的对应比特位清零sigemptyset(&sset);// 将信号集sset添加特定的信号,设置1sigaddset(&sset, 2);// sigprocmask函数用来对block表进行操作(阻塞信号)sigset_t oldset;sigemptyset(&oldset);sigprocmask(SIG_SETMASK, &sset, &oldset);// 重复打印当前进程的pending表。// 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0sigset_t pending_t; // 获取pending表int cnt = 0;while (true){// sigpending函数用来获取当前进程中的未决信号集pending表int n = sigpending(&pending_t);if (n < 0) continue; // 打印// 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0for (int i = 31; i >= 1; i--){if (sigismember(&pending_t, i)){cout << "1";}else{cout << "0";}}cout << endl << endl;sleep(1);// 解除阻塞cnt++;if (cnt == 6){cout << "已解除2号信号阻塞" << endl;sigprocmask(SIG_SETMASK, &oldset, nullptr);}}return 0;
}

【程序结果】

在这里插入图片描述

通过以上结果:2 号信号产生后,当前进程的 pending 表中的 2 号信号位被置为 1,表示该信号属于未决状态,并且在六秒之后,阻塞结束,信号递达,进程终止。

哎?奇怪?当阻塞解除后,信号递达,应该看见 pending 表中对应位置的值由 1 变为 0,但为什么没有看到?

这是因为当阻塞解除后信号递达,而2号信号的默认执行动作为终止进程,进程都终止了,当然看不到。

解决方法:自定义2号信号的处理动作动作(别急着退出进程)

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;void handler(int signum)
{cout << "解除" << signum << "号信号的阻塞 " << endl;// 最终不退出进程 没加exit
}int main()
{// 捕捉2号信号signal(2, handler);// 在用户层定义sigset_t变量sigset_t sset;// 初始化信号集,将所有信号的对应比特位清零sigemptyset(&sset);// 将信号集sset添加特定的信号,设置1sigaddset(&sset, 2);// sigprocmask函数用来对block表进行操作(阻塞信号)sigset_t oldset;sigemptyset(&oldset);sigprocmask(SIG_SETMASK, &sset, &oldset);// 重复打印当前进程的pending表。// 虽然我们阻塞了2号信号,但是只有没产生信号,pending表一定是全0sigset_t pending_t; // 获取pending表int cnt = 0;while (true){// sigpending函数用来获取当前进程中的未决信号集pending表int n = sigpending(&pending_t);if (n < 0)continue;// 打印// 判断当前信号集中是否存在该信号,如果存在,输出1,否则输出0for (int i = 31; i >= 1; i--){if (sigismember(&pending_t, i)){cout << "1";}else{cout << "0";}}cout << endl<< endl;sleep(1);// 解除阻塞cnt++;if (cnt == 4){sigprocmask(SIG_SETMASK, &oldset, nullptr);}}return 0;
}

【程序结果】

在这里插入图片描述

那如果将所有的信号全部屏蔽掉,那是不是信号就不会被递达(处理)了?

我们能想到的,操作系统的设计者肯定考虑到了,肯定有一些信号是无法被屏蔽的

9号和19号不可被屏蔽,也不可被捕捉。我们可以来验证

#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;int main()
{cout << "my pid is " << getpid() << endl;sleep(3);sigset_t sset, oldset;sigemptyset(&sset);sigemptyset(&oldset);for (int i = 1; i <= 31; i++){sigaddset(&sset, i);}// 将所有的信号屏蔽sigprocmask(SIG_SETMASK, &sset, &oldset);sigset_t pending_t;while (true){int n = sigpending(&pending_t);if (n < 0)continue;for (int signo = 31; signo >= 1; signo--){cout << sigismember(&pending_t, signo);}cout << endl<< endl;sleep(1);}return 0;
}

代码如上,大家可以通过kill -num <pid>命令自行去试。

六、总结

在这里插入图片描述

七、相关代码

本篇博客的相关代码:点击跳转

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

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

相关文章

【医学影像】RK3588+FPGA:满足远程诊疗系统8K音视频编解码及高效传输需求

医学影像 提供基于Intel平台、NXP平台、Rockchip平台的核心板、Mini-ITX主板、PICO-ITX主板以及工业整机等计算机硬件。产品板载内存&#xff0c;集成超高清编码/解码视频引擎&#xff0c;具有出色的数据处理能力和图形处理能力&#xff0c;功能高集成&#xff0c;可应用于超声…

【教学类-69-01】20240721铠甲勇士扑克牌涂色(男孩篇)

背景需求&#xff1a; 【教学类-68-01】20240720裙子涂色&#xff08;女孩篇&#xff09;-CSDN博客文章浏览阅读250次。【教学类-68-01】20240720裙子涂色&#xff08;女孩篇&#xff09;https://blog.csdn.net/reasonsummer/article/details/140578153 前期制作了女孩涂色延…

直播带货|主播、运营怎么考核 怎么分钱

在直播公司中&#xff0c;有两个非常重要的岗位&#xff1a;直播运营和主播。那么&#xff0c;直播公司应该如何正确发放工资呢&#xff1f;许多公司为这两个岗位的工资发放采取的是销售额提成的方式。 销售额提成存在一个问题&#xff1a;直播公司很容易通过广告费来刺激销售额…

MT6825磁编码IC在智能食品包装设备的应用

艾毕胜马达控制平台专家 MT6825磁编码IC&#xff0c;作为一款先进的传感器解决方案&#xff0c;在智能食品包装设备中的应用正日益广泛 MT6825磁编码IC&#xff0c;作为一款先进的传感器解决方案&#xff0c;在智能食品包装设备中的应用正日益广泛。它凭借卓越的性能和可靠性…

Windows及Linux系统加固

君衍. 一、Windows加固1、配置简介2、账户配置3、本地配置4、安全设置 二、Linux加固1、配置简介2、网络配置3、日志和审计配置4、访问认证和授权配置5、系统运维配置 一、Windows加固 1、配置简介 通常在Windows安全配置中有两类对象 一类是Windows Server&#xff0c;如win …

功能测试与APPSCAN自动化测试结合的提高效率测试策略

背景 手工探索性测试&#xff08;Manual Exploratory Testing&#xff0c;简称MET&#xff09;是一种软件测试方法&#xff0c;它依赖于测试人员的直觉、经验和即兴发挥来探索应用程序或系统。与传统的脚本化测试相比&#xff0c;手工探索性测试不遵循固定的测试脚本&#xff0…

敲详细的springboot中使用RabbitMQ的源码解析

这里介绍的源码主要是涉及springboot框架下的rabbitmq客户端代码&#xff08;具体在springframework.amqp.rabbit包下&#xff0c;区分一下不由springboot直接接管的spring-rabbit的内容&#xff09;&#xff0c;springboot基于RabbitMQ的Java客户端建立了简便易用的框架。 sp…

重生之我在学数据结构——队列

一.队列的概念 队列是一种先进先出(First In First Out &#xff0c;FIFO)的数据结构&#xff0c;可以简单理解为排队的概念。在队列中&#xff0c;数据项按照插入的顺序排列&#xff0c;并且只能在队列的一端插入&#xff08;称为队尾&#xff09;&#xff0c;在另一端删除&a…

品牌策划新手指南:如何让你的品牌脱颖而出?

品牌策划&#xff0c;简单来说&#xff0c;就是为品牌制定一套全方位的成长和发展计划&#xff0c;就像给品牌设计一条成长路线图&#xff0c;让它能够更好地吸引顾客、建立信任、提升知名度&#xff0c;最终实现销售和市场份额的增长。 品牌策划是什么&#xff1f; 想象一下…

【性能优化】在大批量数据下使用 HTML+CSS实现走马灯,防止页面卡顿(一)

切换效果 页面结构变化 1.需求背景 项目首页存有一个小的轮播模块,保密原因大概只能这么展示,左侧图片右侧文字,后端一次性返回几百条数据(开发环境下,生产环境只会更多).无法使用分页解决,前端需要懒加载防止页面卡顿 写个小demo演示,如下 2.解决思路 获取到数据后,取第一…

二、链表(2)

24. 两两交换链表中的节点 法一&#xff1a;迭代&#xff0c;while循环&#xff0c;注意要获取next给变量&#xff0c;得先判断非null, 需要4个变量&#xff0c; n0是前&#xff0c;n1 n2是交换的两&#xff0c;n3是n2的下一个可能为空&#xff0c;这种先把变量保存起来&#…

Leetcode1688. 比赛中的配对次数

问题描述&#xff1a; 给你一个整数 n &#xff0c;表示比赛中的队伍数。比赛遵循一种独特的赛制&#xff1a; 如果当前队伍数是 偶数 &#xff0c;那么每支队伍都会与另一支队伍配对。总共进行 n / 2 场比赛&#xff0c;且产生 n / 2 支队伍进入下一轮。如果当前队伍数为 奇…

JavaWeb连接(JDBC)数据库实现增删改查

JavaWeb连接(JDBC)数据库实现增删改查 1、数据库结构 (1)、创建数据库&#xff08;source_db&#xff09; (2)、创建数据表&#xff08;tb_source&#xff09;&#xff0c;结构如下 字段名说明字段类型长度备注id编号int主键&#xff0c;自增&#xff0c;增量为 1name名称v…

前端面试 vue 按钮级的权限控制

方案一 按钮权限也可以用v-if判断 但是如果页面过多&#xff0c;每个页面页面都要获取用户权限role和路由表里的meta.btnPermissions&#xff0c;然后再做判断 这种方式就不展开举例了 方案二 使用自定义指令实现 按钮级的权限控制 思维导图 心就是自定义指令的书写 首先…

【人工智能】Transformers之Pipeline(四):零样本音频分类(zero-shot-audio-classification)

​​​​​​​ 目录 一、引言 二、零样本音频分类&#xff08;zero-shot-audio-classification&#xff09; 2.1 概述 2.2 意义 2.3 应用场景 2.4 pipeline参数 2.4.1 pipeline对象实例化参数​​​​​​​ 2.4.2 pipeline对象使用参数 2.4 pipeline实战 2.5 模…

【MySQL】:对库和表的基本操作方法

数据库使用的介绍 什么是SQL 学习数据库的使用——>基于 SQL编程语言 来对数据库进行操作 重点表述的是“需求”&#xff0c;期望得到什么结果。&#xff08;至于结果是如何得到的&#xff0c;并不关键&#xff0c;都是数据库服务器在背后做好了&#xff09; 重点表述的是…

线程之间的通信

第一题 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <dirent.h> #include <…

蔡司小乐圆:护航青少年视力健康,专业应对近视挑战

在科技日新月异的今天&#xff0c;电子产品已深度融入青少年的日常&#xff0c;为生活带来便利的同时&#xff0c;也悄然间对他们的视力构成了威胁。近视&#xff0c;这一日益严峻的健康问题&#xff0c;正牵动着无数家庭的心弦。蔡司眼镜&#xff0c;作为眼镜行业的领军者&…

7月21日,贪心练习

大家好呀&#xff0c;今天带来一些贪心算法的应用解题、 一&#xff0c;柠檬水找零 . - 力扣&#xff08;LeetCode&#xff09; 解析&#xff1a; 本题的贪心体现在对于20美元的处理上&#xff0c;我们总是优先把功能较少的10元作为找零&#xff0c;这样可以让5元用处更大 …

代码随想录算法训练营第35天|LeetCode 01背包问题 二维、01背包问题 一维、416. 分割等和子集

1. LeetCode 01背包问题 二维 题目链接&#xff1a;https://kamacoder.com/problempage.php?pid1046 文章链接&#xff1a;https://programmercarl.com/背包理论基础01背包-1.html#算法公开课 视频链接&#xff1a;https://www.bilibili.com/video/BV1cg411g7Y6/ 思路&#xf…