信号机制是在软件层次上堆中断机制的一种模拟,也就是说信号是一种代码异步执行的方式。故而信号也有类似于中断管理的相关软件层实现。包括:
+ 中断向量表: 进程控制块task_struct中的sig指针,指向一个signal_struct结构。
+ 中断状态控制器: signal_struct当中的struct siginfo
+ 中断请求寄存器: signal_struct中的信号队列:pending
+ 中断屏蔽寄存器: signal_struct中的blocked
进程控制块当中有一个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[code]查找得到一个“中断描述符struct sigaction”,然后读取sagaciton中的sa_flags,解析调用handler即可进入ISR。
在早期,task_struct中设置了两个位图:sigset_t signal和sigset_t blocked。分别用来模拟中断请求控制器(中断状态控制器)和中断频比寄存器。每当有信号来临,就设置位图中对应的位,这样也就只能简单地记录某信号有没有产生,而不能记录有多少次中断产生。这样的缺陷在于,如果某个中断发生多次,就可能会被“合并”为一次,也就是说可能发送了两个信号却只得到一次响应。为了克服这种设计的缺陷,在task_struct中设置了一个信号队列struct sigpending,每产生一个信号,就将其挂入该队列中即可。struct sigpending的定义如下:
[include/linux/signal.h]
17 struct sigpending {
18 struct sigqueue *head, **tail;
19 sigset_t signal;
20 };
另外,stask_struct中还设置了一个整形的sigpending,这个sigpending用于标示当前是否有信号等待处理。如果有信号等待处理,就查找信号队列并且处理之。
标准库层面向用户提供了两个接口用于安装信号,分别是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用于处理那些新增加的信号。
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规定的丢弃动作。
sigprocmask
该函数用于设置task_struct结构中的信号屏蔽位blocked。这个屏蔽位与struct sigaction中的sa_mask不同,blocked的屏蔽是起全局作用的,而sa_mask只是在某个信号handler在执行时起作用而已。可以说blocked的设置,是进程压根不想相应某种信号。而sa_mask则是在处理X信号时,Y信号如果来临会影响X信号的处理,所以屏蔽Y信号以保证X的正确执行,是不得已而为之。另外,所谓屏蔽与忽略的意义并不同,屏蔽只是暂时阻止信号响应,信号依旧会正常投递到进程,只是得不到响应。一旦屏蔽接触,已经投递到的信号,也就是挂载到pending队列中的信号会得到处理
sigpending()
该函数用于检查有哪些信号已经到达而尚未处理。也就是检查哪些信号被屏蔽了。
sigsuspend()
该函数设置了进程的信号屏蔽位blocked之后,进入睡眠。等待任意信号唤醒。
内核中有两个系统调用可以用于向特定进程发送信号,分别是kill(pid_t pid, int sig)和sig_queue(pid_t pid, int sig, const union sigval val)。前文我们说过,过去的Linux对信号的投递,仅仅是将task_struct当中的位图pending相应位置位,只能记录某种信号的到来与否。但后来为每种信号设置了一个队列,将投递的信号挂到队列上,这样不仅能表达某种信号的道来与否,还能够表示有多少该种信号的到来。其中kill就是老的一套信号机制中实现的信号发送函数,sigqueue则是新的信号机制中实现的信号发送函数。
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函数进行处理。