zoukankan      html  css  js  c++  java
  • 2.3 linux中的信号分析 阻塞、未达

    信号的阻塞、未达:

      linux中进程1向进程2发送信号,要经过内核,内核会维护一个进程对某个信号的状态,如下图所示:

    当进程1向进程2发送信号时,信号的传递过程在内核中是有状态的,内核首先要检查这个信号是不是处于阻塞状态,然后检查这个信号是不是处于未决状态,最后检查是不是忽略该信号。

    更详细的信号传递过程如下:

    一个信号送到进程2时,先检查这个进程的信号屏蔽字block,如果该信号对应位是1,表示进程把这个信号是屏蔽(阻塞)了,然后内核就将pending状态字的相应位置为1,表示信号未抵达,当我们在进程2中调用一个函数将block中的相应位置为0时,pending中的对应位就会被置为0,这时候刚才未达的信号就可以继续往后走了,然后检查进程2对这个信号是不是忽略,如果不是忽略就调用相应的信号处理函数。

      下面介绍几个操作信号集的函数:

    int sigemptyset(sigset_t  *set)   把信号集(64bit)全部清零

    int sigfillset(sigset_t *set)     把信号集全部置为1

    int sigaddset(sigset_t *set, int signo) 根据signo,把信号集中的相应位置为1

    int sigdelset(sigset_t *set, int signo) 根据signo,把信号集中相应的位清0

    int sigismember(const sigset_t *set,  int signo) 判断signo是否在信号集中

       获取block状态字状态的函数:

    int sigprocmask(int how, const sigset_t *set, sigset_t *oset)  读取或者更改进程的信号屏蔽状态字(block)

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

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

    int sigpending(sigset_t *set)  获取进程没有抵达的状态字

    信号阻塞、未达示例程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 
     8 void my_handler(int num)
     9 {
    10     if(SIGINT == num)
    11     {
    12         printf("recv signal SIGINT
    ");
    13     }
    14     else if(SIGQUIT == num)
    15     {
    16         sigset_t uset;
    17         sigemptyset(&uset);
    18         sigaddset(&uset, SIGINT);
    19         sigprocmask(SIG_UNBLOCK, &uset, NULL);
    20         printf("recv signal num = %d
    ", num);
    21     }
    22 
    23 }
    24 
    25 void printsigset(sigset_t *set)
    26 {
    27     int i = 0;
    28     for(i = 1; i < 65; i++)
    29     {
    30         if(sigismember(set, i))
    31         {
    32             putchar('1');
    33         }
    34         else
    35         {
    36             putchar('0');
    37         }
    38     }
    39     
    40     printf("
    ");
    41 }
    42 
    43 int main()
    44 {
    45     sigset_t bset;
    46     sigset_t pset;
    47     
    48     sigemptyset(&bset);
    49     sigaddset(&bset, SIGINT);
    50     
    51     if(signal(SIGINT, my_handler) == SIG_ERR)
    52     {
    53         perror("signal error");
    54         exit(0);
    55     }
    56     
    57     if(signal(SIGQUIT, my_handler) == SIG_ERR)
    58     {
    59         perror("signal error");
    60         exit(0);
    61     }
    62     
    63     sigprocmask(SIG_BLOCK, &bset, NULL);
    64     
    65     for(;;)
    66     {
    67         sigpending(&pset);
    68         printf("bset : 
    ");
    69         printsigset(&bset);
    70         printf("pset : 
    ");
    71         printsigset(&pset);
    72         
    73         sleep(2);
    74     }
    75     
    76     return 0;
    77 }

    在主函数中,我们将SIGINT设置为阻塞,执行程序时,当没有按下ctrl+c(产生SIGINT)时,pending状态字为全0,说明没有未达信号,当按下ctrl+c时,由于block中将SIGINT设置为了阻塞,所以当产生SIGINT时,pending中的第2位(SIGINT对应的位,信号从第1位开始算起)被置为了1。执行程序,结果如下:

     ctrl+c产生SIGINT, ctrl+产生SIGQUIT。按下ctrl+触发信号处理函数,产生SIGQUIT信号,解除对SIGINT的阻塞,而刚刚那个未达的SIGINT被送达,再一次触发信号处理函数,打印出recv signal SIGINT。SIGINT被送达后,相应的pending位被清0了。我们在信号处理函数中将block中的阻塞位清除,但是并没有起作用(原因未知,在信号处理函数中设置block,只是临时起作用,例如:现在有一个未达信号SIGINT,我们产生SIGQUIT进入信号处理函数,临时将block中的阻塞位解除,然后处理这个未达信号,处理完之后,将阻塞位恢复原样,然后继续执行。 从执行结果也可以看出,是先打印出recv signal SIGINT,又打印出recv signal num = 3)。

      block中设置SIGINT屏蔽字,按下ctrl+c,pending相应位置为1,按下ctrl+,SIGQUIT被送达一次,信号处理函数被调用,SIGINT又被送达一次(因为刚才处于pending),SIGQUIT被送达的那次,在信号处理函数中将block中SIGINT位清0,并恢复SIGINT的处理函数为默认。再次按下ctrl+c,应该退出程序,但是没有退出,而是pending中又被置为1,再次按下ctrl+,则信号处理函数中SIGQUIT相关的打印被输出,然后程序退出。

    sigaction注册信号处理函数:

      sigaction也用来做信号的安装,该函数功能比signal更强大。函数原型如下:

    int sigaction(int signum, const struct sigaction *act, const struct sigaction *old)

      该函数的第一个参数为信号的值,可以为除SIGKILL和SIGSTOP外的任何一个有效信号值(为这两个信号定义自己的处理函数,将导致安装错误)。

      第二个参数为指向sigaction的一个实例的指针,在结构sigaction中指定了对特定信号的处理,可以为空,进程会以缺省方式对信号进行处理。

      第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定为NULL。

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

    struct sigaction

    {

      void (*sa_handler)(int)  //信号处理程序,不接受额外数据,老的处理函数

      void (*sa_sigaction)(int, siginfo_t *, void *) // 信号处理程序,能接受额外数据,和sigqueue配合使用

      sigset_t sa_mask;

      int sa_flags;  //影响信号的行为,SA_SIGINFO表示能接受数据,如果进程想要接收额外数据,则应设置该位

      void (*sa_restorer)(void)  //废弃

    }

    sa_handler和sa_sigaction不能同时存在,两个都赋值的话,优先调用sa_sigaction。

    示例程序:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 
     8 void my_handler(int num)
     9 {
    10     printf("recv signal num = %d
    ", num);
    11 }
    12 
    13 
    14 int main()
    15 {
    16     struct sigaction act = {0};
    17     
    18     act.sa_handler = my_handler;
    19     
    20     sigaction(SIGINT, &act, NULL);
    21     
    22     for(;;)
    23     {
    24         sleep(2);
    25     }
    26     
    27     return 0;
    28 }

    执行结果如下:

    赋值sa_sigaction的示例程序:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 
     8 void my_handler(int num)
     9 {
    10     printf("recv signal num = %d
    ", num);
    11 }
    12 
    13 void my_sa_sigaction(int num, siginfo_t *info, void *p)
    14 {
    15     printf("recv sig : %d
    ", num);
    16 }
    17 
    18 int main()
    19 {
    20     struct sigaction act = {0};
    21     
    22     act.sa_handler = my_handler;
    23     act.sa_sigaction = my_sa_sigaction;
    24     
    25     sigaction(SIGINT, &act, NULL);
    26     
    27     for(;;)
    28     {
    29         sleep(2);
    30     }
    31     
    32     return 0;
    33 }

    执行结果如下:

    sigqueue函数:

      新的发送信号的系统调用,主要是针对实时信号提出的信号带有参数,与函数sigaction()配合使用。比kill函数强大,函数原型如下:

    int sigqueue(pid_t pid, int sig, const union sigval value)

      第一个参数指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

    sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。

    sigval联合体如下:

      typedef union sigval

      {

        int sival_int;

        void *sival_ptr;

      }sigval_t;

     下面我们编写带有额外数据的信号发送处理函数,先给出信号处理函数中第二个参数siginfo_t的具体定义:

    siginfo_t {
      int si_signo; /* Signal number */
      int si_errno; /* An errno value */
      int si_code; /* Signal code */
      int si_trapno; /* Trap number that caused
      hardware-generated signal
      (unused on most architectures) */
      pid_t si_pid; /* Sending process ID */
      uid_t si_uid; /* Real user ID of sending process */
      int si_status; /* Exit value or signal */
      clock_t si_utime; /* User time consumed */
      clock_t si_stime; /* System time consumed */
      sigval_t si_value; /* Signal value */
      int si_int; /* POSIX.1b signal */
      void *si_ptr; /* POSIX.1b signal */
      int si_overrun; /* Timer overrun count; POSIX.1b timers */
      int si_timerid; /* Timer ID; POSIX.1b timers */
      void *si_addr; /* Memory location which caused fault */
      int si_band; /* Band event */
      int si_fd; /* File descriptor */
    }

    程序如下:

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <unistd.h>
     4 #include <signal.h>
     5 
     6 
     7 
     8 void my_handler(int num)
     9 {
    10     printf("recv signal num = %d
    ", num);
    11 }
    12 
    13 void my_sa_sigaction(int num, siginfo_t *info, void *p)
    14 {
    15     int myintnum = 0;
    16     myintnum = info->si_value.sival_int;
    17     printf("%d  %d 
    ", myintnum, info->si_int);
    18 }
    19 
    20 int main()
    21 {
    22     pid_t pid;
    23     struct sigaction act = {0};
    24     
    25     act.sa_sigaction = my_sa_sigaction;
    26     sigemptyset(&act.sa_mask);
    27     act.sa_flags = SA_SIGINFO;
    28     
    29     if (sigaction(SIGINT, &act, NULL) < 0)
    30     {
    31         perror("sigaction error");
    32         exit(0);
    33     }
    34     
    35     pid = fork();
    36     
    37     if(pid == -1)
    38     {
    39         perror("fork error");
    40     }
    41     
    42     if(pid == 0)
    43     {
    44         union sigval usig;
    45         usig.sival_int = 100;
    46         int n = 5;
    47         while(n > 0)
    48         {
    49             sigqueue(getppid(), SIGINT, usig);
    50             n--;
    51             sleep(2);
    52         }
    53         exit(0);
    54     }
    55     
    56     for(;;)
    57     {
    58         sleep(2);
    59     }
    60     
    61     return 0;
    62 }

    执行结果如下:

      如果子进程不睡眠,而是一直发信号,则可能造成信号丢失,因为SIGINT是不可靠信号。

    实时信号与非实时信号示例程序如下:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <unistd.h>
      4 #include <signal.h>
      5 
      6 
      7 void my_sigaction(int signum, siginfo_t *info, void *p)
      8 {
      9     if(SIGINT == signum)
     10     {
     11         printf("recv SIGINT, num = %d
    ", signum);
     12     }
     13     else if(SIGRTMIN == signum)
     14     {
     15         printf("recv SIGRTMIN num = %d
    ", signum);
     16     }
     17     else if(SIGUSR1 == signum)
     18     {
     19         sigset_t set;
     20         sigemptyset(&set);
     21         sigaddset(&set, SIGINT);
     22         sigaddset(&set, SIGRTMIN);
     23         sigprocmask(SIG_UNBLOCK, &set, NULL);
     24     }
     25     else
     26     {
     27         printf("recv else
    ");
     28     }
     29 }
     30 
     31 int main()
     32 {
     33     pid_t pid;
     34     int ret = 0;
     35     struct sigaction act = {0};
     36     sigemptyset(&act.sa_mask);
     37     act.sa_flags = SA_SIGINFO;
     38     act.sa_sigaction = my_sigaction;
     39     
     40     if (sigaction(SIGINT, &act, NULL) < 0)
     41     {
     42         perror("sigaction error");
     43         exit(0);
     44     }
     45     
     46     if(sigaction(SIGRTMIN, &act, NULL) < 0 )
     47     {
     48         perror("sigaction error");
     49         exit(0);
     50     }
     51     
     52     if(sigaction(SIGUSR1, &act, NULL) < 0 )
     53     {
     54         perror("sigaction error");
     55         exit(0);
     56     }
     57     
     58     sigset_t bset;
     59 
     60     sigemptyset(&bset);
     61     sigaddset(&bset, SIGINT);
     62     sigaddset(&bset, SIGRTMIN);
     63     
     64     sigprocmask(SIG_BLOCK, &bset, NULL);
     65     
     66     pid = fork();
     67     
     68     if(pid == -1)
     69     {
     70         perror("fork error");
     71         exit(0);
     72     }
     73     
     74     if(pid == 0)
     75     {
     76         int i = 0;
     77         union sigval v;
     78         v.sival_int = 200;
     79         
     80         for(i = 0; i < 3; i++)
     81         {
     82             ret = sigqueue(getppid(), SIGINT, v);
     83             if(ret != 0)
     84             {
     85                 printf("sent SIGINT failed
    ");
     86             }
     87             else
     88             {
     89                 printf("sent SIGINT success
    ");
     90             }
     91             
     92         }
     93         
     94         v.sival_int = 300;
     95         for(i = 0; i < 3; i++)
     96         {
     97             ret = sigqueue(getppid(), SIGRTMIN, v);
     98             if(ret != 0)
     99             {
    100                 printf("sent SIGRTMIN failed
    ");
    101             }
    102             else
    103             {
    104                 printf("sent SIGRTMIN success
    ");
    105             }
    106         }
    107         
    108         v.sival_int = 400;
    109         kill(getppid(), SIGUSR1);
    110         
    111         exit(0);
    112     }
    113     
    114     while(1)
    115     {
    116         sleep(1);
    117     }
    118     
    119     printf("end main ...
    ");
    120     
    121     return 0;
    122 }

    我们注册了非实时信号SIGINT和实时信号SIGRTMIN,还有一个用户自定义信号SIGUSR1,在本例中负责发送解除命令。它们为同一个处理程序,只是有不同的分支,在子进程中发送了3次SIGINT和三次SIGRTMIN,一开始,这两个信号都是阻塞的,因此发送完成后它们都处于未决状态,当发送SIGUSR1后,临时解除阻塞,未决信号重新被发送,但是非实时信号只被发送了一次,属于不可靠信号。而实时信号发送原来的次数,属于可靠信号。执行结果如下所示:

     实时的信号会存储在进程的结构中,所以不会丢失,会缓存到内核中,但是存储的数量也是有上限的,超过缓存上限后,再到来的信号会直接扔掉而不会将之前的冲掉,具体上限可以使用ulimit -a查看,pending signals如下所示:

    而非实时信号,linux内核是不缓存的,发送多少也无所谓,最终只缓存一条。

  • 相关阅读:
    Sitecore Digital Marketing System, Part 1: Creating personalized, custom content for site visitors(自定义SiteCore中的 Item的Personalize的Condition) -摘自网络
    Send email alert from Performance Monitor using PowerShell script (检测windows服务器的cpu 硬盘 服务等性能,发email的方法) -摘自网络
    使用Mono Cecil 动态获取运行时数据 (Atribute形式 进行注入 用于写Log) [此文报考 xxx is declared in another module and needs to be imported的解决方法]-摘自网络
    秒杀 ILSpy 等反编译利器 DotNet Resolver
    Nagios:企业级系统监控方案
    C# Asp.net中的AOP框架 Microsoft.CCI, Mono.Cecil, Typemock Open-AOP API, PostSharp -摘自网络 (可以利用反射 Attribute 进行面向切面编程 可以用在记录整个方法的Log方面)
    Windows性能监视器之CPU、硬盘、IO等监控方法详解-摘自网络
    网站防刷方案 -摘自网络
    利用XSD配合XSLT產出特定格式Word檔案 -摘自网络
    asp页面快速找到菜单按钮转向的页面的方法
  • 原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9375211.html
Copyright © 2011-2022 走看看