信号在产生之后,到未被处理的这段时间内,是保存在进程的PCB结构体内的一张位图中的,位图的每个比特位的编号就代表着改信号是否产生,比特位为1表示该信号产生,0表示不存在。
本篇文章就来详细的解答信号在内核中具体的保存和处理方式。
注意,本文谈到的都是非实时信号。
一,三大集合表
1,block集合表代表着该信号是否被阻塞,如果该某个信号的block位图被修改为1,即表示该信号被阻塞,即使产生了该信号,进程也不会处理该信号。
2,pending集合代表的是该信号是否产生了,如果对应的比特位为1,代表着该信号已经产生,如果此时该信号未被阻塞,那么就会在合适的时候对该信号进行处理。(这个合适的时候就是当进程从内核态转化为用户态的时候)。 处理的方式就是对应handler表中的方式。
3,handler集合代表着该信号的处理方式,处理方式有三种
一是SIG_DFL,即该信号的默认处理方式,比如2号信号就是结束进程,那么当2号信号被处理的时候,进程就会被结束。
二是SIG_IGN,即忽略该信号。当该信号需要被处理的时候,什么也不做,就是对该信号进行忽略处理。
三是用户自定义方法,当我们用signal或者sigaction函数对某个信号设置了自定义的捕捉方法,那么该信号处理的时候就会调用我们自定义的方法,而不是默认的信号处理方法。
二,信号集
Linux系统为我们提供了对block和pending信号集的操作方式。
1,概念:
每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。 因此,未决和阻塞标志可以用相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型可以表示每个信号 的“有效”或“无效”状态。
阻塞信号集也叫做当 前进程的信号屏蔽字(Signal Mask)。
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有 效”和“无效”的含义是该信号是否处于未决状态。
未决状态是指一个信号产生但是还未被处理的状态。
2,信号集操作函数
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统 实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_ t变量。
#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信号是否存在于该信号集中 存在返回1 ,反之返回0
通过以上的介绍,我们就可以自己先设置好一个信号集,将某个信号添加到我们自定义的信号集中,然后再通过下面的 sigprocmask 函数将自定义的信号集设置到进程的内核block信号集中。
sigprocmask函数
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号 屏蔽字备份到oset里,然后 根据set和how参数更改信号屏蔽字。如果不需要备份老的信号屏蔽字就将oset设置为nullptr。
sigpending函数
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
下面我们就使用上面谈到的函数写一个小的实验。
实验内容:
1,自定义一个信号集,将2号信号设置阻塞,并将该信号添加到该进程的内核block表中并 读取该进程原来的block表,将该表存放在我们自定义的oldset信号集中。
2,对2号信号进行捕捉,并在10秒内发送2号信号,观察结果。
3,在执行10秒之后,再将原本的block信号集设置回内核中。再次发送2号信号,观察结果
代码:
#include <iostream>
#include <signal.h>
#include <unistd.h>
using namespace std;
void handler(int signo)
{
cout << "signal num : " << signo << " pid : "<< getpid() <<endl;
}
int main()
{
signal(2, handler);//对2号信号设置自定义捕捉方法
sigset_t set, oldset;
//对自定义信号集进行初始化
sigemptyset(&set);
sigemptyset(&oldset);
sigaddset(&set, 2);//将2好信号添加到set中
//将set设置到该进程的内核block表中,并将原本的block表的信息存放到oldset中。
sigprocmask(SIG_SETMASK, &set, &oldset);
int cnt = 10;
while(1)
{
//循环等待我们发送2号信号
sleep(1);
cout << "main pid : " << getpid() << endl;
if(--cnt == 0)
{
sigprocmask(SIG_SETMASK, &oldset, nullptr);
}
}
return 0;
}
实验现象:
我们可以看到当我们在10秒内发送2好信号的时候,对2号信号的自定义捕捉的行为并没有发生。
因为此时的block表中2号比特位的值为1,表示该信号是被阻塞的状态。即使pending中的位图为1,2号信号也不会被处理。
而在10秒过后,原本老的block信号集被设置回内核中,即恢复了对2号信号的阻塞,所以2号信号就执行了我们自定义的捕捉方法。