信号的作用
信号(signal)是很短的消息,可以被发送到一个进程或一组进程。发送给进程的唯一信息通常是一个数,以此来标识信号。在标准信号中,对参数、消息或者其他相随的信息没有给予关注。
使用信号的两个主要目的是:1.让进程知道已经发生了一个特定的事件。2.强迫进程执行它自己代码中的信号处理程序。

除了在这张表中描述的常规信号(regular signal)外,POSIX标准还引入了一类新的信号,叫做实时信号(real-time signal);在Linux中它们的编码范围为32~64。它们与常规信号有很大的不同,因为它们必须排队以便发送的多个信号能被接收到。另一方面,同种类型的常规信号并不排队:如果一个常规信号被连续发送多次,那么,只有其中的一个发送到接收进程。尽管Linux内核并不使用实时信号,它还是通过几个特定的系统调用完全实现了POSIX标准。
许多系统调用允许程序员发送信号,并决定他们的进程如何响应所接收的信号。表11-2简洁地描述了这些系统调用,更详细的内容将在后面“与信号处理相关的系统调用”一节中描述。


信号的一个重要特点是它们可以随时被发送给状态经常不可预知的进程。发送给非运行进程的信号必须由内核保存,直到进程恢复执行。阻塞一个信号(后面描述)要求信号的传递拖延,直到随后解除阻塞,这使得信号产生一段时间之后才能对其传递这一问题变得更加严重。因此,内核区分信号传递的两个不同阶段:
信号产生
内核更新目标进程的数据结构以表示一个新信号已被发送。
信号传递内核强迫目标进程通过以下方式对信号做出反应:或改变目标进程的执行状态,或开始执行一个特定的信号处理程序,或两者都是。
每个所产生的信号至多被传递一次。信号是可消费资源:一旦它们已传递出去,进程描述符中有关这个信号的所有信息都被取消。已经产生但还没有传递的信号称为挂起信号(pending signal)。任何时候,一个进程仅存在给定类型的一个挂起信号,同一进程同种类型的其他信号不被排队,只被简单地丢弃。
但是,实时信号是不同的:同种类型的挂起信号可以有好几个。一般来说,信号可以保留不可预知的挂起时间。必须考虑下列因素:
1. 信号通常只被当前正运行的进程传递(即由current进程传递)。2.给定类型的信号可以由进程选择性地阻塞(blocked)(参见“修改阻塞信号的集合”
一节)。这种情况下,在取消阻塞前进程将不接收这个信号。3.当进程执行一个信号处理程序的函数时,通常“屏蔽”相应的信号,即自动阻塞这个信号直到处理程序结束。因此,所处理的信号的另一次出现不能中断信号处理程序,所以,信号处理函数不必是可重入的。
1.记住每个进程阻塞哪些信号。2.当从内核态切换到用户态时,对任何一个进程都要检查是否有一个信号已到达。这
几乎在每个定时中断时都发生(大约每毫秒发生一次)。3.确 定 是否 可 以 忽略信 号 。 这 发生 在 下列 所 有 的 条 件都 满 足 时 :3.1. 目标进程没有被另一个 进程跟踪(进程描述符中pt race字段的PT_PTRACED标志等于0)。3.2.信号没有被目标进程阻塞。3.3.信号 被 目 标 进程忽略 ( 或 者 因 为进 程 已 显 式 地 忽 略 了 信号 , 或 者 因 为 进程 没 有改变信号的缺省操作且这个缺省操作就是“忽略”)。4.处理这样的信号,即信号可能在进程运行期间的任一时刻请求把进程切换到一个信
号处 理 函 数 , 并 在 这 个 函 数 返 回 以后 恢 复 原 来 执 行 的 上 下文 。此外 , Li nu x 必 须 考 虑 B S D 和 S y s t em V 所 采 用 的 不 同 的 信 号 语 义 , 而 且, 还 必 须 与相当麻烦的POSIX标准相兼容。
传递信号之前所执行的操作
进程以三种方式对一个信号做出应答:
1. 显式地忽略信号。2 . 执 行与 信 号 相 关 的 缺 省 操 作 ( 参 见 表 1 1 - 1 )。由 内 核预定义 的 缺 省 操 作 取 决于 信 号的类型,可以是下列类型之一:T e rm i n a t e进程被终止(杀死)。D u m p进程 被 终 止 (杀 死 ) , 并 且 , 如 果 可 能 , 创 建包 含 进 程执行上 下 文 的 核 心 转储 文件;这个文件可以用于调试。l g n o re信号被忽略。S t o p进程被停止,即把进 程置为TASK_STOPPED状态(参见第 三章的“进程状态”一 节 ) 。C o n t i nu e如果进程被停止(TASK _STOPPED) ,就把它置为 TASK_RUNNING状态。3. 通过调用相应的信号处理函数捕获信号。
如果一个进程正在被跟踪时接收到一个信号,内核就停止这个进程,并向跟踪进程发送一个SIGCHLD信号以通知它一下。跟踪进程又可以使用SIGCOUNT信号重新恢复被跟踪进程的执行。
如果信号的传递会引起内核杀死一个进程,那么这个信号对该进程就是致命的。SIGKILL信号总是致命的;而且,缺省操作为Terminate的每个信号,以及不被进程捕获的信号对该进程也是致命的。注意,如果一个被进程所捕获的信号,其对应的信号处理函数终止了这个进程,那么这个信号就不是致命的,因为进程自己选择了终止,而不是被内核杀死。
POSIX信号和多线程应用
POSIX 1003.1标准对多线程应用的信号处理有一些严格的要求:
1.信号处理程序必须在多线程应用的所有线程之间共享;不过,每个线程必须有自己
的挂起信号掩码和阻塞信号掩码。2.POSIX库函数kill()和sigqueue()(见稍后“与信号处理相关的系统调用”一节 ) 必 须向 所有 的 多线 程 应 用 而 不 是 某个 特殊 的线 程发 送 信号 。 所有由 内核 产生的信号同样如此(如:SIGCHLD、SIGINT或SIGQUIT)。3.每个发送给多线程应用的信 号仅传送给一个线程,这个 线程是由内核在从不会阻塞该信号的线程中随意选择出来的。4.如果向多线程应用发送了一 个致命的信号,那么内核将 杀死该应用的所有线程,而不仅仅是杀死接收信号的那个线程。
有两个例外:不可能给进程0(swapper)发送信号,而发送给进程1(init)的信号在捕
与信号相关的数据结构
对系统中的每个进程来说,内核必须跟踪什么信号当前正在挂起或被屏蔽,以及每个

t y pe d e f s tr u c t {u n s i g n e d l o n g s i g [ 2 ] ;} s i g s e t _ t ;
因为每个无符号长整数由32位组成,所以在Linux中可以声明的信号最大数是64
(_NSIG宏表示这个值)。没有值为0的信号,因此,信号的编号对应于sigset_t类型
信号描述符和信号处理程序描述符