Linux进程信号之阻塞信号
前两天写了信号的基本概念以及如何去产生信号,欢迎大家戳博客链接:https://blog.csdn.net/apt1203JN/article/details/79955014
先来了解一下信号的三种状态:
* 信号递达(Delivery):实际执行信号的处理动作
* 信号未决(Pending):信号从产生到递达之间的状态
* 信号阻塞(Block):进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程接触对此信号的阻塞,才执行递达的动作
注意阻塞和忽略的不同之处:
* 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作
下面来看一下操作系统为每个进程提供的一套信号机制:
上图的三张表分别为:阻塞表(Block)、未决表(Pending)、递达表
这三张表分别对应三种不同的状态:信号阻塞、信号未决、信号递达之后的自定义捕捉
前两张表都是通过位图来存储的(决定了当前是否能收到信号),信号被阻塞就将相应位置置1,否则就置0。而在pending表中,当前位是1时表示信号存在,置0时表不存在。(pending表中的数据是判断信号是否存在的重要因素)
在上图当中:
SIGHUP信号(也就是(1)号信号)未阻塞也未产生过,当它抵达时执行默认处理工作。
SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
SIGQUIT信号从未产生过,一旦产生,SIGQUIT信号将被阻塞,它的处理动作是用户自定义的函数singhandler。
小问题:
倘若在进程解除对某种信号的阻塞之前这种信号产生过多次,将会如何处理?
解析:Linux下的实现方式:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
* 小知识点:普通信号允许丢失、实时信号是不允许的
信号集(sigset_t)
因为在上图中,阻塞和未决标志都可以用相同的数据结构(位图)来表示,因此,阻塞和未决标志可以用相同的数据类型(sigset_t)来存储。
sigset_t称之为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态
信号集操作函数
#include <signal.h>
// 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信息
int sigemptyset(sigset_t *set)
// 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信息包含系统支持的所有信号
int sigfillset(sigset_t *set)
int sigaddset(sigset_t *set, int signo)
int sigdelset(sigset_t *set, int signo)
int sigismember(const sigset_t *set, int signo)
注意点:(1)在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。
(2)初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号
信号屏蔽字(sigprocmask):通过调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)
#include <signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
// 返回值:若成功则为0,若出错则为-1
(1)若oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
(2)若set是非空指针,则更改进程的信号屏蔽字,参数how至少如何更改
(3)若oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字
来看一下how参数的可选值
* SIG_BLOCK:set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set
* SIG_UNBLOCK:set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set
* SIG_SETMASK:设置当前信号屏蔽字为set所指向的值,相当于mask=set
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达
#include <signal.h>
sigpending
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
下面来写代码看看:
阻塞2号SIGINT信号,让其处于未决状态,去看看信号集数据的变化
代码如下(程序运行,每秒打印各信号的未决状态,让我们来观察一下现象):
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void diaplaysigset(sigset_t *set)
{
int i = 0;
for (; i<32; i++)
{
//判断指定信号是否在目标集合中
if (sigismember(set, i))
{
putchar('1');
}
else
{
putchar('0');
}
}
puts("");
}
int main()
{
// 定义信号集对象,并清空初始化
sigset_t set, oset;
sigemptyset(&set);
// SIGINT:2号信号,相当有ctrl+C
sigaddset(&set, SIGINT);
// 设置阻塞信号集,阻塞SIGINT信号
sigprocmask(SIG_BLOCK, &set, NULL);
while (1)
{
// 获取未决信号集
sigpending(&oset);
diaplaysigset(&oset);
sleep(1);
}
return 0;
}
运行之:
由上图实现结果我们看到SIGINT信号被阻塞,所以按ctrl+C之后SIGINT信号处于未决状态,此时信号集数据由0表1。因为SIGQUIT信号未被阻塞,所以按ctrl+\终止程序。
上一篇: Linux信号的有关概念及使用