Linux信号机制
2016-04-21 23:43
309 查看
Linux信号机制
Linux信号机制一管理层次及结构
1 数据结构
11 中断向量表的模拟
12 中断请求寄存器的模拟
二 信号的安装
1 概述
2 代码分析
3 其它函数
三信号发送
1 kill函数和sigqueue
2 代码分析
四响应信号
一、管理层次及结构
1.1 数据结构
信号机制是在软件层次上堆中断机制的一种模拟,也就是说信号是一种代码异步执行的方式。故而信号也有类似于中断管理的相关软件层实现。包括:+ 中断向量表: 进程控制块task_struct中的sig指针,指向一个signal_struct结构。
+ 中断状态控制器: signal_struct当中的struct siginfo
+ 中断请求寄存器: signal_struct中的信号队列:pending
+ 中断屏蔽寄存器: signal_struct中的blocked
1.1.1 中断向量表的模拟
进程控制块当中有一个sig指针,指向一个signal_struct结构,这个结构定义如下:[include/linux/sched.h]243 struct signal_struct { 244 atomic_t count; 245 struct k_sigaction action[_NSIG]; 246 spinlock_t siglock; 247 };
我们可以将245的action理解成为中断向量表,也正如其名,这个结构就是管理注册的信号handler的结构。可以看到struct k_sigaction的定义如下:
[include/asm-i386/signal.h]
150 struct k_sigaction { 151 struct sigaction sa; 152 }; 156 struct sigaction { 157 union { 158 __sighandler_t _sa_handler; 159 void (*_sa_sigaction)(int, struct siginfo *, void *); 160 } _u; 161 sigset_t sa_mask; 162 unsigned long sa_flags; 163 void (*sa_restorer)(void); 164 }; 16 typedef struct siginfo { 17 int si_signo; 18 int si_errno; 19 int si_code; 20 21 union { 22 int _pad[SI_PAD_SIZE]; 23 24 /* kill() */ 25 struct { 26 pid_t _pid; /* sender's pid */ 27 uid_t _uid; /* sender's uid */ 28 } _kill; 29 30 /* POSIX.1b timers */ 31 struct { 32 unsigned int _timer1; 33 unsigned int _timer2; 34 } _timer; 35 36 /* POSIX.1b signals */ 37 struct { 38 pid_t _pid; /* sender's pid */ 39 uid_t _uid; /* sender's uid */ 40 sigval_t _sigval; 41 } _rt; 42 43 /* SIGCHLD */ 44 struct { 45 pid_t _pid; /* which child */ 46 uid_t _uid; /* sender's uid */ 47 int _status; /* exit code */ 48 clock_t _utime; 49 clock_t _stime; 50 } _sigchld; 51 52 /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ 53 struct {
可以看到k_sigaction只是堆struct sigaction的又一次封装,struct sigaction中,_u中的函数指针是安装的信号handler。具体要引用_u中的哪个函数,有sa_flags来说明。当sa_flags中的SA_SIGINFO置位的时候,就调用_sa_sigaction作为handler,关于该函数,后文会有更为详细的叙述。另外sa_mask是中断屏蔽标志,用作位图,当handler执行的时候,sa_mask中被置位的信号都会被屏蔽。指的注意的是如果信号X被响应,则X自动被添加到sa_mask中,也就是说X的handler执行时,内核自动屏蔽X信号,以免产生信号嵌套处理。设计目的与防止硬中断嵌套是一致的。
至于_sa_sigaction中的第四个参数,用于在进入ISR时,传递一些关于中断的信息。这好比在硬中断进入ISR时会传入一个额外的void *dev一样。用于ISR针对信号做一些特别的处理。其定义如下:
[include/asm-i386/siginfo.h]
16 typedef struct siginfo { 17 int si_signo; 18 int si_errno; 19 int si_code; 20 21 union { 22 int _pad[SI_PAD_SIZE]; 23 24 /* kill() */ 25 struct { 26 pid_t _pid; /* sender's pid */ 27 uid_t _uid; /* sender's uid */ 28 } _kill; 29 30 /* POSIX.1b timers */ 31 struct { 32 unsigned int _timer1; 33 unsigned int _timer2; 34 } _timer; 35 36 /* POSIX.1b signals */ 37 struct { 38 pid_t _pid; /* sender's pid */ 39 uid_t _uid; /* sender's uid */ 40 sigval_t _sigval; 41 } _rt; 42 43 /* SIGCHLD */ 44 struct { 45 pid_t _pid; /* which child */ 46 uid_t _uid; /* sender's uid */ 47 int _status; /* exit code */ 48 clock_t _utime; 49 clock_t _stime; 50 } _sigchld; 51 52 /* SIGILL, SIGFPE, SIGSEGV, SIGBUS */ 53 struct { 54 void *_addr; /* faulting insn/memory ref. */ 55 } _sigfault; 56 57 /* SIGPOLL */ 58 struct { 59 int _band; /* POLL_IN, POLL_OUT, POLL_MSG */ 60 int _fd; 61 } _sigpoll; 62 } _sifields; 63 } siginfo_t;
因为不同的信号的相关信息不一,所以对于这个结构体中的主题,也就是_sifields,会根据sig_code的不同而取不同的成员。
这里小结一下:当进程收到一个信号的时候,就会根据信号的code在task_struct.signal_struct.action
查找得到一个“中断描述符struct sigaction”,然后读取sagaciton中的sa_flags,解析调用handler即可进入ISR。1.1.2 中断请求寄存器的模拟
在早期,task_struct中设置了两个位图:sigset_t signal和sigset_t blocked。分别用来模拟中断请求控制器(中断状态控制器)和中断频比寄存器。每当有信号来临,就设置位图中对应的位,这样也就只能简单地记录某信号有没有产生,而不能记录有多少次中断产生。这样的缺陷在于,如果某个中断发生多次,就可能会被“合并”为一次,也就是说可能发送了两个信号却只得到一次响应。为了克服这种设计的缺陷,在task_struct中设置了一个信号队列struct sigpending,每产生一个信号,就将其挂入该队列中即可。struct sigpending的定义如下: [include/linux/signal.h] [code] 17 struct sigpending { 18 struct sigqueue *head, **tail; 19 sigset_t signal; 20 };
另外,stask_struct中还设置了一个整形的sigpending,这个sigpending用于标示当前是否有信号等待处理。如果有信号等待处理,就查找信号队列并且处理之。
二、 信号的安装
2.1 概述
标准库层面向用户提供了两个接口用于安装信号,分别是signal(int signum, sighandler_t hanlder)和sigaction(int signum, const struct sigaction * newact, const struct sigaction *oact);显然前者是传统的信号安装函数,它只能设置handler。而后者可以设置一个struct sigaction,从而可以设置_sa_sigaction函数来获得更强大的功能。signal的内核实现为sys_signal,而后者的内核实现为sys_sigaction和sys_rt_sigaction,内核会根据signum来确定具体落实到sys_sigaction或者sys_rt_sigaction上去。sys_rt_action用于处理那些新增加的信号。2.2 代码分析
sys_signal和sys_rt_sigaction的具体实现如下:245 /* 1246 * For backwards compatibility. Functionality superseded by sigaction. 1247 */ 1248 asmlinkage unsigned long 1249 sys_signal(int sig, __sighandler_t handler) 1250 { 1251 struct k_sigaction new_sa, old_sa; 1252 int ret; 1253 1254 new_sa.sa.sa_handler = handler; 1255 new_sa.sa.sa_flags = SA_ONESHOT | SA_NOMASK; 1256 1257 ret = do_sigaction(sig, &new_sa, &old_sa); 1258 1259 return ret ? ret : (unsigned long)old_sa.sa.sa_handler; 1260 } 1188 asmlinkage long 1189 sys_rt_sigaction(int sig, const struct sigaction *act, struct sigaction *oact, 1190 size_t sigsetsize) 1191 { 1192 struct k_sigaction new_sa, old_sa; 1193 int ret = -EINVAL; 1194 1195 /* XXX: Don't preclude handling different sized sigset_t's. */ 1196 if (sigsetsize != sizeof(sigset_t)) 1197 goto out; 1198 1199 if (act) { 1200 if (copy_from_user(&new_sa.sa, act, sizeof(new_sa.sa))) 1201 return -EFAULT; 1202 } 1203 1204 ret = do_sigaction(sig, act ? &new_sa : NULL, oact ? &old_sa : NULL); 1205 1206 if (!ret && oact) { 1207 if (copy_to_user(oact, &old_sa.sa, sizeof(old_sa.sa))) 1208 return -EFAULT; 1209 } 1210 out: 1211 return ret; 1212 }
从注释中可以看到,signal的存在只是为了保持向后兼容,实际上的实现是sigaction来实现的。无论是sys_signal还是sys_rt_sigaction的实现,都是调用do_sigaciton来完成的。所不同的是,signal只从应用程序中得到了handler,而sys_rt_sigaction得到了更多的信息。
函数do_sigaction具体实现如下:
1012 int 1013 do_sigaction(int sig, const struct k_sigaction *act, struct k_sigaction *oact) 1014 { 1015 struct k_sigaction *k; 1016 1017 /*不允许改变SIGKILL 和SIGSTOP的hanlder, 因为这两个信号需要内核进行特殊处理*/ 1018 if (sig < 1 || sig > _NSIG || 1019 (act && (sig == SIGKILL || sig == SIGSTOP))) 1020 return -EINVAL; 1021 1022 k = ¤t->sig->action[sig-1]; 1023 1024 spin_lock(¤t->sig->siglock); 1025 1026 if (oact) 1027 *oact = *k; 1028 1029 if (act) { 1030 /*安装中断*/ 1031 *k = *act; 1032 /*SIGKILL和SIGSTOP也是不可屏蔽的,这里要清除屏蔽*/ 1033 sigdelsetmask(&k->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP)); 1034 1035 /* 1036 * POSIX 3.3.1.3: 1037 * "Setting a signal action to SIG_IGN for a signal that is 1038 * pending shall cause the pending signal to be discarded, 1039 * whether or not it is blocked." 1040 * 1041 * "Setting a signal action to SIG_DFL for a signal that is 1042 * pending and whose default action is to ignore the signal 1043 * (for example, SIGCHLD), shall cause the pending signal to 1044 * be discarded, whether or not it is blocked" 1045 * 1046 * Note the silly behaviour of SIGCHLD: SIG_IGN means that the 1047 * signal isn't actually ignored, but does automatic child 1048 * reaping, while SIG_DFL is explicitly said by POSIX to force 1049 * the signal to be ignored. 1050 */ 1051 1052 /*如果新设置的hanlder为SIG_IGN或者DFL,且被设置的信号为SIGCONT SIGCHLD SIGWINGCH 1053 *则将已到达的该信号丢弃,因为这些信号与系统管理紧密相关,需要即时更新。 1054 * 1055 * */ 1056 if (k->sa.sa_handler == SIG_IGN 1057 || (k->sa.sa_handler == SIG_DFL 1058 && (sig == SIGCONT || 1059 sig == SIGCHLD || 1060 sig == SIGWINCH))) { 1061 spin_lock_irq(¤t->sigmask_lock); 1062 if (rm_sig_from_queue(sig, current)) 1063 recalc_sigpending(current); 1064 spin_unlock_irq(¤t->sigmask_lock); 1065 } 1066 } 1067 1068 spin_unlock(¤t->sig->siglock); 1069 return 0; 1070 }
do_sigaction函数的功能很简单,只是将action中signum的struct sigaction取出填充到oact后将act写入action[signum]中。额外还有一些检查并且执行Posix规定的丢弃动作。
2.3 其它函数
sigprocmask该函数用于设置task_struct结构中的信号屏蔽位blocked。这个屏蔽位与struct sigaction中的sa_mask不同,blocked的屏蔽是起全局作用的,而sa_mask只是在某个信号handler在执行时起作用而已。可以说blocked的设置,是进程压根不想相应某种信号。而sa_mask则是在处理X信号时,Y信号如果来临会影响X信号的处理,所以屏蔽Y信号以保证X的正确执行,是不得已而为之。另外,所谓屏蔽与忽略的意义并不同,屏蔽只是暂时阻止信号响应,信号依旧会正常投递到进程,只是得不到响应。一旦屏蔽接触,已经投递到的信号,也就是挂载到pending队列中的信号会得到处理
sigpending()
该函数用于检查有哪些信号已经到达而尚未处理。也就是检查哪些信号被屏蔽了。
sigsuspend()
该函数设置了进程的信号屏蔽位blocked之后,进入睡眠。等待任意信号唤醒。
三、信号发送
3.1 kill函数和sigqueue
内核中有两个系统调用可以用于向特定进程发送信号,分别是kill(pid_t pid, int sig)和sig_queue(pid_t pid, int sig, const union sigval val)。前文我们说过,过去的Linux对信号的投递,仅仅是将task_struct当中的位图pending相应位置位,只能记录某种信号的到来与否。但后来为每种信号设置了一个队列,将投递的信号挂到队列上,这样不仅能表达某种信号的道来与否,还能够表示有多少该种信号的到来。其中kill就是老的一套信号机制中实现的信号发送函数,sigqueue则是新的信号机制中实现的信号发送函数。3.2 代码分析
kill 的内核实现为sys_kill,该函数代码在kernel/signal中。[kernel/signal.c]
980 asmlinkage long 981 sys_kill(int pid, int sig) 982 { 983 struct siginfo info; 984 985 info.si_signo = sig; 986 info.si_errno = 0; 987 info.si_code = SI_USER; 988 info.si_pid = current->pid; 989 info.si_uid = current->uid; 990 991 return kill_something_info(sig, &info, pid); 992 } 993
这段代码很简单,就是简单地填充一下siginfo结构然后调用kill_something_info函数进行进一步的处理。kill_something_info的代码也在该文件中,如下:
652 * kill_something_info() interprets pid in interesting ways just like kill(2). 653 * 654 * POSIX specifies that kill(-1,sig) is unspecified, but what we have 655 * is probably wrong. Should make it like BSD or SYSV. 656 */ 657 658 static int kill_something_info(int sig, struct siginfo *info, int pid) 659 { 660 /*如果pid = 0则将信号发送到整个进程组*/ 661 if (!pid) { 662 return kill_pg_info(sig, info, current->pgrp); 663 } else if (pid == -1) { 664 /*如果pid = -1就将信号发送到所有进程*/ 665 int retval = 0, count = 0; 666 struct task_struct * p; 667 668 read_lock(&tasklist_lock); 669 for_each_task(p) { 670 if (p->pid > 1 && p != current) { 671 int err = send_sig_info(sig, info, p); 672 ++count; 673 if (err != -EPERM) 674 retval = err; 675 } 676 } 677 read_unlock(&tasklist_lock); 678 return count ? retval : -ESRCH; 679 /*如果pid < 0 则发送到pid = abs(pid)的进程中*/ 680 } else if (pid < 0) { 681 return kill_pg_info(sig, info, -pid); 682 } else { 683 /*如果pid > 0 则发送到pid = pid 的进程中*/ 684 return kill_proc_info(sig, info, pid); 685 } 686 }
kill_something_info的任务主要是根据pid的值决定要将信号投递到进程组还是特定进程,或者是系统中的所有进程。从上面的代码可以看出,真正执行信号投递动作的是kill_xxx_info函数。其中kill_proc_info为向特定的进程发送信号。
与kill不同,sigqueue只允许想一个特定的进程发送信号,并且siginfo结构也是用户自行设置的。该函数在内核中的实现如下:
[kernel/signal.c]
998 asmlinkage long 999 sys_rt_sigqueueinfo(int pid, int sig, siginfo_t *uinfo) 1000 { 1001 siginfo_t info; 1002 1003 if (copy_from_user(&info, uinfo, sizeof(siginfo_t))) 1004 return -EFAULT; 1005 1006 /* Not even root can pretend to send signals from the kernel. 1007 Nor can they impersonate a kill(), which adds source info. */ 1008 if (info.si_code >= 0) 1009 return -EPERM; 1010 info.si_signo = sig; 1011 1012 /* POSIX.1b doesn't mention process groups. */ 1013 return kill_proc_info(sig, &info, pid); 1014 }
该函数的任务简单明了,就是进行参数检查,然后调用kill_proc_info函数进行信号投递。这里kill_proc_info会使用Pid查找得到进程的task_struct函数,然后调用send_sig_info函数投递信号。该函数代码也在同一文件内。
508 int 509 send_sig_info(int sig, struct siginfo *info, struct task_struct *t) 510 { 511 unsigned long flags; 512 int ret; 513 514 /*开始进行参数检查*/ 515 #if DEBUG_SIG 516 printk("SIG queue (%s:%d): %d ", t->comm, t->pid, sig); 517 #endif 518 519 ret = -EINVAL; 520 if (sig < 0 || sig > _NSIG) 521 goto out_nolock; 522 /* The somewhat baroque permissions check... */ 523 ret = -EPERM; 524 if (bad_signal(sig, info, t)) 525 goto out_nolock; 526 527 /* The null signal is a permissions and process existance probe. 528 No signal is actually delivered. Same goes for zombies. */ 529 ret = 0; 530 if (!sig || !t->sig) 531 goto out_nolock; 532 /*参数检查结束*/ 533 spin_lock_irqsave(&t->sigmask_lock, flags); 534 handle_stop_signal(sig, t); 535 536 /* Optimize away the signal, if it's a signal that can be 537 handled immediately (ie non-blocked and untraced) and 538 that is ignored (either explicitly or by default). */ 539 /*查看进程控制块中的action中的handler是否是SA_IGNORE,看该信号是否被忽略*/ 540 if (ignored_signal(sig, t)) 541 goto out; 542 543 /* Support queueing exactly one non-rt signal, so that we 544 can get more detailed information about the cause of 545 the signal. */ 546 if (sig < SIGRTMIN && sigismember(&t->pending.signal, sig)) 547 goto out; 548 549 /*开始投递*/ 550 ret = deliver_signal(sig, info, t); 551 out: 552 spin_unlock_irqrestore(&t->sigmask_lock, flags); 553 if ((t->state & TASK_INTERRUPTIBLE) && signal_pending(t)) 554 wake_up_process(t); 555 out_nolock: 556 #if DEBUG_SIG 557 printk(" %d -> %d\n", signal_pending(t), ret); 558 #endif 559 560 return ret; 561 } 497 static int deliver_signal(int sig, struct siginfo *info, struct task_struct *t) 498 { 499 int retval = send_signal(sig, info, &t->pending); 500 501 /*如果信号没有被屏蔽且进程正在休眠,则唤醒进程*/ 502 if (!retval && !sigismember(&t->blocked, sig)) 503 signal_wake_up(t); 504 505 return retval; 506 }
投递信号,只是将siginfo挂入pending队列中。信号投递完毕后,就会检查进程是否屏蔽该信号,如果没有屏蔽且进程处于睡眠状态,就唤醒睡眠。注意如果被唤醒的进程优先级高于当前进程,那么在返回用户空间的前夕,产生的调度就会使得当前进程失去运行权
四、响应信号
在中断、异常、系统调用结束之后返回用户空间的前夕,或者进程被唤醒之后,就会检查stask_struct中的sigpending,看是否有信号等待处理。如果有信号等待处理,就调用do_signal函数进行处理。相关文章推荐
- Linux socket 初步
- Linux Kernel 4.0 RC5 发布!
- linux lsof详解
- linux 文件权限
- Linux 执行数学运算
- 10 篇对初学者和专家都有用的 Linux 命令教程
- Linux 与 Windows 对UNICODE 的处理方式
- Ubuntu12.04下QQ完美走起啊!走起啊!有木有啊!
- 解決Linux下Android开发真机调试设备不被识别问题
- 运维入门
- 运维提升
- Linux 自检和 SystemTap
- Ubuntu Linux使用体验
- c语言实现hashmap(转载)
- Linux 信号signal处理机制
- linux下mysql添加用户
- Scientific Linux 5.5 图形安装教程
- Linux 下无损图片压缩小工具介绍