Linux信号机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u013298300/article/details/51214129

Linux信号机制

一、管理层次及结构

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[code]查找得到一个“中断描述符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]

 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 = &current->sig->action[sig-1];
1023 
1024     spin_lock(&current->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(&current->sigmask_lock);
1062             if (rm_sig_from_queue(sig, current))
1063                 recalc_sigpending(current);
1064             spin_unlock_irq(&current->sigmask_lock);
1065         }
1066     }
1067 
1068     spin_unlock(&current->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函数进行处理。

没有更多推荐了,返回首页