前言
上篇博客我们了解了进程信号的概念和信号如何产生。
本篇我们将学习进程信号如何保存。
一. 阻塞信号
首先我们需要一些预备知识
- 实际
执行信号
的处理动作称为信号递达
(Delivery)。- 信号
从产生到递达
之间的状态,称为信号未决
(Pending)。- 进程可以选择
阻塞
(Block)某个信号。被阻塞的信号产生时将保持在未决状态
,直到进程解除对此信号的阻塞,才执行递达的动作。- 注意,
阻塞和忽略是不同的
,只要信号被阻塞就不会递达,而忽略是递达之后可选的一种处理动作。
并且pcb的task_struct内部其实维护有三张表
pending 表
:位图结构
。 比特位的位置,表示哪一个信号;比特位的内容,代表是否收到该信号block 表
:位图结构
。比特位的位置,表示哪一个信号;比特位的内容,代表对应的信号是否需要被阻塞handler 表
:函数指针数组
,内部的函数指针,是有一个int的参数,返回值是void 的函数指针
该数组的下标,代表信号编号,数组的特定下标的内容,表示该信号的递达动作
二. 递达动作
在上一篇我们说进程对一个信号的处理有三种:默认动作,自定义动作,忽略
我们讲了自定义动作,其实就是我们自己编写,返回值是void,有一个int参数的函数,并用signal函数对特定信号进行捕捉。
而默认动作和忽略其实是在这样的
默认动作是SIG_DFL,忽略是SIG_IGN
二者其实都是将整型强转成函数指针类型,其中SIG_DFL就是Default action终止进程
三. 信号集
未决和阻塞
标志可以用相同的数据类型sigset_t
来存储,sigset_t称为信号集
,这个类型可以表示每个信号的“ 有效 ”或“ 无效 ”状态,在阻塞信号集中“ 有效 ”和“ 无效 ”的含义是该信号是否被阻塞,而在未决信号集中“ 有效 ”和“ 无效 ”的含义是该信号是否处于未决状态。
阻塞信号集
也叫做当前进程的信号屏蔽字(Signal Mask)
,这里的“屏蔽”应该理解为阻塞而不是忽略。
操作系统给我们提供了信号集类型
sigset其实也是位图,是长度更长的位图,因为一个unsigned long int 是4字节,也就是32位,而使用数组结构,就可以有更多的比特位了。
假如要访问第127位,是这样操作的
-val[ 127 / (sizeof( unsigned long int ) * 8 ) ],表示访问第3个unsigned long int
再访问127 % (sizeof( unsigned long int ) *8)
但是,我们不需要这样操作,操作系统给我们提供了相应操作的函数
四. 信号集操作函数
#include <signal.h>
头文件
int sigemptyset (sigset_t *set);将所有比特位都置为0
int sigfillset (sigset_t *set);将所有比特位都置为1
int sigaddset (sigset_t *set, int signo);添加第signo位信号
int sigdelset (sigset_t *set, int signo);删除第signo位信号
int sigismember(const sigset_t *set, int signo);判断signo信号是否则在该位图里
sigprocmask()
可以读取或更改
进程的信号屏蔽字(阻塞信号集)
oldset是输出型参数,返回旧的sigset_t,修改后可直接恢复
如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,how参数有如下几个可选值
SIG_BLOCK
:set包含了我们希望添加
到当前信号屏蔽字的信号,相当于mask=mask | set
SIG_UNBLOCK
:set包含了我们希望从当前信号屏蔽字中解除
阻塞的信号,相当于mask=mask&~set
SIG_SETMASK
:设置当前信号屏蔽字为set所指向的值,相当于mask=set
以下实验我们对2号信号进行阻塞
,并且可以获取原先的block表和改变后的block表
我们给set添加2号信号,然后再将使用
SIG_SETMASK
,将set直接赋值给当前进程的block表
,这样就成功阻塞了2号信号。从结果看到,我们在程序运行时,即使使用ctrl+c
也无法终止进程。
然后前5次打印oldset,我们是打印原先的block表,在第6次循环时,我们再获取当前进程的block表,可以发现,2号位置为1
,表明2号信号被阻塞了。
sigpending()
sigpending()函数可以获取当前进程的pending表
sigset_t * set:
输出型参数
,会将当前进程的pending表赋值给set
接下来我们再做一个实验,也是阻塞2号信号,同时我们获取pending表,然后发送2号信号,观察变化
#include<iostream>
#include<cassert>
#include<signal.h>
#include<unistd.h>
using namespace std;
//打印pending表
void printPending(const sigset_t &pending)
{
for(int signo=1;signo<=31;signo++)
{
//查询pending表内是否有signo号信号
if(sigismember(&pending,signo))
{
cout<<1;
}
else
{
cout<<0;
}
}
cout<<endl;
}
int main()
{
//信号集
//set是更改的信号集,oset获取旧的信号集
sigset_t set,oset;
//将两个信号集都置0
sigemptyset(&set);
sigemptyset(&oset);
//添加阻塞的信号,如2号信号
sigaddset(&set,2);
//阻塞信号
sigprocmask(SIG_BLOCK,&set,&oset);
//获取pending表
while(true)
{
sigset_t pending;
sigemptyset(&pending);
//获取pending表
int n=sigpending(&pending);
assert(n==0);
(void)n;//使用一下n变量,防止release版本assert被忽略,而引发的报错
//打印pending表
printPending(pending);
//休眠一下
sleep(1);
}
return 0;
}
结果如下:
可以看到,pending 表最开始是全0,也就是一个信号都没有收到,而当我们按下了
ctrl+c
,也就是发送了2号信号,可以看到pending表第2位变成了1
,又因为我们对2号信号进行了阻塞
,所以进程即使收到2号信号,pending表2号位置变成1,但是还是没有终止进程,因为我们阻塞了2号信号,进程并不会执行相应的动作。并且因为2号信号没有处理
,所以pending表中2号位置一直为1。
同时,我们也可以阻塞一段时间后,再使用sigprocmask恢复原先的block表
结束语
本篇内容到此就结束了,感谢你的阅读!
如果有补充或者纠正的地方,欢迎评论区补充,纠错。如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。