39-装信号的容器 sigset

看到这个标题有同学会产生一些疑问(手动黑人问号),信号能被装进容器吗?答曰:能。

为什么需要容器来装信号?其实还是方便 linux 内核处理信号,当然也方便用户程序处理。试想一下,如果同时发送了好多不同的信号给进程,然而进程一次只能处理一个信号,所以进程得把接收到的信号先保存起来才行。

1. sigset_t

POSIX 定义一个数据结构 —— sigset_t ,从名称也可以看出这是通过 typedef 定义的数据类型。本质上它是一个 64 位宽度的整数(有符号无符号都无所谓了)。

为何叫 sigset ? 既然出现了 set,这就表明了这是一个集合,所以每种信号只会在这个集合里出现一次。前面你已经知道,信号的编号是从1 到 64 这个范围。而 sigset_t 正好是一个 64 位整数,这正好可以让每一个比特位表示一个信号。所以有时候 sigset_t 也被称为信号位图。

2. 操作 sigset_t 的函数

既然 sigset_t 是一种数据结构,那就有操作这种数据结构的一系列函数了。操作 sigset_t 的函数主要有 5 个。

  • sigemptyset (清空集合,所有比特位置 0)
  • sigfillset (填充集合,所有比特位置 1)
  • sigaddset (添加信号到集合,将某一比特位置 1)
  • sigdelset (删除某个信号,将某一比特位置 0)
  • sigismember (是否存在某个信号,判断某一比特位是否为 1)

这些函数的功能都非常简单易懂,后面我用例子来说明。

3. 实例

这段代码稍微有点长,这里分成几个小的实验来拆解它。(完整代码见 3.7 节)

3.1 打印 sigset_t 的函数

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("");
}

这个函数主要用来打印 sigset_t 中所有的比特位。

3.2 验证 sigset_t 是64位整数

unsigned int test[2] = {0xf0f0f0f0, 0xf0f0f0f0};
printsigset((sigset_t*)test);

打印后的结果为:

00001111000011110000111100001111 00001111000011110000111100001111

3.3 创建一个 sigset_t

sigset_t st; 
printsigset(&st);

由于 sigset_t 未初始化,所以这里打印的结果是随机的。

3.3 填充 sigset_t

sigfillset(&st);
printsigset(&st);

结果:

11111111111111111111111111111110 01111111111111111111111111111111

上面的结果发现有 2 个比特位为 0,他们分别是信号 32 和 33 对应的位置,你可以通过 kill -l 查看下,为什么出现这样的结果。

3.3 清空 sigset_t

sigemptyset(&st);
printsigset(&st);

结果:

00000000000000000000000000000000 00000000000000000000000000000000

清空集合后,所有的比特位都被置 0 了。

3.4 添加信号

sigaddset(&st, SIGHUP);
sigaddset(&st, SIGINT);
sigaddset(&st, SIGKILL);
sigaddset(&st, SIGSYS);
sigaddset(&st, SIGRTMIN);
sigaddset(&st, SIGRTMAX);
printsigset(&st);

上面这段代码一共添加了 6 个不同的信号。

结果:

11000000100000000000000000000010 01000000000000000000000000000001

可以发现信号对应的比特位都被置 1 了

3.5 删除信号

sigdelset(&st, SIGKILL);
printsigset(&st);

这里只删除了信号 9(SIGKILL).

结果:

11000000000000000000000000000010 01000000000000000000000000000001

发现第 9 个比特位被置 0了。

3.6 判断集合里是否存在某个信号

if (sigismember(&st, SIGKILL)) {
  printf("SIGKILL is member\n");
}
if (sigismember(&st, SIGINT)) {
  printf("SIGINT is member\n");
}

结果:

SIGINT is member

因为 SIGKILL 刚刚被删除了,所以这里只打印了 SIGINT.

3.7 完整代码

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

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


int main() {
  sigset_t st; 
  printf("1. create set\n");
  printsigset(&st);

  printf("\n2. vertify sigset_t is a 64-bit integer\n");
  unsigned int test[2] = {0xf0f0f0f0, 0xf0f0f0f0};
  printsigset((sigset_t*)test); // 这种方法不被推荐,仅供测试用。


  // fill set
  printf("\n3. fill set\n");
  sigfillset(&st);
  printsigset(&st);

  // empty set
  printf("\n4. empty set\n");
  sigemptyset(&st);
  printsigset(&st);

  // add sig
  printf("\n5. add SIGHUP(1), SIGINT(2), SIGKILL(9), SIGSYS(31), SIGRTMIN(34) and SIGRTMAX(64) to set\n");
  sigaddset(&st, SIGHUP);
  sigaddset(&st, SIGINT);
  sigaddset(&st, SIGKILL);
  sigaddset(&st, SIGSYS);
  sigaddset(&st, SIGRTMIN);
  sigaddset(&st, SIGRTMAX);
  printsigset(&st);

  // delete sig
  printf("\n6. delete SIGKILL from set\n");
  sigdelset(&st, SIGKILL);
  printsigset(&st);

  // is member
  printf("\n");
  if (sigismember(&st, SIGKILL)) {
    printf("SIGKILL is member\n");
  }
  if (sigismember(&st, SIGINT)) {
    printf("SIGINT is member\n");
  }

  return 0;
}

4. 总结

  • 掌握信号位图数据结构 sigset_t
  • 掌握操作 sigset_t 的 5 个函数

虽然可以使用整数直接赋值的方法来修改 sigset_t 的内容,但是这种方法是不被推荐的。也许在这个系统上是用 64 位整数来存储数据,但是你不能保证别的系统上也这样。所以我们尽量还是使用前面讲到的 5 个操作函数来处理 sigset_t 。

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