Linux signal 那些事儿(2)
来源:互联网 发布:中国乐队 知乎 编辑:程序博客网 时间:2024/06/07 20:33
origin:http://blog.chinaunix.net/uid-24774106-id-4064447.html
上一篇博文,基本算是给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;
- }
这个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;
- }
有这两个进程,我们就可以实验了 。
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver&
- [1] 23416
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PIDis 23416
- signal for signo(32) failed(Invalid argument)
- signal for signo(33) failed(Invalid argument)
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 23416 10 10000
- pid = 23416,signo= 10,times = 10000
- done
- manu@manu-hacks:~/code/c/self/signal$ sleep 20 ; ./signal_sender 23416 2 1
- pid = 23416,signo= 2,times = 1
- done
- ./signal_receiver:signal 10 caught 2507 times
- [1]+ Done./signal_receiver
俗话说不怕不识货,就怕货比货 ,我们让可靠信号参战,看下效果:
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver &
- [1] 26067
- ./signal_receiver:PID is 26067
- signal for signo(32) failed(Invalid argument)
- signal for signo(33) failed(Invalid argument)
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 10 10000
- pid = 26067,signo= 10,times = 10000
- done
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 36 10000
- pid = 26067,signo= 36,times = 10000
- done
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 26067 2 1
- pid = 26067,signo= 2,times = 1
- done
- ./signal_receiver:signal 10 caught 2879 times
- ./signal_receiver:signal 36 caught 10000 times
- [1]+ Done./signal_receiver
这个如果还不够直观,我们在比较一次,让signal_receiver先屏蔽所有信号一段时间,如30s,然后解除屏蔽。
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver 30 &
- [1] 27639
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PID is 27639
- signal for signo(32) failed(Invalid argument)
- signal for signo(33) failed(Invalid argument)
- I will sleep 30 second
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 10 10000
- pid = 27639,signo= 10,times = 10000
- done
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 36 10000
- pid = 27639,signo= 36,times = 10000
- done
- manu@manu-hacks:~/code/c/self/signal$
- manu@manu-hacks:~/code/c/self/signal$ signo(10):User defined signal 1
- signo(36):Real-time signal 2
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 27639 2 1
- pid = 27639,signo= 2,times = 1
- done
- ./signal_receiver:signal10 caught 1 time
- ./signal_receiver:signal36 caught 10000 times
- [1]+ Done./signal_receiver 30
- manu@manu-hacks:~/code/c/self/signal$ ulimit-a
- core file size (blocks, -c) 0
- data seg size (kbytes, -d) unlimited
- scheduling priority (-e) 0
- file size (blocks,-f) unlimited
- pending signals (-i) 15408
- max locked memory (kbytes,-l) 64
- max memory size (kbytes, -m) unlimited
- open files (-n) 1024
- pipe size (512 bytes,-p) 8
- POSIX message queues (bytes,-q) 819200
- real-time priority (-r) 0
- stack size (kbytes,-s) 8192
- cpu time (seconds,-t) unlimited
- max user processes (-u) 15408
- virtual memory (kbytes,-v) unlimited
- file locks (-x) unlimited
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver 30 &
- [1] 16488
- manu@manu-hacks:~/code/c/self/signal$./signal_receiver:PID is 16488
- signal for signo(32) failed(Invalid argument)
- signal for signo(33) failed(Invalid argument)
- I will sleep 30 second
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 16488 36 1000000
- pid = 16488,signo= 36,times = 1000000
- done
- manu@manu-hacks:~/code/c/self/signal$ signo(36):Real-time signal 2
- manu@manu-hacks:~/code/c/self/signal$./signal_sender 16488 2 1
- pid = 16488,signo= 2,times = 1
- done
- ./signal_receiver:signal 36 caught15408 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;
我们看到了,kill也好,tkill也罢,最终都走到了_send_signal.当然了kill系统调用根据pid的情况会分成多个分支如pid >0 pid = 0 pid=-1;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 signalsand 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 signalsfor 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. Itis implementation
- * defined whether kill() does so. We attemptto do so,on
- * the principle of least surprise, but since killis not
- * allowedto fail with EAGAIN when lowon memory we just
- * make sure at least one signal gets deliveredand don't
- * passon the info struct.
- */
- if(sig< SIGRTMIN)
- override_rlimit =(is_si_special(info)|| info->si_code>= 0);
- else
- override_rlimit = 0;
- 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 rtand sent by user using something
- * other than kill().
- */
- result = TRACE_SIGNAL_OVERFLOW_FAIL;
- ret =-EAGAIN;
- goto ret;
- }else {
- /*
- * Thisis 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 intlegacy_queue(struct sigpending*signals,int sig)
- {
- return (sig< SIGRTMIN)&& sigismember(&signals->signal, sig); //是不可靠信号,并且该信号已经存在挂起信号,
- 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;
- }
而对于可靠信号,会分配一个sigqueue的结构,然后讲sigqueue链入到sigpending结构的中链表中。从而就不会丢失信号。当然对pending信号的总数作了限制,限制最多不可超过15408.当然了这个值是可以修改的:
参考文献:
1 Linux programming interface
2 深入理解linux内核
3 linux kernel 3.8.0 内核源码
- Linux signal 那些事儿(2)
- Linux signal 那些事儿(2)
- Linux signal那些事儿
- Linux signal那些事儿
- Linux signal那些事儿
- Linux signal 那些事儿 (3)
- Linux signal 那些事儿 (3)
- Linux signal 那些事儿 (3)
- Linux signal那些事儿1
- Linux signal 那些事儿(4)信号的deliver顺序
- Linux的那些事儿(2)----vi的使用
- IHE那些事儿(2)
- Linux那些事儿之我是Hub(2)
- 操作系统那些事儿(二)-Linux
- Linux usb那些事儿
- Linux权限那些事儿
- Linux权限那些事儿
- Linux权限那些事儿
- 一些简单的小程序_3——C语言篇
- 安装Mysql
- [hdu 6230 Palindrome] Manacher+树状数组
- git 更新远程代码到本地和将本地代码推送到远程。
- Ubuntu14.04+caffe+matlab(cpu)配置
- Linux signal 那些事儿(2)
- Linux系统中修改用户名的两种方案
- noip2017游记
- CNN目标检测(一):Faster RCNN详解
- Safari中的印象笔记剪切插件默认要我登陆evernote账号但我只有印象笔记的账号啊!没关系一步教你解决!
- IDEA设置字体大小
- 百度地图、高德地图、谷歌地图离线瓦片下载研究(一)
- VC修改mac地址的方法
- Java 如何获取控制台日志输出的信息? WriterAppender 可以实现? 配置文件如何配置