信号:基本概念
这篇将一口气学完信号的基本概念,但是有很多的细节,所以篇幅较长,请做好心理准备。
有以下主题:
各种不同信号及其用途
内核可能为进程产生信号的环境,以及某一进程向另一进程发送信号所使用的系统调用。
进程在默认情况下对信号的响应方式,以及进程改变对信号响应方式的手段,特别是借助于信号处理器程序的手段,即程序收到信号时自动去调用的函数,由程序员定义。
使用进程信号掩码来阻塞信号,以及等待信号的相关概念。
如何暂停进程的执行,并等待信号的到达。
概念和概述
信号是事件发生时对进程的通知机制。有时也称之为软件中断。
信号与硬件中断的相似之处在于打断了程序执行的正常流程,大多数情况下,无法预测信号到达的精确时间。
一个(具有合适权限的)进程能够向另一进程发送信号。
信号的这一用法可作为一种同步技术,甚至是进程间通信(IPC)的原始形式。
进程也可以向自身发送信号。
然而,发往进程的诸多信号,通常都是源于内核。
引发内核为进程产生信号的各类事件如下。
硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。硬件异常的例子包括执行一条异常的机器语言指令,诸如,被0除,或者引用了无法访问的内存区域。
用户键入了能够产生信号的终端特殊字符。其中包括中断字符(通常是Control-C)、暂停字符(通常是Control-Z)。
发生了软件事件。例如,针对文件描述符的输出变为有效,调整了终端窗口的大小,定时器到期,进程执行的CPU时间超限,或者该进程的某个子进程退出。
针对每个信号,都定义了一个唯一的(小)整数,从1开始顺序展开。<signal.h>以SIGxxxx形式的符号名对这些整数做了定义。由于每个信号的实际编号随系统不同而不同,所以在程序中总是使用这些符号名。
例如,当用户键入中断字符时,将传递给进程SIGINT信号(信号编号为2)。
信号分为两大类。
第一组用于内核向进程通知事件,构成所谓传统或者标准信号。Linux中标准信号的编号范围为1~31。另一组信号由实时信号构成,与标准信号的差异会在后面继续学习。
信号因某些事件而产生。
信号产生后,会于稍后被传递给某一进程,而进程也会采取某些措施来来响应信号。在产生和到达期间,信号处于等待(pending)状态。
通常,一旦(内核)接下来要调度该进程运行,等待信号会马上送达,或者如果进程正在运行,则会立即传递信号(例如,进程向自身发送信号)。然而,有时需要确保一段代码不为传递来的信号所中断。为了做到这一点,可以将信号添加到进程的信号掩码中——目前会阻塞该组信号的到达。
如果所产生的信号属于阻塞之列,那么信号将保持等待状态,直至稍后对其解除阻塞(从信号掩码中移除)。进程可使用各种系统调用对其信号掩码添加和移除信号。
信号到达后,进程视具体信号执行如下默认操作之一。
忽略信号:也就是说,内核将信号丢弃,信号对进程没有产生任何影响(进程永远都不知道曾经出现过该信号)。
终止(杀死)进程:这有时是指进程异常终止,而不是进程因调用exit而发生的正常终止。
产生核心转储文件,同时进程终止:核心转储文件包含对进程虚拟内存的镜像,可将其加载到调试器中以检查进程终止时的状态。
停止进程:暂停进程的执行。
于之前暂停后再度恢复进程的执行。
除了根据特定信号而采取默认行为之外,程序也能改变信号到达时的响应行为。也将此称之为对信号的处置(disposition)设置。程序可以将对信号的处置设置为如下之一。
采取默认行为。这适用于撤销之前对信号处置的修改、恢复其默认处置的场景。
忽略信号。这适用于默认行为为终止进程的信号。
执行信号处理器程序。
信号处理器程序是由程序员编写的函数,用于为响应传递来的信号而执行适当任务。例如,shell为SIGINT信号(由中断字符串Control-C产生)提供了一个处理器程序,令其停止当前正在执行的工作,并将控制返回到(shell的)主输入循环,并再次向用户呈现shell提示符。
通知内核应当去调用某一处理器程序的行为,通常称之为安装或者建立信号处理器程序。调用信号处理器程序以响应传递来的信号,则称之为信号已处理(handled),或者已捕获(caught)。
请注意,无法将信号处置设置为终止进程或者核心转储(除非这是对信号的默认处置)。效果最为近似的是为信号安装一个处理器程序,并于其中调用exit或者abort。abort函数为进程产生一个SIGABRT信号,该信号将引发进程转储核心文件并终止。
Linux特有的/proc/PID/status文件包含有各种位掩码字段,通过检查这些掩码可以确定进程对信号的处理。位掩码以十六进制数形式显示,最低有效位代表信号1,相临的左边一位代表信号2,以此类推。这些字段分别为SigPnd(基于线程的等待信号)、ShdPnd(进程级等待信号,始于Linux 2.6)、SigBlk(阻塞信号)、SigIgn(忽略信号)和SigCgt(捕获信号)。
信号在UNIX实现中出现很早,诞生之后又历经变革。在早期实现中,信号在特定场景下可能会丢失(即,没有传递到目标进程)。此外,尽管系统提供了关键代码时阻塞信号传递的机制,但阻塞有时也不大可靠。4.2BSD利用所谓可靠信号解决了这些问题。(BSD在创新上还更进一步,增加了额外信号来支持shell作业控制。)
System V后来也为信号增加了可靠语义,但采用的模型与BSD无法兼容。这一不兼容性直到POSIX.1-1990标准出台后才得以解决。该标准针对可靠信号所采用的规范主要基于BSD模型。