一个信号产生后,它可能有两种状态。
未决状态:该信号没有被处理,正在等待处理。
递达状态:该信号已被处理(忽略、捕捉、执行默认动作)
而在进程的 PCB 中存在着阻塞信号集和未决信号集。
阻塞信号集:被阻塞的信号的集合
未决信号集:没有被处理的信号的集合
当我们在阻塞信号集中添加某个信号之后,该信号将被阻塞,也就是说,如果此时进程收到了这个信号,将不会对其进行处理,并把它放入未决信号集中。
另外我们要知道,所有的信号集都是 sigset_t 类型的,该类型的结构如下:
我们可以在 /usr/include/bits/sigset.h 下可以找到这个类型的定义。
其中,sizeof(unsigned long int)的值是 4 ,我们就知道,其实这个类型是个包含 32 个元素的数组,对应着 32 个信号,当该元素的值为 1 时,说明该信号在该信号集中存在;而当该元素为 0 时,说明该信号不被包含在该信号集中。
所以针对于未决信号集和阻塞信号集之间的关系,可以具体描述为:
信号产生时,处于未决状态,进程收到该信号后,把它放在未决信号集等待处理,处理之前需要做一件事情,判断阻塞信号集中该信号对应的标志位是否为1,如果为0,则处理该信号;如果为1,则不处理,该信号将一直留在未决信号集中,当阻塞信号集中,该信号的标志位变为0的时候,该信号才会被处理。
当我们想把一个信号阻塞,就需要把它放入阻塞信号集中,但PCB是在内核中的,我们无法直接对其进行操作,所以,系统提供了一些接口函数供我们使用。
首先我们通过 sigset_t 创建一个自定义的信号集,然后通过以下函数对其中的标志位进行修改。
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(sigset_t* set, int signo); //判断信号是否在该集合中存在,存在返回 1,不存在返回 0
在我们把自己需要阻塞的信号的标志位在自定义信号集中设置完成之后,就需要把它赋给PCB中的阻塞信号集。当然,也是通过系统提供的函数接口来完成的。
下面这个函数的作用就是 把自定义信号集中的内容设置到内核的阻塞信号集中。
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
参数:
how
SIG_BLOCK : 把自定信号集中为 1 的标志位添加到阻塞信号集 mask = mask | set
SIG_UNBLOCK : 解除自定义信号集中标志位为 1 的信号的阻塞,相当于 mask = mask & ~set
SIG_SETMASK: 直接用自定义的信号集覆盖该阻塞信号集,相当于 mask = set
set
自定义信号集
oldset
传出参数,把设置之前的状态保存在这里。如果对之前的状态不感兴趣传NULL。
还有下面一个函数需要知道,该函数的作用是读取当前进程的未决信号集。
int sigpending(sigset_t* set);
参数 set 作为传出参数,内核把未决信号集写入set
有了上面这些了解,我们写一个程序,对内核的未决信号集状态进行读取并输出到屏幕,其具体代码及注解如下:
#include
#include
#include
#include
int main(int argc, char* argv[])
{
//首先有一个自定义信号集,做一些我们自己的设置
sigset_t myset;
//把自定信号集清零
sigemptyset(&myset);
//手动屏蔽某几个信号
sigaddset(&myset, SIGINT); //2号信号 ,ctrl c
sigaddset(&myset, SIGQUIT); //3号信号,ctrl加反斜杠
sigaddset(&myset, SIGKILL); //9号信号,不能被阻塞、捕捉、忽略
//把自定义的信号集设置给内核的阻塞信号集
sigprocmask(SIG_BLOCK, &myset, NULL);
while(1)
{
sigset_t sig_recv;
sigpending(&sig_recv);
//判断1~31号信号
for(int i = 1; i < 32; ++i)
{
if(sigismember(&sig_recv, i)) //判断该信号是否被设置阻塞
{
printf("1");
}
else
{
printf("0");
}
}
printf("\n");
sleep(1);
}
}
其运行结果为:
让我们分析一下这一过程:
首先,打印出的标志位全是 0 ,说明PCB中的未决信号集为空。然后我们按下 ctrl c,会给进程发送一个2号信号SIGINT,本应被终止的进程却还在继续运行,因为我们在程序中把 SIGINT 信号添加到了阻塞信号集中, 所以该信号将被一直留在未决信号集中,则未决信号集的该信号的标志位由 0 变成了 1。从输出结果我们可以明显看到,接下来的 SIGQUIT 信号也是同理。
最后,我们也把 SIGKILL 信号添加到了阻塞信号集中,但还是可以通过发送该信号终止进程,证明了 SIGKILL 函数不能够被阻塞、忽略、捕获。