Linux signal那些事儿

时间:2021-04-02 00:10:35

Linux signal 那些事儿(1)

http://blog.chinaunix.net/uid-24774106-id-4061386.html

Bean_lee

Linux编程,信号是一个让人爱恨交加又不得不提的一个领域。最近我集中学习了Linux的signal相关的内容,分享出来,也为防止自己忘记。
    信号的本质是异步。异步一这个词,听着高端大气上档次,又让人云山雾绕,其则不然。其实我们想想,我们这个世界是异步的,每个人干事儿,并不总是A->B->C->D这种。比如我在网上买了东西,我其实并不知道快递几时能到。我可能在公司里面,在喝水,在回邮件,在查bug,在写代码,突然收到了快递小哥的电话,注意这就是信号的delivery。由于快递的到来,我不得不停下我手头的活儿,去签收快递。这就是传说中的典型的异步。我不知道快递小哥几时给我电话,但是我收到电话就去签收,这是我的信号处理函数。更高级一点,如果我在参加重要的会议,我可能需要屏蔽快递小哥的电话(假如我知道其电话),这已经是linux下信号的高级应用(sigprocmask)了。
    信号是一种机制,是在软件层次对中断机制的一种模拟,内核让某进程意识到某特殊事情发生了。强迫进程去执行相应的信号处理函数。至于信号的来源可能来自硬件如按下键盘或者硬件故障(如ctrl+c发送SIGINT),可能来自其他进程(kill,sigqueue),可能来自自己进程(raise)。 
    信号的本质是一种进程间的通信,一个进程可以向另一个进程发送信号,至少传递了signo这个int值。实际上,通信的内容,可以远不止是signo,可以通过SA_SIGINFO标志位通知进程去取额外的信息。
    我痛恨片汤话儿,可是上面一大坨片汤话儿,却真真的道出了信号的本质。
    前面也提到了,signal是个让人爱恨交加的feature,原因在于沉重的历史包袱。下面我将一一道来。
    在上古时期,UNIX就已经有了signal这个feature,但是当时的signal存在几个问题:
   1 传统的信号处理函数是一次性的,而非永久性的。
    linux为了向下兼容,依然实现了这个有缺陷的signal系统调用。你可看到signal系统调用的内核代码中有SA_ONESHOT这个标志位。

  1. #ifdef __ARCH_WANT_SYS_SIGNAL
  2. /*
  3.  * For backwards compatibility. Functionality superseded by sigaction.
  4.  */
  5. SYSCALL_DEFINE2(signal,int, sig, __sighandler_t, handler)
  6. {
  7.     struct k_sigaction new_sa, old_sa;
  8.     int ret;

  9.     new_sa.sa.sa_handler= handler;
  10.     new_sa.sa.sa_flags= SA_ONESHOT | SA_NOMASK;
  11.     sigemptyset(&new_sa.sa.sa_mask);

  12.     ret = do_sigaction(sig, &new_sa, &old_sa);

  13.     return ret ? ret :(unsigned long)old_sa.sa.sa_handler;
  14. }
  15. #endif /* __ARCH_WANT_SYS_SIGNAL*/

    这个SA_ONESHOT标志位,等同于SA_RESETHAND标志:在arch/x86/include/uapi/asm/signal.h中有:

  1. #define SA_ONESHOT    SA_RESETHAND

    信号产生,到信号处理函数开始执行,中间肯定是有时间差的。内核开始开始强迫进程对信号作出响应,这叫作信号的传递。也就是说信号产生,内核只是在进程描述符记录了一笔,该进程收到信号X一枚,并没有马上强迫进程对信号作出响应。已经产生但尚未传递的信号叫挂起信号。对于非实时而言,信号不排队,位图占个位即可。对于实时信号,则排队,同一信号可能有多个挂起信号。这个不多说,后面自然提到。
    Linux signal那些事儿
    上图反映了内核如何传递信号。基本就是选择一个挂起信号,然后处理一个信号。get_signal_to_deliver 是在进程中选择一个信号来handle。代码在kernel/signal.c,其中有如下code:

  1.         if (ka->sa.sa_handler== SIG_IGN)/* Do nothing.*/
  2.             continue;
  3.         if (ka->sa.sa_handler!= SIG_DFL){
  4.             /* Run the handler.*/
  5.             *return_ka = *ka;

  6.             if (ka->sa.sa_flags& SA_ONESHOT)
  7.                 ka->sa.sa_handler= SIG_DFL;

  8.             break; /* will return non-zero"signr" value */
  9.         }

    我们看到了linux也实现了signal这个有缺陷的系统调用。传统的signal系统调用,他的信号处理函数是一次性的,执行过后,该信号的信号处理函数就变成了SIG_DFL。
    值得一提的是,glibc的signal函数,调用的已经不是传统的signal系统调用,而是rt_sigaction系统调用,这种一次性的缺陷早已经解决了。怎么证明: 

  1. manu@manu-hacks:~/code/c/self/signal$ cat signal_fault_1.c
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <signal.h>
  5. #include <string.h>
  6. #include <errno.h>

  7. #define MSG "OMG , I catch the signal SIGINT\n"
  8. #define MSG_END "OK,finished process signal SIGINT\n"
  9. int do_heavy_work()
  10. {
  11.     int i ;
  12.     int k;
  13.     srand(time(NULL));

  14.     for(i= 0 ; i < 100000000;i++)
  15.     {
  16.         k = rand()%1234589;
  17.     }

  18. }

  19. void signal_handler(int signo)
  20. {
  21.     write(2,MSG,strlen(MSG));
  22.     do_heavy_work();
  23.     write(2,MSG_END,strlen(MSG_END));
  24. }

  25. int main()
  26. {
  27.     char input[1024]= {0};

  28. #if defined TRADITIONAL_SIGNAL_API
        if(syscall(SYS_signal ,SIGINT,signal_handler) == -1)
    #elif defined SYSTEMV_SIGNAL_API
        if(sysv_signal(SIGINT,signal_handler) == -1)
    #else
        if(signal(SIGINT,signal_handler) == SIG_ERR)
    #endif

  29.     {
  30.         fprintf(stderr,"signal failed\n");
  31.         return -1;
  32.     }

  33.     printf("input a string:\n");
  34.     if(fgets(input,sizeof(input),stdin)==NULL)
  35.     {
  36.         fprintf(stderr,"fgets failed(%s)\n",strerror(errno));
  37.         return -2;
  38.     }
  39.     else
  40.     {
  41.         printf("you entered:%s",input);
  42.     }

  43.     return 0;

  44.     
  45. }

    编译的时候,我没有定义SYSTEMV_SIGNAL_API,就是标准的glibc的signal函数,我用strace跟踪glibc的signal函数调用的系统调用是: 

  1. rt_sigaction(SIGINT, {0x8048736,[INT], SA_RESTART}, {SIG_DFL,[], 0}, 8)= 0

    测试结果如下:

  1. manu@manu-hacks:~/code/c/self/signal$ gcc-o signal_glibc signal_fault_1.c
  2. manu@manu-hacks:~/code/c/self/signal$./signal_glibc
  3. input a string:
  4. input^COMG , I catch the signal SIGINT
  5. ^COK,finished process signal SIGINT
  6. OMG , I catch the signal SIGINT
  7. OK,finished process signal SIGINT
  8. ^COMG , I catch the signal SIGINT
  9. OK,finished process signal SIGINT
  10. ^COMG , I catch the signal SIGINT
  11. OK,finished process signal SIGINT
  12. ^Z
  13. [1]+ Stopped./signal_glibc

    我们安装的信号处理函数并不是一次性的,原因就是glibc的signal函数调用的函数并非是signal系统调用,并没有SA_ONESHOT标志位。
    我们如何体验下老古董的signal,glibc提供了一个sysv_signal接口,manual中这样描述:

  1.    However sysv_signal() provides the System V unreliable signal semantics, that is: a) the disposition of the sig‐
  2.    nal is reset to the default when the handler is invoked; b) deliveryof further instances of the signal is not
  3.    blocked while the signal handler is executing;and c) if the handler interrupts (certain) blocking system calls,
  4.    then the system call is not automatically restarted.

    对于我们的例子只需要:

  1. gcc -DSYSTEMV_SIGNAL_API-o signal_sysv signal_fault_1.c

    我们看下:

  1. manu@manu-hacks:~/code/c/self/signal$./signal_sysv
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. ^C
  5. manu@manu-hacks:~/code/c/self/signal$ man sysv_signal

    第二个ctrl+C导致了进程的推出,原因是sysv_signal这种传统的signal的安装函数是一次性的。strace也证明了这一点:

  1. rt_sigaction(SIGINT, {0x8048756,[], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_DFL, [], 0}, 8)= 0

    还记得么:

  1. #define SA_ONESHOT SA_RESETHAND

    我们发现sysv调用的不是signal系统调用,而是rt_sigaction系统调用。如果你非要品尝传统的signal系统调用,这也不难。

  1. gcc -DTRADITIONAL_SIGNAL_API  -o signal_traditional signal_fault_1.c 

    我们发现第二个SIGINT信号的信号处理函数已经SIG_DFL,使进程退出了。

  1. manu@manu-hacks:~/code/c/self/signal$./signal_traditional
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. ^C

    我们通过strace可以证明,的确调用了signal系统调用: 

  1. signal(SIGINT, 0x8048736)= 0 (SIG_DFL) 

    2早期的信号,没有屏蔽正在处理的信号。
   
如何证明这一点呢?我上面的例子中故意在信号处理函数中做了很heavy很耗时的操作,从而容易造出处理信号A的时候,另一信号A又被deliver的场景。
    因为do_heavy_work是个很耗费时间的操作,信号处理完成我们会在标准错误上输出处理完成的语句,这就表征了信号处理结束了没有。
    我们看下传统signal的,收到一个SIGINT的信号的情况:

  1. manu@manu-hacks:~/code/c/self/signal$./signal_traditional
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. OK,finished process signal SIGINT
  5. fgets failed(Interrupted system call)
  6. manu@manu-hacks:~/code/c/self/signal$

    如果我在进程处理信号处理函数的时候,再次发送一个SIGINT,这个SIGINT也可能被内核deliver。那么信号处理函数就被中断掉,

  1. manu@manu-hacks:~/code/c/self/signal$./signal_traditional
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. ^C
  5. manu@manu-hacks:~/code/c/self/signal$

    我们看到我们收到了I catch the signal SIGINT的打印,但是,并没有收到OK,I finished process signal SIGINT,这表明传统的signal并没有屏蔽正在处理的信号。
    那么我们现在的glibc的signal函数如何?
    strace又来帮忙了?

  1. rt_sigaction(SIGINT,{0x8048736,[INT], SA_RESTART}, {SIG_DFL,[], 0}, 8)= 0

    glibc的signal函数,调用的是rt_sigaction 系统调用:
    

  1. SYSCALL_DEFINE4(rt_sigaction,int, sig,
  2.         const struct sigaction __user *, act,
  3.         struct sigaction __user *, oact,
  4.         size_t, sigsetsize)

  5. struct sigaction {
  6.     union {
  7.      __sighandler_t _sa_handler;
  8.      void (*_sa_sigaction)(int,struct siginfo *, void*);
  9.     } _u;
  10.     sigset_t sa_mask;
  11.     unsigned long sa_flags;
  12.     void (*sa_restorer)(void);
  13. }

    我们把strace中的信息,和sigaction数据对比,发现,[INT],就是传说中的sa_mask,当处理SIGINT的时候,看起来是在处理SIGINT信号处理函数的时候,SIGINT会被被屏蔽,防止重入。实际如何呢? 

  1. manu@manu-hacks:~/code/c/self/signal$./signal_glibc
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. ^C^C^C^COK,finished process signal SIGINT
  5. OMG , I catch the signal SIGINT
  6. ^C^COK,finished process signal SIGINT
  7. OMG , I catch the signal SIGINT
  8. OK,finished process signal SIGINT
  9. ^COMG , I catch the signal SIGINT
  10. OK,finished process signal SIGINT
  11. ^COMG , I catch the signal SIGINT
  12. ^Z
  13. [2]+ Stopped./signal_glibc

    从未出现OMG,I catch the SIGINT连续出现。这是偶然还是必然呢?答案是必然,内核是如何做到的呢?
    在上图的handle_signal函数的末尾,调用了signal_delivered函数: 

  1. /**
  2.  * signal_delivered -
  3.  * @sig:        number of signal being delivered
  4.  * @info:        siginfo_t of signal being delivered
  5.  * @ka:            sigaction setting that chose the handler
  6.  * @regs:        user register state
  7.  * @stepping:        nonzeroif debugger single-stepor block-stepin use
  8.  *
  9.  * This function should be called when a signal has succesfully been
  10.  * delivered. It updates the blocked signals accordingly(@ka->sa.sa_mask
  11.  * is always blocked,and the signal itself is blocked unless %SA_NODEFER
  12.  * is set in @ka->sa.sa_flags. Tracingis notified.
  13.  */
  14. void signal_delivered(int sig, siginfo_t*info, struct k_sigaction*ka,
  15.             struct pt_regs *regs,int stepping)
  16. {
  17.     sigset_t blocked;

  18.     /* A signal was successfully delivered,and the
  19.      saved sigmask was stored on the signal frame,
  20.      and will be restored by sigreturn. So we can
  21.      simply clear the restore sigmask flag.*/
  22.     clear_restore_sigmask();

  23.     sigorsets(&blocked,&current->blocked,&ka->sa.sa_mask);
  24.     if (!(ka->sa.sa_flags& SA_NODEFER))
  25.         sigaddset(&blocked, sig);
  26.     set_current_blocked(&blocked);
  27.     tracehook_signal_handler(sig, info, ka, regs, stepping);
  28. }

    这个函数很有意思,只要用户没有指定SA_NODEFER标志位,当前处理的信号总是加入到屏蔽信号之中。深入理解Linux内核在也提到了这一点。经典教材是这么说的:

  1. 当进程执行一个信号处理程序的函数时,通常屏蔽相应的信号,即自动阻塞这个信号,直到处理程序结束。因此,所处理的信号的另一次出现,并不能中断信号处理程序,所以信号处理函数不必是可以重入的。

    这个结论很震惊吧。是的你用glibc的signal函数,不必担心信号处理函数的嵌套问题。至于重入问题我们后文讨论。
    那么传统的signal系统调用和sysv_signal又如何?为何他们存在信号的可重入问题?   

  1. SYSCALL_DEFINE2(signal,int, sig, __sighandler_t, handler)
  2. {
  3.     struct k_sigaction new_sa, old_sa;
  4.     int ret;

  5.     new_sa.sa.sa_handler= handler;
  6.     new_sa.sa.sa_flags= SA_ONESHOT |SA_NOMASK;
  7.     sigemptyset(&new_sa.sa.sa_mask);

  8.     ret = do_sigaction(sig, &new_sa, &old_sa);

  9.     return ret ? ret :(unsigned long)old_sa.sa.sa_handler;
  10. }

#define SA_NOMASK SA_NODEFER

    至于sysv_signal

  1. rt_sigaction(SIGINT, {0x8048756,[], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_DFL, [], 0}, 8)= 0

    不多说了,不作死就不会死,signal系统调用和sysv_signal都作死:sa_mask是空,更要命的是都有SA_NODEFER 。自作孽,不可活。之所以如此自作孽,就是为了向下兼容,向传统的signal致敬。
    
    3 早期的signal,会中断系统调用。  
    何意?  
    某些系统调用可能会被信号中断,此时系统调用返回错误EINTR,表示被信号中断了。非常多的系统调用都会被中断,我前面有篇博文重启系统调用探究,就详细介绍了系统被信号中断的问题,传统的signal会出现这个问题。那么glibc的signal函数有没有这个问题?答案是没有这个问题,glibc的signal函数很不错。

  1. rt_sigaction(SIGINT, {0x8048736,[INT],SA_RESTART}, {SIG_DFL,[], 0}, 8)= 0

    signal系统调用和sysv_signal都有这个弊端:请看: 

  1. manu@manu-hacks:~/code/c/self/signal$./signal_traditional
  2. input a string:
  3. ^COMG , I catch the signal SIGINT
  4. OK,finished process signal SIGINT
  5. fgets failed(Interrupted systemcall)
  6. manu@manu-hacks:~/code/c/self/signal$./signal_sysv
  7. input a string:
  8. ^COMG , I catch the signal SIGINT
  9. OK,finished process signal SIGINT
  10. fgets failed(Interrupted systemcall)
  11. manu@manu-hacks:~/code/c/self/signal$

    原因就是没有SA_RESTART标志位。内核代码如何体现:

  1. static void
  2. handle_signal(unsigned long sig, siginfo_t*info, struct k_sigaction*ka,
  3.         struct pt_regs *regs)
  4. {
  5.     /* Are we from a systemcall? */
  6.     if (syscall_get_nr(current, regs)>= 0){
  7.         /*If so, check systemcall restarting..*/
  8.         switch (syscall_get_error(current, regs)){
  9.         case -ERESTART_RESTARTBLOCK:
  10.         case -ERESTARTNOHAND:
  11.             regs->ax= -EINTR;
  12.             break;

  13.         case -ERESTARTSYS:
  14.             if (!(ka->sa.sa_flags& SA_RESTART)){
  15.                 regs->ax= -EINTR;
  16.                 break;
  17.             }
  18.         /* fallthrough*/
  19.         case -ERESTARTNOINTR:
  20.             regs->ax= regs->orig_ax;
  21.             regs->ip-= 2;
  22.             break;
  23.         }
  24.     }
  25.     。。。
  26. }

    很多文章都都将signal函数描述的多么不堪,其实glibc的signal函数非常靠谱,传统的signal的几个弊端都不存在,在日常的工作中,signal完全可以满足需要。
但是存在一个问题,就会可移植性。由于不同的平台可能不同。单就linux平台而言,glibc的signal函数还不错。
    那么signal还有什么问题呢?为啥有引入了实时信号?那是下一篇内容。

参考文献
1 深入理解linunx内核
 linux内核源代码情景分析
3 signal ppt 
 蘇維農
4 linux系统编程

 

Linux signal 那些事儿(2)

 

  1. 上一篇博文,基本算是给glibc的signal函数翻了个身。现在glibc的signal基本修正了传统的UNIX的一些弊端,我们说signal并没有我们想象的那么不堪。但是signal也有不尽人意的地方。比如信号处理期间,我们期望屏蔽某些信号,而不仅仅是屏蔽自身,这时候signal就不行了。信号既然是进程间通信IPC的一种机制,我们期望获取更多的信息,而不仅仅是signo,这时候signal/kill这个机制就基本不行了。
        上面所说的都是signal的一些毛病,但是这些都不是致命的,致命的问题在于老的signal机制的不可靠。信号分成可靠性信号和非可靠性信号,并不是说用sigaction安装,用sigqueue发送的信号就是可靠性性信号,用signal安装,kill/tkill发送的信号就是非可靠性信号。这种理解是错误的。这在Linux环境进程间通信(二):信号(上)一文中讲的非常清楚了。
        信号值位于[SIGRTMIN,SIGRTMAX] 之间的信号,就是可靠信号,位于[SIGHUP,SIGSYS]之间信号,都是非可靠性信号,与安装函数是signal还是sigaction无关,与发送函数是kill还是sigqueue无关。
    Linux signal那些事儿
        1~31之间的所有信号都称为不可靠信号,原因就在于信号不可排队,如果kernel发现同一个信号已经有挂起信号,当前信号就会被丢弃,就好象从来没有被发送过一样,无法引起信号的传递,也无法让进程执行信号处理函数。这种实现的机理,造成了这些信号的不可靠。这正所谓:我本将心向明月,奈何明月照沟渠。
        为了解决这个问题,Linux引入了实时信号,信号值在[32~64]区间内,或者称之为可靠信号。这种信号,kernel不会ignore,哪怕已经有了好多同一个信号,kernel会把新收到信号放入queue之中,等待被传递出去。
        空口说白话,不是我们的风格,我现在用代码证明之。我参考了Linux Programming Interface 一书的例子,写了两个程序,一个是signal_receiver ,一个是signal_sender.
        先看signal_receiver的code:    
    1. manu@manu-hacks:~/code/c/self/signal$ cat signal_receiver.c
    2. #include <stdio.h>
    3. #include <stdlib.h>
    4. #include <unistd.h>
    5. #include <signal.h>
    6. #include <string.h>
    7. #include <errno.h>


    8. static int sig_cnt[NSIG];
    9. static volatile sig_atomic_t get_SIGINT = 0;

    10. void handler(int signo)
    11. {
    12.     if(signo== SIGINT)
    13.         get_SIGINT = 1;
    14.     else
    15.         sig_cnt[signo]++;
    16. }

    17. int main(int argc,char* argv[])
    18. {
    19.     int i = 0;
    20.     sigset_t blockall_mask ;
    21.     sigset_t pending_mask ;
    22.     sigset_t empty_mask ;
    23.     printf("%s:PID is %ld\n",argv[0],getpid());

    24.     
    25.     for(i= 1; i < NSIG; i++)
    26.     {
    27.         if(i== SIGKILL|| i== SIGSTOP)
    28.             continue;

    29.         if(signal(i,&handler)== SIG_ERR)
    30.         {
    31.             fprintf(stderr,"signal for signo(%d) failed (%s)\n",i,strerror(errno));
    32. //            return-1;
    33.         }
    34.     }

    35.     if(argc> 1)
    36.     {
    37.         int sleep_time = atoi(argv[1]);
    38.         sigfillset(&blockall_mask);

    39.         if(sigprocmask(SIG_SETMASK,&blockall_mask,NULL)== -1)
    40.         {
    41.             fprintf(stderr,"setprocmask to block all signal failed(%s)\n",strerror(errno));
    42.             return -2;
    43.         }

    44.         printf("I will sleep %d second\n",sleep_time);

    45.         sleep(sleep_time);
    46.         if(sigpending(&pending_mask)== -1)
    47.         {
    48.             fprintf(stderr,"sigpending failed(%s)\n",strerror(errno));
    49.             return -2;
    50.         }

    51.         for(i= 1 ; i < NSIG ; i++)
    52.         {
    53.             if(sigismember(&pending_mask,i))
    54.             printf("signo(%d) :%s\n",i,strsignal(i));
    55.         }

    56.         sigemptyset(&empty_mask);
    57.         if(sigprocmask(SIG_SETMASK,&empty_mask,NULL)== -1)
    58.         {
    59.             fprintf(stderr,"setprocmask to release all signal failed(%s)\n",strerror(errno));
    60.             return -3;
    61.         }
    62.         
    63.     }

    64.     while(!get_SIGINT)
    65.         continue ; //why not use pause? I will explain later

    66.     for(i= 1; i < NSIG ; i++)
    67.     {
    68.         if(sig_cnt[i]!= 0 )
    69.         {
    70.             printf("%s:signal %d caught %d time%s\n",
    71.                     argv[0],i,sig_cnt[i],(sig_cnt[i]>1)?"s":"");
    72.         }
    73.     }

    74.     return 0;

    75. }
         因为我们知道,SIGKILL和SIGSTOP这两个信号是不能够定制自己的信号处理函数的,当然也不能block,原因很简单,OS或者说root才是final boss,必须有稳定终结进程的办法。假如所有的信号,进程都能ignore,OS如何终结进程?
        这个signal_receiver会等待所有的信号,接收到某信号后,该信号的捕捉到的次数++,SIGINT会终结进程,进程退出前,会打印信号的捕捉统计。
        如果进程有参数,表示sleep时间,signal_receiver会先屏蔽所有信号(当然,SIGKILL和SIGSTOP并不能被真正屏蔽)。然后sleep 一段时间后,取消信号屏蔽。我们可以想象,在信号屏蔽期间,我们收到的信号,都会在kernel记录下来,但是并不能delivery,这种信号称之挂起信号。如果在sleep期间或者说信号屏蔽期间,我收到SIGUSR1 这个信号1次和10000次,对内核来说,都是没差别的,因为后面的9999次都会被ignore掉。SIGUSR1属于不可靠信号,位图表示有没有挂起信号,有的话,直接ignore,没有的话,则记录在kernel。
        然后我们看下,signal_sender: 
    1. manu@manu-hacks:~/code/c/self/signal$ cat signal_sender.c
    2. #include <stdio.h>
    3. #include <stdlib.h>
    4. #include <getopt.h>
    5. #include <signal.h>
    6. #include <string.h>
    7. #include <errno.h>

    8. void usage()
    9. {
    10.     fprintf(stderr,"USAGE:\n");
    11.     fprintf(stderr,"--------------------------------\n");
    12.     fprintf(stderr,"signal_sender pid signo times\n");
    13. }

    14. int main(int argc,char* argv[])
    15. {
    16.     pid_t pid = -1 ;
    17.     int signo = -1;
    18.     int times = -1;
    19.     int i ;


    20.     if(argc< 4 )
    21.     {
    22.         usage();
    23.         return -1;
    24.     }
    25.     
    26.     pid = atol(argv[1]);
    27.     signo = atoi(argv[2]);
    28.     times = atoi(argv[3]);

    29.     if(pid<= 0 || times < 0|| signo <1 ||signo>=64 ||signo == 32|| signo ==33)
    30.     {
    31.         usage();
    32.         return -1;
    33.     }

    34.     printf("pid = %ld,signo = %d,times = %d\n",pid,signo,times);

    35.     for( i= 0 ; i < times ; i++)
    36.     {
    37.         if(kill(pid,signo)== -1)
    38.         {
    39.             fprintf(stderr,"send signo(%d) to pid(%ld) failed,reason(%s)\n",signo,pid,strerror(errno));
    40.             return -2;
    41.         }
    42.     }
    43.     fprintf(stdout,"done\n");
    44.     return 0;

    45. }
         signal_sender需要三个参数,pid signo times,就是向拿个进程发送什么信号多少次的意思。如 signal_sender 1234 10 10000,含义是向pid=1234的 进程发送10号信号(SIGUSR1),连续发送10000次。
        有这两个进程,我们就可以实验了  。 
    1. manu@manu-hacks:~/code/c/self/signal$./signal_receiver&
    2. [1] 23416
    3. manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PIDis 23416
    4. signal for signo(32) failed(Invalid argument)
    5. signal for signo(33) failed(Invalid argument)

    6. manu@manu-hacks:~/code/c/self/signal$./signal_sender 23416 10 10000
    7. pid = 23416,signo= 10,times = 10000
    8. done
    9. manu@manu-hacks:~/code/c/self/signal$ sleep 20 ; ./signal_sender 23416 2 1
    10. pid = 23416,signo= 2,times = 1
    11. done
    12. ./signal_receiver:signal 10 caught 2507 times
    13. [1]+ Done./signal_receiver
        signal_receiver等待signal的来临,singal_sender向其发送SIGUSR1 10000次,然后sleep 20秒,确保sig_receiver处理完成。但是我们发现,其实一共才caught信号SIGUSR1  2507次,7000多次的发送都丢失了,所以我们称SIGUSR1 是非可靠信号,存在丢信号的问题。
        俗话说不怕不识货,就怕货比货 ,我们让可靠信号参战,看下效果:
    1. manu@manu-hacks:~/code/c/self/signal$./signal_receiver &
    2. [1] 26067
    3. ./signal_receiver:PID is 26067
    4. signal for signo(32) failed(Invalid argument)
    5. signal for signo(33) failed(Invalid argument)
    6. manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 10 10000
    7. pid = 26067,signo= 10,times = 10000
    8. done
    9. manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 36 10000
    10. pid = 26067,signo= 36,times = 10000
    11. done
    12. manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 2 1
    13. pid = 26067,signo= 2,times = 1
    14. done
    15. ./signal_receiver:signal 10 caught 2879 times
    16. ./signal_receiver:signal 36 caught 10000 times
    17. [1]+ Done./signal_receiver
        可靠性信号36,发送10000次,signal_receiver全部收到,不可靠性信号10,共收到2879次。这个数字是不可预期的,取决于内核进程的调度。
        这个如果还不够直观,我们在比较一次,让signal_receiver先屏蔽所有信号一段时间,如30s,然后解除屏蔽。
    1. manu@manu-hacks:~/code/c/self/signal$./signal_receiver 30 &
    2. [1] 27639
    3. manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PID is 27639
    4. signal for signo(32) failed(Invalid argument)
    5. signal for signo(33) failed(Invalid argument)
    6. I will sleep 30 second

    7. manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 10 10000
    8. pid = 27639,signo= 10,times = 10000
    9. done
    10. manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 36 10000
    11. pid = 27639,signo= 36,times = 10000
    12. done
    13. manu@manu-hacks:~/code/c/self/signal$
    14. manu@manu-hacks:~/code/c/self/signal$ signo(10):User defined signal 1
    15. signo(36):Real-time signal 2

    16. manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 2 1
    17. pid = 27639,signo= 2,times = 1
    18. done
    19. ./signal_receiver:signal10 caught 1 time
    20. ./signal_receiver:signal36 caught 10000 times
    21. [1]+ Done./signal_receiver 30
          这个比较反差比较大,不可靠signal10 共收到1次,可靠性信号36 共caught到10000次。原因就在于sigprocmask将所有的信号都屏蔽了,造成所有的信号都不能delivery。对1~31的信号,内核发现已经有相应的挂起信号,则ignore到新来的信号。但是可靠性信号则不同,会添加队列中去,尽管已经有了相同的信号。需要注意的是,signal pending有上限,并不能无限制的发:
    1. manu@manu-hacks:~/code/c/self/signal$ ulimit-a
    2. core file size (blocks, -c) 0
    3. data seg size (kbytes, -d) unlimited
    4. scheduling priority (-e) 0
    5. file size (blocks,-f) unlimited
    6. pending signals (-i) 15408
    7. max locked memory (kbytes,-l) 64
    8. max memory size (kbytes, -m) unlimited
    9. open files (-n) 1024
    10. pipe size (512 bytes,-p) 8
    11. POSIX message queues (bytes,-q) 819200
    12. real-time priority (-r) 0
    13. stack size (kbytes,-s) 8192
    14. cpu time (seconds,-t) unlimited
    15. max user processes (-u) 15408
    16. virtual memory (kbytes,-v) unlimited
    17. file locks (-x) unlimited
        我发送100万,最终会收到15408个可靠信号:
    1. manu@manu-hacks:~/code/c/self/signal$./signal_receiver 30 &
    2. [1] 16488
    3. manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PID is 16488
    4. signal for signo(32) failed(Invalid argument)
    5. signal for signo(33) failed(Invalid argument)
    6. I will sleep 30 second

    7. manu@manu-hacks:~/code/c/self/signal$./signal_sender 16488 36 1000000
    8. pid = 16488,signo= 36,times = 1000000
    9. done
    10. manu@manu-hacks:~/code/c/self/signal$ signo(36):Real-time signal 2

    11. manu@manu-hacks:~/code/c/self/signal$./signal_sender 16488 2 1
    12. pid = 16488,signo= 2,times = 1
    13. done
    14. ./signal_receiver:signal 36 caught15408 times
    15. [1]+ Done./signal_receiver 30
          内核是怎么做到的?
        Linux signal那些事儿
        上图是内核中signal相关的数据结构。其中task_struct中有sigpending类型的成员变量pending
        
    1. struct task_struct {
    2.     volatile long state; /* -1 unrunnable, 0 runnable,>0 stopped */
    3.     void *stack;
    4.     atomic_t usage;
    5.     unsigned int flags;/* per process flags, defined below*/
    6.     unsigned int ptrace;
    7.         ...
    8.         ...
    9. /* signal handlers*/
    10.     struct signal_struct *signal;
    11.     struct sighand_struct *sighand;

    12.     sigset_t blocked, real_blocked;
    13.     sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used*/
    14.     struct sigpending pending;

    15.        ...
    16. }

    17. struct signal_struct {
    18.       atomic_t     sigcnt;
    19.       atomic_t     live;
    20.       int     nr_threads;
    21.       ...
    22.       ...
    23.       /* shared signal handling:*/
    24.       struct sigpending    shared_pending;
    25.       ...
    26. }

    27. struct sigpending {
    28.     struct list_head list;
    29.     sigset_t signal;
    30. };

    31. #define _NSIG        64

    32. #ifdef __i386__
    33. # define _NSIG_BPW    32
    34. #else
    35. # define _NSIG_BPW    64
    36. #endif

    37. #define _NSIG_WORDS    (_NSIG / _NSIG_BPW)

    38. typedef unsigned long old_sigset_t;        /* at least 32 bits*/

    39. typedef struct {
    40.     unsigned long sig[_NSIG_WORDS];
    41. } sigset_t;
          task_struct中的pending,和signal->shared_pending都是记录挂起信号的数据结构,读到此处,你可能会迷惑,为何有两个这样的结构。这牵扯到thread与信号的一些问题,我们此处简化,就认为是一个就好,后面讲述线程与信号关系的时候,再展开。
        Linux signal那些事儿  
        我们看到了,kill也好,tkill也罢,最终都走到了_send_signal.当然了kill系统调用根据pid的情况会分成多个分支如pid >0 pid = 0 pid=-1;pid < 0&pid !=-1,总之了,我的图只绘制了pid >0 的分支。tkill也有类似情况。
        那么kernel是怎么做到的非可靠信号和可靠信号的的这些差别的呢?
    1. static int __send_signal(int sig, struct siginfo*info, struct task_struct*t,
    2.             int group,int from_ancestor_ns)
    3. {
    4.     struct sigpending *pending;
    5.     struct sigqueue *q;
    6.     int override_rlimit;
    7.     int ret= 0, result;

    8.     assert_spin_locked(&t->sighand->siglock);

    9.     result = TRACE_SIGNAL_IGNORED;
    10.     if(!prepare_signal(sig, t,
    11.             from_ancestor_ns ||(info== SEND_SIG_FORCED)))
    12.         goto ret;

    13.     pending = group? &t->signal->shared_pending: &t->pending;
    14.     /*
    15.      * Short-circuit ignored signalsand support queuing
    16.      * exactly one non-rt signal, so that we can get more
    17.      * detailed information about the cause of the signal.
    18.      */
    19.     result = TRACE_SIGNAL_ALREADY_PENDING;
    20.     if(legacy_queue(pending, sig)) //如果是低于32的信号,并且已经在pending中出现了的信号,就直接返回了,ignore
    21.         goto ret;

    22.     result = TRACE_SIGNAL_DELIVERED;
    23.     /*
    24.      * fast-pathed signalsfor kernel-internal things like SIGSTOP
    25.      *or SIGKILL.
    26.      */
    27.     if(info== SEND_SIG_FORCED)
    28.         goto out_set;

    29.     /*
    30.      * Real-time signals must be queued if sent by sigqueue,or
    31.      * some other real-time mechanism. Itis implementation
    32.      * defined whether kill() does so. We attemptto do so,on
    33.      * the principle of least surprise, but since killis not
    34.      * allowedto fail with EAGAIN when lowon memory we just
    35.      * make sure at least one signal gets deliveredand don't
    36.      * passon the info struct.
    37.      */
    38.     if(sig< SIGRTMIN)
    39.         override_rlimit =(is_si_special(info)|| info->si_code>= 0);
    40.     else
    41.         override_rlimit = 0;
          //分配sigqueue结构,并且链入到相应的pending。
    1.     q =__sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
    2.         override_rlimit);
    3.     if(q){
    4.         list_add_tail(&q->list,&pending->list);
    5.         switch ((unsigned long) info) {
    6.         case(unsigned long) SEND_SIG_NOINFO:
    7.             q->info.si_signo= sig;
    8.             q->info.si_errno= 0;
    9.             q->info.si_code= SI_USER;
    10.             q->info.si_pid= task_tgid_nr_ns(current,
    11.                             task_active_pid_ns(t));
    12.             q->info.si_uid= from_kuid_munged(current_user_ns(), current_uid());
    13.             break;
    14.         case(unsigned long) SEND_SIG_PRIV:
    15.             q->info.si_signo= sig;
    16.             q->info.si_errno= 0;
    17.             q->info.si_code= SI_KERNEL;
    18.             q->info.si_pid= 0;
    19.             q->info.si_uid= 0;
    20.             break;
    21.         default:
    22.             copy_siginfo(&q->info, info);
    23.             if(from_ancestor_ns)
    24.                 q->info.si_pid= 0;
    25.             break;
    26.         }

    27.         userns_fixup_signal_uid(&q->info, t);

    28.     }else if (!is_si_special(info)){
    29.         if(sig>= SIGRTMIN&& info->si_code!= SI_USER){
    30.             /*
    31.              * Queue overflow, abort. We may abort if the
    32.              * signal was rtand sent by user using something
    33.              * other than kill().
    34.              */
    35.             result = TRACE_SIGNAL_OVERFLOW_FAIL;
    36.             ret =-EAGAIN;
    37.             goto ret;
    38.         }else {
    39.             /*
    40.              * Thisis a silent loss of information. We still
    41.              * send the signal, but the*info bits are lost.
    42.              */
    43.             result = TRACE_SIGNAL_LOSE_INFO;
    44.         }
    45.     }

    46. out_set:
    47.     signalfd_notify(t, sig);
    48.     sigaddset(&pending->signal, sig);  //加入位图
    49.     complete_signal(sig, t, group);
    50. ret:
    51.     trace_signal_generate(sig, info, t, group, result);
    52.     return ret;
    53. }

    54. static inline intlegacy_queue(struct sigpending*signals,int sig)
    55. {
    56.     return (sig< SIGRTMIN)&& sigismember(&signals->signal, sig); //是不可靠信号,并且该信号已经存在挂起信号,
        那么15408的限制是在哪里呢?在__sigqueue_alloc 里面。
    1. static struct sigqueue*
    2. __sigqueue_alloc(int sig,struct task_struct *t, gfp_t flags,int override_rlimit)
    3. {
    4.     struct sigqueue *q = NULL;
    5.     struct user_struct *user;

    6.     /*
    7.      * Protect access to @t credentials.This can go away when all
    8.      * callers hold rcu read lock.
    9.      */
    10.     rcu_read_lock();
    11.     user = get_uid(__task_cred(t)->user);
    12.     atomic_inc(&user->sigpending);    //计数器+1
    13.     rcu_read_unlock();

    14.     if (override_rlimit||
    15.      atomic_read(&user->sigpending)<=
    16.             task_rlimit(t, RLIMIT_SIGPENDING)) {
    17.         q = kmem_cache_alloc(sigqueue_cachep, flags);
    18.     } else {
    19.         print_dropped_signal(sig);
    20.     }

    21.     if (unlikely(q== NULL)) {
    22.         atomic_dec(&user->sigpending);
    23.         free_uid(user);
    24.     } else {
    25.         INIT_LIST_HEAD(&q->list);
    26.         q->flags= 0;
    27.         q->user= user;
    28.     }

    29.     return q;
    30. }
         我们看到,legacy_queue就是用来判断是否是非可靠信号(signo低于32),并且相同signo值已经存在在挂起信号之中,如果是,直接返回。
         而对于可靠信号,会分配一个sigqueue的结构,然后讲sigqueue链入到sigpending结构的中链表中。从而就不会丢失信号。当然对pending信号的总数作了限制,限制最多不可超过15408.当然了这个值是可以修改的:
        Linux signal那些事儿


    参考文献:
    1 Linux programming interface
    2 深入理解linux内核
    3 linux kernel 3.8.0 内核源码  

    Linux signal 那些事儿 (3)
  2. 这篇博客,想集中在signal 与线程的关系上,顺带介绍内核signal相关的结构。如何组织我其实并没想好,想到哪就写到哪里吧。主题一定会落在signal之内而不跑题。
        提到signal与thread的关系,就得先提POSIX标准。POSIX标准决定了Linux为何将signal如此实现:
       1 信号处理函数必须在多线程应用的所有线程之间共享,但是,每个线程要有自己的挂起信号掩码和阻塞信号掩码。
       2 POSIX 函数kill/sigqueue必须面向所有的多线程应用而不是某个特殊的线程。
       3 每个发给多线程应用的信号仅传送给1个线程,这个线程是由内核从不会阻塞该信号的线程中随意选出。
       4 如果发送一个致命信号到多线程,那么内核将杀死该应用的所有线程,而不仅仅是接收信号的那个线程。

        上面是POSIX标准,也就是提出来的要求,Linux要遵循POSIX标准,那Linux是怎么做到的呢?
        到了此处,我们需要理清一些基本的概念:
    1. struct task_struct {

    2.     pid_t pid;
    3.     pid_t tgid
    4.       .....
    5.     struct task_struct *group_leader;    /* threadgroup leader */
    6.       ......
    7.     struct list_head thread_group;
    8.         ....
    9. }
        从字面意思上看 pid,是process id,其则不然,pid是thread id。从字面意思上看,tgid是thread group id,其则是真正的pid。
        有点绕是不是?对于一个多线程的程序,无论是哪个线程执行getpid,结果都是一样的,最终返回的同一个值 tgid。如果我们实现了gettid(很不幸的是glibc没有这个函数,所以我们要用syscall),我们就会发现,各个线程返回的值不同,此时,返回的值是内核task_struct中的pid。对于多线程应用/proc/pid/task可以看到的,就是线程的thread id,也就是task_struct中的pid。
       Linux signal那些事儿
        我在我的博文Linux线程之线程 线程组 进程 轻量级进程(LWP)提到了这个问题。我不想多浪费笔墨赘述。
        group leader字段,指向线程组的第一个线程。对于我们自己的程序而言,main函数所在的线程,也就是线程组的第一个线程,所以group leader就会他自己。一旦用pthread_create创建了线程,那么main所在的线程,还有创建出来的线程,隶属于同一个线程组,线程的group leader还是main函数所在的线程id。
        thread_group,同一线程组的所有线程的队列。对于group_leader,这是一个队列头,对于同一线程组的其他线程,通过这个字段挂入队列。可以根据这个队列,遍历线程组的所有线程。
         是时候看看内核代码了,下面的代码属于do_fork函数及copy_process函数的一些代码。       
    1.     p->pid = pid_nr(pid);
    2.     p->tgid = p->pid;
    3.     if (clone_flags & CLONE_THREAD)//创建线程,tgid等于当前线程的
    4.         p->tgid = current->tgid;


    5.     p->group_leader = p;
    6.     INIT_LIST_HEAD(&p->thread_group);

    7.     if (clone_flags & CLONE_THREAD) { //线程处理部分,group_leader都是第一个线程。同时挂入队列
    8.         current->signal->nr_threads++;
    9.         atomic_inc(&current->signal->live);
    10.         atomic_inc(&current->signal->sigcnt);
    11.         p->group_leader = current->group_leader;
    12.         list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
    13.     }
        代码表明,第一个线程呢,pid和tgid相同,都是分配的那个pid,group_leader也是自己。后面第二个线程,pid是自己的,但是tgid 等于创建者的tgid,group_leader指向第一个线程的task_struct. 后面创建的所有的线程,都会挂入队列,方便遍线程组的所有线程。
        有了线程组的概念,我们就可以进一步解释signal相关的内容了。 
    1.     /* signal handlers */
    2.     struct signal_struct *signal;
    3.     struct sighand_struct *sighand;

    4.     sigset_t blocked, real_blocked;
    5.     sigset_t saved_sigmask;    /* restored if set_restore_sigmask() was used */
    6.     struct sigpending pending;
        Linux signal那些事儿
        线程组里面的所有成员共享一个signal_struct类型结构,同一线程组的多线程的task_struct 中的signal指针都是指向同一个signal_struct。sighand成员变量也是如此,统一个线程组的多个线程指向同一个signalhand_struct结构。  
    1. static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
    2. {
    3.     struct signal_struct *sig;

    4.     if (clone_flags & CLONE_THREAD) //线程,直接返回,表明同一线程组共享
    5.         return 0;

    6.     sig = kmem_cache_zalloc(signal_cachep, GFP_KERNEL);
    7.     tsk->signal = sig;
    8.     if (!sig)
    9.         return -ENOMEM;

    10.     sig->nr_threads = 1;
    11.     atomic_set(&sig->live, 1);
    12.     atomic_set(&sig->sigcnt, 1);
    13.     init_waitqueue_head(&sig->wait_chldexit);
    14.     sig->curr_target = tsk;
    15.         。。。。
    16. }


    17. static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
    18. {
    19.     struct sighand_struct *sig;

    20.     if (clone_flags & CLONE_SIGHAND) {
    21.         atomic_inc(&current->sighand->count); //如果发现是线程,直接讲引用计数++,无需分配sighand_struct结构
    22.         return 0;
    23.     }
    24.     sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
    25.     rcu_assign_pointer(tsk->sighand, sig);
    26.     if (!sig)
    27.         return -ENOMEM;
    28.     atomic_set(&sig->count, 1);
    29.     memcpy(sig->action, current->sighand->action, sizeof(sig->action));
    30.     return 0;
    31. }
        这就基本实现了多线程应用中,信号处理程序是共享的,因为他们共用一个signalhand_struct。
        上一篇博文提到,signal->shared_pending 和pending两个挂起信号相关的数据结构,此处我们可以具体讲解了。signal是线程组共享的结构,自然下属的shared_pending也是线程组共享的。就像POSIX提到的,kill/sigqueue发送信号,发送的对象并不是线程组某个特定的线程,而是整个线程组。自然,如果kernel会将信号记录在全线程组共享的signal->shared_pending,表示,线程组收到信号X一枚。
        有筒子说了,我就要给某个特定的线程发信号,有没有办法,内核怎么办?这是个好问题。   
    1.   int tkill(int tid, int sig);

    2.   int tgkill(int tgid, int tid, int sig)
        这两个API是给线程组特定线程发信号的,毫不意外,内核会将信号记录在线程自己的结构pending中。
    1. pending = group ? &t->signal->shared_pending : &t->pending;
        对于kill/sigqueue,__send_signal传进来的是group是true,对于tkill/tgkill传进来的是false。会将信号写入相应的挂起信号位图。
    1. static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
    2.             int group, int from_ancestor_ns)
    3. {
    4.     struct sigpending *pending;
    5.     struct sigqueue *q;
    6.     int override_rlimit;
    7.     int ret = 0, result;

    8.     assert_spin_locked(&t->sighand->siglock);

    9.     result = TRACE_SIGNAL_IGNORED;
    10.     if (!prepare_signal(sig, t,
    11.             from_ancestor_ns || (info == SEND_SIG_FORCED)))
    12.         goto ret;

    13.     pending = group ? &t->signal->shared_pending : &t->pending;   // tkill用的自己的pending,
    14.                                                                   // kill/sigqueue用的线程组共享的signal->shared_pending
    15.     /*
    16.      * Short-circuit ignored signals and support queuing
    17.      * exactly one non-rt signal, so that we can get more
    18.      * detailed information about the cause of the signal.
    19.      */
    20.     result = TRACE_SIGNAL_ALREADY_PENDING;
    21.     if (legacy_queue(pending, sig))
    22.         goto ret;

    23.     result = TRACE_SIGNAL_DELIVERED;
    24.     /*
    25.      * fast-pathed signals for kernel-internal things like SIGSTOP
    26.      * or SIGKILL.
    27.      */
    28.     if (info == SEND_SIG_FORCED)
    29.         goto out_set;

    30.     /*
    31.      * Real-time signals must be queued if sent by sigqueue, or
    32.      * some other real-time mechanism. It is implementation
    33.      * defined whether kill() does so. We attempt to do so, on
    34.      * the principle of least surprise, but since kill is not
    35.      * allowed to fail with EAGAIN when low on memory we just
    36.      * make sure at least one signal gets delivered and don't
    37.      * pass on the info struct.
    38.      */
    39.     if (sig < SIGRTMIN)
    40.         override_rlimit = (is_si_special(info) || info->si_code >= 0);
    41.     else
    42.         override_rlimit = 0;

    43.     q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
    44.         override_rlimit);
    45.     if (q) {
    46.         list_add_tail(&q->list, &pending->list);
    47.         switch ((unsigned long) info) {
    48.         case (unsigned long) SEND_SIG_NOINFO:
    49.             q->info.si_signo = sig;
    50.             q->info.si_errno = 0;
    51.             q->info.si_code = SI_USER;
    52.             q->info.si_pid = task_tgid_nr_ns(current,
    53.                             task_active_pid_ns(t));
    54.             q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
    55.             break;
    56.         case (unsigned long) SEND_SIG_PRIV:
    57.             q->info.si_signo = sig;
    58.             q->info.si_errno = 0;
    59.             q->info.si_code = SI_KERNEL;
    60.             q->info.si_pid = 0;
    61.             q->info.si_uid = 0;
    62.             break;
    63.         default:
    64.             copy_siginfo(&q->info, info);
    65.             if (from_ancestor_ns)
    66.                 q->info.si_pid = 0;
    67.             break;
    68.         }

    69.         userns_fixup_signal_uid(&q->info, t);

    70.     } else if (!is_si_special(info)) {
    71.         if (sig >= SIGRTMIN && info->si_code != SI_USER) {
    72.             /*
    73.              * Queue overflow, abort. We may abort if the
    74.              * signal was rt and sent by user using something
    75.              * other than kill().
    76.              */
    77.             result = TRACE_SIGNAL_OVERFLOW_FAIL;
    78.             ret = -EAGAIN;
    79.             goto ret;
    80.         } else {
    81.             /*
    82.              * This is a silent loss of information. We still
    83.              * send the signal, but the *info bits are lost.
    84.              */
    85.             result = TRACE_SIGNAL_LOSE_INFO;
    86.         }
    87.     }

    88. out_set:
    89.     signalfd_notify(t, sig);
    90.     sigaddset(&pending->signal, sig);//修改位图,表明该信号存在挂起信号。
    91.     complete_signal(sig, t, group);
    92. ret:
    93.     trace_signal_generate(sig, info, t, group, result);
    94.     return ret;
    95. }
        线程存在一个很让人迷惑的问题,如何让线程组的所有线程一起退出。我们都知道,多线程的程序有一个线程访问了非法地址,引发段错误,会造成所有线程一起退出。这也是多线程程序脆弱的地方。但是如何做到的呢?
        do_signal--->get_signal_to_deliver中,会选择信号,如果发现需要退出,会执行do_group_exit。这个名字顾名思义了,线程组退出。   
    1. void
    2. do_group_exit(int exit_code)
    3. {
    4.     struct signal_struct *sig = current->signal;

    5.     BUG_ON(exit_code & 0x80); /* core dumps don't get here */

    6.     if (signal_group_exit(sig))
    7.         exit_code = sig->group_exit_code;
    8.     else if (!thread_group_empty(current)) {
    9.         struct sighand_struct *const sighand = current->sighand;
    10.         spin_lock_irq(&sighand->siglock);
    11.         if (signal_group_exit(sig))
    12.             /* Another thread got here before we took the lock. */
    13.             exit_code = sig->group_exit_code;
    14.         else {
    15.             sig->group_exit_code = exit_code;
    16.             sig->flags = SIGNAL_GROUP_EXIT;
    17.             zap_other_threads(current);
    18.         }
    19.         spin_unlock_irq(&sighand->siglock);
    20.     }

    21.     do_exit(exit_code);
    22.     /* NOTREACHED */
    23. }
        如果是多线程,会走入到else中,主要的操作都在zap_other_threads函数中:
    1. /*
    2.  * Nuke all other threads in the group.
    3.  */
    4. int zap_other_threads(struct task_struct *p)
    5. {
    6.     struct task_struct *t = p;
    7.     int count = 0;

    8.     p->signal->group_stop_count = 0;

    9.     while_each_thread(p, t) {
    10.         task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK);
    11.         count++;

    12.         /* Don't bother with already dead threads */
    13.         if (t->exit_state)
    14.             continue;
    15.         sigaddset(&t->pending.signal, SIGKILL);
    16.         signal_wake_up(t, 1);
    17.     }

    18.     return count;
    19. }
        不多说了,就是给每一个线程都挂上一个SIGKILL的信号,当CPU选择线程执行时候的时候,自然会处理这个信号,而对SIGKILL的处理,会再次调用do_group_exit。这一次会调用do_exit退出。当线程组所有进程都执行过之后,整个线程组就消亡了。
        
        讲完这些,需要讲block了。我第一篇就讲到,我们有时候需要阻塞某些信号。POSIX说了多线程中每个线程要有自己的阻塞信号。不必说,task_struct中的blocked就是阻塞信号位图。我们的glibc的sigprocmask函数,就是设置进程的blocked。
        那些block的信号为何不能传递,内核是怎么做到的?
            Linux signal那些事儿

    1. int next_signal(struct sigpending *pending, sigset_t *mask)
    2. {
    3.     unsigned long i, *s, *m, x;
    4.     int sig = 0;

    5.     s = pending->signal.sig;
    6.     m = mask->sig;

    7.     /*
    8.      * Handle the first word specially: it contains the
    9.      * synchronous signals that need to be dequeued first.
    10.      */
    11.     x = *s &~ *m;
    12.     if (x) {
    13.         if (x & SYNCHRONOUS_MASK)
    14.             x &= SYNCHRONOUS_MASK;
    15.         sig = ffz(~x) + 1;
    16.         return sig;
    17.     }

    18.     switch (_NSIG_WORDS) {
    19.     default:
    20.         for (i = 1; i < _NSIG_WORDS; ++i) {
    21.             x = *++s &~ *++m;
    22.             if (!x)
    23.                 continue;
    24.             sig = ffz(~x) + i*_NSIG_BPW + 1;
    25.             break;
    26.         }
    27.         break;

    28.     case 2:
    29.         x = s[1] &~ m[1];
    30.         if (!x)
    31.             break;
    32.         sig = ffz(~x) + _NSIG_BPW + 1;
    33.         break;

    34.     case 1:
    35.         /* Nothing to do */
    36.         break;
    37.     }

    38.     return sig;
    39. }
        m就是task_struct中的blocked,阻塞的信号就不会不会被取出传递了。很有意思的一点是信号传递的顺序。在Linux programming interface一书中提到小signo优先的策略,比如SIGINT(2)和SIGQUIT(3)同时存在,SIGINT(2) 先deliver,然后才是SIGQUIT(3).我们看代码,很有意思的是有同步信号:
    1. #define SYNCHRONOUS_MASK \
    2.     (sigmask(SIGSEGV) | sigmask(SIGBUS) | sigmask(SIGILL) | \
    3.      sigmask(SIGTRAP) | sigmask(SIGFPE) | sigmask(SIGSYS))
        有SIGSEGV SIGBUS SIGILL SIGTRAP SIGFPE SIGSYS,那么这几个信号优先。没有这几个信号,按照小信号优先。当然了,这些是Linux kernel的实现,毕竟不是POSIX标准,不可依赖这种顺序。
       另外,dequeue很有意思,先去task_struct中的pending中取,取不到再去整个线程组共享的shered_pending位图去取。    
    1. int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
    2. {
    3.     int signr;

    4.     /* We only dequeue private signals from ourselves, we don't let
    5.      * signalfd steal them
    6.      */
    7.     signr = __dequeue_signal(&tsk->pending, mask, info);
    8.     if (!signr) {
    9.         signr = __dequeue_signal(&tsk->signal->shared_pending,
    10.                      mask, info);
           。。。。
    }

    参考文献:
    1 Linux2.6内核中的线程组初探
    2 Linux kernel 3.8.0


    Linux signal 那些事儿(4)信号的deliver顺序
    上一篇博文提到了,如果同时有多个不同的信号处于挂起状态,kernel如何选择deliver那个信号。
        Linux signal那些事儿
        next_signal 负责从挂起信号中选择deliver的signo:当然,有线程显存私有的penging,有线程组共有的pending,对于线程而言,先从自己私有的pending中选,处理完毕私有的才会去处理线程组共有的pending,这个逻辑的代码在:
    1. int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
    2. {
    3.     int signr;

    4.     /* We only dequeue private signals from ourselves, we don't let
    5.      * signalfd steal them
    6.      */
    7.     signr = __dequeue_signal(&tsk->pending, mask, info);  //线程私有的penging优先
    8.     if (!signr) {
    9.         signr = __dequeue_signal(&tsk->signal->shared_pending,
    10.                      mask, info);
    11.        。。。。
    12. }
        换句话说,如果存在挂起队列中,我们用tkill/tgkill发送的信号会先于用kill发送的信号被deliver,这个我们按下不提,我们看在同一个penging队列中如何挑选下个deliver的signal:
    1. int next_signal(struct sigpending *pending, sigset_t *mask)
    2. {
    3.     unsigned long i, *s, *m, x;
    4.     int sig = 0;

    5.     s = pending->signal.sig;
    6.     m = mask->sig;

    7.     /*
    8.      * Handle the first word specially: it contains the
    9.      * synchronous signals that need to be dequeued first.
    10.      */
    11.     x = *s &~ *m;
    12.     if (x) {
    13.         if (x & SYNCHRONOUS_MASK)
    14.             x &= SYNCHRONOUS_MASK;
    15.         sig = ffz(~x) + 1;
    16.         return sig;
    17.     }

    18.     switch (_NSIG_WORDS) {
    19.     default:
    20.         for (i = 1; i < _NSIG_WORDS; ++i) {
    21.             x = *++s &~ *++m;
    22.             if (!x)
    23.                 continue;
    24.             sig = ffz(~x) + i*_NSIG_BPW + 1;
    25.             break;
    26.         }
    27.         break;

    28.     case 2:
    29.         x = s[1] &~ m[1];
    30.         if (!x)
    31.             break;
    32.         sig = ffz(~x) + _NSIG_BPW + 1;
    33.         break;

    34.     case 1:
    35.         /* Nothing to do */
    36.         break;
    37.     }

    38.     return sig;
    39. }

    1. #define SYNCHRONOUS_MASK \
    2.     (sigmask(SIGSEGV) | sigmask(SIGBUS) | sigmask(SIGILL) | \
    3.      sigmask(SIGTRAP) | sigmask(SIGFPE) | sigmask(SIGSYS))

        上一篇博客讲了处于SYNCHRONOUS_MASK里面的信号是优先处理的信号,他们都是一些硬件相关的信号,多是由于异常出错引起。其次是传统信号,[32,64]之间的实时信号,优先级最低。
        换句话说所有信号分成三个等级,{SIGILL(4),SIGTRAP(5),SIGBUS(7),SIGFPE(8),SIGSEGV(11),SIGSYS(31)},这是第一等级,传统信号中排除第一等级的信号,就是第二等级的信号,[34,64]之间的信号属于第三等级。如果同一等级内,存在多个信号,按照小信号优先的顺序去deliver。
        举个例子:  
    1. kill -10 $signal_pid
    2. kill -3 $signal_pid
    3. kill -12 $signal_pid
    4. kill -11 $signal_pid
    5. kill -39 $signal_pid
    6. kill -2 $signal_pid
    7. kill -5 $signal_pid
    8. kill -4 $signal_pid
    9. kill -36 $signal_pid
    10. kill -24 $signal_pid
    11. kill -38 $signal_pid
    12. kill -37 $signal_pid
    13. kill -31 $signal_pid
    14. kill -8 $signal_pid
    15. kill -7 $signal_pid
        我们可以看到,我们向同一进程发送多个信号,加入进程阻塞了所有信号(当然SIGKILL/SIGSTOP 不考虑)。这时候,这些个信号,都会位于挂起信号之中,一旦进程解除阻塞,那么考验deliver顺序的时候到了。
        我们按照kernel的规则,顺序应该是{4,5,7,8,11,31,          2,3,10,12,24,             36,37,38}这么个顺序。
        写个测试程序:  
    1. root@manu-hacks:~/Dropbox/Note/signal# cat signal_delivery_order.c
    2. #include <stdio.h>
    3. #include <stdlib.h>
    4. #include <unistd.h>
    5. #include <signal.h>
    6. #include <string.h>
    7. #include <errno.h>


    8. static int sig_cnt[NSIG];
    9. static number= 0 ;
    10. int sigorder[128]= {0};

    11. #define MSG "#%d:receiver signal %d\n"

    12. void handler(int signo)
    13. {
    14.     sigorder[number++] = signo;
    15. }

    16. int main(int argc,char* argv[])
    17. {
    18.     int i = 0;
    19.     int k = 0;
    20.     sigset_t blockall_mask ;
    21.     sigset_t pending_mask ;
    22.     sigset_t empty_mask ;
    23.     struct sigaction sa ;
    24.     

    25.     sigfillset(&blockall_mask);
    26. #ifdef USE_SIGACTION
    27.     sa.sa_handler = handler;
    28.     sa.sa_mask = blockall_mask ;
    29.     sa.sa_flags = SA_RESTART;
    30. #endif


    31.     printf("%s:PID is %ld\n",argv[0],getpid());

    32.     
    33.     for(i = 1; i < NSIG; i++)
    34.     {
    35.         if(i == SIGKILL || i == SIGSTOP)
    36.             continue;
    37. #ifdef USE_SIGACTION
    38.         if(sigaction(i,&sa, NULL)!=0)
    39. #else
    40.         if(signal(i,handler)== SIG_ERR)
    41. #endif
    42.         {
    43.             fprintf(stderr,"sigaction for signo(%d) failed (%s)\n",i,strerror(errno));
    44. //            return -1;
    45.         }
    46.     }

    47.     if(argc > 1)
    48.     {
    49.         int sleep_time = atoi(argv[1]);

    50.         if(sigprocmask(SIG_SETMASK,&blockall_mask,NULL) == -1)
    51.         {
    52.             fprintf(stderr,"setprocmask to block all signal failed(%s)\n",strerror(errno));
    53.             return -2;
    54.         }

    55.         printf("I will sleep %d second\n",sleep_time);

    56.         sleep(sleep_time);
    57.         if(sigpending(&pending_mask) == -1)
    58.         {
    59.             fprintf(stderr,"sigpending failed(%s)\n",strerror(errno));
    60.             return -2;
    61.         }

    62.         for(i = 1 ; i < NSIG ; i++)
    63.         {
    64.             if(sigismember(&pending_mask,i))
    65.             printf("signo(%d) :%s\n",i,strsignal(i));
    66.         }

    67.         sigemptyset(&empty_mask);
    68.         if(sigprocmask(SIG_SETMASK,&empty_mask,NULL) == -1)
    69.         {
    70.             fprintf(stderr,"setprocmask to release all signal failed(%s)\n",strerror(errno));
    71.             return -3;
    72.         }

    73.         for( i = 0 ; i < 1000000 ; i++)
    74.         {
    75.             k = random()%1234567;
    76.             
    77.         }
    78.         
    79.     }

    80.     for(i = 0 ; i< number ; i++)
    81.     {
    82.      if(sigorder[i] != 0)
    83.         {
    84.             printf("#%d: signo=%d\n",i,sigorder[i]);
    85.         }
    86.     }

    87.     return 0;

    88. }
        注意这个USE_SIGACTION宏包裹的部分是我后来想到的,在我开始的版本中用的是signal(i,handler)来注册函数。handler函数很有意思,是我精心设计的:  
    1. void handler(int signo)
    2. {
    3.     sigorder[number++] = signo;
    4. }
       按照执行的顺序,我会讲signo的值记录在全局数组中,等到进程退出前,我打印数组的值,就能得到信号deliver的顺序。
        这个进程会阻塞所有信号一段时间,在这段时间内,我会向该进程发送一坨信号,待阻塞解除后,打印数组的值,从而获得deliver的顺序。
        看下测试程序:   
    1. root@manu-hacks:~/code/c/self/signal_deliver# cat test_order.sh 
      #!/bin/bash 


      if [ $1 -eq 0 ]
      then
      ./sigaction_delivery_order  30  &    #正确的程序
      else
      ./signal_delivery_order  30 &        #我最初的程序,信号处理函数执行期间,没有屏蔽其他信号。
      fi
      signal_pid=$!


      sleep 2
      kill -10  $signal_pid
      kill -3  $signal_pid
      kill -12  $signal_pid
      kill -11  $signal_pid
      kill -39  $signal_pid
      kill -2   $signal_pid
      kill -5   $signal_pid
      kill -4   $signal_pid
      kill -36  $signal_pid
      kill -24  $signal_pid
      kill -38  $signal_pid
      kill -37  $signal_pid
      kill -31  $signal_pid
      kill -8   $signal_pid
      kill -7   $signal_pid


        设计很精巧,设计出这个程序后,我很得意,认为验证deliver传递顺序是水到渠成的事情。
        梦想很丰满,无奈现实很骨感,我看了执行结果,那是当头一棒啊:
    1. root@manu-hacks:~/Dropbox/Note/signal#
    2. root@manu-hacks:~/Dropbox/Note/signal# ./test_order.sh 1 
    3. ./signal_delivery_order:PID is 31403
    4. sigaction for signo(32) failed (Invalid argument)
    5. sigaction for signo(33) failed (Invalid argument)
    6. I will sleep 30 second
    7. root@manu-hacks:~/Dropbox/Note/signal# signo(2) :Interrupt
    8. signo(3) :Quit
    9. signo(4) :Illegal instruction
    10. signo(5) :Trace/breakpoint trap
    11. signo(7) :Bus error
    12. signo(8) :Floating point exception
    13. signo(10) :User defined signal 1
    14. signo(11) :Segmentation fault
    15. signo(12) :User defined signal 2
    16. signo(24) :CPU time limit exceeded
    17. signo(31) :Bad system call
    18. signo(36) :Real-time signal 2
    19. signo(37) :Real-time signal 3
    20. signo(38) :Real-time signal 4
    21. signo(39) :Real-time signal 5
    22. #0: signo=39
    23. #1: signo=38
    24. #2: signo=37
    25. #3: signo=36
    26. #4: signo=24
    27. #5: signo=12
    28. #6: signo=10
    29. #7: signo=3
    30. #8: signo=2
    31. #9: signo=31
    32. #10: signo=11
    33. #11: signo=8
    34. #12: signo=7
    35. #13: signo=5
    36. #14: signo=4
        顺序恰恰相反!!!!
       
    我最初完全解释不通,我google了类似的topic,我发现,我不是第一个发现这个问题的人,绚丽也尘埃在一篇博客中提到: 
    1. 在网上找到这样一段话:
    2. 信号的优先级:信号实质上是软中断,中断有优先级,信号也有优先级。如果一个进程有多个未决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。如果存 在多个未决的实时信号,则值(或者说编号)越小的越先被递送。如果既存在不可靠信号,又存在可靠信号(实时信号),虽然POSIX对这一情况没有明确规 定,但Linux系统和大多数遵循POSIX标准的操作系统一样,将优先递送不可靠信号。

    3. 经过我反反复复地试验,我发现实验结果和上面描述的刚好相反,信号的编号越大越先被递送,一个进程如果处理SIGQUIT(3),SIGINT(2),SIGHUP(1)(通过”kill -l”可以查看信号的编号),那么先后给该进程发送SIGINT,SIGHUP,SIGQUIT,处理的顺序会是SIGQUIT,SIGINT,SIGHUP,不论改变这个三个信号的发送顺序,处理的顺序都是一样的。
        看到了,这位前辈遇到了和我一样的困惑,测试的结果和kernel完全相反。内核不会错,glibc也肯定不会瞎掺合,一定是我的测试程序存在问题:
        今天我坐公交车上,突然意识到问题的所在,我的信号处理函数没有屏蔽信号!!!
        换句话说,4号信号是先被deliver的,但是还没能handler执行,被5号信号中断掉了,5号信号还没开始执行,被7号信号中断掉了,依次类推,所以我们测试的结果和deliver的结果正好相反。
        意识到这一点,我就改进了我的程序,信号执行期间,屏蔽所有信号,这样,就能测试信号deliver的顺序了。对于我的程序而言就是加上-DUSE_SIGACTION选项,让sigaction安装信号时,指明信号处理函数执行期间,屏蔽所有信号。
         在我的64位Ubuntu上执行,结果和kernel代码以及手册上的一样。也就是说,并不是手册描述错了,而是我们的老的测试程序,在signal处理期间,没有屏蔽其他信号导致混乱。那么按照正确的方法测试:
    1. root@manu-hacks:~/code/c/self/signal_deliver# ./test_order.sh 0
    2. ./sigaction_delivery_order:PID is 3652
    3. sigaction for signo(32) failed (Invalid argument)
    4. sigaction for signo(33) failed (Invalid argument)
    5. I will sleep 30 second
    6. root@manu-hacks:~/code/c/self/signal_deliver# signo(2) :Interrupt
    7. signo(3) :Quit
    8. signo(4) :Illegal instruction
    9. signo(5) :Trace/breakpoint trap
    10. signo(7) :Bus error
    11. signo(8) :Floating point exception
    12. signo(10) :User defined signal 1
    13. signo(11) :Segmentation fault
    14. signo(12) :User defined signal 2
    15. signo(24) :CPU time limit exceeded
    16. signo(31) :Bad system call
    17. signo(36) :Real-time signal 2
    18. signo(37) :Real-time signal 3
    19. signo(38) :Real-time signal 4
    20. signo(39) :Real-time signal 5
    21. #0: signo=4
    22. #1: signo=5
    23. #2: signo=7
    24. #3: signo=8
    25. #4: signo=11
    26. #5: signo=31
    27. #6: signo=2
    28. #7: signo=3
    29. #8: signo=10
    30. #9: signo=12
    31. #10: signo=24
    32. #11: signo=36
    33. #12: signo=37
    34. #13: signo=38
    35. #14: signo=39
         和我们预想的完全符合,和内核代码已经手册完全一样。那么这个问题完美解决。
    1. {4,5,7,8,11,31, 2,3,10,12,24, 36,37,38}
         多个挂起信号时,delivery的策略如下:     
        1  {SIGILL(4),SIGTRAP(5),SIGBUS(7),SIGFPE(8),SIGSEGV(11),SIGSYS(31)}第一等级
        2  非实时信号中其他信号是第二等级(SIGKILL SIGSTOP除外)
        3  实时信号是第三等级。
        存在第一等级的信号挂起,那么优先选择第一等级,
         没有第一等级,那么如果存在第二等级的信号,优先选择第二等级内信号。 
        既没有第一等级,又没有第二等级,那么选择第三等级的信号。
        如果同一个等级内都存在多个挂起信号,则小信号优先。

        这只是我们用程序测试的结果,其实systemtap提供了signal_deliver这个event让我们monitor,我们可以直观的看到信号传递的顺序:   
    1. root@manu-hacks:~/code/c/self/signal_deliver# cat signal_deliver.stp
    2. probe kernel.trace("signal_deliver"){
    3.     if(pid() == target())
    4.     {
    5.      printf("signo(%2d) is delivered to PID %8d\n",$sig,pid());
    6.     }
    7. }
         我们可以用test_order.sh 1 ,故意用signal那个给我带来困扰的程序测试,我们会看到,传递的顺序依然是对的,这证明了我前面的推测,4是最先deliver的,只不过是因为没有屏蔽其他信号,被5号信号中断了,5又被7号信号中断了,依次类推,导致了我们看到了相反的执行顺序,给我们带来了困扰。  
    1. root@manu-hacks:~/code/c/self/signal_deliver# ./test_order.sh 1
      ./signal_delivery_order:PID is 4051
      sigaction for signo(32) failed (Invalid argument)
      sigaction for signo(33) failed (Invalid argument)
      I will sleep 30 second
      root@manu-hacks:~/code/c/self/signal_deliver# stap -x 4051 signal_deliver.stp 
      signo(2) :Interrupt
      signo(3) :Quit
      signo(4) :Illegal instruction
      signo(5) :Trace/breakpoint trap
      signo(7) :Bus error
      signo(8) :Floating point exception
      signo(10) :User defined signal 1
      signo(11) :Segmentation fault
      signo(12) :User defined signal 2
      signo(24) :CPU time limit exceeded
      signo(31) :Bad system call
      signo(36) :Real-time signal 2
      signo(37) :Real-time signal 3
      signo(38) :Real-time signal 4
      signo(39) :Real-time signal 5
      #0: signo=39
      #1: signo=38
      #2: signo=37
      #3: signo=36
      #4: signo=24
      #5: signo=12
      #6: signo=10
      #7: signo=3
      #8: signo=2
      #9: signo=31
      #10: signo=11
      #11: signo=8
      #12: signo=7
      #13: signo=5
      #14: signo=4
      signo( 4)  is delivered to PID     4051
      signo( 5)  is delivered to PID     4051
      signo( 7)  is delivered to PID     4051
      signo( 8)  is delivered to PID     4051
      signo(11)  is delivered to PID     4051
      signo(31)  is delivered to PID     4051
      signo( 2)  is delivered to PID     4051
      signo( 3)  is delivered to PID     4051
      signo(10)  is delivered to PID     4051
      signo(12)  is delivered to PID     4051
      signo(24)  is delivered to PID     4051
      signo(36)  is delivered to PID     4051
      signo(37)  is delivered to PID     4051
      signo(38)  is delivered to PID     4051
      signo(39)  is delivered to PID     4051


      ^Croot@manu-hacks:~/code/c/self/signal_deliver# 


         话说systemtap提供了很多signal相关的example脚本,非常好用,如下图,收集signal的发送情况:
        Linux signal那些事儿

    参考文献
    Linux实时信号排队
    2  LKML:
        Subject [PATCH -tip v4 2/3] tracepoint: Add signal deliver even