信号阻塞
在处理信号时,有些进程不希望被突如其来的信号中断当前的执行,也不想忽略信号,而是希望等到处理完手头上的信号后再处理这些信号,这种情况就涉及到信号的阻塞了。
信号阻塞也称为信号屏蔽。
信号集
之前介绍信号的时候列出了64种信号,超过了一个整型数能表示的范围(不能用整型量的每一位代表一种信号),POSIX标准定义了数据类型sigset_t来表示信号集,并且定义了一系列的函数来操作信号集,在shell下输入man sigsettops
可以查看它们的函数原型:
#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 sigismemember( const sigset_t *set, int signum );
- 函数sigemptyset用来初始化信号集,使信号集不包括任何信号,成功返回0,失败返回-1
- 函数sigfillset用来初始化信号集,使信号集包含所有信号,成功返回0,失败返回-1
- 函数sigaddset用来向set指定的信号集中添加由signum指定的信号,成功返回0,失败返回-1
- 函数sigdelset用来向set指定的信号集中删除由signum指定的信号,成功返回0,失败返回-1
- 函数sigismemember用来测试信号signum是否包含在set指定的信号集中,在的话返回1,不在返回0,出错返回-1
- 在所有的程序使用信号集之前,都要对信号集进行初始化,因为C语言编译器将不赋初值的外部变量和静态变量都初始化为0
sigprocmask()检测修改信号掩码
每个进程都有一个信号掩码,它规定了当前受到阻塞而不能传递给进程的信号集,调用sigprocmask()可以检测或修改进程的信号掩码,函数原型如下:
#include <signal.h>
int sigprocmask( int how, const sigset_t *newset, sigset_t *oldset );
- 函数调用成功返回0,出错返回-1,错误代码存入errno中
- 进程的信号掩码通过oldset指针返回
- 参数newset可以把指向的信号集设置为新的信号掩码
- 参数how代表对当前信号掩码的操作,how的取值如下:
SIG_BLOCK:将newset所指向的信号集中的信号添加到当前信号掩码中作为新的信号屏蔽字
SIG_UNBLOCK:将newset所指向的信号集中的信号从当前的信号掩码中移除
SIG_SETMASK:设置信号掩码为newset所指信号集中的·全部信号
sigpending()查看未决信号队列
函数sigpending()可以用来获取当前进程中因为被阻塞而不能传递和当前未决的信号集,就是查看有哪些信号被阻塞了,函数原型如下:
#include <signal.h>
int sigpending( sigset_t *set );
- 未决信号集通过参数set返回
- 函数执行成功返回0,出错返回-1,错误代码存在errno中
sigsuspend()等待信号
函数sigsuspend()主要实现等待一个信号的到来,即将当前进程挂起,函数原型如下:
#include <signal.h>
int sigsuspending( const sigset_t *mask );
- 参数mask是一个sigset_t类型结构体,指向一个信号集,当函数被调用时,参数mask所指向的信号集中的信号会复制给信号掩码,随后进程会被挂起,直到信号捕捉到,当相应的信号处理函数返回时,该函数才会返回。此时,信号掩码恢复为调用函数之前的值。
- 函数总是返回-1,并将errno设置为EINTR
典型程序
通过一个程序熟悉一下信号的阻塞:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
/* 出错提示函数,报告出错的函数和出错的行号 */
void error( const char *err_string, int line )
{
fprintf( stderr, "line :%d",line );
perror( err_string );
exit( 1 );
}
/* SIGINT信号处理函数 */
void handler_sigint( int signo )
{
printf("recv SIGIINT\n");
}
int main( void )
{
/* 定义信号集 */
sigset_t newmask, oldmask, pendmask;
/* 安装信号处理函数 */
if( signal( SIGINT, handler_sigint ) == SIG_ERR )
err( "signal", __LINE__ );/* __LINE__返回 */
sleep( 10 );
/* 初始化信号集 */
sigemptyset( &newmask );
/* 将SIGINT添加到newmask信号集中 */
sigaddset( &newmask, SIGINT );
/* 屏蔽信号SIGINT,使用newmask信号集 */
if( sigprocmask( SIG_BLOCK, &newmask, &oldmask ) < 0 )
{
err( "sigprocmask", __LINE__ );
}
else
{
printf("SIGINT blocked\n");
}
sleep( 10 );
/* 获取未决信号队列,存入pendmask中 */
if( sigpending( &pendmask ) < 0 )
{
err( "sigpending",__LINE__ );
}
/* 检查未决信号队列pendmask中是否有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:
err( "sigismember", __LINE__ );
break;
default :
break;
}
/* 解除SIGINT的屏蔽,设置信号掩码为旧的就可以了 */
if( sigprocmask( SIG_SETMASK, &oldmask, NULL ) < 0 )
{
err( "sigprocmask", __LINE__ );
}
else
{
printf("SIGINT unblocked\n");
}
while( 1 )
;
return 0;
}
结果说明:
第一次先发送一个信号,此时还没有将SIGINT添加到信号掩码中,所以在信号处理函数中显示接收到信号。然后进入睡眠,随后将SIGINT信号添加到信号掩码中阻塞信号。然后再次进入睡眠,睡眠期间,多次发送SIGINT信号,此时发送的信号SIGINT是不可靠信号,所以在排队时丢的只剩下一个信号了(可以这样理解),睡眠之后,获取未决信号队列,查看SIGINT信号是否在队列中,输出信息表示SIGINT在未决队列中。然后就解除对SIGINT信号的屏蔽,此时光看程序代码似乎是打印SIGINT unblocked
这句话,但实际上由于未决队列中还有信号(丢的只剩下一个SIGINT信号),所以要先执行未决信号,在返回函数。所以就先执行了一次信号处理函数,随后才打印SIGINT unblocked
。
记住一点:有未决 先执行 再返回