上一篇博文,基本算是给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无关。 1~31之间的所有信号都称为不可靠信号,原因就在于信号不可排队,如果kernel发现同一个信号已经有挂起信号,当前信号就会被丢弃,就好象从来没有被发送过一样,无法引起信号的传递,也无法让进程执行信号处理函数。这种实现的机理,造成了这些信号的不可靠。这正所谓:我本将心向明月,奈何明月照沟渠。 为了解决这个问题,Linux引入了实时信号,信号值在[32~64]区间内,或者称之为可靠信号。这种信号,kernel不会ignore,哪怕已经有了好多同一个信号,kernel会把新收到信号放入queue之中,等待被传递出去。 空口说白话,不是我们的风格,我现在用代码证明之。我参考了Linux Programming Interface 一书的例子,写了两个程序,一个是signal_receiver ,一个是signal_sender. 先看signal_receiver的code: manu@manu-hacks:~/code/c/self/signal$ cat signal_receiver.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 volatile sig_atomic_t get_SIGINT = 0;void handler(int signo){ if(signo == SIGINT) get_SIGINT = 1; else sig_cnt[signo]++;}int main(int argc,char* argv[]){ int i = 0; sigset_t blockall_mask ; sigset_t pending_mask ; sigset_t empty_mask ; printf("%s:PID is %ld\n",argv[0],getpid()); for(i = 1; i NSIG; i++) { if(i == SIGKILL || i == SIGSTOP) continue; if(signal(i,&handler) == SIG_ERR) { fprintf(stderr,"signal for signo(%d) failed (%s)\n",i,strerror(errno));// return -1; } } if(argc > 1) { int sleep_time = atoi(argv[1]); sigfillset(&blockall_mask); 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; } } while(!get_SIGINT) continue ; //why not use pause ? I will explain later for(i = 1; i NSIG ; i++) { if(sig_cnt[i] != 0 ) { printf("%s:signal %d caught %d time%s\n", argv[0],i,sig_cnt[i],(sig_cnt[i] >1)?"s":""); } } return 0;} 因为我们知道,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: manu@manu-hacks:~/code/c/self/signal$ cat signal_sender.c#include stdio.h>#include stdlib.h>#include getopt.h>#include signal.h>#include string.h>#include errno.h>void usage(){ fprintf(stderr,"USAGE:\n"); fprintf(stderr,"--------------------------------\n"); fprintf(stderr,"signal_sender pid signo times\n");}int main(int argc,char* argv[]){ pid_t pid = -1 ; int signo = -1; int times = -1; int i ; if(argc 4 ) { usage(); return -1; } pid = atol(argv[1]); signo = atoi(argv[2]); times = atoi(argv[3]); if(pid = 0 || times 0 || signo 1 ||signo >=64 ||signo == 32 || signo ==33) { usage(); return -1; } printf("pid = %ld,signo = %d,times = %d\n",pid,signo,times); for( i = 0 ; i times ; i++) { if(kill(pid,signo) == -1) { fprintf(stderr, "send signo(%d) to pid(%ld) failed,reason(%s)\n",signo,pid,strerror(errno)); return -2; } } fprintf(stdout,"done\n"); return 0;} signal_sender需要三个参数,pid signo times,就是向拿个进程发送什么信号多少次的意思。如 signal_sender 1234 10 10000,含义是向pid=1234的 进程发送10号信号(SIGUSR1),连续发送10000次。 有这两个进程,我们就可以实验了 。 manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver &[1] 23416manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 23416signal for signo(32) failed (Invalid argument)signal for signo(33) failed (Invalid argument)manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 23416 10 10000pid = 23416,signo = 10,times = 10000donemanu@manu-hacks:~/code/c/self/signal$ sleep 20 ; ./signal_sender 23416 2 1pid = 23416,signo = 2,times = 1done./signal_receiver:signal 10 caught 2507 times[1]+ Done ./signal_receiver signal_receiver等待signal的来临,singal_sender向其发送SIGUSR1 10000次,然后sleep 20秒,确保sig_receiver处理完成。但是我们发现,其实一共才caught信号SIGUSR1 2507次,7000多次的发送都丢失了,所以我们称SIGUSR1 是非可靠信号,存在丢信号的问题。 俗话说不怕不识货,就怕货比货 ,我们让可靠信号参战,看下效果:manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver &[1] 26067./signal_receiver:PID is 26067signal for signo(32) failed (Invalid argument)signal for signo(33) failed (Invalid argument)manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 10 10000pid = 26067,signo = 10,times = 10000donemanu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 36 10000pid = 26067,signo = 36,times = 10000donemanu@manu-hacks:~/code/c/self/signal$ ./signal_sender 26067 2 1pid = 26067,signo = 2,times = 1done./signal_receiver:signal 10 caught 2879 times./signal_receiver:signal 36 caught 10000 times[1]+ Done ./signal_receiver 可靠性信号36,发送10000次,signal_receiver全部收到,不可靠性信号10,共收到2879次。这个数字是不可预期的,取决于内核进程的调度。 这个如果还不够直观,我们在比较一次,让signal_receiver先屏蔽所有信号一段时间,如30s,然后解除屏蔽。manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver 30 &[1] 27639manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 27639signal for signo(32) failed (Invalid argument)signal for signo(33) failed (Invalid argument)I will sleep 30 secondmanu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 10 10000pid = 27639,signo = 10,times = 10000donemanu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 36 10000pid = 27639,signo = 36,times = 10000donemanu@manu-hacks:~/code/c/self/signal$manu@manu-hacks:~/code/c/self/signal$ signo(10) :User defined signal 1signo(36) :Real-time signal 2manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 27639 2 1pid = 27639,signo = 2,times = 1done./signal_receiver:signal 10 caught 1 time./signal_receiver:signal 36 caught 10000 times[1]+ Done ./signal_receiver 30 这个比较反差比较大,不可靠signal10 共收到1次,可靠性信号36 共caught到10000次。原因就在于sigprocmask将所有的信号都屏蔽了,造成所有的信号都不能delivery。对1~31的信号,内核发现已经有相应的挂起信号,则ignore到新来的信号。但是可靠性信号则不同,会添加队列中去,尽管已经有了相同的信号。需要注意的是,signal pending有上限,并不能无限制的发:manu@manu-hacks:~/code/c/self/signal$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0file size (blocks, -f) unlimitedpending signals (-i) 15408max locked memory (kbytes, -l) 64max memory size (kbytes, -m) unlimitedopen files (-n) 1024pipe size (512 bytes, -p) 8POSIX message queues (bytes, -q) 819200real-time priority (-r) 0stack size (kbytes, -s) 8192cpu time (seconds, -t) unlimitedmax user processes (-u) 15408virtual memory (kbytes, -v) unlimitedfile locks (-x) unlimited 我发送100万,最终会收到15408个可靠信号:manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver 30 &[1] 16488manu@manu-hacks:~/code/c/self/signal$ ./signal_receiver:PID is 16488signal for signo(32) failed (Invalid argument)signal for signo(33) failed (Invalid argument)I will sleep 30 secondmanu@manu-hacks:~/code/c/self/signal$ ./signal_sender 16488 36 1000000pid = 16488,signo = 36,times = 1000000donemanu@manu-hacks:~/code/c/self/signal$ signo(36) :Real-time signal 2manu@manu-hacks:~/code/c/self/signal$ ./signal_sender 16488 2 1pid = 16488,signo = 2,times = 1done./signal_receiver:signal 36 caught 15408 times[1]+ Done ./signal_receiver 30 内核是怎么做到的? 上图是内核中signal相关的数据结构。其中task_struct中有sigpending类型的成员变量pending struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; atomic_t usage; unsigned int flags; /* per process flags, defined below */ unsigned int ptrace; ... .../* signal handlers */ struct signal_struct *signal; struct sighand_struct *sighand; sigset_t blocked, real_blocked; sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ struct sigpending pending; ...}struct signal_struct { atomic_t sigcnt; atomic_t live; int nr_threads; ... ... /* shared signal handling: */ struct sigpending shared_pending; ...}struct sigpending { struct list_head list; sigset_t signal;};#define _NSIG 64#ifdef __i386__# define _NSIG_BPW 32#else# define _NSIG_BPW 64#endif#define _NSIG_WORDS (_NSIG / _NSIG_BPW)typedef unsigned long old_sigset_t; /* at least 32 bits */typedef struct { unsigned long sig[_NSIG_WORDS];} sigset_t; task_struct中的pending,和signal->shared_pending都是记录挂起信号的数据结构,读到此处,你可能会迷惑,为何有两个这样的结构。这牵扯到thread与信号的一些问题,我们此处简化,就认为是一个就好,后面讲述线程与信号关系的时候,再展开。 我们看到了,kill也好,tkill也罢,最终都走到了_send_signal.当然了kill系统调用根据pid的情况会分成多个分支如pid >0 pid = 0 pid=-1;pid 0 的分支。tkill也有类似情况。 那么kernel是怎么做到的非可靠信号和可靠信号的的这些差别的呢?static int __send_signal(int sig, struct siginfo *info, struct task_struct *t, int group, int from_ancestor_ns){ struct sigpending *pending; struct sigqueue *q; int override_rlimit; int ret = 0, result; assert_spin_locked(&t->sighand->siglock); result = TRACE_SIGNAL_IGNORED; if (!prepare_signal(sig, t, from_ancestor_ns || (info == SEND_SIG_FORCED))) goto ret; pending = group ? &t->signal->shared_pending : &t->pending; /* * Short-circuit ignored signals and support queuing * exactly one non-rt signal, so that we can get more * detailed information about the cause of the signal. */ result = TRACE_SIGNAL_ALREADY_PENDING; if (legacy_queue(pending, sig)) //如果是低于32的信号,并且已经在pending中出现了的信号,就直接返回了,ignore goto ret; result = TRACE_SIGNAL_DELIVERED; /* * fast-pathed signals for kernel-internal things like SIGSTOP * or SIGKILL. */ if (info == SEND_SIG_FORCED) goto out_set; /* * Real-time signals must be queued if sent by sigqueue, or * some other real-time mechanism. It is implementation * defined whether kill() does so. We attempt to do so, on * the principle of least surprise, but since kill is not * allowed to fail with EAGAIN when low on memory we just * make sure at least one signal gets delivered and don't * pass on the info struct. */ if (sig SIGRTMIN) override_rlimit = (is_si_special(info) || info->si_code >= 0); else override_rlimit = 0; //分配sigqueue结构,并且链入到相应的pending。 q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE, override_rlimit); if (q) { list_add_tail(&q->list, &pending->list); switch ((unsigned long) info) { case (unsigned long) SEND_SIG_NOINFO: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_USER; q->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t)); q->info.si_uid = from_kuid_munged(current_user_ns(), current_uid()); break; case (unsigned long) SEND_SIG_PRIV: q->info.si_signo = sig; q->info.si_errno = 0; q->info.si_code = SI_KERNEL; q->info.si_pid = 0; q->info.si_uid = 0; break; default: copy_siginfo(&q->info, info); if (from_ancestor_ns) q->info.si_pid = 0; break; } userns_fixup_signal_uid(&q->info, t); } else if (!is_si_special(info)) { if (sig >= SIGRTMIN && info->si_code != SI_USER) { /* * Queue overflow, abort. We may abort if the * signal was rt and sent by user using something * other than kill(). */ result = TRACE_SIGNAL_OVERFLOW_FAIL; ret = -EAGAIN; goto ret; } else { /* * This is a silent loss of information. We still * send the signal, but the *info bits are lost. */ result = TRACE_SIGNAL_LOSE_INFO; } }out_set: signalfd_notify(t, sig); sigaddset(&pending->signal, sig); //加入位图 complete_signal(sig, t, group);ret: trace_signal_generate(sig, info, t, group, result); return ret;}static inline int legacy_queue(struct sigpending *signals, int sig){ return (sig SIGRTMIN) && sigismember(&signals->signal, sig); //是不可靠信号,并且该信号已经存在挂起信号, 那么15408的限制是在哪里呢?在__sigqueue_alloc 里面。static struct sigqueue *__sigqueue_alloc(int sig, struct task_struct *t, gfp_t flags, int override_rlimit){ struct sigqueue *q = NULL; struct user_struct *user; /* * Protect access to @t credentials. This can go away when all * callers hold rcu read lock. */ rcu_read_lock(); user = get_uid(__task_cred(t)->user); atomic_inc(&user->sigpending); //计数器+1 rcu_read_unlock(); if (override_rlimit || atomic_read(&user->sigpending) = task_rlimit(t, RLIMIT_SIGPENDING)) { q = kmem_cache_alloc(sigqueue_cachep, flags); } else { print_dropped_signal(sig); } if (unlikely(q == NULL)) { atomic_dec(&user->sigpending); free_uid(user); } else { INIT_LIST_HEAD(&q->list); q->flags = 0; q->user = user; } return q;} 我们看到,legacy_queue就是用来判断是否是非可靠信号(signo低于32),并且相同signo值已经存在在挂起信号之中,如果是,直接返回。 而对于可靠信号,会分配一个sigqueue的结构,然后讲sigqueue链入到sigpending结构的中链表中。从而就不会丢失信号。当然对pending信号的总数作了限制,限制最多不可超过15408.当然了这个值是可以修改的: 参考文献:1 Linux programming interface2 深入理解linux内核3 linux kernel 3.8.0 内核源码 09-30 06:36