next_signal 负责从挂起信号中选择deliver的signo:当然,有线程显存私有的penging,有线程组共有的pending,对于线程而言,先从自己私有的pending中选,处理完毕私有的才会去处理线程组共有的pending,这个逻辑的代码在:
- int dequeue_signal(struct task_struct *tsk, sigset_t *mask, siginfo_t *info)
- {
- int signr;
-
- /* We only dequeue private signals from ourselves, we don't let
- * signalfd steal them
- */
- signr = __dequeue_signal(&tsk->pending, mask, info); //线程私有的penging优先
- if (!signr) {
- signr = __dequeue_signal(&tsk->signal->shared_pending,
- mask, info);
- 。。。。
- }
- int next_signal(struct sigpending *pending, sigset_t *mask)
- {
- unsigned long i, *s, *m, x;
- int sig = 0;
-
- s = pending->signal.sig;
- m = mask->sig;
-
- /*
- * Handle the first word specially: it contains the
- * synchronous signals that need to be dequeued first.
- */
- x = *s &~ *m;
- if (x) {
- if (x & SYNCHRONOUS_MASK)
- x &= SYNCHRONOUS_MASK;
- sig = ffz(~x) + 1;
- return sig;
- }
-
- switch (_NSIG_WORDS) {
- default:
- for (i = 1; i < _NSIG_WORDS; ++i) {
- x = *++s &~ *++m;
- if (!x)
- continue;
- sig = ffz(~x) + i*_NSIG_BPW + 1;
- break;
- }
- break;
-
- case 2:
- x = s[1] &~ m[1];
- if (!x)
- break;
- sig = ffz(~x) + _NSIG_BPW + 1;
- break;
-
- case 1:
- /* Nothing to do */
- break;
- }
-
- return sig;
- }
- #define SYNCHRONOUS_MASK \
- (sigmask(SIGSEGV) | sigmask(SIGBUS) | sigmask(SIGILL) | \
- sigmask(SIGTRAP) | sigmask(SIGFPE) | sigmask(SIGSYS))
换句话说所有信号分成三个等级,{SIGILL( 4),SIGTRAP( 5),SIGBUS(7),SIGFPE(8),SIGSEGV(11),SIGSYS(31) },这是第一等级,传统信号中排除第一等级的信号,就是第二等级的信号,[34,64 ]之间的信号属于第三等级。如果同一等级内,存在多个信号,按照小信号优先的顺序去deliver。
举个例子:
- 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
我们按照kernel的规则,顺序应该是{4,5,7,8,11,31, 2,3,10,12,24, 36,37,38 }这么个顺序。
写个测试程序:
- root@manu-hacks:~/Dropbox/Note/signal# cat signal_delivery_order.c
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <signal.h>
- #include <string.h>
- #include <errno.h>
-
-
- static int sig_cnt[NSIG];
- static number= 0 ;
- int sigorder[128]= {0};
-
- #define MSG "#%d:receiver signal %d\n"
-
- void handler(int signo)
- {
- sigorder[number++] = signo;
- }
-
- int main(int argc,char* argv[])
- {
- int i = 0;
- int k = 0;
- sigset_t blockall_mask ;
- sigset_t pending_mask ;
- sigset_t empty_mask ;
- struct sigaction sa ;
-
-
- sigfillset(&blockall_mask);
- #ifdef USE_SIGACTION
- sa.sa_handler = handler;
- sa.sa_mask = blockall_mask ;
- sa.sa_flags = SA_RESTART;
- #endif
-
-
- printf("%s:PID is %ld\n",argv[0],getpid());
-
-
- for(i = 1; i < NSIG; i++)
- {
- if(i == SIGKILL || i == SIGSTOP)
- continue;
- #ifdef USE_SIGACTION
- if(sigaction(i,&sa, NULL)!=0)
- #else
- if(signal(i,handler)== SIG_ERR)
- #endif
- {
- fprintf(stderr,"sigaction for signo(%d) failed (%s)\n",i,strerror(errno));
- // return -1;
- }
- }
-
- if(argc > 1)
- {
- int sleep_time = atoi(argv[1]);
-
- if(sigprocmask(SIG_SETMASK,&blockall_mask,NULL) == -1)
- {
- fprintf(stderr,"setprocmask to block all signal failed(%s)\n",strerror(errno));
- return -2;
- }
-
- printf("I will sleep %d second\n",sleep_time);
-
- sleep(sleep_time);
- if(sigpending(&pending_mask) == -1)
- {
- fprintf(stderr,"sigpending failed(%s)\n",strerror(errno));
- return -2;
- }
-
- for(i = 1 ; i < NSIG ; i++)
- {
- if(sigismember(&pending_mask,i))
- printf("signo(%d) :%s\n",i,strsignal(i));
- }
-
- sigemptyset(&empty_mask);
- if(sigprocmask(SIG_SETMASK,&empty_mask,NULL) == -1)
- {
- fprintf(stderr,"setprocmask to release all signal failed(%s)\n",strerror(errno));
- return -3;
- }
-
- for( i = 0 ; i < 1000000 ; i++)
- {
- k = random()%1234567;
-
- }
-
- }
-
- for(i = 0 ; i< number ; i++)
- {
- if(sigorder[i] != 0)
- {
- printf("#%d: signo=%d\n",i,sigorder[i]);
- }
- }
-
- return 0;
-
- }
- void handler(int signo)
- {
- sigorder[number++] = signo;
- }
这个进程会阻塞 所有信号一段时间,在这段时间内,我会向该进程发送一坨信号,待阻塞解除后,打印数组的值,从而获得deliver的顺序。
看下测试程序:
- 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
梦想很丰满,无奈现实很骨感,我看了执行结果,那是当头一棒啊:
- root@manu-hacks:~/Dropbox/Note/signal#
- root@manu-hacks:~/Dropbox/Note/signal# ./test_order.sh 1
- ./signal_delivery_order:PID is 31403
- sigaction for signo(32) failed (Invalid argument)
- sigaction for signo(33) failed (Invalid argument)
- I will sleep 30 second
- root@manu-hacks:~/Dropbox/Note/signal# 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
我最初完全解释不通,我google了类似的topic,我发现,我不是第一个发现这个问题的人,绚丽也尘埃在一篇博客中提到:
- 在网上找到这样一段话:
- 信号的优先级:信号实质上是软中断,中断有优先级,信号也有优先级。如果一个进程有多个未决信号,则对于同一个未决的实时信号,内核将按照发送的顺序来递送信号。如果存 在多个未决的实时信号,则值(或者说编号)越小的越先被递送。如果既存在不可靠信号,又存在可靠信号(实时信号),虽然POSIX对这一情况没有明确规 定,但Linux系统和大多数遵循POSIX标准的操作系统一样,将优先递送不可靠信号。
-
- 经过我反反复复地试验,我发现实验结果和上面描述的刚好相反,信号的编号越大越先被递送,一个进程如果处理SIGQUIT(3),SIGINT(2),SIGHUP(1)(通过”kill -l”可以查看信号的编号),那么先后给该进程发送SIGINT,SIGHUP,SIGQUIT,处理的顺序会是SIGQUIT,SIGINT,SIGHUP,不论改变这个三个信号的发送顺序,处理的顺序都是一样的。
今天我坐公交车上,突然意识到问题的所在,我的信号处理函数没有屏蔽信号!!!
换句话说,4号信号是先被deliver的,但是还没能handler执行,被5号信号中断掉了,5号信号还没开始执行,被7号信号中断掉了,依次类推,所以我们测试的结果和deliver的结果正好相反。
意识到这一点,我就改进了我的程序,信号执行期间,屏蔽所有信号,这样,就能测试信号deliver的顺序了。对于我的程序而言就是加上-DUSE_SIGACTION选项,让sigaction安装信号时,指明信号处理函数执行期间,屏蔽所有信号。
在我的64位Ubuntu上执行,结果和kernel代码以及手册上的一样。也就是说,并不是手册描述错了,而是我们的老的测试程序,在signal处理期间,没有屏蔽其他信号导致混乱。那么按照正确的方法测试:
- root@manu-hacks:~/code/c/self/signal_deliver# ./test_order.sh 0
- ./sigaction_delivery_order:PID is 3652
- 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# 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=4
- #1: signo=5
- #2: signo=7
- #3: signo=8
- #4: signo=11
- #5: signo=31
- #6: signo=2
- #7: signo=3
- #8: signo=10
- #9: signo=12
- #10: signo=24
- #11: signo=36
- #12: signo=37
- #13: signo=38
- #14: signo=39
- {4,5,7,8,11,31, 2,3,10,12,24, 36,37,38}
1 {SIGILL( 4),SIGTRAP( 5),SIGBUS(7),SIGFPE(8),SIGSEGV(11),SIGSYS(31) }第一等级
2 非实时信号中其他信号是第二等级(SIGKILL SIGSTOP除外)
3 实时信号是第三等级。
存在第一等级的信号挂起,那么优先选择第一等级,
没有第一等级,那么如果存在第二等级的信号,优先选择第二等级内信号。
既没有第一等级,又没有第二等级,那么选择第三等级的信号。
如果同一个等级内都存在多个挂起信号,则小信号优先。
这只是我们用程序测试的结果,其实systemtap提供了signal_deliver这个event让我们monitor,我们可以直观的看到信号传递的顺序:
- root@manu-hacks:~/code/c/self/signal_deliver# cat signal_deliver.stp
- probe kernel.trace("signal_deliver"){
- if(pid() == target())
- {
- printf("signo(%2d) is delivered to PID %8d\n",$sig,pid());
- }
- }
- 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#
参考文献
1 Linux实时信号排队
2 LKML:
Subject | [PATCH -tip v4 2/3] tracepoint: Add signal deliver even |