《UNIX环境高级编程》第10章 信号 笔记
信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法,例如,终端用户键入中断键,会通过信号机制停止一个程序,或及早终止管道中的下一个程序。
信号概念
每个信号都有一个名字,这些名字都以3个字符SIG开头,比如:SIGINT中断信号、SIGABRT夭折信号、SIGALRM闹钟信号。
产生信号的条件:
- 当用户按某些终端键时,引发终端产生的信号。
- 硬件异常产生信号:除数为0、无效的内存引用等。
- 进程调用ki11(2)函数可将任意信号发送给另一个进程或进程组。
- 用户可用ki11(1)命令将信号发送给其他进程
- 当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量(如 errno)来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。
在某个信号出现时,可以告诉内核按下列3种方式之一进行处理,我们称之为信号的处理或与信号相关的动作:
- 忽略此信号,有两种信号不能被忽略,它们是 SIGKILL和 SIGSTOP。这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。
- 捕捉信号,通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。
- 执行系统默认操作
函数signal
UNX系统信号机制最简单的接口是 signal函数,其用于捕获信号,并捕获以后发生什么事情。
1 |
|
signo
参数是信号名。func
的值是常量SIG_IGN、常量SIG_DEL或当接到此信号后要调用的函数的地址。SIG_IGN
表示向内核表示忽略此信号(记住有两个信号 SIGKILI和 SIGSTOP不能忽略)。SIG_DEL表示接到此信号后的动作是系统默认动作。
当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为捕获该信号,称此函数为信号处理程序( signal handler)或信号捕捉函数 (signal-catching function)
程序启动
当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切地讲,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就不能再捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。
进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程内存映像,所以信号捕捉函数的地址在子进程中是有意义的
不可靠信号
在早期的UNX版本中(如V7),信号是不可靠的。不可靠在这里指的是,信号可能会丢失。一个信号发生了,但进程却可能一直不知道这一点。同时,进程对信号的控制能力也很差,它能捕捉信号或忽略它。有时用户希望通知内核阻塞某个信号:不要忽略该信号,在其发生时记住它然后在进程做好了准备时再通知它。这种阻塞信号的能力当时并不具备
这些早期版本的另一个问题是:在进程不希望某种信号发生时,它不能关闭该信号。进程能做的一切就是忽略该信号。
中断的系统调用
早期UNX系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其erno设置为EINTR。
为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,包括:
- 如果某些类型文件(如读管道、终端设备和网络设备)的数据不存在,则读操作可能会使调用者永远阻塞;
- 如果这些数据不能被相同的类型文件立即接受,则写操作可能会使调用者永远阻塞
- 在某种条件发生之前打开某些类型文件,可能会发生阻塞(例如要打开一个终端设备需要先等待与之连接的调制解调器应答)
- pause函数(按照定义,它使调用进程休眠直至捕捉到一个信号)和wait函数;
- 某些ioct操作
- 某些进程间通信函数
在这些低速系统调用中,一个值得注意的例外是与磁盘I/O有关的系统调用。虽然读、写个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,IO操作总会很快返回,并使调用者不再处于阻塞状态。
可以用中断系统调用这种方法来处理的一个例子是:一个进程启动了读终端操作,而使用该终端设备的用户却离开该终端很长时间。在这种情况下,进程可能处于阻塞状态几个小时甚至数天,除非系统停机,否则一直如此
可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列(这类似于发生硬件中断时所做的)。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。处理信号处理程序以后原来程序中有一部分会重复执行。
在信号处理函数中保证调用安全的函数(可以被安全中断的函数),这些函数是可重入的并被称之为异步信号安全的。
大多数函数是不可重入的,因为
(a)已知它们使用静态数据结构;
(b)它们调用mal1oc或free;
(c)它们是标准I/O函数。标准IO库的很多实现都以不可重入方式使用全局数据结构
可靠信号术语与语义
我们需要先定义一些在讨论信号时会用到的术语。首先,当造成信号的事件发生时,为进程产生一个信号(或向一个进程发送一个信号)。事件可以是硬件异常(如除以0)、软件条件(如a1arm定时器超时)、终端产生的信号或调用ki11函数。当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。
当对信号采取了这种动作时,我们说向进程递送了一个信号。在信号产生(generation)和递送(delivery)之间的时间间隔内,称信号是未决的(pending)
进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者将对此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作。进程调用sigpending
函数来判定哪些信号是设置为阻塞并处于未决状态的。
每个进程都有一个信号屏蔽字( signal mask.),它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,该屏蔽字中都有一位与之对应。对于某种信号,若其对应位已设置,则它当前是被阻塞的。进程可以调用 sigprocmask
来检测和更改其当前信号屏蔽字。
函数kill和raise
ki11函数将信号发送给进程或进程组。 ralse函数则允许进程向自身发送信号。
1 |
|
进程将信号发送给其他进程需要权限。超级用户可将信号发送给任一进程。对于非超级用户,其基本规则是发送者的实际用户DD或有效用户ID必须等于接收者的实际用户ID或有效用户ID。
函数alarm和pause
使用alarm函数可以设置一个定时器(闹钟时间),在将来的某个时刻该定时器会超时。当定时器超时时,产生 SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该a1arm函数的进程
1 |
|
每个进程只能有一个闹钟时间。如果在调用a1arm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次a1arm函数调用的值返回。以前注册的闹钟时间则被新值代替。
如果有以前注册的尚未超过的闹钟时间,而且本次调用的 seconds值是0,则取消以前的闹钟时间,其余留值仍作为a1arm函数的返回值
pause函数使调用进程挂起直至捕捉到一个信号
1 |
|
只有执行了一个信号处理程序并返回时,pause才返回
信号集
我们需要有一个能表示多个信号—信号集(signal set)的数据类型。我们将在sigprocmask类函数中使用这种数据类型,以便告诉内核不允许发生该信号集中的信号。不同的信号的编号可能超过一个整型量所包含的位数,所以一般而言,不能用整型量中的一位代表一种信号,也就是不能用一个整型量表示信号集。POSIX 定义数据类型sigset_t以包含一个信号集,并且定义了下列5个处理信号集的函数。
1 |
|
函数sigprocmask
调用函数sigprocmask
可以检测或更改,或同时进行检测和更改进程的信号屏蔽字。
1 |
|
如果oset为是非空指针,则进程当前的信号屏蔽字通过oset返回
其次,若ser是一个非空指针,则参数how指示如何修改当前信号屏蔽字。主要的操作有并集、交集、替代
在调用sigprocmask
后如果有任何未决的、不再阻塞的信号,则在sigprocmask
返回前至少将其中之一递送给该进程
函数sigpending
sigpending
函数返回一信号集,对于调用进程而言,其中的各信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过set参数返回
1 |
|
函数sigaction
sigaction
函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。此函数取代了UNX早期版本使用的 signal函数。
1 |
|
参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。
函数abort
abort函数的功能是使程序异常终止,此函数将 SIGABRT信号发送给调用进程(进程不应忽略此信号)。
1 |
|