信号及信号处理--信号处理--信号的屏蔽
时 间: Thu Sep 3 13:49:38 2009
点 击: 18
2009-07-04 22:34 1、信号集
前面列出的信号总数目达64个,超过了一个整型数能表示的位数(一个整型变量通常
为32位),因此不能用整型量中的一位代表一种信号。POSIX标准定义了数据类型sigset_
t来表示信号集,并且定义了一系列函数操作信号集。在Shell下输入man sigsetops可查看
它们的函数原型如下:
#include <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);
这些函数的具体含义为:
函数sigemptyset用来初始化一个信号集,使其不包括任何信号。
函数sigfillset用来初始化一个信号集,使其包括所有信号。
函数sigaddset用来向set指定的信号集中添加由signum指定的信号。
函数sigdelset用来从set指定的信号集中删除由signum指定的信号。
函数sigismember用来测试信号signum是否包括在set指定的信号集中。
函数sigemptyset,sigfillse,sigaddset,以及sigdelset在执行成功时返回0,失败返
回-1。函数sigismember返回1表示测试的信号在信号集中,返回0表示测试的信号不在信
号集中,出错返回-1。
注意:所有应用程序在使用信号集中,要对该信号集调用一次sigemptyset或
sigfillset以初始化信号集。这是因为C语言编译器将不赋值的外部和静态变量都初始化为
0。
2、信号屏蔽
信号屏蔽又称为信号阻塞,在Shell下输入man sigprocmask可获取信号阻塞的一系列
函数的说明。
#include <signal.h>
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
这些函数的具体解释如下:
(1)sigprocmask函数:
每个进程都有一个信号屏蔽码,它规定了当前阻塞而不能递送给该进程的信号集。调
用函数sigprocmask可以检测或更改进程的信号屏蔽码。如果参数oldset是非空指针,则该
进程之前的信号屏蔽码通过oldset返回;如果参数set是非空指针,则该函数将根据参数
how来修改信号当前屏蔽码,how的取值如下:
SIG_BLOCK:将进程新的信号屏蔽码设置为当前信号屏蔽码和set指向信号集的并集。
SIG_UNBLOC:将进程新的信号屏蔽码设置为当前信号屏蔽码中,删除set所指向信号集
,即set包含了我们希望解除阻塞的信号。即使对当前信号屏蔽码中不存在的信号使用SIG
_UNBLOCK也是合法操作。
SIG_SETMASK:将进程新的信号屏蔽码设置为set指向的值。
函数执行成功返回0,当有错误发生时则返回-1,错误代码存入errno中,详细的错误
代码说明请参考man手册。
(2)sigpending函数
函数sigpending用来获取调用进程因被阻塞而不能递送和当前未决的信号集。该信号
集通过参数set返回。
函数执行成功返回0,当有错误发生时则返回-1,错误代码存入errno中,详细的错误
代码说明请参考man手册,例9-8演示了函数sigprocmask和函数sigpending的用法。
例9-8
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
/*自定义的错误处理函数*/
void my_err(const char *err_string,int line)
{
fprintf(stderr,"line:%d ",line);
perror(err_string);
exit(1);
}
/*SIGINT的处理函数*/
void hander_sigint(int signo)
{
printf("recv SIGINT/n");
}
int main()
{
sigset_t newmask,oldmask,pendmask;//定义信号集
/*安装信号处理函数*/
if (signal(SIGINT,hander_sigint) == SIG_ERR){
my_err("signal",__LINE__);
}
/*睡眠10秒*/
sleep(10);
/*初始化信号集newmask并将SIGINT添加进去*/
sigemptyset(&newmask);
sigaddset(&newmask,SIGINT);
/*屏蔽信号SIGINT*/
if (sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0){
my_err("sigprocmask",__LINE__);
}else{
printf("SIGINT blocked/n");
}
sleep(10);
/*获取未决信号队列*/
if (sigpending(&pendmask) < 0){
my_err("sigpending",__LINE__);
}
/*检查未决信号队列里面是否有SIGINT*/
switch (sigismember(&pendmask,SIGINT)){
case 0:
printf("SIGINT is not in pending queue/n");
break;
case 1:
printf("SIGINT is in pending queue/n");
break;
case -1:
my_err("sigismember",__LINE__);
break;
default:
break;
}
/*解除对SIGINT的屏蔽*/
if (sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0){
my_err("sigprocmask",__LINE__);
}else{
printf("SIGINT unblock/n");
}
while(1)
;
return 0;
}
程序说明:
程序首先安装SIGINT的信号处理函数,然后睡眠10秒(此时按下<Ctrl+c>组合键)
。之后将SIGINT阻塞,然后再睡眠10秒(此时可以多次按下<Ctrl+c>键),并检测SIGI
NT是否在未决信号队列中,最后解除SIGINT信号的阻塞。执行结果如下:
$ ./9-8
^Crecv SIGINT
SIGINT blocked
^C
SIGINT is in pending queue
recv SIGINT
SIGINT unblock
^Crecv SIGINT
^/退出
从结果可以看出,第二个“recv SIGINT”,先于“SIGINT unblocked”打印出来。这
是因为第二次调用sigprocmask解除对SIGINT信号的阻塞以后,进程未决信号队列非空,首
先执行信号处理函数,然后才执行后面的“printf("SIGINT unblocked/n");”
程序中设置SIGINT为阻塞时,先保存了进程的信号屏蔽字在oldmask中,以方便后面解
除对SIGINT的阻塞。在解除SIGINT阻塞时,重新设置进程的信号屏蔽字(SIG_SETMASK)为
oldmask。或者也可以使用SIG_UNBLOCK使信号不被阻塞,但是这样可能会产生一个问题,
当一个大的程序中其他地方可能也阻塞了些信号时,使用SIG_UNBLOCK就会把其它地方的设
置修改掉,因此,建议使用SIG_SETMASK恢复进程的信号屏蔽字而不是使用SIG_UNBLOCK解
除特定信号的阻塞。
程序的结果也再次证明了不可靠信号不支持排队,有可能丢失信号。因为第二次按下
<Ctrl+c>多次,在解除对SIGINT信号的阻塞后,只打印了一个“recv SIGINT”。
(3)sigsuspend函数
函数sigsuspend将进程的信号屏蔽码设置为mask,然后与pause函数一样等待信号的发
生并执行完信号处理函数。信号处理函数执行完成后再把进程的信号屏蔽码设置为原来的
屏蔽字,然后sigsuspend函数才返回。sigsuspend函数保证改变进程的屏蔽码和将进程挂
起等待信号是原子操作。
sigsuspend函数总是返回-1,并将errno置为EINTR,例9-9演示了sigsuspend函数的
用法。
例9-9
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
/*自定义的错误处理函数*/
void my_err(const char *err_string,int line)
{
fprintf(stderr,"line:%d ",line);
perror(err_string);
exit(1);
}
/*SIGINT的处理函数*/
void handler_sigint(int signo)
{
printf("recv SIGINT/n");
}
int main()
{
sigset_t newmask,oldmask,zeromask; //定义信号集
/*安装信号*/
if (signal(SIGINT,handler_sigint) == SIG_ERR){
my_err("signal",__LINE__);
}
sigemptyset(&newmask);
sigemptyset(&zeromask);
sigaddset("signal",__LINE__);
/*屏蔽信号SIGINT*/
if (sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0){
my_err("sigprocmask",__LINE__);
}else{
printf("SIGINT block/n");
}
/*临界区*/
/*使用sigsuspend取消所有信号的屏蔽并等待信号的触发*/
if (sigsuspend(&zeromask) != -1) { //sigsuspend总是返回-1
my_err("sigsuspend",__LINE__);
}else{
printf("recv a signo,return from sigsuspend/n");
}
/*---------------------------------------------
//如果使用sigprocmask加上pause可能会出现错误
if (sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0){
my_err("sigprocmask",__LINE__);
}
pause();
---------------------------------------------*/
/*将信号屏蔽字恢复*/
if (sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0){
my_err("sigprocmask",__LINE__);
}
while(1)
;
return 0;
}
程序说明:
程序使用sigprocmask()屏蔽信号SIGINT,然后使用sigsuspend()取消对所有信号的屏
蔽,并挂起等待信号的触发,执行程序结果如下:
$ ./9-9 &
SIGINT block
[1] 16130
$ ps -a
PID TTY TIME CMD
16130 pts/0 00:00:00 9-9
16135 pts/0 00:00:00 ps
$ kill -s SIGRTMIN 16130
$
[1]+ 实时信号 0 ./9-9
由于所有实时信号的默认响应都是终止进程,因此程序在收到信号SIGRTMIN之后,解
除sigsuspend()的挂起状态,并将信号屏蔽字修改为第一次调用sigprocmask()前的状态。
如果为了达到相同的效果,不使用sigsuspend()而是使用注释掉的那部分代码,虽然
表面上看没有问题,并且执行程序也基本上不会发现问题,但潜在的bug将一直存在,特别
是在复杂的实际应用中就有可能暴露出来。如果信号发生在sigprocmask()之后pause()之
前,则这个信号就会丢失掉了,且如果信号只发生一次,程序将永远挂起在pause()上。
信号及信号处理--信号处理--信号的屏蔽
最新推荐文章于 2022-11-21 22:07:55 发布