看到这个标题有同学会产生一些疑问(手动黑人问号),信号能被装进容器吗?答曰:能。
为什么需要容器来装信号?其实还是方便 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 。