Linux进程信号 ----- (信号保存)

前言

        信号从产生到执行,并不会被立即处理,这就意味着需要一种 “方式” 记录信号是否产生,对于 31 个普通信号来说,一个 int 整型就足以表示所有普通信号的产生信息了;信号还有可能被 “阻塞”,对于这种多状态、多结果的事物,操作系统会将其进行描述、组织、管理,这一过程称为 信号保存 阶段。

一、深入认识信号

1.1 概念

信号 传递过程:信号产生 -> 信号未决 -> 信号递达

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

在这三种过程之前,均有可能出现 信号阻塞 的情况

  • 信号阻塞(Block):使信号传递 “停滞”,无论是否产生,都无法进行处理

信号递达后的三种处理方式:

  1. SIG_DFL 默认处理动作,大多数信号最终都是终止进程
  2. SIG_IGN 忽略动作,即进程收到信号后,不做任何处理动作
  3. handler 用户自定义的信号执行动作

注意:

  • 信号阻塞 是一种手段,可以发生在 信号处理 前的任意时段
  • 信号阻塞 与 忽略动作 不一样,虽然二者的效果差不多:什么都不干,但前者是 干不了,后者则是 不干了,需要注意区分

1.2 信号在内核中的表示

对于传递中的信号来说,需要存在三种状态表达:

  1. 信号是否阻塞
  2. 信号是否未决
  3. 信号递达时的执行动作

在内核中,每个进程都需要维护这三张与信号状态有关的表:block 表、pending 表、handler 表

所谓的 block 表 和 pending 表 其实就是 位图结构

一个 整型 int 就可以表示 31 个普通信号(实时信号这里不讨论)

  • 比如 1 号信号就是位图中的 0 位置处,0 表示 未被阻塞/未产生未决,1 则表示 阻塞/未决
  • 对于信号的状态修改,其实就是修改 位图 中对应位置的值(0/1
  • 对于多次产生的信号,只会记录一次信息(实时信号则会将冗余的信号通过队列组织)

如何记录信号已产生 -> 未决表中对应比特位置置为 1 ?

        假设已经获取到了信号的 pending 表
        只需要进行位运算即可:pending |= (1 << (signo - 1))
        其中的 signo 表示信号编号,-1 是因为信号编号从 1 开始,需要进行偏移
如果想要取消 未决 状态也很简单:pending &= (~(1 << (signo - 1)))
至于 阻塞 block 表,与 pending 表 一模一样

对于上图的解读:

  1. SIGHUP 信号未被阻塞,未产生,一旦产生了该信号,pending 表对应的位置置为 1,当信号递达后,执行动作为默认
  2. SIGINT 信号被阻塞,已产生,pending 表中有记录,此时信号处于阻塞状态,无法递达,一旦解除阻塞状态,信号递达后,执行动作为忽略该信号
  3. SIGQUIT 信号被阻塞,未产生,即使产生了,也无法递达,除非解除阻塞状态,执行动作为自定义

阻塞 block 与 未决 pending 之间并没很强的关联性,阻塞不过是信号未决的延缓剂

  • 信号在 产生 之前,可以将其 阻塞,信号在 产生 之后(未决),依然可以将其 阻塞

至于 handler 表是一个 函数指针表,格式为:返回值为空,参数为 int 的函数

可以看看 默认动作 SIG_DEL 和 忽略动作 SIG_IGN 的定义

/* Type of a signal handler.  */
typedef void (*__sighandler_t) (int);/* Fake signal functions.  */
#define SIG_ERR	((__sighandler_t) -1)		/* Error return.  */
#define SIG_DFL	((__sighandler_t) 0)		/* Default action.  */
#define SIG_IGN	((__sighandler_t) 1)		/* Ignore signal.  */

        默认动作就是将 0 强转为函数指针类型,忽略动作则是将 1 强转为函数指针类型,分别对应 handler 函数指针数组表中的 01 下标位置;除此之外,还有一个 错误 SIG_ERR 表示执行动作为 出错

1.3、sigset_t 信号集

        无论是 block 表 还是 pending 表,都是一个位图结构,依靠 除、余 完成操作,为了确保不同平台中位图操作的兼容性,将信号操作所需要的 位图 结构封装成了一个结构体类型,其中是一个 无符号长整型数组

/* A `sigset_t' has a bit for each signal.  */# define _SIGSET_NWORDS	(1024 / (8 * sizeof (unsigned long int)))
typedef struct{unsigned long int __val[_SIGSET_NWORDS];} __sigset_t;#endif

注:_SIGSET_NWORDS 大小为 32,所以这是一个可以包含 32 个 无符号长整型 的数组,而每个 无符号长整型 大小为 4 字节,即 32 比特,至多可以使用 1024 个比特位

sigset_t 是信号集,其中既可以表示 block 表信息,也可以表示 pending 表信息,可以通过信号集操作函数进行获取对应的信号集信息;信号集 的主要功能是表示每个信号的 “有效” 或 “无效” 状态

block 表 通过信号集称为 阻塞信号集或信号屏蔽字(屏蔽表示阻塞),pending 表 通过信号集中称为 未决信号集

如何根据 sigset_t 位图结构进行比特位的操作?

  • 假设现在要获取第 127 个比特位
  • 首先定位数组下标(对哪个数组操作):127 / (8 * sizeof (unsigned long int)) = 3
  • 求余获取比特位(对哪个比特位操作):127 % (8 * sizeof (unsigned long int)) = 31
  • 对比特位进行操作即可:
  • 假设待操作对象为 XXX
  • 置 1XXX._val[3] |= (1 << 31)
  • 置 0XXX._val[3] &= (~(1 << 31))

所以可以仅凭 sigset_t 信号集,对 1024 个比特位进行任意操作,关于 位图 结构的实现后续介绍


二、信号集操作函数

对于 信号 的 产生或阻塞 其实就是对 block 和 pending 两张表的 增删改查

2.1 增删查改

对于 位图 的 增删改查 是这样操作的:

  • 增:| 操作,将比特位置为 1
  • 删:& 操作,将比特位置为 0
  • 改:| 或 & 操作,灵活变动
  • 查:判断指定比特位是否为 1 即可

比特作为基本单位,不推荐让我们直接进行操作,操作系统也不同意,于是提供了一批 系统接口,用于对 信号集 进行操作

#include <signal.h>int sigemptyset(sigset_t *set);	//初始化信号集
int sigfillset(sigset_t *set);	//初识化信号集
int sigaddset(sigset_t *set, int signum);	//增
int sigdelset(sigset_t *set, int signum);	//删
int sigismember(const sigset_t *set, int signum);	//查  

这些函数都是 成功返回 0,失败返回 -1

至于参数,非常简单,无非就是 待操作的信号集变量、待操作的比特位

注意: 在创建 信号集 sigset_t 类型后,需要使用 sigemptyset 或 sigfillset 函数进行初始化,确保 信号集 是合法可用的

2.2 sigprocmask

sigprocmask 函数可用用来对 block 表 进行操作

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

返回值:成功返回 0,失败返回 -1 并将错误码设置

参数1:对 屏蔽信号集 的操作

  • SIG_BLOCK 希望添加至当前进程 block 表 中阻塞信号,从 set 信号集中获取,相当于 mask |= set
  • SIG_UNBLOCK 解除阻塞状态,也是从 set 信号集中获取,相当于 mask &= (~set)
  • SIG_SETMASK 设置当前进程的 block 表为 set 信号集中的 block 表,相当于 mask = set

参数2:就是一个信号集,主要从此信号集中获取屏蔽信号信息

参数3:也是一个信号集,保存进程中原来的 block 表(相当于给你操作后,反悔的机会)

        这个函数就是 参数 1 比较有讲究,主打的就是一个 从 set 信号集 中获取阻塞信号相关信息,然后对进程中的 block 表进行操作,并且有三种不同的操作方式

演示程序1:将 2 号信号阻塞,尝试通过 键盘键入 发出 2 信号

显然,当 2 号信号被阻塞后,是 无法被递达 的,进程也就无法终止了

演示程序2:在程序运行五秒后,解除阻塞状态

现象:在 2 号信号发出、程序运行五秒解除阻塞后,信号才被递达,进程被终止

如何证明信号已递达?

  • 当 n == 5 时,解除阻塞状态,程序立马结束
  • 并只打印了 五条 语句,证明在第六秒时,程序就被终止了
  • 至于如何进一步证明,需要借助 未决信号表

2.3 sigpending

这个函数很简单,获取当前进程中的 未决信号集

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

返回值:成功返回 0,失败返回 -1 并将错误码设置

参数:待获取的 未决信号集

如何根据 未决信号集 打印 pending 表

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

所以可以将上面的 演示程序 修改下:

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
using namespace std;static void DisplayPending(const sigset_t pending)
{//打印 pending 表cout << "当前进程的 pending 表为: ";int i = 1;while(i < 32){if(sigismember(&pending, i))cout << "1";elsecout << "0";i++;}cout << endl;
}int main()
{// 创建信号集sigset_t set, oset;// 初始化信号集sigemptyset(&set);sigemptyset(&oset);// 阻塞2号信号sigaddset(&set, 2);	//记录 2 号信号// 设置当前进程的 屏蔽信号集sigprocmask(SIG_BLOCK, &set, &oset);// 死循环int n = 0;while (true){if (n == 5){// 采用 SIG_SETMASK 的方式,覆盖进程的 block 表sigprocmask(SIG_SETMASK, &oset, nullptr);   // 不接收进程的 block 表}//获取进程的 未决信号集sigset_t pending;sigemptyset(&pending);int ret = sigpending(&pending);assert(ret == 0);(void)ret;    //欺骗编译器,避免 release 模式中出错DisplayPending(pending);n++;sleep(1);}return 0;
}

结果:当 2 号信号发出后,当前进程的 pending 表中的 2 号信号位被置为 1,表示该信号属于 未决 状态,并且在五秒之后,阻塞结束,信号递达,进程终止

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

  • 很简单,因为当前 2 号信号的执行动作为终止进程,进程都终止了,当然看不到
  • 解决方法:给 2 号信号先注册一个自定义动作(别急着退出进程)

综上,信号在发出后,在处理前,都是保存在 未决表 中的

注意:

  • 针对信号的 增删改查 都需要通过 系统调用 来完成,不能擅自使用位运算
  • sigprocmasksigpending 这两个函数的参数都是 信号集,前者是 屏蔽信号集,后者是 未决信号集
  • 在对 信号集 进行增删改查前,一定要先初始化
  • 信号在被解除 阻塞状态 后,很快就会 递达 了

以上关于 信号、信号集 的操作都是在进程中进行的,不影响操作系统

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

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

相关文章

在Win11上部署Stable Diffusion WebUI Forge

Stable Diffusion WebUI Forge 是 Stable Diffusion WebUI&#xff08;基于 Gradio&#xff09;之上的平台&#xff0c;可简化开发、优化资源管理并加快推理速度。“Forge”这个名字的灵感来自“Minecraft Forge”。这个项目旨在成为SD WebUI的Forge。 与原始 WebUI&#xff0…

MySQL数据库调优之关联查询、排序查询、分页查询、子查询、Group by优化

关联查询优化 1.准备工作 CREATE TABLE IF NOT EXISTS type(id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,card INT(10) UNSIGNED NOT NULL,PRIMARY KEY(id));CREATE TABLE IF NOT EXISTS book( bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, card INT(10) UNSIGNED N…

堆C++(Acwing)

代码&#xff1a; #include <iostream> #include <algorithm> #include <string.h>using namespace std;const int N 100010;int h[N], hp[N], ph[N], cnt;void heap_swap(int a, int b) {swap(ph[hp[a]] ,ph[hp[b]]);swap(hp[a], hp[b]);swap(h[a], h[b])…

1904_ARM Cortex M系列芯片特性小结

1904_ARM Cortex M系列芯片特性小结 全部学习汇总&#xff1a; g_arm_cores: ARM内核的学习笔记 (gitee.com) ARM Cortex M系列的MCU用过好几款了&#xff0c;也涉及到了不同的内核。不过&#xff0c;关于这些内核的基本的特性还是有些不了解。从ARM的官方网站上找来了一个对比…

[力扣 Hot100]Day33 排序链表

题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 出处 思路 归并排序即可。 代码 class Solution { public:ListNode* merge(ListNode *h1,ListNode *h2) {ListNode *head nullptr;if(h1->val<h2->val){head h1;h1h1-…

Sora:颠覆性AI视频生成工具

Sora是一款基于人工智能&#xff08;AI&#xff09;技术的视频生成工具&#xff0c;它彻底改变了传统视频制作的模式&#xff0c;为创作者提供了高效、便捷、高质量的视频内容生成方式。通过深度学习和自然语言处理等先进技术&#xff0c;Sora实现了从文字描述到视频画面的自动…

计算机体系架构初步入门

&#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质就是极致重复! 目录 1 计算机五大…

数据结构-列表LinkedList

一,链表的简单的认识. 数组,栈,队列是线性数据结构,但都算不上是动态数据结构,底层都是依托静态数组,但是链表是确实真正意义上的动态数组. 为什么要学习链表? 1,链表时最简单的动态数据结构 2,掌握链表有助于学习更复杂的数据结构,例如,二叉树,trie. 3,学习链表有助于更深入…

【深度学习笔记】卷积神经网络——多输入多输出通道

多输入多输出通道 虽然我们在subsec_why-conv-channels中描述了构成每个图像的多个通道和多层卷积层。例如彩色图像具有标准的RGB通道来代表红、绿和蓝。 但是到目前为止&#xff0c;我们仅展示了单个输入和单个输出通道的简化例子。 这使得我们可以将输入、卷积核和输出看作二…

EasyRecovery2024电脑版软件评测与使用教程

一、EasyRecovery电脑版软件评测 EasyRecovery电脑版是一款功能强大、操作简便的数据恢复软件。它适用于多种场景&#xff0c;无论是误删除、格式化、分区丢失还是硬件故障&#xff0c;都能提供有效的恢复方案。该软件界面直观&#xff0c;即便没有技术背景的用户也能轻松完成…

使用 React 和 MUI 创建多选 Checkbox 树组件

在本篇博客中&#xff0c;我们将使用 React 和 MUI&#xff08;Material-UI&#xff09;库来创建一个多选 Checkbox 树组件。该组件可以用于展示树形结构的数据&#xff0c;并允许用户选择多个节点。 前提 在开始之前&#xff0c;确保你已经安装了以下依赖&#xff1a; Reac…

GEE入门篇|遥感专业术语(实践操作3):时间分辨率(Temporal Resolution)

目录 时间分辨率&#xff08;Temporal Resolution&#xff09; 1.Landsat 2.Sentinel-2 时间分辨率&#xff08;Temporal Resolution&#xff09; 时间分辨率是指特定传感器图像流的重访时间或时间节奏&#xff0c;重访时间是指卫星连续访问地球表面同一位置…

公众号平台迁移公证怎么操作?

公众号迁移有什么作用&#xff1f;只能变更主体吗&#xff1f;公众号账号迁移的作用可不止是变更主体哦&#xff01;还可以把多个公众号的粉丝、文章合并起来&#xff0c;打造一个超级大 V&#xff1b;还可以变更公众号主体、名称、类型&#xff0c;增加留言功能&#xff1b;个…

javascript监听浏览器离开、进入行为

document.addEventListener(visibilitychange, () > {if (document.visibilityState hidden) {alert(离开)}if (document.visibilityState visible) {alert(进入)}}) visibilitychange是浏览器新添加的一个事件&#xff0c;当其选项卡的内容变得可见或被隐藏时&#xff0…

PyPDF2:项目实战源码分享(PDF裁剪)

目录&#x1f4d1; 1. 背景&#x1f4d1;2. 源码模块解析&#x1f4d1;2.1 读取PDF页数2.2 获取指定页的宽高尺寸2.3 裁剪单页PDF2.4 批量裁剪PDF 总结&#x1f4d1; 1. 背景&#x1f4d1; 接PyPDF2模块推荐博文中提到的实际需求&#xff08;将银行网站下载来的多页且单页多张…

10s了解 共享动画

1. 目的&#xff1a; 界面切换&#xff0c;两控件变化关联&#xff0c;看起来更丝滑流程 2.怎么配置 为关联两控件加上相同transitionName 3.在Navigation开启共享动画 跳转到的界面 接收共享动画 4.在Activity中开启共享动画 同3 &#xff0c;在共享动画两个View加上相同的t…

政安晨【示例演绎虚拟世界开发】(一):Cocos Creator 的 Hello World

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: AI虚拟世界大讲堂 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正。 前言 Cocos Creator是一款非常强大的游戏开发引擎&#xff0c;它有着优秀…

[HTML]Web前端开发技术29(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

centos7部署单机项目和自启动

centos7部署单机项目和服务器自启动 1.安装jdk和tomact1.1上传jdk、tomcat安装包1.2解压两个工具包1.3.配置并且测试jdk安装1.4.启动tomcat1.5.防火墙设置1.6配置tomcat自启动 2.安装mysql2.1卸载mariadb&#xff0c;否则安装MySql会出现冲突(先查看后删除再查看)2.2在线下载My…

Linux学习笔记9——adduser,passwd用户创建

Linux是一个多用户的操作系统&#xff0c;允许多用户访问&#xff0c;对系统进行一些操作&#xff0c;其中根用户为root拥有系统一切权限 其中&#xff0c;useradd是新建用户&#xff0c;passwd是给新建用户添加密码&#xff0c;su新建用户名&#xff0c;是切换到该用户对系统进…