【Linux】信号保存
目录
一、信号的保存:
二、内核中的信号表示:
handler:
pending:
block:
sigset_t:
sigprocmask:
sigpending:
应用:
一、信号的保存:
当我们信号产生后,并不会立即处理这个信号,而是有一段时间窗口,在这个时间窗口内,信号没有被处理,而是保存起来了,那么进程是怎样保存对应的信号的呢?
我们知道,一定是OS给进程发送信号的,并且这个信号肯定是在我们进程的PCB中,所以在进程的PCB中就一定有一个变量来存放信号,这个变量就是一个整数int,当然,我们不能够仅仅看到这个整数,更要看到的是这是一个位图结构:
1、比特位的内容是1就证明着当前进程接受到了信号
2、第几个比特位被修改位1,就证明收到了几号信号3、那么OS向进程发送信号的本质就是OS修改PCB中,对应信号的位图结构的对应比特位,所以OS发信号就是首先找到进程里面描述信号的字段,然后将对应的比特位的位置修改为1
当OS向我们的进程发送多个相同信号的时候,并不会处理很多次,只会执行一次(这就相当于当我们在打游戏的时候,同时妈妈喊我们吃饭,会喊很多次,但是我们仅仅只会最后执行一次),毕竟位图只有0和1的结构,但是以上是对于普通信号的,如果是实时信号,那么发送了几次就要执行几次,是采用的是队列数据结构进行管理的
接下来了解关于信号的新概念:
信号产生:由上一篇文章所讲的几种产生方式
信号递达:实际执行信号的处理动作
信号未决:信号从产生到递达之间的状态
信号阻塞:也可叫做信号屏蔽,可随时屏蔽阻塞,并且在解除屏蔽之前都无法进行信号处理
当信号递达了就会有3种处理方式:
默认处理(SIG_DFL),忽略(SIG_IGN),自定义动作(handler)
二、内核中的信号表示:
如上,每一个进程都维护着如上的三张表,分别是block,pending和handler,其中block和pending表都是位图结构的,handler就是处理方式,这三张表是要横着看的
handler:
handler表就是当信号递达1时的处理方式,这是一个有着31个成员的函数指针数组(我们只考虑普通信号,所以也就是只有31个位置)格式为:返回值为空,参数为int的函数
如下是源码:
/* 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. */
如上,这个handler表的自定义中就是使用signal()函数重新设定对应的动作
pending:
当pending中的对应信号的值为1就证明这个信号是处于未决状态,所以如何记录已经产生的信号? ----- 将pending表中的对应的比特位置为1即可
所以pending表中存放的就是该信号产生到未处理的过程中的信号,这就是未决状态,
其实,我们信号的实现,是模拟的我们的硬件中断,这里的pinding信号的编号,就类似于中断编号,而函数指针数组,就类似于我们的中断向量表
block:
这个就是将对应的信号进行屏蔽了,这样,即使收到了该信号,也不会进行递达,除非对这个信号解除屏蔽,这里的block数组和pending数组类似,都是位图结构,
关于屏蔽的理解:屏蔽只是一种状态,和信号有没有产生没有任何关系,只是如果将信号屏蔽了,然后信号产生后就会存在未决,而如果信号没有屏蔽,而信号产生后就会到信号递达
信号忽略与信号阻塞:
这两者是不同的概念的,信号忽略是已经信号递达了,然后对这个信号的处理动作是忽略的
信号阻塞是信号屏蔽,当产生信号的时候对其屏蔽,使其一直处于信号未决状态,没有对其进行处理
注意:
和捕捉信号一样,9号信号和19号信号不能被屏蔽
sigset_t:
上述三张表是OS内核中的数据,所以在用户层是不能够直接对其进行修改的,所以OS就给我们用户层提供了接口,用来对信号进行处理的,当在用户层和内核层进行数据的交互的时候,就需要进行数据的拷贝,所以OS就需要设计输入输出型参数来进行处理,所以就有了位图结构:四个sigset_t 这个数据类型,这个数据类型底层上就是封装了一个unsigned long int 类型的数组,如下
我们是不能够直接对这个类型进行位操作的,需要使用OS提供的系统调用接口来进行操作
#inlcude <signal.h>库文件
int sigemptyset(sigset_t *set);清空信号集
int sigfillset(sigset_t *set);设置位图,将位图全部置为1
int sigaddset (sigset_t *set, int signo);向指定的信号集中添加特定的信号
int sigdelset(sigset_t *set, int signo);在指定的信号集当中,去掉一个信号
int sigismember(const sigset_t *set, int signo);判断一个信号是否在信号集当中
sigprocmask:
调用sigprocmask可以读取或者更改进程的的信号屏蔽字(阻塞信号集)
形参how有三个选项:
SIG_BLOCK希望添加至当前进程block表中阻塞信号,从set信号集中获取,相当于 mask |= set
SIG_UNBLOCK解除阻塞状态,也是从set信号集中获取,相当于 mask &= (~set)
SIG_SETMASK设置当前进程的block表为set信号集中的block表,相当于mask=set
如果oldset是非空指针,则读取进程的当前信号屏蔽字通过oldset参数传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改
如果oldset和set都是非空指针,则先将原来的信号屏蔽字备份到oldset里,然后根据set和how参数更改信号屏蔽字

如上,返回值:成功返回0,失败返回-1,设置错误码
sigpending:
这个函数就是获取当前进程的信号集,将其从内核中带到用户层
参数:将当前进程的未决信号集带出来
返回值:成功零被返回,失败-1被返回,错误码被设置
应用:
void Printpending(sigset_t pending)
{
for(int i = 31; i>=1; i--)
{
if(sigismember(&pending,i))
{
cout << "1";
}
else
{
cout << "0";
}
}
cout << endl;
}
int main()
{
sigset_t nset,oset;
//首先初始化
sigemptyset(&nset);
sigemptyset(&oset);
//然后在将2号信号添加到指定的信号集中
sigaddset(&nset,2);
//然后设置信号屏蔽字为nset所指向的值
sigprocmask(SIG_SETMASK,&nset,&oset);//这个时候就把2号信号屏蔽了
sigset_t pending;
int cnt = 0;
while(true)
{
int n = sigpending(&pending);
if(n < 0) continue;
//打印信号集
Printpending(pending);
cnt++;
sleep(1);
//解除屏蔽
if(cnt == 10)
{
sigprocmask(SIG_SETMASK,&oset,nullptr);//解除屏蔽
}
}
return 0;
}
如上,当发送2号信号后,就会是未决状态,当第10秒的时候解除屏蔽2号信号,就会立即执行该信号,就会退出进程