C - sigaction详解

注: 阻塞的意思是延迟相应信号
sigaction,是为替代signal 来设计的较稳定的信号处理。
signal的使用比较简单。signal(signalNO,signalproc);
但是signal不能完成的任务是:
1.不知道信号产生的原因;
2.处理信号中不能阻塞其他的信号
而signaction,则可以设置比较多的消息。尤其是在信号处理函数过程中接受信号,进行何种处理。
sigaction函数用于改变进程接收到特定信号后的行为。该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)。第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等。
sigaction结构定义如下:
struct sigaction {
                  void (*sa_handler)(int);
                  void (*sa_sigaction)(int, siginfo_t *, void *);
                  sigset_t sa_mask;
                  int sa_flags;
                  void (*sa_restorer)(void);
              }

其中,sa_restorer,已过时,POSIX不支持它,不应再被使用。
使用示例为:
#include <stdio.h>
#include <signal.h>
void WrkProcess(int nsig)
{
        printf("WrkProcess .I get signal.%d threadid:%d\n",nsig,pthread_self());
        int i=0;
        while(i<5){
                printf("threadid:%d \n",pthread_self());
                sleep(1);
                i++;
        }
}

int main()
{
        struct sigaction act,oldact;
        act.sa_handler  = WrkProcess;
//      sigaddset(&act.sa_mask,SIGQUIT);
//      sigaddset(&act.sa_mask,SIGTERM)
        act.sa_flags = SA_NODEFER | SA_RESETHAND;  
//      act.sa_flags = 0;
        sigaction(SIGINT,&act,&oldact);
        printf("main threadid:%d \n",pthread_self());
        while(1)
        {
          printf("waiting for signal...\n");
          sleep(5);
        }
        return 0;
}
 
1)执行改程序时,ctrl+c,第一次不会导致程序的结束。而是继续执行,当用户再次执行ctrl+c的时候,程序采用结束。
2)如果对程序稍微进行一下改动,则会出现另外一种情况。
改动为:act.sa_flags = SA_NODEFER;
经过这种改变之后,无论对ctrl+c操作多少次,程序都不会结束,并且在执行ctrl+c的处理函数的过程中,如果接收到ctrl+c信号,会立即执行一次处理函数,如果快速发送多次ctrl+c信号,那么多个ctrl+c的处理函数可以并行运行。
3)下面如果再对程序进行一次改动,则会出现第三种情况。
For example:  act.sa_flags = 0;
在执行信号处理函数这段期间,多次操作ctrl+c,程序也不会调用信号处理函数,而是在本次信号处理函数完成之后,再执行一次信号处理函数(无论前面产生了多少次ctrl+c信号)。
如果在2)执行信号处理函数的过程中,再次给予ctrl+c信号的时候,会导致再次调用信号处理函数。
4)如果在程序中设置了sigaddset(&act.sa_mask,SIGQUIT);程序在执行信号处理函数的过程中,发送ctrl+/信号,程序也不会退出,而是在信号处理函数执行完毕之后才会执行SIGQUIT的信号处理函数,然后程序退出。如果不添加这项设置,则程序将会在接收到ctrl+/信号后马上执行退出,无论是否在ctrl+c的信号处理函数过程中。
原因如下:
1)情况下,第一次产生ctrl+c信号的时候,该信号被自己设定的信号处理函数进行了处理。在处理过程中,由于我们设定了SA_RESETHAND标志位,又将该信号的处理函数设置为默认的信号处理函数(系统默认的处理方式为IGN),所以在第二次发送ctrl+c信号的时候,是由默认的信号处理函数处理的,导致程序结束;
2)情况下,我们去掉了SA_RESETHAND了标志位,导致程序中所有的ctrl+c信号均是由我们自己的信号处理函数来进行了处理,所以我们发送多少次ctrl+c信号程序都不会退出;
3)情况下,我们去掉了SA_NODEFER标志位。程序在执行信号处理函数过程中,ctrl+c信号将会被阻止,但是在执行信号处理函数期发送的ctrl+c信号将会被阻塞,直到信号处理函数执行完成,才有机会处理信号函数执行期间产生的ctrl+c,但是在信号函数执行产生的多次ctrl+c,最后只会产生ctrl+c。2)情况下,由于设置了SA_NODEF,ctrl+d信号将不会被阻塞。所以能够并行执行下次的信号处理函数。
4)情况下,我们是设置了在执行信号处理函数过程中,我们将屏蔽该信号,当屏蔽该信号的处理函数执行完毕后才会进行处理该信号。
附:
当我们按下ctrl+c的时候,操作为:向系统发送SIGINT信号,SIGINT信号的默认处理,退出程序。
当我们按下ctrl+/的时候,操作为:向系统发送SIGQUIT信号,该信号的默认处理为退出程序。
上面常规的sigaction的使用已经讲完,下面说下sigaction的高级应用。
1、 联合数据结构中的两个元素*sa_handler以及*sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。
2、 由_sa_handler指定的处理函数只有一个参数,即信号值,所以信号不能传递除信号值之外的任何信息;由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。第一个参数为信号值,第三个参数没有使用 (posix没有规范使用该参数的标准),第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:
在/usr/include/bits/siginfo.h中是这样定义的
typedef struct siginfo 
{
     int si_signo;            /* Signal number.     */
     int si_errno;            /* If non-zero, an errno value associated with
                      this signal, as defined in <errno.h>.     */
     int si_code;             /* Signal code.     */
     union
     {
       int _pad[__SI_PAD_SIZE];
       /* kill().     */
       struct
       {
         __pid_t si_pid;          /* Sending process ID.     */
         __uid_t si_uid;          /* Real user ID of sending process.     */
       } _kill;
       /* POSIX.1b timers.     */
       struct
       {
         int si_tid;          /* Timer ID.     */
         int si_overrun;          /* Overrun count.     */
         sigval_t si_sigval;      /* Signal value.     */
       } _timer;
       /* POSIX.1b signals.     */
       struct
       {
         __pid_t si_pid;          /* Sending process ID.     */
         __uid_t si_uid;          /* Real user ID of sending process.     */
         sigval_t si_sigval;      /* Signal value.     */
       } _rt;
       /* SIGCHLD.     */
       struct
       {
         __pid_t si_pid;          /* Which child.     */
         __uid_t si_uid;          /* Real user ID of sending process.     */
         int si_status;           /* Exit value or signal.     */
         __clock_t si_utime;
         __clock_t si_stime;
       } _sigchld;
       /* SIGILL, SIGFPE, SIGSEGV, SIGBUS.     */
       struct
       {
         void *si_addr;           /* Faulting insn/memory ref.     */
       } _sigfault;
       /* SIGPOLL.     */
       struct
       {
         long int si_band;        /* Band event for SIGPOLL.     */
         int si_fd;
       } _sigpoll;
     } _sifields;
} siginfo_t;

是不是太复杂了,在网上有一个简洁版我搬过来用一下
siginfo_t {
                     int         si_signo;     /* 信号值,对所有信号有意义*/
                     int         si_errno;     /* errno值,对所有信号有意义*/
                     int         si_code;      /* 信号产生的原因,对所有信号有意义*/
union{      /* 联合数据结构,不同成员适应不同信号 */ 
//确保分配足够大的存储空间
int _pad[SI_PAD_SIZE];
//对SIGKILL有意义的结构
struct{
...
     }...
... ...
... ... 
//对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构
         struct{
...
     }...
... ...
     }
}

sigaction.sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定SA_NODEFER或者SA_NOMASK标志位。
注:请注意sa_mask指定的信号阻塞的前提条件是:在由sigaction()安装信号的处理函数执行过程中由sa_mask指定的信号才被阻塞。
sigaction.sa_flags中包含了许多标志位,包括刚刚提到的SA_NODEFER及SA_NOMASK标志位。另一个比较重要的标志位是 SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的 sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为 sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)。
注:很多文献在阐述该标志位时都认为,如果设置了该标志位,就必须定义三参数信号处理函数。实际不是这样的,验证方法很简单:
自己实现一个单一参数信号处理函数,并在程序中设置该标志位,可以察看程序的运行结果。实际上,可以把该标志位看成信号是否传递参数的开关,如果设置该位,则传递参数;否则,不传递参数。
结构说的也差不多了,其实在我看到这些东西的时候给弄晕了好几回,简单的说几句就是
上面说了那么多结构体和复杂的很的siginfo,其实siginfo我们在实际应用中并不需要去初始化他或者做什么,只是在信号触发时我们可以从这个结构体中提取一些信息。
SA_SIGINFO的测试程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>

void WrkProcess(int n,struct siginfo *siginfo,void *myact)
{
        
        printf("WrkProcess .I get signal.%d threadid:%d\n",nsig,pthread_self());
        printf("signal number:%d\n",n);/** 打印出信号值 **/
        printf("siginfo signo:%d\n",siginfo->si_signo); /** siginfo结构里保存的信号值 **/
        printf("siginfo errno:%d\n",siginfo->si_errno); /** 打印出错误代码 **/
        printf("siginfo code:%d\n",siginfo->si_code);   /** 打印出出错原因 **/
        int i=0;
        while(i<5){
                printf("threadid:%d \n",pthread_self());
                sleep(1);
                i++;
        }
}

int main()
{
         /** install signal use sigaction **/
         struct sigaction act;
         sigemptyset(&act.sa_mask);   /** 清空阻塞信号 **/
         act.sa_flags=SA_SIGINFO;     /** 设置SA_SIGINFO 表示传递附加信息到触发函数 **/
         act.sa_sigaction=WrkProcess;
         if(sigaction(SIGINT,&act,NULL) < 0)
         {
                 printf("install signal error\n");
         }
        while(1)
        {
          printf("waiting for signal...\n");
          sleep(5);
        }
        return 0;
}

可以看到当按下CTRL+C结束程序时信号将触发执行test函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值