Linux信号处理浅析

一、信号从发送到被处理经历的过程

1、常见概念

(1) 信号阻塞

阻塞,即被进程拉黑,信号被发送后,分为两种情况,一种是被阻塞了(被拉黑了),一种是没有被阻塞。

(2) 信号未决

在信号被进程处理之前的过程,都可以称为信号未决

(3) 信号递达

信号被进程处理,我们称为信号递达

(4) 信号忽略

进程收到信号后直接忽略,不进行处理,是否还有默认处理呢?(忽略中断信号,验证是否会中断)

(5) 阻塞信号集

指一个进程中当前阻塞而不能够递送给该进程的信号集

(6) 未决信号集

指当前进程未处理的信号集,收到阻塞的信号,未决信号集该信号位会置1,解除阻塞后会置0。

2、信号保存的方式(内核级)

block表:对应的信号是否被阻塞,0表示不阻塞,1表示阻塞

pending表信号是否被收到,0表示未收到该信号,1表示收到该信号

handler表:表示对应信号的处理方式(函数),存放的是函数的地址(函数指针)

3、信号从发送到被处理

第一列表示第 N 号信号,以第二行为例,阻塞信号为 1 ,说明该信号被阻塞了(相当于在进程的黑名单里),后面的未决信号是0还是1都无所谓了

以第四行为例,阻塞信号为0,该信号没有被阻塞,未决信号为1,说明收到这个信号了,对应的处理方式是 sighandler函数,那就会执行该函数

注意:

SIG_DFL(signal default):信号默认处理方式

SIG_IGN(signal ignore):忽略信号
 

总结

先看block,如果block为1,即信号被拉黑,是否收到信号都不重要了

如果block不为1,那就再看pending是否为1,即是否收到信号

二、信号集操作(信号保存)

阻塞信号其实就是要修改block表,但是要怎么修改呢?如果只是传递单个信号,或许可以直接以某种方式告诉OS我希望阻塞哪个信号,但是,如果要修改多个信号呢??OS给出了一种方案来解决传递单个或者多个信号,那就是直接传递位图!!

收到一个信号之后执行信号处理函数,在执行信号处理函数的过程中如果又来了一个相同的信号,那么这个信号将会被阻塞,直到信号处理函数执行完之后,再响应被阻塞的信号,注意如果信号被阻塞期间又收到了该信号,那么多个信号的处理会被合并为1次。

信号集就是用来记录当前收到了哪个信号,会把当前信号的标志位置成“正在处理”,如果此时再收到该信号,那么信号就阻塞等待,使用数据类型sigset_t表示信号集,在Linux中该类型是一个32位无符号整数,这是因为在Linux中定义了32种信号,每一个信号用32位无符号整型变量中的一位来标志,如果该位置为1,那么表示正在处理该信号,如果置为0表示可以处理该信号。

未决信号集保存当前为处理的信号集。

信号屏蔽字又叫阻塞信号集,是指一个进程中当前阻塞而不能够递送给该进程的信号集。每个进程都有一个信号屏蔽字,它规定了当前要阻塞递达到该进程的信号集

1、特定的数据类型sigset_t

既然要传递位图,要用什么来表示位图呢?第一想法是int/uint32_t,不同的操作系统,实现位图的方式可能有所不同,Linux操作系统不光给我们提供了 保存位图的数据类型 sigset_t,还有对应的函数来操作位图(下面说)

——》sigset_t 可以看作是一个信号集,保存着1~31号信号的状态,0表示没收到,1表示收到了

2、操作信号集的函数

(1) sigemptyset函数,清空信号集,全部位置置0。GUN C库中的非标准函数

int  sigemptyset ( sigset_t  *set);

成功返回0,失败返回-1

(2) sigfillset函数:初始化信号集,全部置1。GUN C库中的非标准函数

int  sigfillset( sigset_t  *set);

成功返回0,失败返回-1

(3) sigaddset函数:向信号集中添加一个信号,将对应信号置1。标准函数

假设一开始所有的比特位都是 0(0000 0000 ...),如果我们希望把第二个信号阻塞,那么就需要先把信号集的第二个比特位设置为1(0100 0000....),然后再传给信号阻塞函数

int  sigaddset( sigset_t  *set, int  signum);

 第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1

(4) sigdelset函数:从信号集中删除一个信号,将对应信号置0。标准函数

(假设前面已经阻塞了2号信号)这个时候的比特位显示为0100 0000....,现在我们不希望阻塞2号信号,那么需要把第二个位置(信号)设置为 0 (0000 0000....)

int  sigdelset( sigset_t  *set, int  signum);

 第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,成功返回0,失败返回-1

(5) sigismember函数:判断某个信号是否为信号集的成员(判断是否在信号集中)。标准函数

如果对应位置(信号)的比特位为1,说明在信号集中,如果为0,说明不在信号集中

int  sigismember( sigset_t  *set, int  signum);

成功返回1,失败返回0

如果使用sigemptyset()初始化信号集(信号集的所有位置0),此时可以接收到所有的信号

如果使用sigfillset()初始化信号集(信号集的所有位置1),此时屏蔽所有信号,当然SIGKILL和SIGQUIT信号是不能屏蔽的

sigaddset()和sigdelset()的意义在于指定屏蔽某信号或者接收某信号
 

int  sigprocmask (int  how,  const  sigset_t  *restrict set,  sigset_t  *restrict oset);

how取值有下列三个:

SIG_BLOCK :set中包含的是希望阻塞的附加信号,合并set中的信号集

SIG_UNBLOCK:set中包含的是希望解除阻塞的信号,删除set中的信号集

SIG_SETMASK:set中包含的是现有屏蔽字的代替值,替换set中的信号集

若oset是非空指针,则返回进程的当前信号屏蔽字。

若set为空,则进程信号屏蔽字不变,how值无意义。

另外,注意一点,不可以阻塞SIGKILL和SIGSTOP信号。

注意:调用该接口将信号设置为非阻塞时,若该信号已经是阻塞状态,则在该函数返回前马上会发给该线程

int  sigpending ( sigset_t  *set );

此函数通过set返回当前检查未决的信号集。其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。

signal 函数的使用方法简单,但并不属于 POSIX 标准,在各类 UNIX 平台上的实现不尽相同,因此其用途受到了一定的限制。而 POSIX 标准定义的信号处理接口是 sigaction 函数。

int  sigaction ( int  signum,  const struct  sigaction  *act, struct  sigaction  *oldact ) ;

signum:要操作的信号。

act:要设置的对信号的新处理方式。

oldact:原来对信号的处理方式。

返回值:0 表示成功,-1 表示有错误发生。

struct  sigaction
{
        void (*sa_handler)(int);
        void (*sa_sigaction)(int,  siginfo_t *,  void*);
        sigset_t  sa_mask;
        int  sa_flags;
        void (*sa_restorer)(void);
};

这个结构体中,成员 sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。成员sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。当 sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。

sa_mask 用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。

sa_flags 用于指定信号处理的行为,它可以是一下值的“按位或”组合。

◆ SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
◆ SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。
◆ SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。
◆ SA_NODEFER:一般情况下,当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记,那么在该信号处理函数运行时,内核将不会阻塞该信号。
◆ SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL。
◆ SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

re_restorer 是一个已经废弃的数据域,不要使用。

可重入函数

一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。一个函数要被重入,只有两种情况:

1、多线程同时执行这个函数

2、函数自身(可能经过多层调用之后)调用自身

一个函数可重入,表示这个函数被重入后不会产生任何不良后果。要成人可重入函数必须有以下特定:

1、不使用任何(局部)静态或全局的非const变量

2、不返回任何(局部)静态或全局的非const变量的指针

3、依赖调用方提供参数

4、不依赖任何单个资源的锁

5、不调用任何不可重入的函数

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

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

相关文章

RT-Thread:SPI万能驱动 SFUD 驱动Flash W25Q64,通过 STM32CubeMX 配置 STM32 SPI 驱动

关键词:SFUD,FLASH,W25Q64,W25Q128,STM32F407 说明:RT-Thread 系统 使用 SPI万能驱动 SFUD 驱动 Flash W25Q64,通过 STM32CubeMX 配置 STM32 SPI 驱动。 提示:SFUD添加后的存储位置 1.打开RT-Thread Sett…

K8S的部署策略,重建更新和滚动更新

Deployment Strategies 部署战略 When it comes time to change the version of software implementing your service, a Kubernetes deployment supports two different rollout strategies: RecreateRollingUpdate 当需要更改实施服务的软件版本时,Kubernetes …

跟着我学Python进阶篇:02.面向对象(上)

往期文章 跟着我学Python基础篇:01.初露端倪 跟着我学Python基础篇:02.数字与字符串编程 跟着我学Python基础篇:03.选择结构 跟着我学Python基础篇:04.循环 跟着我学Python基础篇:05.函数 跟着我学Python基础篇&#…

力扣2696. 删除子串后的字符串最小长度

Problem: 2696. 删除子串后的字符串最小长度 文章目录 思路解题方法复杂度Code 思路 可以知道能够消除的只有AB 和CD 的者两种排列顺序方式,但是也许在发生一次消除后还会引发后续的消除可能性。 元素从前向后进行检测,如果是A或者C进行标记入栈&#xf…

Linux C/C++ 显示NIC流量统计信息

NIC流量统计信息是由操作系统维护的。当数据包通过NIC传输时,操作系统会更新相关的计数器。这些计数器记录了数据包的发送和接收数量、字节数等。通过读取这些计数器,我们可以获得关于网络流量的信息。 为什么需要这些信息? 可以使用这些信息来监控网络…

Java建筑工程建设智慧工地源码

智慧工地管理平台依托物联网、互联网,建立云端大数据管理平台,形成“端云大数据”的业务体系和新的管理模式,从施工现场源头抓起,最大程度的收集人员、安全、环境、材料等关键业务数据,打通从一线操作与远程监管的数据…

C //练习 5-4 编写函数strend(s, t)。如果字符串t出现在字符串s的尾部,该函数返回1;否则返回0。

C程序设计语言 (第二版) 练习 5-4 练习 5-4 编写函数strend(s, t)。如果字符串t出现在字符串s的尾部,该函数返回1;否则返回0。 注意:代码在win32控制台运行,在不同的IDE环境下,有部分可能需要…

C++11_lambda表达式

文章目录 一、lambda表达式1.lambda的组成2.[capture-list] 的其他使用方法2.1混合捕捉 二、lambda表达式的使用场景1.替代仿函数 总结 一、lambda表达式 lambda表达式是C11新引入的功能,它的用法与我们之前学过的C语法有些不同。 1.lambda的组成 [capture-list] …

【Java万花筒】解锁Java并发之门:深入理解多线程

Java并发编程艺术:深度剖析多线程利器 前言 在当今软件开发的世界中,多线程编程已经变得愈发重要。面对多核处理器的普及和复杂的系统架构,开发人员需要深入了解并发编程的原理和实践,以充分发挥硬件的性能潜力。本文将带您深入…

【C++进阶05】AVL树的介绍及模拟实现

一、AVL树的概念 二叉搜索树的缺点 二叉搜索树虽可以缩短查找效率 但如果数据有序或接近有序 二叉搜索树将退化为单支树 查找元素相当于在顺序表中搜索元素,效率低下 AVL树便是解决此问题 向二叉搜索树中插入新结点 并保证每个结点的左右子树 高度之差的绝对值不超…

新能源汽车的三电指的是什么,作用有什么区别。

问题描述:新能源汽车的三电指的是什么,作用有什么区别。 问题解答: "新能源汽车的三电"通常指的是新能源汽车中的三大核心电气系统,包括:高压电池系统、电动机驱动系统和电子控制系统。这三者协同工作&…

Java诊断利器Arthas

https://arthas.aliyun.com/doc/https://arthas.aliyun.com/doc/ 原理 利用java.lang.instrument(容器类) 做动态 Instrumentation(执行容器) 是 JDK5 的新特性。使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent)&…

汽车IVI中控开发入门及进阶(十二):手机投屏

前言: 汽车座舱有车载中控大屏、仪表/HUD多屏的显示能力,有麦克风/喇叭等车载环境更好的音频输入输出能力,有方控按键、旋钮等方便的反向控制输入能力,还有高精度的车辆数据等。但汽车座舱中控主机硬件计算能力升级迭代周期相对较长,汽车的应用和服务不够丰富。现在很多汽…

.NetCore部署微服务(二)

目录 前言 概念 一 Consul注册服务中心 1.1 consul下载 1.2 consul运行 二 服务注册 2.1 安装Consul包 2.2 修改配置文件 2.3 注入Consul服务 2.3 修改Controller,增加HealthCheck方法 三 运行服务 3.1 docker运行服务 前言 上一篇讲到微服务要灵活伸缩…

「超级细菌」魔咒或将打破,MIT 利用深度学习发现新型抗生素

作者:加零 编辑:李宝珠、三羊 MIT 利用图神经网络 Chemprop 识别潜在抗生素,特异性杀死鲍曼不动杆菌。 自然界中充满了各种各样的微生物,例如结核杆菌(导致肺结核)、霍乱弧菌(导致霍乱&#…

数据结构实验4:链表的基本操作

目录 一、实验目的 二、实验原理 1. 节点 2. 指针 3.链表的类型 3.1 单向链表 3.2 双向链表 3.3 单向循环链表 3.4 双向循环链表 4. 单链表的插入 4.1 头插法 4.2 尾插法 4.3 在指定位置插入元素 5. 单链表的删除 5.1 删除指定数值的节点 5.2 删除指定位置的节点 …

Pytorch从零开始实战16

Pytorch从零开始实战——ResNeXt-50算法的思考 本系列来源于365天深度学习训练营 原作者K同学 对于上次ResNeXt-50算法,我们同样有基于TensorFlow的实现。具体代码如下。 引入头文件 import numpy as np from tensorflow.keras.preprocessing.image import Ima…

Rust基础类型之布尔类型和字符

布尔类型 Rust 中的布尔类型为 bool,仅仅有两个值,true 和 false。比如下方代码: let flag1 true; let flag2: bool false;字符 Rust 中的字符类型是 char,值用单引号括起来。 fn main() {let char1 z;let char2: char ℤ…

TensorRt(5)动态尺寸输入的分割模型测试

文章目录 1、固定输入尺寸逻辑2、动态输入尺寸2.1、模型导出2.2、推理测试2.3、显存分配问题2.4、完整代码 这里主要说明使用TensorRT进行加载编译优化后的模型engine进行推理测试,与前面进行目标识别、目标分类的模型的网络输入是固定大小不同,导致输入…

Java单元测试 1.SpringBoot 2.普通java工程

1)SpringBoot工程 1.注意:新建SpringBoot工程时,一定要根据你当前的jdk版本选择好SpringBoot的版本,比如:jdk为11,则不能创建3.x的SpringBoot工程,有时候2021版本的idea强制我创建3.x版本的SpringBoot&…