Linux信号编程实践(三) 信号在内核中的表示(sigaction&sigqueue)

信号在内核中的表示

    实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。信号在内核中的表示可以看作是这样的:


1)block集(阻塞集、屏蔽集):一个进程所要屏蔽的信号,在对应要屏蔽的信号位置1

2)pending集(未决信号集):如果某个信号在进程的阻塞集中,则也在未决集中对应位置1,表示该信号不能被递达,不会被处理

3)handler(信号处理函数集):表示每个信号所对应的信号处理函数,当信号不在未决集中时,将被调用。

4)block状态字、pending状态字均64位(bit);

5)block状态字用户可以读写,pending状态字用户只能读;这是信号设计机制

那么我们该如何对信号的屏蔽字状态进行改变和读取呢?接下来我们介绍一组信号集操作函数

  1. #include <signal.h>    
  2. int sigemptyset(sigset_t *set); //把信号集清零;(64bit/8=8字节)    
  3. int sigfillset(sigset_t *set);  //把信号集64bit全部置为1    
  4. int sigaddset(sigset_t *set, int signo);    //根据signo,把信号集中的对应位置成1    
  5. int sigdelset(sigset_t *set, int signo);    //根据signo,把信号集中的对应位置成0    
  6. int sigismember(const sigset_t *set, int signo);    //判断signo是否在信号集中    

sigprocmask    功能:读取或者更改进程的信号屏蔽字(Block)

  1. int sigprocmask(int how, const sigset_t *set, sigset_t *oset);    

  返回值:若成功则为0,若出错则为-1

  读取:如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。

  更改:如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。


sigpending获取信号未决状态字(pending)信息,保存至set态,NSIG信号的最大值=64。

  1. #include <signal.h>    
  2. int sigpending(sigset_t *set);    

sigismember函数

    用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1,否则返回0。如果有错误则返回-1。出错的情况及其错误代码见下:

EFAULT 参数set指针地址无法存取
EINVAL 参数signum 非合法的信号编号

  1. int sigismember(const sigset_t *set,int signum);  
我们注册一个SIGINT信号,打印出pending的状态,结果如下:

  1. void handler(int sig)  
  2. {  
  3.         printf("recv a sig=%d\n",sig);  
  4. }  
  5. void printsigset(sigset_t *set)  
  6. {  
  7.         int i;  
  8.         for(i=1;i<NSIG;i++)  
  9.         {  
  10.                 if(sigismember(set,i))  
  11.                         putchar('1'); //打印出未决态  
  12.                 else  
  13.                         putchar('0');  
  14.         }  
  15.         printf("\n");  
  16. }  
  17. int main()  
  18. {  
  19.         sigset_t pset;  
  20.         if(signal(SIGINT,handler)==SIG_ERR)  
  21.                 ERR_EXIT("signal error!");  
  22.         while(1)  
  23.         {  
  24.                 sigpending(&pset);  
  25.                 printsigset(&pset);  
  26.                 sleep(1);  
  27.         }  
  28.         return 0;  
  29.    
  30. }  

信号没有阻塞,不会发生未决状态,直接递达。

     在接下来的例子中,我们先屏蔽SIGINT信号, 但是如果该进程接收到了SIGQUIT信号, 则将对SIGINT信号的屏蔽节解除,当然,我们需要先注册SIGINT和SIGQUIT信号。

  1. /*开始阻塞信号的程序,产生未决状态*/  
  2. void handler(int sig)  
  3. {  
  4.         if(sig==SIGINT)  
  5.                 printf("recv a sig=%d\n",sig);  
  6.         else if(sig==SIGQUIT) //解除SIGINT的屏蔽  
  7.         {  
  8.                 sigset_t uset;  
  9.                 sigemptyset(&uset);  
  10.                 sigaddset(&uset,SIGINT);  
  11.                 sigprocmask(SIG_UNBLOCK,&uset,NULL);  
  12.    
  13.         }  
  14. //        printf("recv a sig=%d\n",sig);  
  15. }  
  16. void printsigset(sigset_t *set)  
  17. {  
  18.         int i;  
  19.         for(i=1;i<NSIG;i++)  
  20.         {  
  21.                 if(sigismember(set,i))  
  22.                         putchar('1');  
  23.                 else  
  24.                         putchar('0');  
  25.         }  
  26.         printf("\n");  
  27. }  
  28. int main()  
  29. {  
  30.         sigset_t pset;  
  31.         sigset_t bset;  
  32.         sigemptyset(&bset);  
  33.         sigaddset(&bset,SIGINT);  
  34.         if(signal(SIGINT,handler)==SIG_ERR)  
  35.                 ERR_EXIT("signal error!");  
  36.         if(signal(SIGQUIT,handler)==SIG_ERR)  
  37.                 ERR_EXIT("signal error!");  
  38.         sigprocmask(SIG_BLOCK,&bset,NULL);//屏蔽SIGINT信号  
  39.         while(1)  
  40.         {  
  41.                 sigpending(&pset);  
  42.                 printsigset(&pset);  
  43.                 sleep(1);  
  44.         }  
  45.         return 0;  
  46.    
  47. }  

    当我们按下ctrl+c产生信号时,信号被阻塞,处于未决状态。接收到SIGQUIT信号时,解除阻塞,进入递达状态,但是只会对信号做出一次反应,即使你按了很多次ctrl+c,原因就在于,SIGINT是不可靠信号,不支持排队,只保留了一个。

    如果我们采用实时信号的话,例如SIGRTMIN,那么对信号来说是支持排队的,不会发生丢失的情况,在解除阻塞后,会对每个信号做出处理。

Sigaction

前面我们讲过了使用signal安装不可靠信号,虽然signal不如sigaction功能丰富,但是也可以安装可靠信号;

  1. #include <signal.h>    
  2. int sigaction(int signum, const struct sigaction *act,    
  3.                      struct sigaction *oldact);    

功能:

   sigaction函数用于改变进程接收到特定信号后的行为。


简而言之参数就是(信号,指针,原行为)

关于sigaction结构体

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等

  1. struct sigaction {    
  2. //信号处理程序 不接受额外数据(比较过时)    
  3.     void (*sa_handler)(int);    
  4.     
  5. //信号处理程序能接受额外数据,和sigqueue配合使用(支持信号排队,信号传送其他信息),推荐使用    
  6.     void (*sa_sigaction)(int, siginfo_t *, void *);             
  7.     
  8. sigset_t sa_mask;   //屏蔽    
  9.     int sa_flags;       //表示信号的行为:SA_SIGINFO表示能接受数据    
  10.     void (*sa_restorer)(void); //废弃不用了    
  11. };    

      sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

  sa_sigaction的原型是一个带三个参数,类型分别为int,struct siginfo *,void *,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

  sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞

  sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

  sa_restorer已过时,POSIX不支持它,不应再使用。

注意:回调函数sa_handler和sa_sigaction只能选一个

  因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,

实例1:利用sigaction实现了signal函数的功能

  1. __sighandler_t my_signal(int sig,__sighandler_t handler)  
  2. {  
  3.         struct sigaction act;  
  4.         struct sigaction oldact;  
  5.         act.sa_handler=handler;  
  6.         sigemptyset(&act.sa_mask);  
  7.         act.sa_flags=0;  
  8.         if(sigaction(sig,&act,&oldact)<0)  
  9.                 return SIG_ERR;  
  10.         return oldact.sa_handler;  
  11. }  
  12. void handler(int sig)  
  13. {  
  14.         printf("recv a sig=%d\n",sig);  
  15. }  
  16. int main()  
  17. {  
  18.   /*      struct sigaction act; 
  19.         act.sa_handler=handler; 
  20.         sigemptyset(&act.sa_mask); 
  21.         act.sa_flags=0; 
  22.         if(sigaction(SIGINT,&act,NULL)<0) 
  23.                 ERR_EXIT("sigaction error\n"); 
  24. */  
  25.         my_signal(SIGINT,handler);  
  26.    
  27.         while(1)  
  28.                 pause();  
  29.         return 0;  
  30.    
  31. }  
sa_mask选项

 在执行handler 的时候, 如果此时进程收到了sa_mask所包含的信号, 则这些信号将不会被响应, 直到handler函数执行完毕。

 sigprocmask使其即使发生了也不能递达,但是sa_mask 仅是在处理handler是屏蔽外来的信号;两者的区别还是要好好搞一搞的。

  1. void handler(int sig)  
  2. {  
  3.         printf("recv a sig=%d\n",sig);  
  4.         sleep(5);  
  5. }  
  6. int main()  
  7. {  
  8.         struct sigaction act;  
  9.         act.sa_handler=handler;  
  10.         sigemptyset(&act.sa_mask);  
  11.         sigaddset(&act.sa_mask,SIGQUIT);//屏蔽SIGQUIT信号  
  12.         act.sa_flags=0;  
  13.         if(sigaction(SIGINT,&act,NULL)<0)  
  14.                 ERR_EXIT("sigaction error\n");  
  15.    
  16.         while(1)  
  17.                 pause();  
  18.         return 0;  
  19.    
  20. }  

在响应SIGINT信号即handler处理时,SIGQUIT暂时被屏蔽,但是一旦handler函数处理完后,立即对SIGQUIT进行响应。

siginfo_t结构:

  1. siginfo_t{    
  2.     int      si_signo;    /* Signal number */    
  3.     int      si_errno;    /* An errno value */    
  4.     int      si_code;     /* Signal code */    
  5.     int      si_trapno;   /* Trap number that caused  
  6.                                         hardware-generated signal  
  7.                                         (unused on most architectures) */    
  8.     pid_t    si_pid;      /* Sending process ID */    
  9.     uid_t    si_uid;      /* Real user ID of sending process */    
  10.     int      si_status;   /* Exit value or signal */    
  11.     clock_t  si_utime;    /* User time consumed */    
  12.     clock_t  si_stime;    /* System time consumed */    
  13.     sigval_t si_value;    /* Signal value */    
  14.     int      si_int;      /* POSIX.1b signal */    
  15.     void    *si_ptr;      /* POSIX.1b signal */    
  16.     int      si_overrun;  /* Timer overrun count; POSIX.1b timers */    
  17.     int      si_timerid;  /* Timer ID; POSIX.1b timers */    
  18.     void    *si_addr;     /* Memory location which caused fault */    
  19.     long     si_band;     /* Band event (was int in  
  20.                                         glibc 2.3.2 and earlier) */    
  21.     int      si_fd;       /* File descriptor */    
  22.     short    si_addr_lsb; /* Least significant bit of address  
  23.                                         (since Linux 2.6.32) */    
  24. }    

sigqueue

  1. #include <signal.h>    
  2. int sigqueue(pid_t pid, int sig, const union sigval value);    

功能

   sigqueue是新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用

   和kill函数相比多了一个参数:const union sigval value(int kill(pid_t pid, int sig)),因此sigqueue()可以比kill()传递更多的信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

参数

   参数1是指定接收信号的进程id,参数2确定即将发送的信号;

   参数3是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

  注意:要想在进程间通信的话,sa_flags要置为SA_SIGINFO

sigval联合体

  1. typedef union sigval{    
  2.     int sival_int;    
  3.     void *sival_ptr;    
  4. } sigval_t;    
接下来我们模拟一下进程间通信的实例:

先运行hello开启接收,然后使用send发送信号;通过这种方式可以达到进程见通信的目的。

  1. Hello.c  
  2. void handler(int sig,siginfo_t *info,void *ctx)  
  3. {  
  4.    
  5.         printf("recv a sig=%d data=%d\n",sig,info->si_value.sival_int);  
  6.            
  7. }  
  8. int main()  
  9. {  
  10.         struct sigaction act;  
  11.         act.sa_sigaction=handler;  
  12.         sigemptyset(&act.sa_mask);  
  13.         act.sa_flags=SA_SIGINFO;  
  14.         if(sigaction(SIGINT,&act,NULL)<0)  
  15.                 ERR_EXIT("sigaction error\n");  
  16.    
  17.         while(1)  
  18.                 pause();  
  19.         return 0;  
  20.    
  21. }  
  22.   
  23. Send  
  24. int main(int argc,char *argv[])  
  25. {  
  26.         if(argc!=2)  
  27.         {  
  28.                 fprintf(stderr,"Usage %s pid\n",argv[0]);  
  29.                 exit(EXIT_FAILURE);  
  30.         }  
  31.         pid_t pid=atoi(argv[1]);  
  32.         union sigval v;  
  33.         v.sival_int=100;  
  34.         sigqueue(pid,SIGINT,v);  
  35.         return 0;  
  36. }  



阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页