信号集与信号阻塞详解

信号集

多个信号可以用一个称之为信号集的数据结构来表示,不同的信号的编号可能超过一个整型量所包含的位数,所以不能用一个整型量表示信号集。POSIX定义信号量的数据类型为sigset_t,并定义了下列5个处理信号集的函数

1
2
3
4
#include<signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
//成功返回0,失败返回-1

函数sigemptyset初始化由set指向的信号集,清除其中的所有信号,即初始化为空。函数sigfillset初始化由set指向的信号集,使其包含所有信号(包括实时信号)。
在使用信号集之前,必须使用sigemptyset或sigfillset来初始化信号集,因为C编译程序不对自动变量进行初始化,会将不赋初值的静态变量和全局变量初始化为0。而这与给定系统上信号集的实现是否相对应并不清楚,即使对应,也会导致可移植性差。

信号集初始化后,就可以在该信号集中增、删特定信号。

1
2
3
4
5
#include<signal.h>
int sigaddset(sigset_t *set,int sig);
int sigdelset(sigset_t *set,int sig);
//成功返回0,失败返回-1
int sigismember(const sigset_t *set,int sig); //是返回1,不是返回0

sigaddset将指定编号的信号添加到已有的信号集中,sigdelset将指定编号的信号从已有信号集中删除。
sigismember用来测试信号sig是不是信号集set的成员。
一个简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<signal.h>
#include<unistd.h>
int main(){
sigset_t *set;
if(sigemptyset(set) == -1)
exit(1); //初始化错误
if(sigaddset(set,SIGINT) == -1)
exit(1); //添加错误
if(sigdelset(set,SIGALRM) == -1)
exit(1); //删除错误
if(sigismember(set,SIGCHLD) == 1)
printf("yes\n");
else printf("no\n");
return 0;
}

信号阻塞

内核为每个进程维护了一个信号掩码,即一组信号,并将阻塞这些信号对该进程的传递,如果将某个阻塞了的信号发送给该进程,那么对该信号的传递将延后,直至从进程信号掩码中移除该信号,从而解除阻塞为止。

1
2
3
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
//成功返回0,出错返回-1

使用sigprocmask既可以修改进程的信号掩码,也可以获取现有掩码,或者同时修改和获取。how参数有3个不同的取值对应三种不同的操作:

  • SIG_BLOCK

    将set所指信号集内的所有信号都添加到信号掩码中,即或操作

  • SIG_UNBLOCK

    将set所指信号集内的信号从信号掩码中移除,即使要解除阻塞的信号当前未处于阻塞状态也不会出错

  • SIG_SETMASK

    将set指向的信号集赋给信号掩码

上述情况下,如果oldset不为空,则将其指向一个sigset_t结构缓冲区,用于返回之前的信号掩码。如果想获取当前的信号掩码但是不对其做更改,可以将set置为空,这时将忽略how参数。系统将忽略试图阻塞SIGKILL和SIGSTOP信号的请求,此时sigprocmask不会作为,也不会出错。
同样看一段简单的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<signal.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
void handler(int sig){
printf("ouch\n");
}
int main(){
sigset_t oldset,newset;
sigemptyset(&oldset);
sigemptyset(&newset);
if(sigaddset(&newset,SIGINT))
exit(1);
if(sigprocmask(SIG_BLOCK,&newset,&oldset) == -1)
exit(1);
if(signal(SIGINT,handler) == SIG_ERR)
exit(1);
for(int j = 1;i<10;j++){
printf("%d\n",j);
if(j>5)
if(sigprocmask(SIG_SETMASK,&oldset,NULL) == -1)
exit(1);
sleep(1);
}
exit(0);
}

在j<=5之前按下ctrl-C是没有反应的,当j>5之后进程处理刚被延后的SIGINT信号,ouch才会打印,并且我们还可以发现,5秒前不管按多少次ctrl-C,解除阻塞后只打印一遍。即不对信号进行排队处理
如果进程收到正在阻塞的信号,那么会将该信号添加到进程的等待信号集中,解除阻塞后,会随之将该这些信号传递给进程。可以使用sigpending确定进程中处于阻塞状态的是哪些信号。

1
2
#include<signal.h>
int sigpending(sigset_t *set); //成功返回0,出错返回-1;

sigpending为调用进程返回处于等待状态的信号集,并将其置于set指向的sigset_t结构中。如果阻塞期间修改了信号处理器,解除阻塞后会按照新的处理器程序处理该信号。

最后想吐槽下apue的翻译…


参考资料:
《深入理解计算机系统 3th》(CSAPP)
《linux/UNIX系统编程手册》(tlpi)
《UNIX环境高级编程 3th》(APUE)