信号的发送方式有四种,在被发送后,我们知道如果暂时不处理信号,信号会被保存在进程控制块中,而且是以位图的方式保存;等时机合适了,就去处理收到的信号
下面会从内核的角度作进一步的分析,信号被发送后,可以被进程阻塞!!不仅如此,我们还能查看进程收到了哪些信号
目录
一、信号从发送到被处理经历的过程(内核角度)
1、信号常见概念
(1) 信号阻塞
(2) 信号未决
(3) 信号递达
(4) 信号忽略(与信号阻塞的区别)
2、信号保存的方式(内核级)
3、信号从发送到被处理
4、总结
二、信号集操作(信号保存)
1、特定的数据类型sigset_t
2、操作信号集的函数
(1) sigemptyset函数:清空信号集
(2) sigfillset函数:初始化信号集
(3) sigaddset函数:向信号集中添加一个信号
(4) sigdelset函数:从信号集中删除一个信号
(5) sigismember函数:判断某个信号是否为信号集的成员(判断是否在信号集中)
三、信号阻塞/解除阻塞函数 sigprocmask(修改block表)
1、参数解析
(1) 第一个参数 how
(2) 第二个参数 set
(3) 第三个参数 oset
2、小测试:屏蔽/阻塞 2号信号和8号信号
四、查看当前进程收到的所有信号 sigpending函数(获取pending表)
信号被发送后,分为两种情况,一种是被阻塞了(被拉黑了),一种是没有被阻塞
举一个例子,一个粉丝想给xx明星送礼物,礼物由经纪人代为接收。
——》如果被阻塞了,虽然礼物确实是收到了,但是送的礼物在黑名单里,经纪人不会递达给xx明星,所以最后该明星看不到这个礼物;
——》如果没有被阻塞,而且经纪人收到礼物了,这个时候xx明星就会去处理这件礼物。
在信号被进程处理之前的过程,都可以称为信号未决
信号被进程处理,我们称为信号递达
继续上面的例子,xx明星处理礼物的时候,可以选择忽略这件礼物,也可以在收到礼物以后给粉丝回信,所以信号忽略是进程处理信号的一种方式
而信号阻塞呢?进程压根看不到这个信号!!
下面我们通过一张图来理解,信号在进程控制块的存储方式是以位图的方式
block表:对应的信号是否被阻塞,0表示不阻塞,1表示阻塞
pending表:信号是否被收到,0表示未收到该信号,1表示收到该信号
handler表:表示对应信号的处理方式(函数),存放的是函数的地址(函数指针)
上面这个图我们要看一行
红色字代表第 N 号信号,以第二行为例,block 为 1 ,说明该信号被阻塞了(相当于在进程的黑名单里),后面的pending是0还是1都无所谓了
以第三行为例,block为0,该信号没有被阻塞,pending为1,说明收到这个信号了,对应的处理方式是 sighandler函数,那就会执行该函数
注意:
SIG_DFL(signal default):信号默认处理方式
SIG_IGN(signal ignore):忽略信号
先看block,如果block为1,即信号被拉黑,是否收到信号都不重要了
如果block不为1,那就再看pending是否为1,即是否收到信号
阻塞信号其实就是要修改block表,但是要怎么修改呢?如果只是传递单个信号,或许可以直接以某种方式告诉OS我希望阻塞哪个信号,但是,如果要修改多个信号呢??OS给出了一种方案来解决传递单个或者多个信号,那就是直接传递位图!!
既然要传递位图,要用什么来表示位图呢?第一想法是int/uint32_t,不同的操作系统,实现位图的方式可能有所不同,Linux操作系统不光给我们提供了 保存位图的数据类型 sigset_t,还有对应的函数来操作位图(下面说)
——》sigset_t 可以看作是一个信号集,保存着1~31号信号的状态,0表示没收到,1表示收到了
注意:这个位图和我们以往遇到的位图不一样,并不适用 &、|、^等操作
操作信号最基本的操作无非就是增删查改,下面就逐一来介绍
sigemptyset函数将位图所有的比特位置0。按照以往的习惯,清空不就是与上一个 0 吗?但是前面也说了,不适用!下面是这个函数的声明
参数是传入一个信号集的地址,成功返回0,失败返回-1。下面是使用方法
sigfillset函数将位图所有的比特位都置为1
参数是传入一个信号集的地址,成功返回0,失败返回-1。
假设一开始所有的比特位都是 0(0000 0000 ...),如果我们希望把第二个信号阻塞,那么就需要先把信号集的第二个比特位设置为1(0100 0000....),然后再传给信号阻塞函数
第一个参数是传入信号集的地址,第二个参数是要把哪个位置(信号)设置成1,返回值同上
(假设前面已经阻塞了2号信号)这个时候的比特位显示为0100 0000....,现在我们不希望阻塞2号信号,那么需要把第二个位置(信号)设置为 0 (0000 0000....)
参数解析同sigaddset函数
如果对应位置(信号)的比特位为1,说明在信号集中,如果为0,说明不在信号集中
成功返回1,失败返回0
信号阻塞也可以称为信号屏蔽,这里的mask是屏蔽的意思。下面是这个函数的声明,头文件依然是signal.h
信号的阻塞与否分为三种情况,也就对应了三个可选值
第一种,在原有的基础上,添加要阻塞的信号 —— SIG_BLOCK
第二种,在原有的基础上,解除一些信号的阻塞限制 —— SIG_UNBLOCK
第三种,按照自己传入的信号集来设置要阻塞的信号 —— SIG_SETMASK
这是一个输入型参数,输入你希望屏蔽或者阻塞的一个/多个信号
这是一个输出型参数,输出在你修改block之前的block表(或者叫做信号屏蔽字)
测试内容是这样,一开始,屏蔽2号和8号信号,5s后解除屏蔽,如果收到信号就打印收到了几号信号。具体代码如下(Makefile省略不展示)
这里我们测试两次,第一次测试,前5s内按下Ctrl + C,看一下是否会中止进程;第二次测试,5s后按下Ctrl + C,看一下解除以后是否会中止
第一次测试我们发现,按下Ctrl + C以后,进程继续运行,等到限制解除以后,看到自己收到了2号信号,然后就去处理2号信号
=======================sigpending函数=======================
不对pending位图进行修改,只是获取pending表里的内容。你或许会觉得奇怪,但不要忘记,进程会自己捕获信号,捕获到的信号由OS将pending表的对应位置由 0 改为 1 。获取pending表的函数声明如下
参数是一个输出型参数,我们可以通过sigismember函数查看是否收到了某个信号 。成功时返回0,失败返回-1
=======================结合信号阻塞案例测试=======================
现在我们可以把上面的printf("process is running") 换成打印pending表中的31个信号的位图
pending表信号集我们可以通过sigpending获取,但是要如何打印呢??
我们可以通过sigismember来看看每一个信号的接收状态,如果接收到了,打印1,否则打印0
从测试结果我们发现,即便信号被阻塞,pending表也会显示信号接收到了,一旦阻塞解除,就会展示给进程,让进程处理这个信号