信号阻塞
实时信号34~64号信号之所以是可靠信号,是因为其信号模型中存在一个信号队列,当一个信号递达,但进程正在处理其它信号时,该信号会被加入信号队列,等待进程处理。而1~31号信号中没有信号队列,若信号递达时进程正在处理其它信号,那么进程就会对该信号做忽略处理,也可以说该信号被丢弃。
对进程来说,信号的发送过于密集,即在处理信号的同时再次收到信号时,进程会将后到的信号丢弃。对于信号的发送方来说,应该发送的信号已经发送,自然不会再次发送;但对于作为信号接收方的进程来说,未对信号做出应有处理,这显然是不符合预期的。
信号屏蔽机制专门用于解决常规信号不可靠这一问题。在进程的PCB中,存在两个信号集,一个称为信号掩码(signal mask),另一个称为未决信号集(signal pending)。这两个信号集的实质都是位图,其中的每一位对应一个信号:若mask中某个信号对应的位被设置为1,信号会被屏蔽,进入阻塞状态;此时内核会修改pending中该信号对应的位为1,使该信号处于未决态,之后除非该信号被解除屏蔽,否则内核不会再向进程发送这个信号。
用户是不能直接操作未决信号集的,但可以在程序中以自定义的set位图,与mask进行位操作,以达到屏蔽或解除屏蔽的目的,从而进一步对pending造成影响。
1、 信号集设定函数
Linux系统中提供了一组函数,用于设定自定义信号集,这些函数都存在于函数库signal.h中,函数声明分别如下:
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
这些函数中的参数set是一个sigset_t类型的指针,sigset_t是系统自定义类型,其实质是一个位图,一般用户是不会直接对位进行操作的,针对位的操作也由以上函数完成。
信号集设定函数的功能分别如下:
● sigempty()——将指定信号集清0;
● sigfillset()——将指定信号集置1;
● sigaddset()——将某个信号加入指定信号集;
● sigdelset()——将某个信号从信号集中删除;
● sigismember()——判断某个信号是否已被加入指定信号集。
2、 sigprocmask()函数
自定义位图设定完成后,要与mask位图进行位操作,以改变mask位图中的数据。Linux系统提供了一个用于位操作的函数——sigprocmask(),该函数位于函数库signal.h中,其函数声明如下:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
函数sigprocmask()若调用成功则返回0,否则返回-1,并设置errno。该函数共有三个参数,参数set和oldset都是指向位图的指针:set是一个传入参数,一般指向用户自定义位图,该位图用于与mask位图进行位操作;oldset是一个传出参数,用于记录原mask位图的值。参数how用于设置位操作的方式,其取值分别如下:
● 当how设置为SIG_BLOCK时,set位图中记录需要屏蔽的信号,sigprocmask()函数相当于使mask与set进行位或操作,即:mask=mask|set;
● 当how设置为SIG_UNBLOCK时,set位图中记录需要解除屏蔽的信号,sigprockmask()函数相当于使mask按位与上set的取反,即:mask=mask&~set;
● 当how设置为SIG_SETMASK时,set位图表示用于替代mask的新屏蔽集,sigprocmask()函数的实际操作为:mask=set。
需要注意,系统中什么时候产生什么信号是有规律的,用户进程不应随便对mask进行修改,因此在用户进程中的功能实现之后,应尽量使用sigprocmask()的传出参数oldset恢复mask。
3、 sigpending()函数
在编程过程中,若想了解进程中信号的状态,可以使用sigpending()函数,该函数的功能是获取当前进程中未决信号集的信息,存在于函数库signal.h中,函数声明如下:
int sigpending(sigset_t *set);
sigpending()函数调用成功时返回1,否则返回-1,其参数set是一个传出参数,用户可设置一个位图传入该参数,来获取未决信号集信息。
案例4:以2号信号为例,通过位操作函数sigprocmask()与sigpending()函数函数获取信号状态。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <signal.h>
6 void printset(sigset_t *ped) //pending打印函数
7 {
8 int i;
9 for(i=1;i<32;i++){
10 if((sigismember(ped,i)==1))
11 putchar('1');
12 else
13 putchar('0');
14 }
15 printf("\n");
16 }
17 int main()
18 {
19 sigset_t set,oldset,ped; //信号集定义
20 sigemptyset(&set); //初始化自定义信号集set
21 sigaddset(&set,SIGINT); //将2号信号SIGINT加入set
22 sigprocmask(SIG_BLOCK,&set,&oldset);//位操作
23 while(1){
24 sigpending(&ped);
25 printset(&ped);
26 sleep(1);
27 }
28 return 0;
29 }
编译案例,执行程序,终端会不断打印进程PCB中的未决信号集,初始情况下进程未决信号集中的每一位都应为0,因此打印的信息如下:
0000000000000000000000000000000
使用kill命令或组合按键Ctrl+C驱使内核发送信号SIGINT给当前进程,进程第一次接收到信号SIGINT后,sigprocmask()函数被触发,此后终端打印的信息如下:
0100000000000000000000000000000
之后继续向进程发送SIGINT信号,终端打印信息不变,说明信号SIGINT成功被屏蔽。