欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux进程信号之阻塞信号

程序员文章站 2022-07-12 10:30:58
...

前两天写了信号的基本概念以及如何去产生信号,欢迎大家戳博客链接:https://blog.csdn.net/apt1203JN/article/details/79955014
先来了解一下信号的三种状态:

* 信号递达(Delivery):实际执行信号的处理动作
* 信号未决(Pending):信号从产生到递达之间的状态
* 信号阻塞(Block):进程可以选择阻塞某个信号,被阻塞的信号产生时将保持在未决状态,直到进程接触对此信号的阻塞,才执行递达的动作

注意阻塞和忽略的不同之处:

* 阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在抵达之后可选的一种处理动作

下面来看一下操作系统为每个进程提供的一套信号机制:
Linux进程信号之阻塞信号
上图的三张表分别为:阻塞表(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  *setint  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;
}

运行之:
Linux进程信号之阻塞信号
由上图实现结果我们看到SIGINT信号被阻塞,所以按ctrl+C之后SIGINT信号处于未决状态,此时信号集数据由0表1。因为SIGQUIT信号未被阻塞,所以按ctrl+\终止程序。

相关标签: 信号 阻塞信号