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";
        else
            cout << "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 这两个函数的参数都是 信号集,前者是 屏蔽信号集,后者是 未决信号集
  • 在对 信号集 进行增删改查前,一定要先初始化
  • 信号在被解除 阻塞状态 后,很快就会 递达 了

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙中的九种布局概述

鸿蒙中的九种布局概述 概述 鸿蒙开发中包含就种布局&#xff0c;分别为线性布局、层叠布局、弹性布局、相对布局、栅格布局、媒体布局、列表、网格、轮播。 线性布局 线性布局通过Row和Column进行构建&#xff0c;是其他布局的基础。其中Row是水平方向排列&#xff0c;Colu…

电路设计(25)——4位数字频率计的multisim仿真及PCB设计

1.设计要求 使用4位数码管&#xff0c;显示输入信号的频率。完成功能仿真后&#xff0c;用AD软件&#xff0c;画出原理图以及PCB。 2.电路设计 输入信号的参数为&#xff1a; 可见&#xff0c;输入为168HZ&#xff0c;测量值为170HZ&#xff0c;误差在可接受的范围内。 3.PCB设…

Jenkins解决Host key verification failed (2)

Jenkins解决Host key verification failed 分析原因情况 一、用OpenSSH的人都知ssh会把你每个你访问过计算机的公钥(public key)都记录在~/.ssh/known_hosts。当下次访问相同计算机时&#xff0c;OpenSSH会核对公钥。如果公钥不同&#xff0c;OpenSSH会发出警告&#xff0c;避免…

【Java程序设计】【C00299】基于Springboot的仓库管理系统(有论文)

基于Springboot的仓库管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的仓库管理系统&#xff0c;本系统有管理员角色权限&#xff1b; 系统整体功能有&#xff1a;个人信息管理、仓库管理、物资管理、物资…

matplotlib子图绘制

文章目录 子图组合网格布局GridSpec matplotlib教程&#xff1a; 初步 子图 从绘图流程出发&#xff0c;【plt】图像有三个层级&#xff0c;依次是窗口、坐标系以及图像。在一个坐标系中可以有多条曲线&#xff0c;即表示多个图像&#xff1b;相应地在一个窗口中&#xff0c;…

文心一言 VS 讯飞星火 VS chatgpt (201)-- 算法导论15.2 6题

六、用go语言&#xff0c;证明&#xff1a;对 n 个元素的表达式进行完全括号化&#xff0c;恰好需要 n-1 对括号。 文心一言&#xff0c;代码正常运行&#xff1a; 在Go语言中&#xff0c;证明对n个元素的表达式进行完全括号化恰好需要n-1对括号&#xff0c;可以通过数学归纳…

将yolov8权重文件转为onnx格式并在c#中使用

yolo模型转ONNX 在yolov8中&#xff0c;我们将训练结果的.pt权重文件转换为onnx格式只需要使用ultralytics库中的YOLO类&#xff0c;使用pip安装ultralytics库&#xff0c;然后执行下面python代码 from ultralytics import YOLO# 加载YOLOv8模型 model YOLO("best.pt&q…

听力障碍的康复训练方法有哪些?

康复小知识 回声复语教育科技研究院 知识分享 听力障碍/概况 听力障碍是指“由于各种原因导致双耳不同程度的永久性听力障碍&#xff0c;听不到或听不清周围环境及言语声&#xff0c;以致影响日常生活和社会参与”。 聋儿的语言器官本身并不存在问题&#xff0c;通过现代科学…

数据结构链表力扣例题AC(3)——代码以及思路记录

160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 AC写法一 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {//思…

利用LaTex批量将eps转pdf、png转eps、eps转png、eps转svg、pdf转eps

1、eps转pdf 直接使用epstopdf命令&#xff08;texlive、mitex自带&#xff09;。 在cmd中进入到eps矢量图片的目录&#xff0c;使用下面的命令&#xff1a; for %f in (*.eps) do epstopdf "%f" 下面是plt保存eps代码&#xff1a; import matplotlib.pyplot as…

嵌入式中数据结构二叉树详解与实现

树是数据结构中的重中之重&#xff0c;尤其以各类二叉树为学习的难点。在面试环节中&#xff0c;二叉树也是必考的模块。本文主要讲二叉树操作的相关知识&#xff0c;梳理面试常考的内容。请大家跟随小编一起来复习吧。 本篇针对面试中常见的二叉树操作作个总结&#xff1a; 前…

分享从零开始学习网络设备配置--任务5.1 组建直连式二层无线局域网

任务要求 &#xff08;1&#xff09;组建直连式二层无线局域网&#xff0c;网络拓扑图如图 &#xff08;3&#xff09;路由器、交换机和AC等网络设备端口IP地址规划如表 &#xff08;4&#xff09;组建直连式二层无线局域网&#xff0c;配置AP上线、WLAN业务参数和实现STA能正…

程序员的副业发展

前言 之前总有小伙伴问我&#xff0c;现在没有工作&#xff0c;或者想在空闲时间做一些程序员兼职&#xff0c;怎么做&#xff0c;做什么&#xff0c;能赚点外快 因为我之前发别的文章的时候有捎带着说过一嘴我做一些副业&#xff0c;这里就说一下我是怎么做的&#xff0c;都…

es6 中的生成器 generator / 迭代器 / async /await 到底是个啥,使用场景

生成器 generator 到底是个啥 是一个函数 可以用来遍历数据结构是解决异步编程的一种方案进行数据流的生成和控制协程和状态机返回一个生成器对象/可迭代对象 生成器对象&#xff1a; 生成器对象是由生成器函数返回的对象&#xff0c;它符合迭代器协议&#xff08;Iterator Pr…

异步框架Celery在Django中的运用

参考博客&#xff1a;https://www.cnblogs.com/pyedu/p/12461819.html 参考视频&#xff1a;01 celery的工作机制_哔哩哔哩_bilibili 定义&#xff1a;简单灵活、处理大量消息的分布式系统&#xff0c;专注于实时处理异步队列&#xff0c;支持任务调度 主要架构&#xff1a; …

【C++那些事儿】C++入门 | 命名空间 | 缺省参数 | 引用 | 内联函数 | auto关键字 | 范围for循环 | nullptr

&#x1f4f7; 江池俊&#xff1a; 个人主页 &#x1f525;个人专栏&#xff1a; ✅数据结构冒险记 ✅C那些事儿 &#x1f305; 有航道的人&#xff0c;再渺小也不会迷途。 文章目录 前言1. C关键字(C98)2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入&输出4. 缺…

【Linux基础】vim、常用指令、组管理和组权限

Linux基础 1、目录结构2、vi和vim3、常用指令运行级别找回密码帮助指令时间日期指令搜索查找文件目录操作磁盘管理指令压缩和解压缩 4、组管理和组权限用户操作指令权限 1、目录结构 Linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录“/”&a…

StarRocks——滴滴OLAP的技术实践与发展方向

原文大佬的这篇StarRocks实践文章整体写的很深入&#xff0c;介绍了StarRocks数仓架构设计、物化视图加速实时看板、全局字典精确去重等内容&#xff0c;这里直接摘抄下来用作学习和知识沉淀。 目录 一、背景介绍 1.1 滴滴OLAP的发展历程 1.2 OLAP引擎存在的痛点 1.2.1 运维…

K线实战分析系列之十:市场进入犹豫不定状态——孕线形态

K线实战分析系列之十&#xff1a;市场进入犹豫不定状态——孕线形态 一、重要反转形态二、其他反转形态三、孕线形态四、孕线形态和吞没形态的区别五、十字孕线形态六、总结孕线形态 一、重要反转形态 伞形线吞没形态乌云盖顶刺透形态启明星形态黄昏星形态十字启明星与十字黄昏…

Java之线程池:线程池常用类、接口;线程池执行流程,配置参数,分类

线程池 什么是线程池&#xff1f; 线程池&#xff1a;一种基于池化思想管理和使用线程的机制 线程池常用类和接口 ExecutorService接口&#xff1a;进行线程池的操作访问Executors类&#xff1a;创建线程池的工具类ThreadPoolExecutor及其子类&#xff1a;封装线程池的核心参…