信号集操作函数

一、信号集
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 定义为:
  1. # 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(), sigaddset(), sigdelset() 这几个函数在执行成功时返回 0 ,失败返回 -1。函数 sigismember() 返回 1 表示测试的信号在信号集中,返回 0 表示测试的信号不在信号集中,出错返回 -1 。

注意 所有应用程序在使用信号集前,要对该信号集调用一次 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 所指向的值。
函数执行成功返回 0,有错误时返回 -1,错误代码存入 errno 中。

( 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 = 0i < 5i ++)
         {
                 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 类型如下定义:

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 = 0i < _SIGSET_NWORDSi ++)
     if ( newmask . __val [ i ]  !=  0)
         printf( "%d:%d \n " ,  i , newmask . __val [ i ]);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值