Linux signal 那些事儿(4)信号的deliver顺序【转】

转自:http://blog.chinaunix.net/uid-24774106-id-4084864.html

上一篇博文提到了,如果同时有多个不同的信号处于挂起状态,kernel如何选择deliver那个信号。
    
    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. [email protected]-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. sigfillset(&blockall_mask);
  25. #ifdef USE_SIGACTION
  26. sa.sa_handler = handler;
  27. sa.sa_mask = blockall_mask ;
  28. sa.sa_flags = SA_RESTART;
  29. #endif
  30. printf("%s:PID is %ld\n",argv[0],getpid());
  31. for(i = 1; i < NSIG; i++)
  32. {
  33. if(i == SIGKILL || i == SIGSTOP)
  34. continue;
  35. #ifdef USE_SIGACTION
  36. if(sigaction(i,&sa, NULL)!=0)
  37. #else
  38. if(signal(i,handler)== SIG_ERR)
  39. #endif
  40. {
  41. fprintf(stderr,"sigaction for signo(%d) failed (%s)\n",i,strerror(errno));
  42. //            return -1;
  43. }
  44. }
  45. if(argc > 1)
  46. {
  47. int sleep_time = atoi(argv[1]);
  48. if(sigprocmask(SIG_SETMASK,&blockall_mask,NULL) == -1)
  49. {
  50. fprintf(stderr,"setprocmask to block all signal failed(%s)\n",strerror(errno));
  51. return -2;
  52. }
  53. printf("I will sleep %d second\n",sleep_time);
  54. sleep(sleep_time);
  55. if(sigpending(&pending_mask) == -1)
  56. {
  57. fprintf(stderr,"sigpending failed(%s)\n",strerror(errno));
  58. return -2;
  59. }
  60. for(i = 1 ; i < NSIG ; i++)
  61. {
  62. if(sigismember(&pending_mask,i))
  63. printf("signo(%d) :%s\n",i,strsignal(i));
  64. }
  65. sigemptyset(&empty_mask);
  66. if(sigprocmask(SIG_SETMASK,&empty_mask,NULL) == -1)
  67. {
  68. fprintf(stderr,"setprocmask to release all signal failed(%s)\n",strerror(errno));
  69. return -3;
  70. }
  71. for( i = 0 ; i < 1000000 ; i++)
  72. {
  73. k = random()%1234567;
  74. }
  75. }
  76. for(i = 0 ; i< number ; i++)
  77. {
  78. if(sigorder[i] != 0)
  79. {
  80. printf("#%d: signo=%d\n",i,sigorder[i]);
  81. }
  82. }
  83. return 0;
  84. }

注意这个USE_SIGACTION宏包裹的部分是我后来想到的,在我开始的版本中用的是signal(i,handler)来注册函数。handler函数很有意思,是我精心设计的:

  1. void handler(int signo)
  2. {
  3. sigorder[number++] = signo;
  4. }

按照执行的顺序,我会讲signo的值记录在全局数组中,等到进程退出前,我打印数组的值,就能得到信号deliver的顺序。
    这个进程会阻塞所有信号一段时间,在这段时间内,我会向该进程发送一坨信号,待阻塞解除后,打印数组的值,从而获得deliver的顺序。
    看下测试程序:

  1. [email protected]:~/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. [email protected]-hacks:~/Dropbox/Note/signal#
  2. [email protected]-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. [email protected]-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. [email protected]-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. [email protected]-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. [email protected]-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. [email protected]:~/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
    [email protected]:~/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

    ^[email protected]:~/code/c/self/signal_deliver#

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

参考文献
Linux实时信号排队2  LKML:

    Subject [PATCH -tip v4 2/3] tracepoint: Add signal deliver even
时间: 2024-11-10 08:18:58

Linux signal 那些事儿(4)信号的deliver顺序【转】的相关文章

Linux signal 那些事儿(2)【转】

转自:http://blog.chinaunix.net/uid-24774106-id-4064447.html 上一篇博文,基本算是给glibc的signal函数翻了个身.现在glibc的signal基本修正了传统的UNIX的一些弊端,我们说signal并没有我们想象的那么不堪.但是signal也有不尽人意的地方.比如信号处理期间,我们期望屏蔽某些信号,而不仅仅是屏蔽自身,这时候signal就不行了.信号既然是进程间通信IPC的一种机制,我们期望获取更多的信息,而不仅仅是signo,这时候s

Linux signal那些事儿【转】

转自:http://blog.chinaunix.net/uid-24774106-id-4061386.html Linux编程,信号是一个让人爱恨交加又不得不提的一个领域.最近我集中学习了Linux的signal相关的内容,分享出来,也为防止自己忘记.     信号的本质是异步.异步一这个词,听着高端大气上档次,又让人云山雾绕,其则不然.其实我们想想,我们这个世界是异步的,每个人干事儿,并不总是A->B->C->D这种.比如我在网上买了东西,我其实并不知道快递几时能到.我可能在公司里

Linux signal 那些事儿 (3)【转】

转自:http://blog.chinaunix.net/uid-24774106-id-4065797.html 这篇博客,想集中在signal 与线程的关系上,顺带介绍内核signal相关的结构.如何组织我其实并没想好,想到哪就写到哪里吧.主题一定会落在signal之内而不跑题.     提到signal与thread的关系,就得先提POSIX标准.POSIX标准决定了Linux为何将signal如此实现:    1 信号处理函数必须在多线程应用的所有线程之间共享,但是,每个线程要有自己的挂

linux signal 处理

v/:* {behavior:url(#default#VML);} o/:* {behavior:url(#default#VML);} w/:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} Normal 0 7.8 pt 0 2 false false false EN-US ZH-CN X-NONE /* Style Definitions */ table.MsoNormalTable {mso-s

Linux系统编程——进程间通信:信号中断处理

什么是信号? 信号是 Linux 进程间通信的最古老的方式.信号是url=474nN303T2Oe2ehYZjkrggeXCaJPDSrmM5Unoh4TTuty4wSgS0nl4-vl43AGMFbo0_5uH5OQFr_vaRJaZ-3lq_" style="color:rgb(202,0,0); text-decoration:none">软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 . 信号能够导致一个正在执行的进程被还有一个正在执行的异

linux signal

当服务器close一个连接时,若client端接着发数据.根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统 会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了.根据信号的默认处理规则SIGPIPE信号的默认执行动作是 terminate(终止.退出), 所以client会退出. 若不想客户端退出可以把 SIGPIPE设为SIG_IGN 如:    signal(SIGPIPE,SIG_IGN); 这时SIGPIPE交给了系统处理. 服

&lt;摘录&gt;linux signal 列表

Linux 信号表 Linux支持POSIX标准信号和实时信号.下面给出Linux Signal的简表,详细细节可以查看man 7 signal. 默认动作的含义如下: 中止进程(Term) 忽略信号(Ign) 中止进程并保存内存信息(Core) 停止进程(Stop) 继续运行进程(Cont) 信号 取值 默认动作 含义(发出信号的原因) SIGHUP 1 Term 终端的挂断或进程死亡 SIGINT 2 Term 来自键盘的中断信号 SIGQUIT 3 Core 来自键盘的离开信号 SIGIL

老男孩教育每日一题-第69天-shell脚本知识点:linux系统脚本中trap信号都有哪些,如何进行使用?

题目 shell脚本知识点:linux系统脚本中trap信号都有哪些,如何进行使用? 参考答案: [trap信号命令说明] trap命令用于指定在接收到信号后将要采取的行动,trap命令的一种常见用途是在脚本程序被中断时完成清理工作.历史上,shell总是用数字来代表信号,而新的脚本程序应该使用信号的名字,它们保存在用#include命令包含进来的signal.h头文件中,在使用信号名时需要省略SIG前缀.可以在命令提示符下输入命令trap -l来查看信号编号及其关联的名称. [trap信号命令

linux signal 用法和注意事项

http://blog.chinaunix.net/uid-9354-id-2425031.html 所以希望能用相同方式处理信号的多次出现,最好用sigaction.信号只出现并处理一次,可以用signal.   signal函数每次设置具体的信号处理函数(非SIG_IGN)只能生效一次,每次在进程响应处理信号时,随即将信号处理函数恢复为默认处理方式.所以如果想多次相同方式处理某个信号,通常的做法是,在响应函数开始,再次调用signal设置,如下图: int sig_int(); //My s