一、信号集
sigset_t 类型其实是个结构体,定义在 /usr/include/bits/sigset.h 头文件中:
而 _SIGSET_NWORDS 定义为:
因此,_SIGSET_NWORS 的值为 32 。
POSIX 还定义了一系列函数用来操作信号集。在 shell 下输入 man sigsetops 可查看她们的函数原型如下:
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() 以初始化信号集。这是因为 C 语言编译器将不赋初值的外部和静态量都初始化为 0 。
二、信号屏蔽
信号屏蔽又成为信号阻塞,在 shell 下输入 man sigprocmask 可获得信号阻塞的一系列函数的说明:
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 的取值为:
( 2 )sigpending() 函数
函数 sigpending() 用来获取调用进程因被阻塞而不能递送和当前未决的信号集。该信号通过参数 set 返回。函数执行成功返回 0 ,有错误返回 -1,错误代码存入 errno 中。
测试代码 :
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void my_err ( const char * err_string , int line )
{
fprintf ( stderr , "line:%d" , line );
perror ( err_string );
exit ( 1 );
}
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__ ); /*__LINE__ 表示当前__LINE__ 所在的行数*/
}
printf ( "进入睡眠10秒 \n " );
sleep ( 10 );
printf ( "睡眠醒来 \n " );
/*初始化信号集 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 " );
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 unblocked \n " );
}
while ( 1 )
;
return 0 ;
}
运行及输出 :
说明 :
在输出结果中, ^C 表示按下了一次 Ctrl + C 的组合键。由输出结果可以分析得:
在程序进入睡眠时(10s),只要接收到信号,它就会马上醒过来,然后进行接收到信号处理,如上面的输出:recv SIGINT。注意到,“睡眠醒来“ 醒来这句话是在 “recv SIGINT” 之后的,这是因为程序醒来后,第一件事是要去处理信号,而并不着急要输出睡眠函数的下面的语句。接着,我们阻塞了 SIGINT 信号,然后又进入 10 秒的睡眠。这时,连续多次输入 ctrl+c 后程序并不会就立马响应这些信号。所以,当用 sigismember() 函数来测试 SIGINT 是否在未决信号队列中,由输出 SIGINT blocked 可见,SIGINT 信号确实已经被阻塞。再接着,我们解除了 SIGINT 信号的屏蔽,用的是 sigprocmask ( SIG_SETMASK , & oldmask , NULL ),这里 oldmask 是原来屏蔽的信号集(相当于在设置新的信号集前我们对之前的信号集做了一个备份)。当 SIGINT 信号的屏蔽被解除后,程序马上去处理这个信号。再注意到,SIGINT unblocked 提示后于 recv SIGINT 输出,也是因为一旦解除了被屏蔽的信号且未决信号队列中有这个信号,那么程序会立即处理信号函数,而不着急输出提示。为什么只有一次 recv SIGINT 输出呢?这是因为 SIGINT 信号是不可靠信号,当有多个这样的信号时,信号处理函数往往只会被调用一次。
在测试这个程序时,先在另一个 shell 窗口里用 ps aux 找出这个程序的进程 ID,然后用另外一个程序去测试这个程序,下面的程序的作用主要是一次性发送 5 个可靠的实时信号:
先运行第一个程序 :
在 30 秒中内,我们及时的找出 sig_mask2.exe 这个程序的进程 ID ,然后结合这个 ID 用第二个程序给第一个程序发送信号:
一次性发送了 5 个 SIGRTMIN 信号,第一个程序收到信号后马上醒来响应:
可见,可靠信号是每个都处理的。接着再在这 10秒 内再发送一次 5 个 SIGRTMIN 信号:
这时,继续观察第一个程序的输出:
由输出可见,对于实时的可靠信号,在解除阻塞后,每个信号都得到了处理。
sigset_t 类型其实是个结构体,定义在 /usr/include/bits/sigset.h 头文件中:
1
2
3
4
|
|
typedef__sigset_t sigset_t
typedefstruct {
unsignedlongint __val[ _SIGSET_NWORDS ];
}sigset_t;
而 _SIGSET_NWORDS 定义为:
- # define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
因此,_SIGSET_NWORS 的值为 32 。
POSIX 还定义了一系列函数用来操作信号集。在 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() 或 sigfillset() 以初始化信号集。这是因为 C 语言编译器将不赋初值的外部和静态量都初始化为 0 。
二、信号屏蔽
信号屏蔽又成为信号阻塞,在 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_UNBLOCK : 将进程的信号屏蔽码设置为当前信号屏蔽码中删除了 set 所指向信号集,即 set 包含了我们希望解除阻塞的信号。即使对当前信号屏蔽码中不存在的信号使用 SIG_UNBLOCK 也是合法操作。
- SIG_SETMASK : 将进程新的信号屏蔽码设置为 set 所指向的值。
( 2 )sigpending() 函数
函数 sigpending() 用来获取调用进程因被阻塞而不能递送和当前未决的信号集。该信号通过参数 set 返回。函数执行成功返回 0 ,有错误返回 -1,错误代码存入 errno 中。
测试代码 :
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void my_err ( const char * err_string , int line )
{
fprintf ( stderr , "line:%d" , line );
perror ( err_string );
exit ( 1 );
}
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__ ); /*__LINE__ 表示当前__LINE__ 所在的行数*/
}
printf ( "进入睡眠10秒 \n " );
sleep ( 10 );
printf ( "睡眠醒来 \n " );
/*初始化信号集 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 " );
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 unblocked \n " );
}
while ( 1 )
;
return 0 ;
}
运行及输出 :
beyes@linux-beyes:~/C> ./sig_mask.exe
进入睡眠10秒
^Crecv SIGINT
睡眠醒来
SIGINT blocked
^C^C^C^C^C^C^C^C^CSIGINT is in pending queue
recv SIGINT
SIGINT unblocked
^\退出
说明 :
在输出结果中, ^C 表示按下了一次 Ctrl + C 的组合键。由输出结果可以分析得:
在程序进入睡眠时(10s),只要接收到信号,它就会马上醒过来,然后进行接收到信号处理,如上面的输出:recv SIGINT。注意到,“睡眠醒来“ 醒来这句话是在 “recv SIGINT” 之后的,这是因为程序醒来后,第一件事是要去处理信号,而并不着急要输出睡眠函数的下面的语句。接着,我们阻塞了 SIGINT 信号,然后又进入 10 秒的睡眠。这时,连续多次输入 ctrl+c 后程序并不会就立马响应这些信号。所以,当用 sigismember() 函数来测试 SIGINT 是否在未决信号队列中,由输出 SIGINT blocked 可见,SIGINT 信号确实已经被阻塞。再接着,我们解除了 SIGINT 信号的屏蔽,用的是 sigprocmask ( SIG_SETMASK , & oldmask , NULL ),这里 oldmask 是原来屏蔽的信号集(相当于在设置新的信号集前我们对之前的信号集做了一个备份)。当 SIGINT 信号的屏蔽被解除后,程序马上去处理这个信号。再注意到,SIGINT unblocked 提示后于 recv SIGINT 输出,也是因为一旦解除了被屏蔽的信号且未决信号队列中有这个信号,那么程序会立即处理信号函数,而不着急输出提示。为什么只有一次 recv SIGINT 输出呢?这是因为 SIGINT 信号是不可靠信号,当有多个这样的信号时,信号处理函数往往只会被调用一次。
实时可靠信号测试:
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void my_err( const char * err_string , int line)
{
fprintf( stderr , "line:%d" , line);
perror( err_string);
exit( 1);
}
void hander_sigint( int signo)
{
printf( "recv SIGRTMIN \n ");
}
int main()
{
sigset_t newmask , oldmask , pendmask; //定义信号集
/*安装信号处理函数*/
if ( signal( SIGRTMIN , hander_sigint) == SIG_ERR) {
my_err( "signal" , __LINE__); /*__LINE__ 表示当前__LINE__ 所在的行数*/
}
printf( "进入睡眠30秒 \n ");
sleep( 30);
printf( "睡眠醒来 \n ");
/*初始化信号集 newmask 并将 SIGINT 添加进去*/
sigemptyset( & newmask);
sigaddset( & newmask , SIGRTMIN);
/*屏蔽信号 SIGINT*/
if ( sigprocmask( SIG_BLOCK , & newmask , & oldmask) < 0) {
my_err( "sigprocmask" , __LINE__);
} else {
printf( "SIGRTMIN blocked \n ");
}
printf( "设置了屏蔽实时信号 SIGRTMIN 后再睡眠10秒 \n ");
sleep( 10);
printf( "醒来后要查看悬而未决的SIGRTMIN 信号情况 \n ");
/*获取未决信号队列*/
if ( sigpending( & pendmask) < 0) {
my_err( "sigpending" , __LINE__);
}
/*检查未决信号队列里是否有 SIGINT*/
switch ( sigismember( & pendmask , SIGRTMIN)) {
case 0 :
printf( "SIGRTMIN is not in pending queue \n ");
case 1 :
printf( "SIGRTMIN 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( "SIGRTMIN unblocked \n ");
}
while( 1)
;
return 0;
}
在测试这个程序时,先在另一个 shell 窗口里用 ps aux 找出这个程序的进程 ID,然后用另外一个程序去测试这个程序,下面的程序的作用主要是一次性发送 5 个可靠的实时信号:
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
int main( int argc , char ** argv)
{
int i;
int signum = SIGRTMIN;
pid_t pid;
if ( argc == 2)
pid = atoi( argv [ 1 ]);
else return 0;
for( i = 0; i < 5; i ++)
{
if ( kill( pid , signum) < 0) {
perror( "kill");
exit( 1);
}
}
return 0;
}
先运行第一个程序 :
beyes@linux-beyes:~/C> ./sig_mask2.exe
进入睡眠30秒
在 30 秒中内,我们及时的找出 sig_mask2.exe 这个程序的进程 ID ,然后结合这个 ID 用第二个程序给第一个程序发送信号:
beyes@linux-beyes:~/C> ./my_kill.exe 10546
一次性发送了 5 个 SIGRTMIN 信号,第一个程序收到信号后马上醒来响应:
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
睡眠醒来
SIGRTMIN blocked
设置了屏蔽实时信号 SIGRTMIN 后再睡眠10秒
可见,可靠信号是每个都处理的。接着再在这 10秒 内再发送一次 5 个 SIGRTMIN 信号:
beyes@linux-beyes:~/C> ./my_kill.exe 10546
这时,继续观察第一个程序的输出:
醒来后要查看悬而未决的SIGRTMIN 信号情况
SIGRTMIN is in pending queue
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
recv SIGRTMIN
SIGRTMIN unblocked
^\退出
由输出可见,对于实时的可靠信号,在解除阻塞后,每个信号都得到了处理。
在第一个帖子中,程序设置 SIGINT 阻塞时,先保存了进程的信号屏蔽字在 oldmask 中,这是为了方便以后解除 SIGINT 的阻塞。
在解除 SIGINT 的阻塞时,重新设置进程的信号屏蔽字 (SIG_SETMASK) 为 oldmask。或者也可以使用 SIG_UNBLOCK 使信号不被阻塞,但是这样会产生一个问题,当一个大的程序中其他地方可能也阻塞此信号时,使用 SIG_UNBLOCK 就会把起他地方的设置也改掉( 因为用 SIG_UNBLOCK 方法,则该进程新的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集的“交集”<SIG_BLOCKS 是并集>)。因此,建议使用 SIG_SETMASK 恢复进程的信号屏蔽字而不是使用 SIG_UNBLOCK 解除特定信号的阻塞。()
程序的结果再次证明了不可靠信号不支持排队,有可能丢失信号。
sigset_t 类型
在 sigaction 结构体中有 sa_mask 这个数据成员,它是 sigset_t 类型数据,而 sigset_t 类型如下定义:
在 __val 这个数组中,记录了要阻塞的信号。设置这个数组使用 sigaddset( )函数,例如要阻塞 SIGINT信号,则:
其中,newmask 就是 sigset_t 类型数据。
sigset_t 具体的设置方法为,如果要屏蔽第 1 号信号 SIGHUP,就将 __val[0] 这个数的第 0 位置位;如果要将 SIGINT 信号阻塞,就会设置 __val[0] 的第 1 位;依次类推下去。
可靠信号从 SIGRTMIN 开始。它们的置位从 __val[1] 这个数开始,如要阻塞 SIGRTMIN 这个信号,就将 __val[1] 的第 0 位置位。
这样的设置,可以通过以下代码片段测试得知:
typedef __sigset_t sigset_t
typedef struct {
unsigned long int __val [ _SIGSET_NWORDS ];
} sigset_t;
在 __val 这个数组中,记录了要阻塞的信号。设置这个数组使用 sigaddset( )函数,例如要阻塞 SIGINT信号,则:
sigaddset( & newmask , SIGINT);
其中,newmask 就是 sigset_t 类型数据。
sigset_t 具体的设置方法为,如果要屏蔽第 1 号信号 SIGHUP,就将 __val[0] 这个数的第 0 位置位;如果要将 SIGINT 信号阻塞,就会设置 __val[0] 的第 1 位;依次类推下去。
可靠信号从 SIGRTMIN 开始。它们的置位从 __val[1] 这个数开始,如要阻塞 SIGRTMIN 这个信号,就将 __val[1] 的第 0 位置位。
这样的设置,可以通过以下代码片段测试得知:
sigset_t newmask , oldmask;
sigemptyset( & newmask);
sigaddset( & newmask , SIGHUP);
for ( i = 0; i < _SIGSET_NWORDS; i ++)
if ( newmask . __val [ i ] != 0)
printf( "%d:%d \n " , i , newmask . __val [ i ]);