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内核是不缓存的,发送多少也无所谓,最终只缓存一条。

原文地址:https://www.cnblogs.com/wanmeishenghuo/p/9375211.html