您的位置:首页 > 运维架构 > Linux

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函数进行处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息