信号简介

一个信号就是一条消息,它通知进程系统中发生了一个某种类型的事件。在Linux中man 7 signal就能得到其支持的所有信号列表。

 

序 号 名 称 默认行为 相应事件
1 SIGHUP 终 止 终端线挂断
2 SIGINT 终 止 来自键盘的中断
3 SIGQUIT 终 止 来自键盘的退出
4 SIGILL 终 止 非法指令
5 SIGTRAP 终止并转储存储器 跟踪陷阱
6 SIGABRT 终止并转储存储器 来自abort函数的终止信号
7 SIGBUS 终 止 总线错误
8 SIGFPE 终止并转储存储器 浮点异常
9 SIGKILL 终 止 杀死进程
10 SIGUSR1 终 止 用户定义的信号1
11 SIGSEGV 终止并转储存储器 无效的存储器引用(段故障)
12 SIGUSR2 终 止 用户定义信号2
13 SIGPIPE 终 止 向一个没有读用户的管道做写操作
14 SIGALRM 终 止 来自alarm函数的定时器信号
15 SIGTERM 终 止 软件终止信号
16 SIGSTKFLT 终 止 协处理器上的栈故障
17 SIGCHILD 忽 略 一个子进程停止或者终止
18 SIGCONT 忽 略 继续进程如果该进程停止
19 SIGSTOP 停止直到下一个SIGCONT 不来自终端的停止信号
20 SIGTSTP 停止直到下一个SIGCONT 来自终端的停止信号
21 SIGTTIN 停止直到下一个SIGCONT 后台进程从终端读
22 SIGTTOU 停止直到下一个SIGCONT 后台进程向中断写
23 SIGURG 忽 略 套接字上的紧急情况
24 SIGXCPU 终 止 CPU时间限制超出
25 SIGXFSZ 终 止 文件大小限制超出
26 SIGVTALRM 终 止 虚拟定时器期满
27 SIGPROF 终 止 剖析定时器期满
28 SIGWINCH 忽 略 窗口大小变化
29 SIGIO 终 止 在某个描述符上可执行I/O操作
30 SIGPWR 终 止 电源故障
 
每种信号类型都对应于某种系统事件。底层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。比如,如果一个进程试图除以0,那么内核就发送给它一个SIGFPE信号。如果一个进程执行一条非法指令,那么内核就发送给它一个SIGILL信号。其他信号对应于内核或者其他用户进程中较高层的软件事件。比如,如果当进程在前台运行时,你键入CTL-C,那么内核就会发送一个SIGINT信号给这个前台进程。一个进程可以通过向另一个进程发送一个SIGKILL信号来强制终止它。当一个子进程终止或停止时,内核会发送一个SIGCHLD信号给父进程。
 
传送一个信号到目的进程是由两个不同步骤组成的:
  1. 发送信号。内核通过更新目的进程上下文中的某个状态,发送(投递)一个信号给目的进程。发送信号可以有如下两个原因:1> 内核检测到一个系统事件,比如被零除错误或者子进程终止;2> 一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
  2. 接受信号。当目的进程被内核强迫以某种方式对信号的发送作出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止或通过执行一个称之为信号处理程序(signal handler)的用户层函数捕获这个信号。

 

一个只发出而没有被接收的信号叫待处理信号(pending signal)。在任何时刻,一种类型最多只会有一个待处理信号。如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待,它们只是简单地丢弃。一个进程可以有选择性地阻塞接受某个信号。当一个信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接受,直到进程取消对这种信号的阻塞。一个待处理信号最多只能被接受一次,内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护着被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接受了一个类型为k的信号,内核就会清楚pending中的第k位。

 

Unix系统提供了大量向进程发送信号的机制,所有这些机制都是基于进程组这个概念的。每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的(也就是该进程组的第一个进程ID)。getpgrp函数返回当前进程的进程组ID。默认地,一个子进程和它的父进程同属于一个进程组,一个进程可以通过使用setpgid函数来改变自己或者其他进程的进程组。当我们使用kill函数向另外一个进程发送任何信号时,例如:

   1: // 发送一个编号为9的信号到PID为1523的进程
   2: Linux> kill -s 9 1523

同样,我们可以使用kill函数向进程组发送任何信号,这里我们需要确保kill不是shell的内置命令,例如:

   1: // 发送一个编号为9的信号到PID为1523的进程组
   2: Linux> kill -s 9 -1523

 

当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查进程p的未被阻塞的待处理信号的集合(pending &~ blocked)。如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中的下一条指令(Inext)。否则,内核选择集合中的某个信号k(通常是最小的k),并且强制p接收信号k。收到这个信号会触发进程的某种行为。一旦进程完成了这个行为,那么控制就会传递回p的逻辑控制流中的下一条指令(Inext)。每个信号类型都有一个预定义的默认行为,如上图给出。但用户可以根据signal函数调用设置处理函数,其处理函数的类型为void (*handelr)(int sig)。

 

对于只捕获一个信号并终止的程序来说,信号处理是最简单的。然而,当一个程序要捕获多个信号时,一个细微的问题就产生了。

  1. 待处理信号被阻塞。Unix信号处理程序通常会阻塞当前处理程序正在处理的相同信号类型的待处理信号。比如,假设一个进程捕获了一个SIGINT信号,并且当前正在运行它的SIGINT处理程序。如果另一个SIGINT信号传送到这个进程,那么这个SIGINT将变成待处理的,但是不会被接受,直到处理程序返回。
  2. 待处理型号不会排队等待。任何类型最多只有一个待处理型号。因此,如果有两个同样类型为k的信号传送到一个目的进程,而由于目的进程当前正在执行信号k的处理程序,所以信号k是阻塞的,那么第二个信号就被简单的丢弃,它不会排队等待。关键思想存在一个待处理信号仅仅表明至少已经有一个信号到达了。
  3. 系统调用可以被中断。象read、wait和accepte这样的系统调用潜在地会阻塞进程一段较长的事件,称之为慢速系统调用。在某些系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用在信号处理程序返回时不再继续,而是立即给用户一个错误条件,并将errno设置为EINTER。这种情况在Linux下不会发生,因为Linux默认会重启慢速系统调用,而避免立即给用户一个错误条件带来的隐晦控制流逻辑错误。

 

从上述信号的特性可以得到这样一个教训,那就是不可以用信号来对其他进程中发生的事件计数。例如,其他进程中会发出三个同类型的型号,而接收进程可能正在第一次的处理程序中,因为信号不排队等待,所以第三个信号被简单的丢弃。

posted @ 2012-07-15 00:56  chee z  阅读(332)  评论(0编辑  收藏  举报