40-阻塞信号与未决信号

有时候,你并不希望你的进程处理信号。比如接收到 SIGINT 后对它置之不理。linux 提供了一个函数 sigprocmask 来帮助我们实现此功能。

在一个进程中,保存了两个信号集(在PCB中),分别是阻塞信号集,还有一个未决信号集。当你使用 sigprocmask 的时候,就会修改阻塞信号集。有关未决信号集请阅读本文第 2 节。

如果一个信号加入阻塞信号集,该信号的信号处理函数就不会被调用。

1. sigprocmask 函数

此函数用于修改阻塞信号集。

1.1 函数原型

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

1.2 函数介绍

实际上,该函数不仅可以阻塞你指定的信号,也可以将之前阻塞的信号撤销。具体是通过 how 参数来控制的。

1.3 参数及返回值

  • how 参数

    • SIG_BLOCK 该选项表示将 set 参数指示的信号集中的信号添加到进程阻塞集中
    • SIG_UNBLOCK 该选项与功能 SIG_BLOCK 相反,表示将进程阻塞信号集中指定的信号删除
    • SIG_SETMASK 该选项表示将进程阻塞信号集直接设定为你指定的 set
  • set 参数

表示你指定的信号集合

  • oldset

返回旧的阻塞信号集

  • 返回 int

    0 表示成功,-1 失败。

2. 未决信号

初学者估计看到这个词就懵圈了,我第一次也挺懵圈,搞不懂为啥有些书把简单的东西搞复杂。

说的通俗点,未决信号,就是你的进程已经接收到了信号了,只是还没被信号处理函数处理的那些信号。

特别说明:虽然未决信号的定义是上面这样,但是这里我们需要更加具体一点,未决信号特指进程收到且被阻塞的信号。

当你的进程一收到信号且该信号被阻塞,它首先进入到未决信号集中(就是一个 sigset_t),当未决信号集中的信号被信号处理函数(你自己定义的或者系统默认的)处理,就会从未决信号集中删除。

你可以使用 sigpending 函数获取未决信号集。它的函数原型如下:

int sigpending(sigset_t *set);

使用起来也是相当简单。

3. 实例

该程序的功能是先把 SIGINT、SIGTSTP 加入到了进程阻塞信号集中去。接下来,每隔一秒打印一次未决信号集,第 10 次的时候,又把 SIGINT 信号从阻塞信号集中删除。

  • 代码
// sigblock.c
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void printsigset(const sigset_t *set)
{
  for (int i = 1; i <= 64; i++) {
    if (i==33) putchar(' ');
    if (sigismember(set, i) == 1)
      putchar('1');
    else
      putchar('0');
  }
  puts("");
}

void handler(int sig) {
  if (sig == SIGINT) printf("hello SIGINT\n");
  if (sig == SIGQUIT) printf("hello SIGQUIT\n");
}

int main() {
  printf("I'm %d\n", getpid());

  sigset_t st, oldst;
  sigemptyset(&st);
  sigaddset(&st, SIGINT);
  sigaddset(&st, SIGTSTP);
  sigprocmask(SIG_BLOCK, &st, &oldst);
  printf("new set:");
  printsigset(&st);
  printf("old set:");
  printsigset(&oldst);


  if (SIG_ERR == signal(SIGINT, handler)) {
    perror("signal SIGINT");
    return 1;
  }

  if (SIG_ERR == signal(SIGQUIT, handler)) {
    perror("signal SIGQUIT");
    return 1;
  }

  puts("");

  int n = 0;

  while(1) {
    sigpending(&st);
    printsigset(&st);
    puts("");
    sleep(1);

    if (n == 10) {
      sigset_t tmp;
      sigemptyset(&tmp);
      sigaddset(&tmp, SIGINT);
      sigprocmask(SIG_UNBLOCK, &tmp, NULL); 
    }   

    ++n;
  }
  return 0;
}
  • 编译和运行
$ gcc sigblock.c -o sigblock
$ ./sigblock

运行过程中,按下 Ctrl + CCtrl + Z 程序都没反应,但是可以看到未决信号集中这两个信号比特位被置位。10 秒后,SIGINT 从阻塞信号集中被删除,即使没有按下 Ctrl + C 发送信号,信号处理函数也被执行,这之后发现未决信号集中的 SIGINT 比特位被置 0.

  • 结果
I'm 5939
new set:01000000000000000001000000000000 00000000000000000000000000000000
old set:00000000000000000000000000000000 00000000000000000000000000000000

00000000000000000000000000000000 00000000000000000000000000000000

00000000000000000000000000000000 00000000000000000000000000000000

^C01000000000000000000000000000000 00000000000000000000000000000000 // 此处按下了 ctrl c

01000000000000000000000000000000 00000000000000000000000000000000

^Z01000000000000000001000000000000 00000000000000000000000000000000 // 此处按下了 ctrl z

^\hello SIGQUIT // 此处按下了 ctrl \
01000000000000000001000000000000 00000000000000000000000000000000

01000000000000000001000000000000 00000000000000000000000000000000

01000000000000000001000000000000 00000000000000000000000000000000

01000000000000000001000000000000 00000000000000000000000000000000

01000000000000000001000000000000 00000000000000000000000000000000

01000000000000000001000000000000 00000000000000000000000000000000

hello SIGINT // 执行到这里 SIGINT 从阻塞信号集中被删除
00000000000000000001000000000000 00000000000000000000000000000000 

4. 总结

  • 理解进程PCB的两个信号集(阻塞信号集和未决信号集)
  • sigprocmask 用来修改阻塞信号集
  • sigpending 用来获取未决信号集(不能修改)

最后,有两个信号是无法被阻塞的,你知道是哪两个信号吗?

扩展:本质上,未决信号集是一个抽象概念,PCB 中也不存在这样的成员,实际上,sigpending 函数内部实现是将进程已经接收到的信号集和阻塞信号集进行与操作,即 current->singal & current->blocked 计算得到未决信号集。current 表示当前进程 PCB.

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页