荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: bstone (Back to real world!), 信区: Hacker
标  题: unix环境高级编程--第10章 信号 (上)
发信站: BBS 荔园晨风站 (Wed Mar 29 08:49:57 2000), 转信

发信人: scircle (yuanyuan), 信区: Security
标  题: unix环境高级编程--第10章 信号 (上)
发信站: BBS 水木清华站 (Mon Mar 27 16:01:50 2000)

第十章:信号
101〓引言
信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异
步事件的方法:终端用户键入中断键,则会通过信号机构停止一道程序.Unix的早期
版本,就已经有信号机构,但是这些系统,例如Version 7所提供的信号模型并不可
靠。信号可能被丢失,而且在执行临界区代码时,进程难于关闭所选择的信号.
4.3BSD和SVR3对信号模型都作了更改,增加了可靠信号机制。但是这两种更改之间并
不兼容。幸运的是POSIX.1对可靠信号例程进行了标准化,这正是本章所说明的.本章
先对信号机制进行综述,并说明每种信号的一般用法。然后分析早期实现的问题。在
分析存在的问题之后再说明解决这些问题的方法,这样有助于加深对改进机制的理解.
本章也包含了很多并非100%正确的实例,这样做的目的是为了对其不定之处进行讨论。

102〓信号的概念
首先,每个信号有一个名字。这些名字都以三个字符SIG开头。例如,SIGABRT是夭折
信号,当进程调用abort函数时产生这种信号。SIGALRM是闹钟信号,当由alarm函数设
置的时间已经超过后产生此信号。Version7有十五种不同的信号;SVR4和4.3BSD两者
都有31种不同的信号。
在头文件<signal.h>中,这些信号都被定义为正整数(信号编号)。没有一个信号其编
号为0.在10.9节中我们将会看到kill函数,对信号编号0有特殊的应用。POSIX.1将此
号为0.在10.9节中我们将会看到kill函数,对信号编号0有特殊的应用。POSIX.1将此
种信号编号值称为定信号。很多条件可以产生一个信号。
·当用户按某些终端键时,产生信号。在终端上按DELETE键通常产生中断信号(SIGINT).
  这是停止一道已失去控制程序的方法。(第十一章中将说明此信号可被映照为终端上
  的任一字符)。
·硬件异常产生信号:除数为0,无效的存储访问等等。这些条件通常是由硬件检测到的
  并将其通知系统核。然后系统核为该条件发生时正在运行的进程产生适当的信号。
  例如,对执行一个无效存储访问的进程产生一个SIGSEGV。
·进程用kill(2)函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号
  进程和发送信号进程的属主必须相同,或发送信号进程的属主必须是超级用户。
·用户可用kill(1)命令将信号发送给其它进程。此程序是kill函数的界面。常用此命令
  终止一个失控的后台进程。
·当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。例如SIGURG
  (在网络连接上传来非规定波特率的数据)、SIGPIPE(在管道的读进程已终止后一个进
  程写此管道),以及SIGALRM(进程所设置的闹钟时间已经超时)。
  信号是异步事件的经典实例。产生信号的事件对进程而言是在随机时间出现的。进程
不能只是测试一个变量(例如errno)来判别是否发生了一个信号,代之以进程必须告诉系
统核"在此信号发生时,请执行下列操作"。可以要求系统在某个信号出现时按照下列三种
方式中的一种进行操作。
1.忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。
它们是:SIGKILL和SIGSTOP。这两种信号不能被忽略的原因是:它们向超级用户提供一种
使进程终止(killing)或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例
如非法存储访问或除以0),则进程的行为是末定义的。
如非法存储访问或除以0),则进程的行为是末定义的。
2.捕捉信号。为了做到这一点要通知系统核在某种信号发生时,要调用一个用户函数。在
用户函数中,可执行用户希望对这种事件进行的处理。例如,若我们编写一个命令解释器,
当用户用键盘产生中断信号时,我们很可能希望返回到程序的主循环,终止系统正在为该
用户执行的命令。如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉
函数可以调用Waitpid以取得该子进程的进程ID以及它的终止状态。又例如,如果进程创
建了临时文件,那么我们可能要为SIGTERM函数编写一个信号捕捉函数以清除临时文件
(kill命令传送的系统默认信号是终止信号。)
3.执行系统默认动作。在图10.1中示出了对每一种信号的系统默认动作。注意,对大多数
信号的系统默认动作是终止该进程。图10.1列出所有信号的名字,哪些系统支持此信号以
及对于信号的系统默认动作。在POSIX.1列。表示要求此种信号。job表示这是作业控制信
号(仅当支持作业控制时,才要求此种信号)。
在系统默认动作列,"终止w/core"表示在进程当前工作目录的Core文件中复制了该进程
的存储图象。(该文件名为core,从此可以看出这种功能很久之前就是Unix功能的一部分).
大多数Unix调试程序都使用core文件以检查进程在终止时的状态。在下列条件下不产生
core文件:(a)进程是设置-用户-ID的,而且当前用户并非是程序文件的属主,或者(b)进
程是设置一组-ID的,而且当前用户并非是该程序文件的组属主,或者(C)用户没有写当
前工作目录的许可权,或者(d)文件太大(回忆7.11节中的RLIMIT-CORE)。core文件的许
可权(假定该文件在此之前并不存在)通常是用户读/写,组读和其它读.core文件的产生
不是POSIX.1所属部分,而是很多Unix版本的实现特征。4.3+BSD产生名为core.prog的文
件,其中prog是被执行的程序名的前16个字符。它对core文件给予了某种标识,所以是
一种改进特证。在图10.1中的说明列,"硬件故障"对应于实现定义的硬件故障。这些名
字中有很多取自于Unix早先在PPP-11上的实现。请查看你所使用的系统的手册,以确切
字中有很多取自于Unix早先在PPP-11上的实现。请查看你所使用的系统的手册,以确切
地确定这些信号对应于哪些错误类型。
                          图10.1 Unix信号
下面比较详细地说明这些信号。
SIGABRT 调用abort函数时(1017节)产生此信号。该进程异常终止。
SIGALRM 调用alarm函数时设置的时间已经超过.详细情况见10.10节.看由setitimer(2)
        函数设置的间隔时间已经过时,那么也产生此信号。
SIGBUS 这指示一个实现定义的硬件故障。
SIGCHLD 在一个进程终止或停止时,SIGCHLD信号被送给其父进程。按系统默认,将忽略
        此信号。如果父进程希望了解其子进程的这种状态改变,则应捕捉此信号。信
        号捕捉函数中通常要调用wait函数以取得子进程ID和其终止状态。系统V的早期
        版本有一个名为SIGCLD(无H)的类似信号。这一信号具有非标准的语义,在SVR2
        的手册页就警告在新的程序中尽量小要使用这种信号。应用程序应当使用标准的
        SIGCHLD信号。在10.7节中讨论这两个信号。
SIGCONT 此作业控制信号送给需要其继续运行的处于停止状态的进程。如果接收到此信号
        的进程处于停止状态,则系统默认动作是使该进程继续运行,否则默认动作是忽
        略此信号。例如,vi编辑程序在捕捉到此信号后,重画终端屏幕。关于进一步的
        情况见1020节。
SIGEMT 这指示一个实现定义的硬件故障。
SIGFPE 此信号表示一个算术运算异常,例如除以0,浮点溢出等。
SIGHUP 如果终端界面检测到一个连接断开,则将此信号送给与该终端相关的控制进程(对
       话期首进程)。参见图9.11,此信号送给session结构中s-leader字段所指向的进
       程。仅当终端的CLOCAL标志没有设置时,在上述条件下才产生此信号。(如果所连
       程。仅当终端的CLOCAL标志没有设置时,在上述条件下才产生此信号。(如果所连
       接的终端是本地的,才设置该终端的CLOCAL标志。它告诉终端驱动程序忽略所有
       调制解调器的状态行。在第十一章将说明如何设置此标志)注意,接到此信号的
       对话期首进程可能在后台,作为一个例子见图97。这区别于通常由终端产生的
       信号(中断、退出和挂起),这些信号总是传递给前台进程组。如果对话期前进程
       终止,则也产生此信号。在这种情况,此信号送给前台进程组中的每一个进程。
       通常用此信号通知精灵进程(第十三章)以再读它们的配置文件。为此选用SIGHUP
       的理由是,因为一个精灵进程不会有一个控制终端,而且通常决不会接收到这种
       信号。SIGKILL 此信号指示进程已执行一条非法硬件指令。4.3BSD由abort函数产
       生此信号。SIGABRT现在用于这种情况。
SIGINFO 这是一种4.3+BSD信号,当用户按状态键(常常是Control-T)时,终端驱动程序产
        生此信号并送至前台进程组中的每一个进程(见图98)。此信号通常造成在终
        端上显示前台进程组中各进程的状态信息。
SIGINT 当用户按中断键(常常是DELETE或Control-C)时,终端驱动程序产生此信号
       并送至前台进程组中的每一个进程(见图98)。当一个进程在运行时失控,特别
       是它正在屏幕上产生大量不需要的输出时,常用此信号终止它。
SIGIO 此信号指示一个异步I/O事件。在12.6.2中将对此进行讨论。在图10.1中,对SIGIO
      的系统默认动作是终止或忽略.不幸的是,这依赖于系统.在SVR4中,SIGIO与SIGPOLL
      相同,其默认动作是终止此进程。在43+BSD中(此信号起源于4.2BSD),其默认动
      作是忽略它。
SIGIOT 这指示一个实现定义的硬件故障。IOT这个名字来自于PPP-11对于输入/输出TRAP
       (input/output TRAP)指令的缩写。系统V的早期版本,由abort函数产生此信号.
       SIGABRT现用于这些情况。
       SIGABRT现用于这些情况。
SIGKILL 这是两个不能被捕捉或忽略信号中的一个。它向系统管理员提供了一种可以消灭
        任一进程的可靠方法。
SIGPIPE 如果在读进程已终止时写管道,则产生信号SIGPIPE。在14.2节中将说明管道。
        当套接口的一端已经终止时,一个进程写该插口也产生此信号。
SIGPOLL 这是一种SVR4信号,当在一个可轮询设备上发生一特定事件时产生此信号。在
        12.5.2节中将说明poll函数和此信号.它与4.3+BSD的SIGIO和SIGURG信号相接近.
SIGPROF 将setitimer(2)函数设置的梗概统计 间隔时间已经超过时产生此信号。
SIGPWR 这是一种SVR4信号,它依赖于系统。它主要用于具有不间断电源(UPS)的系统上。
       如果电源失效,则UPS就会起作用,而且通常软件会接到通知。在这种情况下,
       系统依靠蓄电池电源继续运行,所以无须作任何处理。但是如果蓄电池也将不能
       支持工作,则软件通常会再次接到通知,此时,它在15~30秒内使系统各部分
       都停止运行。此时应当传递SIGPWR信号。在大多数系统中使接到蓄电池电压过低
       的进程将信号SIGPWG发送给init进程,然后由init处理停机操作。很多系统V的
       init实现在inittab文件中提供了两个记录项用于此种目的;powerfail以及
       powerwait.目前已能获得低价格的UPS系统,它用RS-232串行连接能够很容易地将
       蓄电池电压过低的条件通知系统,于是这种信号也就更加重要了。
SIGQUIT 当用户在终端上按退出键(常常是Control-\)时,产生此信号,并送至前台进程
        组中的所有进程(见图9.8)。此信号不仅终止前台进程组(如SIGINT所做的那样),
        它也产生一个core文件。
SIGSEGV 此信号指示进程进行了一次无效的存储访问。名字SEG表示"段违例"
        ("segmentation violation")。
SIGSTOP 这是一个作业控制信号,它停止一个进程。它类似于交互停止信号(SIGTSTP),
SIGSTOP 这是一个作业控制信号,它停止一个进程。它类似于交互停止信号(SIGTSTP),
       但是SIGSTOP不能被捕促或忽略。
SIGSYS 这指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,
       但其指示系统调用类型的参数却是无效的。
SIGTERM 这是由kill(1)命令发送的系统默认终止信号。
SIGTRAP 这指示一个实现定义的硬件故障。
此信号名来自于PPP-11的TRAP指令。
SIGTSTP 这是交互停止信号,当用户在终端上按挂起键*(常常是Control-Z)时,终
端驱动程
序产生此信号。
SIGTTIN 当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。
(请参见9
8节中对此问题的讨论)。在下列例外情形下,不产生此信号,此时读操作出错返
回,errn
o设置为EIO:(a)读进程忽略或阻塞此信号,式(b)读进程所属的进程组是孤儿进程
组。
SIGTTOU 当一个后台进程组进程试图写其控制终端时产生此信号(请参见98节对
此问题的
讨论。)与上面所述的SIGTTIN信号不同,一个进程可以选择为允许后台进程写控制
终端。在
第十一章中将讨论如何更改此选择项。如果不允许后台进程写,则与SIGTTIN相似
也有两种
特殊情况:(a)写进程忽略或阻塞此信号,式(b)写进程所属进程组是孤儿进程组。
特殊情况:(a)写进程忽略或阻塞此信号,式(b)写进程所属进程组是孤儿进程组。
在这两种
情况下不产生此信号,写操作出错返回,errno设置为EIO。不论是否允许后台进程
写,某些
除写以外的下列终端操作也能产生此信号:tcsetatlr,tcsendbreak,tcdrain,tcf
lush,tcfl
ow以及tcsetpgrp。在第十一章将说明这些终端操作。
SIGURG 此信号通知进程已经发生一个紧急情况。在网络连接上,接到非规定波特
率的数据
时,此信号是可选择地产生的。
SIGUSR1 这是一个用户定义的信号,可用于应用程序。
SIGUSR2 这是一个用户定义的信号,可用于应用程序。
SIGVTALRM 当一个由setitimer(2)函数设置的虚拟间隔时间已经超过时产生此信号
。
SIGWINCH SVR4和43+BSD系统核保持与每个终端或伪终端相关联的*不幸的是语术
停止(st
op)有不同的意义。在讨论作业控制和信号时我们需提及停止(stopping)和继续作
业。但是
终端驱动程序一直用术语停止表示用Control-S和Control-Q字符停止和起动终输出
。因此,
终端驱动程序将产生交互停止信号和字符称之为挂起字符(suspend)而非停止字符
。
窗口的大小、一个进程可以用ioctl函数(见1112节)得到或设置窗口的大小。如
窗口的大小、一个进程可以用ioctl函数(见1112节)得到或设置窗口的大小。如
果一个进
程用ioctl的设置-窗口-大小命令更改了窗口大小,则系统核将SIGWINCH信号送至
在前台进
程组。
SIGXCPU SVR4和43+BSD支持资源限制的概念(见711节)。如果进程超过了其软
CPU时间限
制,则产生SIGXCPU信号。
SIGXFSZ 如果进程超过了其软文件长度限制(见711节),则SVR4和43+BSD产生
此信号。

103〓signal函数
Unix信号机制的最简单界面是signal函数
#include<signalh>
void (*signal(int signo,void(*func)(int)>>(int);
返回:以前的信号处理配置,出错时SIG-ERR
signal函数是由ANSIC定义的。因为ANSIC不涉及多进程、进程组、终端I/O等,
所以它对
信号的定义非常含糊,以致于对Unix系统而言几乎毫无用处。确实,ANSIC对信号
的说明只用了2页,而POSIX1的说明则用了15页。SVR4也提供signal函数,该
函数可提供老的SVR2不可靠信号语义(在104节中说明这些老的语义)。提供此函
数主要是为了向下兼容要求此老语义的应用程序,新应用程序不应使用它。

窗口的大小、一个进程可以用ioctl函数(见1112节)得到或设置窗口的大小。如
果一个进
程用ioctl的设置-窗口-大小命令更改了窗口大小,则系统核将SIGWINCH信号送至
在前台进
程组。
SIGXCPU SVR4和43+BSD支持资源限制的概念(见711节)。如果进程超过了其软
CPU时间限
制,则产生SIGXCPU信号。
SIGXFSZ 如果进程超过了其软文件长度限制(见711节),则SVR4和43+BSD产生
此信号。

103〓signal函数
Unix信号机制的最简单界面是signal函数
#include<signalh>
void (*signal(int signo,void(*func)(int)>>(int);
返回:以前的信号处理配置,出错时SIG-ERR
signal函数是由ANSIC定义的。因为ANSIC不涉及多进程、进程组、终端I/O等,
所以它对
信号的定义非常含糊,以致于对Unix系统而言几乎毫无用处。确实,ANSIC对信号
的说明只用了2页,而POSIX1的说明则用了15页。SVR4也提供signal函数,该
函数可提供老的SVR2不可靠信号语义(在104节中说明这些老的语义)。提供此函
数主要是为了向下兼容要求此老语义的应用程序,新应用程序不应使用它。


43+BSD也提供Signal函数,但是它是用sigaction函数实现的(在1014节中说明
sigaction函数),所以在43+BSD之下使用它提供新的可靠的信号语义。在本书中
用的signal函数都是程序1012中用sigaction实现的该函数。signo参数是图10
中的信号名。func的值是(a)常数SIG-IGN,或(b)常数SIG-DFL,或(c)当接到此信
号后要调用的函数的地址。如果指定SIG-IGN,则向系统核表示要忽略此信号。(要
记住有两个信号SIGKILL和SIGSTOP是不能忽略的。)如果指定SIG-DFL,则表示接到
此信号后的动作是系统默认动作(见图101中的最后1列)。当指定函数地址后,我
们称此为捕捉此信号。我们称此函数为信号处理程序或信号一捕捉函数。
signal函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向
的函数要
求一个整型参数,但无返回值(void(*)(int))。第一个参数signo是一个整型数。
第二个参
数是函数指针,它所指向的函数需要一个整型参数,去返回值。用一般语言来描述
也就是要
向信号处理程序借送一个整型参数,而它却无返回值。当调用signal设置信号处理
程序时,
第二个参数是指向该函数(也就是信号处理程序)的指针。signal的返回值则是指向
以前的信
号处理程序的指针。
很多系统以附加的依赖于实现的参数来调节信号处理程序。在1021节中将说明可
选择的SV
R4有43+BSD参数。
R4有43+BSD参数。
本节开头所示的signal函数原型太复杂了,如果使用下面的typedef〔plawget 19
92〕,则
可使其简单一些。
typedef void Sigfunc(int)
然后,可将signal函数原型写成:
Sigfunc *signal(int,Sigfunc *);
我们已将此typedef包括在ourhdrh文件中(附录B),并附本章中的函数一起使用
。
如果查看系统的头文件<signalh>,则多年都会找到下列形式的说明:
#degine SIG 迹茫模*常病紼RR
#define SIG 迹茫模*常病紼RR (void (*)(>>-1
#define SIG 迹茫模*常病紻FL (void (*)(>>>0
#define SIG 迹茫模*常病絀GN (Void (*)(>>1
这些常数可用于表示"指向函数的指针,该画数要一个整型参数,而且无返回值。
"signal
的第二个参数及其返回值就可用它们表示。这些常数所使用的三个值不一定要是-
1,0和1。
它们必须是三个值而不能是任一可说明函数的地址。大多数Unix系统使用上面所示
的值。
实例
程序101显示了一个简单的信号处理程序,它捕捉两个用户定义的信号并打印信
号编号。
号编号。
在1010节中说明pause函数,它使调用进程睡眠。
程序101 捕捉SIGUSR1和SIGUSR2的简单处理程序
我们使该程序在后台运行,并且用kill(1)命令将信号送给它。注意,在Unix中,
消灭(kill
)这个术语是用词不当的。kill(1)命令和kill(2)函数只是将一个信号送给一个进
程或进程
组。该信号是否终止该进程则取决于该信号的类型,以及该进程是否安排了捕捉该
信号。
$ aout & 在后台启动进程
[1] 4720 作业控制shell打印作业号和进程ID
$ kill -USR1 4720 向该进程发送SIGUSR1
received SIGUSR1
$ kill -USR2 4720 
received SIGUSR2 …………SIGUSR2
$ kill 4720
[1]+Terminated aout & ………SIGTERM
当向该进程发送SIGTERM信号后,该进程就终止,因为它可捕捉此信号,而对此信
号的系统
默认动作是终止。
程序起动
当exec一道程序时,所有信号的状态都是或系统默认或忽略。通常所有信号都被设
置为它们
置为它们
的系统默认动作,除非调用exec的进程忽略该信号。非常特殊exec函数将原先设置
为要捕捉
的信号都更改为默认动作,其它信号的状态则不变。(一个进程原先要捕捉的信号
,与其exe
c一道新程序后,就自然地不能再捕捉了,因为信号一捕捉函数的地址很可能在所
执行的新
程序文件中已无意义。)
我们经常会碰到的一个具体例子是一个交互shell如何处理对后台进程的中断和退
出信号。
对于一个非作业控制shell。当我们在后台执行一个进程时,例如:
cc mainc &
shell自动地将后台进程中对中断和退出信号的处理方式设置为忽略。于是,当我
们按中断
字符时就不会影响到后台进程。如果没有这样处理,那么当我们按中断字符时,它
不但终止
前台进程,也终止所有后台进程。
很多捕捉这两个信号的交互程序具有下列形式的代码:
int sig 迹茫模*常病絠nt(),sig 迹茫模*常病絨uit();
if(signal(SIGINT,SIG 迹茫模*常病絀GN) !=SIG 迹茫模*常病絀GN)
signal(SIGINT,sig 迹茫模*常病絠nt);
if(signal(SIGQUIT,SIG 迹茫模*常病絀GN)!=SIG 迹茫模*常病絀GN)
signal(SIGQUIT,sig 迹茫模*常病絨uit);
signal(SIGQUIT,sig 迹茫模*常病絨uit);
这样处理后,仅当SIGINT和SIGQUIT原先并不忽略,进程才捕捉它们。
从这些signal调用中可以看到这种函数的限制:不改变信号的处理方式就不能确定
信号的当
前处理方式。我们将在本章的稍后部分说明使用sigaction可以确定一个信号的处
理方式,
而无需改变它。
进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始
时复制了
父进程存储图象,所以信号捕捉函数的地址在子进程中是有意义的。
104〓不可靠的信号
在早期的Unix版本中(例如Version7),信号是不可靠的。不可靠在这里指的是,信
号可能会
被丢失〖CD2〗一个信号发生了,但进程却决不会知道这一点。那时,进程对信号
的控制能
力也很低,它能捕捉信号或忽略它,但有些很需析功能它却并不具备。例如,有时
用户希望
通知系统核阻塞-信号〖CD2〗不要忽略该信号,而是在其发生时记住它,然后在进
程作好了
准备时再通知它。这种阻塞信号的能力当时并不具备。
42BSD对信号机构进行了更改,提供了被称之为可靠信号的机制。然后,SVR3也
一个时间
signal(SIGQUIT,sig 迹茫模*常病絨uit);
这样处理后,仅当SIGINT和SIGQUIT原先并不忽略,进程才捕捉它们。
从这些signal调用中可以看到这种函数的限制:不改变信号的处理方式就不能确定
信号的当
前处理方式。我们将在本章的稍后部分说明使用sigaction可以确定一个信号的处
理方式,
而无需改变它。
进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为子进程在开始
时复制了
父进程存储图象,所以信号捕捉函数的地址在子进程中是有意义的。
104〓不可靠的信号
在早期的Unix版本中(例如Version7),信号是不可靠的。不可靠在这里指的是,信
号可能会
被丢失〖CD2〗一个信号发生了,但进程却决不会知道这一点。那时,进程对信号
的控制能
力也很低,它能捕捉信号或忽略它,但有些很需析功能它却并不具备。例如,有时
用户希望
通知系统核阻塞-信号〖CD2〗不要忽略该信号,而是在其发生时记住它,然后在进
程作好了
准备时再通知它。这种阻塞信号的能力当时并不具备。
42BSD对信号机构进行了更改,提供了被称之为可靠信号的机制。然后,SVR3也
一个时间
一个时间
窗口,在此段时间中,可能发生另一次中断信号。第二个信号会造成执行默认动作
,而对中
断信号则是终止该进程。这种类型的程序段在大多数情况会正常工作,使得我们认
为它们是
编写得正确的,而实际上却并不是如此。
这些早期版本的另一个问题是:在进程不希望某种信号发生时,它不能关闭该信号
。进程能

的就是忽略该信号。有时我们希望通知系统"阻止下列信号发生,如果它们确实产
生了,请
记住它们。"说明这种问题的一个经典实例是下列程序段,它捕捉一个信号,然后
设置一个
表示该信号已发生的标志:
int sig 迹茫模*常病絠nt 迹茫模*常病絝lag; /*当信号发生时设置非0*/

main()
{
int sig 迹茫模*常病絠nt(); /*我们信号处理程序*/
…
signal(SIGINT,sig 迹茫模*常病絠nt);/*设置处理程序 */
…
while(sig 迹茫模*常病絠nt 迹茫模*常病絝lag==0)
困难的。
105〓中断的系统调用
早期Unix系统的一个特性是:如果在进程执行一个低速系统调用而阻塞期间捕捉到
一个信号
,则该系统调用就被中断不再继续执行。该系统调用出错返回,其errno设置为EI
NTR。这样
处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种
事情,所
以是个好机会应当唤醒阻塞的系统调用。
在这里,我们必须区分系统调用的函数。当捕捉到某个信号时,被中断的是在系统
核内执行
的系统调用。
为了支持这种特性,将系统调用分成两类:"低速"系统调用和其它系统调用。低速
系统调
用是可能会使进程永远阻塞的一类系统调用,它们包括:
·在读某些类型的文件时,如果数据并不存在则可能会使调用者永远阻塞(管道、
终端设备
以及网络设备)。
·在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远
阻塞。
·打开文件,在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,
主要等待
到一个所连接的调制解调器回答了电话)。
到一个所连接的调制解调器回答了电话)。
·pause(按照定义,它使调用进程睡眠直至捕捉到一个信号)和wait。
·某种ioctl操作。
·某些进程间通信函数(第十四章)。
值得注意的低速系统调用的例外是与磁盘I/O有关的系统调用。虽然读、写一个
磁盘文件
可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求
期间),但
是除非发生硬件错,I/O操作总会很快返回,并使调用者不再处于阻塞状态。

可以用中断系统调用这种方法来处理的一种情况是:一个进程起动了读终端操作,
而使用该
终端设备的用户却离开该终端很长时间。在这种情况下进程可能处于阻塞状态几个
小时甚至
数天,除非系统停机,否则一直如此。
与被中断的系统调用相关的问题是必须用显式方法处理出错返回。典型的代码序列
(假定进
行一个读操作,它被中断,我们希望重新起动它)可能如下列样式:
again:
if((n=read(fd,buff,BUFFSIZE))<0){
if(errno==EINTR)
goto again; /*一个中断的系统调用 */
/* 处理其它出错 */
到一个所连接的调制解调器回答了电话)。
·pause(按照定义,它使调用进程睡眠直至捕捉到一个信号)和wait。
·某种ioctl操作。
·某些进程间通信函数(第十四章)。
值得注意的低速系统调用的例外是与磁盘I/O有关的系统调用。虽然读、写一个
磁盘文件
可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求
期间),但
是除非发生硬件错,I/O操作总会很快返回,并使调用者不再处于阻塞状态。

可以用中断系统调用这种方法来处理的一种情况是:一个进程起动了读终端操作,
而使用该
终端设备的用户却离开该终端很长时间。在这种情况下进程可能处于阻塞状态几个
小时甚至
数天,除非系统停机,否则一直如此。
与被中断的系统调用相关的问题是必须用显式方法处理出错返回。典型的代码序列
(假定进
行一个读操作,它被中断,我们希望重新起动它)可能如下列样式:
again:
if((n=read(fd,buff,BUFFSIZE))<0){
if(errno==EINTR)
goto again; /*一个中断的系统调用 */
/* 处理其它出错 */
IX1兼容的sigaction则可使它们再起动。但如同在SVR4中一样,在sigaction中
可以使
用SA-RESTART选择项,使系统核再起动由该信号中断的系统调用。
42BSD引进自动再起动功能的一个理由是:有时用户并不知道所使用的输入、输
出设备是
否是低速设备。如果我们编写的程序可以用交互方式运行,则它可能读、写终端低
速设备。
如果在程序中捕捉信号,而系统却可提供再起动功能,则对每次读、写系统调用就
要进行是
否出错返回的测试,如果是被中断的,则再进行读、写。
图102摘录列出了几种实现所提供的信号功能及它们的语义。
〖HT5"SS〗图102 几种信号实现所提供的功能〖HT5SS〗
应当了解,其他厂商提供的Unix系统可能会有不同于图102中所示的处理情况。
例如,Sun
OS 412中的sigaction其默认方式是再起动被中断的系统调用,这与SVR4和4
3+BSD都
不同。
在程序1012中提供了我们自己的signal函数版本,它试图重新起动被中断的系统
调用(
除SIGALRM信号外)。在程序1013中则提供了另一个函数signal-intr,它不进行
再起动。

在所有程序实例中,我们都有目的地显示了信号处理程序的返回(如果它返回的话
在所有程序实例中,我们都有目的地显示了信号处理程序的返回(如果它返回的话
),这种返
回可能中断了一个系统调用。
在125节说明select和poll函数时还会涉及被中断的系统调用。
106〓可再入函数
进程捕捉到信号并继续执行时,它首先执行该信号处理程序中的指令。如果从信号
处理程序
返回(例如没有调用exit或longjmp),则在捕捉到信号时进程正在执行的正常指令
序列就继
续执行。(这类似于硬件中断发生时所做的。)但在信号处理程序中,不能判断捕捉
到信号时
进程执行到何处。如果进程正在执行malloc,在其堆中分配另外的存储空间,而此
时由于捕
捉到信号插入执行该信号处理程序,其中又调用malloc,这时会发生什么?又例如
若进程正
在执行getpwnam(62节)这种将其结果存放在静态存储单元中的函数,而插入执行
的信号处
理程序中又调用这样的函数,这时又会发生什么呢?在malloc例中子,可能会对进
程造成破
坏,因为malloc通常为它所分配的存储区保持一个连接表,而插入执行信号处理程
序时,进
程可能正在更改此连接表。在getpwnam的例子中,正常返回给调用者的信息可能由
返回至信
返回至信
号处理程序的信息覆盖。
POSIX1说明了保证可再入的函数。图103列出了这些可再入函数。图中四个带
*号的函数
并没有按POSIX1说明为是可再入的,但SVR4 SVID 〔AT&T1980〕则将它们列为是
可再入
的。
图103 在信号处理程序中可以调用的可再入函数
没有列入图103中的大多数函数是不可再入的,其原因是(a)已知它们使用静态数
据结构,
式(b)它们调用malloc或Free,或(c)它们是标准I/O函数。标准I/O库的很多实现
都以不可
再入方式使用全局数据结构。
要了解在信号处理程序中即使调用列于图103中的函数,因为每个进程只有一个
errno变量
,所以我们可能修改了其原先的值。考虑一个信号处理程序,它恰好在main刚设置
errno之
后被调用。如果该信号处理程序调用read,则它可能更改errno的值,从而取代了
刚由main
设置的值。因此,作为一个通用的规则,当在信号处理程序中调用图103中列出
的函数时
,应当在其前保存,在后恢复errno。(要了解,常常被捕捉到信号是SIGCHLD,其
信号处理
信号处理
程序通常要调用一种wait函数,而各种wait函数都能改变errno。
POSIX1没有包括图103中的longjmp和siglongjmp。(在1015节将说明siglon
gjmp函数
。)这是因为在主例程以非再入方式正在更新一数据结结构时可能产生信号。不是
从信号处
理程序返回而是调用siglongjmp,可能使该数据结构是部分更新的。如果应用程序
将要做更
新全局数据结构这样的事情,而同时规定要捕捉某些信号,而这些信号的处理程序
又会引起
执行siglongjmp,则在更新这种数据结构时要阻塞此种信号。
实例
在程序102中,信号处理程序my-alarm调用不可再入函数getpwnam,而my-alarm每
秒钟被调
用一次。1010节中将说明alarm函数。在程序102中用其每秒产生一次SIGAL
RM信号
。
运行此程序时,其结果具有附意性。通常,在信号处理程序第一次返回时,该程序
将由SIGS
EGV信号终止。检查core文件,从中可以看到main函数已调用getpwnam,而且当信
号处理程
序调用此同一函数时,某些内部指针示出了问题。偶然,此程序会运行若干秒,然
后因产生
后因产生
SIGSEGV信号而终止。在捕捉到信号后,若main函数仍正确运行,其返回值却有时
错误,有
时正确。有时在信号处理程序中调用getpwnam会出错返回,其出错值为EBADF(无效
文件描述
符)。
从此实例中可以看出,若在信号处理程序中调用一个不可再入函数,则其结果是不
可予见的
。
〖HT5"SS〗程序102 在信号处理程序中调用不可再入函数〖HT5SS

107〓SIGCLD语义
SIGCLD和SIGCHLD这两个信号经常易于混淆。SIGCLD是系统V的一个信号名,其语义
与名为SIGCHLD的BSD信号不同。POSIX1则标用BSD的SIGCHLD信号。
BSD SIGCHLD信号的语义与其它信号的语义相类似。子进程状态改变后产生此信号
,父进程需要调用一个wait类函数以检测发生了什么。
但是系统V因为历史沿袭,至今它处理SIGCLD信号的方式不同于其它信号。如果用
signal或sigset(设置信号配置的较老式的SRV3兼容性函数)设置信号配置,则SVR4
继续了这一具有问题色彩的传统(即兼容性限制)。对SIGCLD的较老处理方式是:
1如果进程特地指令对该信号的配置为SIG-IGN,则调用进程的子进程将不产生僵
死进程。
注意,这与其默认动作(SIG-DFL)忽略(见图101)不同。代之以,在子进程终止时
,将其状态丢弃。如果调用进程最后调用一个wait函数,那么它将阻塞到所有子进
,将其状态丢弃。如果调用进程最后调用一个wait函数,那么它将阻塞到所有子进
程都终止,然后该wait会返回-1,其errno则设置为ECHILD。(此信号的默认配置是
忽略,但这不会造成上述语义。代之以我们必须特地指定其配置为SIG-IGN。)

POSIX1并说明在SIGCHLD被忽略时应产生的后果,所以这种行为是允许的。
43+BSD中,如SIGCHLD被忽略,则允许产生僵死子进程。如果要避免僵死子进程
,则必须
等待子进程。
在SVR4中,如果调用signal或sigset将SIGCHLD的配置设置为忽略,则不会产生僵
死子进程
。另外,使用SVR4版的sigaction,则可设置SA-NOCLDWAIT标志(图105)以避免子
进程僵死
。
2如果将SIGCLD的配置设置为捕捉,则系统核立即检查是否有子进程准备好被等
待,如果
是这样则调用SIGCLD处理程序。
第二项改变了为此信号编写处理程序的方法。
实例
在104节中曾说过进入信号处理程序后,首先要再次调用signal以再设置此信号
处理程序
。(在信号被复置为其默认值时,它可能被丢失,立即重新设置可以减少此窗口时
间。)程序
103显示了这一点。但此程序不能正常工作。如果在SVR2下编译并运行此程序,
103显示了这一点。但此程序不能正常工作。如果在SVR2下编译并运行此程序,
则其输出
是不断重复"SIGCLD received n"。最后进程用完其栈空间并异常终止。
此程序的问题是:在信号处理程序的开始处调用signal,按照上述第二项,系统核
检查是否
有需要等待的子进程(因为我们正在处理一个SIGCLD,所以确实有这种子进程),所
以它产生
另一个对信号处理程序的调用。信号处理程序调用signal,整个过程再次重复。

为了解决这一问题,应当在调用wait取了子进程的终止状态之后再调用signal。此
时仅当其
它子进程终止,系统核再会再次产生此种信号。
如果为SIGCHLD建立了一个信号处理程序,又存在一个已终止的但尚未等待的进程
,则是否
会产生信号?POSIX1对此没有作说明。这样就允许前面所述的工作方式。但是,
因为POSIX
1在信号发生时并没有将信号配置复置为其默认值(假定我们正用POSIX1的sig
action函
数设置其配置),于是在SIGCHLD处理程序中也就不必再为该信号指定一个信号处理
程序。
务必了解你所用的系统中SIGCHLD信号的语义。也应了解在某些系统中#define SI
GCHLD为SI
GCLD或反之。更改这种信号的名字使你可以编译为另一个系统编写的程序,但是如
GCLD或反之。更改这种信号的名字使你可以编译为另一个系统编写的程序,但是如
果该程序
使用该信号的另一种语义,则这样的程序也不能工作。
〖HT5"SS〗程序103 不能正常工作的系统V SIGCLD处理程序〖HT5S
S〗
108〓可靠信号术语和语义
我们需要定义一些在讨论信号时会用到的术语。首先,当造成信号的事件发生时,
为进程产
生一个信号(或向一个进程发送一个信号)。事件可以是硬件异常(例如除以0)、软
件条件(例
如,闹钟时间超过)、终端产生的信号或调用kill函数。在产生了信号时,系统核
通常在进
程表中设置某种形式的一个标志。当对信号做了这种动作时,我们说向一个进程递
送了一
个信号。在信号产生和递送之间的时间间隔内,我们称信号末决。
进程可以选用"信号送送阻塞"。如果为进程产生了一个选择为阻搴 信号,而且对
该信号
的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为末决状态,直到
该进程(a
)对此信号解除了阻塞,或者(b)将对此信号的动作更改为忽略。当传送一个原来被
阻塞的信
号给进程时,而不是在产生该信号时,系统核再决定对它的处理方式。于是进程在
信号传送
信号传送
给它之前仍可改变对它的动作。进程调用sigpending函数(1013节)将指定的信号
设置为阻
塞的未决。
如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,这将如何呢?POSI
X1允许
系统递送该信号一次或多次。如果系统递送该信号多次,则我们先这些信号排了队
。但是大
多数Unix并不排队信号。代之以,Unix系统核只传送这种信号一次。
早期系统V版本的手册页称SIGCLD信号是用排队方式处理的,但实际并非如此。代
之以,系
统核按107节中所述方式产生此信号。AT&T〔1990e〕的sigaction(2)手册页
称SA-SIGI
NFO标志(图105)使信号可靠地排队,这也不正确。表面上此功能存在于系统核内
但在,SV
R4中并不起作用。
如果有多个信号要传送给一个进程,则将如何呢?POSIX1并没有规定这些信号传
送给进程
的顺序。但是POSIX1的原理阐述部分建议:与进程当前状态有关的信号,例如S
IGSEGV在
其它信号之前传送。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞传送到该进程的信号集。对每
种可能的
种可能的
信号在该屏蔽字中都有一位与之对应。对于某种信号,其对应位设置,则它当前是
被阻塞的
。一个进程可以调用sigprocmask(在1012节中说明)来检测和更改其当前信号屏
蔽字。
信号数可能会超过一个整型数所包含的二进制位数,为此POSIX1定义了一个新数
据类型,
sigset-t,它保持一个信号集。例如,信号屏蔽字就保存在这些信号集中的一个中
。1011
节中将说明对信号集进行操作的五个画数。
109〓kill和raise函数
kill函数将一个信号发送给一个进程或一个进程组。raise函数则允许一个进程向
自身发送
一个信号。
raise是由ANSI C而非POSIX1定义的。因为ANSI C并不涉及多进程,所以它不能
定义如kil
l这样要有一个进程ID作为其参数的函数。
#include<sys/typesh>
#include<signalh>
int kill(pid 迹茫模*常病絫 pid,int signo);
int raise(int signo);
两个函数返回:若成功为0,出错为-1
kill的pid参数有四种不同的情况。
kill的pid参数有四种不同的情况。
pid>0 将信号发送给进程ID为pid的进程。
pid==0 将信号发送给其进程组ID关于发送进程的进程组ID,而是发送进程有许可
数问其发
送信号的所有进程。
这里用的术语"所有进程"不包括实现定义的系统进程集。对于大多数Unix系统,系
统进程
集包括:交换进程(pido),init(pid1)以及页精灵进程(pid2)。
pid<0 将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送
信号的所
有进程。如上所述一样,"所有进程"并不包括系统进程集中的进程。
pid==-1 POSIX1未定义此种情况。
SVR4和43+BSD用此广播信号。在广播信号时,并不把信号发送给上述系统进程集
。43+B
SD也不将广播信号发送给发送进程自身。若调用者是超级用户,则将信号发送给所
有进程。
如果调用者不是超级用户,则将信号发送给其实际用户ID或保存的位置-用户-ID等
于调用者
的实际或有效用户ID的所有进程。广播信号只能用于管理方面(例如一个超级用户
进程将该
系统停止运行)。
上面曾提及,一个进程将一个信号发送给其它进程需要许可权。超级用户可将信号
发送给另
kill的pid参数有四种不同的情况。
pid>0 将信号发送给进程ID为pid的进程。
pid==0 将信号发送给其进程组ID关于发送进程的进程组ID,而是发送进程有许可
数问其发
送信号的所有进程。
这里用的术语"所有进程"不包括实现定义的系统进程集。对于大多数Unix系统,系
统进程
集包括:交换进程(pido),init(pid1)以及页精灵进程(pid2)。
pid<0 将信号发送给其进程组ID等于pid绝对值,而且发送进程有许可权向其发送
信号的所
有进程。如上所述一样,"所有进程"并不包括系统进程集中的进程。
pid==-1 POSIX1未定义此种情况。
SVR4和43+BSD用此广播信号。在广播信号时,并不把信号发送给上述系统进程集
。43+B
SD也不将广播信号发送给发送进程自身。若调用者是超级用户,则将信号发送给所
有进程。
如果调用者不是超级用户,则将信号发送给其实际用户ID或保存的位置-用户-ID等
于调用者
的实际或有效用户ID的所有进程。广播信号只能用于管理方面(例如一个超级用户
进程将该
系统停止运行)。
上面曾提及,一个进程将一个信号发送给其它进程需要许可权。超级用户可将信号
发送给另
发送给另
一个进程。对于其它,其基本规则是发送者的实际或有效用户ID必须等于接收者的
实际或有
效用户ID。如果实现支持-POSIX-SAVED-IDS(如SVR4所做的那样),则用保存的设置
-用户-ID
代替有效用户ID。
在对许可权进行测试时也有一个特例:如果被发送的信号是SIGCONT,则进程可将
它发送给
属于同一对话期的任一其它进程。
POSIX1将信号编号0定义为空信号。如果signo参数是0,则kill仍执行正常的错
误检查,
但不发送信号。这常被用来确定一个特定进程是否仍旧存在。如果向一个并不存在
的进程发
送空信号,则kill返回-1,errno则被设置为ESRCH。但是,应当了解,Unix系统在
经过一定
时间后会重新使用进程ID,所以一个现存的具有所给定进程ID的进程并不一定就是
你所想要
的进程。
如果kill调用为调用进程产生信号,而且此信号是不被阻塞的,那么在kill返回之
前,或者
signo,或者某个其它未决的,非阻塞信号被传送号该进程。
1010〓alarm和pause函数
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被
使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被
超过。当
所设置的时间值被超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其
默认动作
是终止该进程。
#include<unistdh>
unsigned int alarm(unsigned int seconds);
Returns:0 or number of 返回:0或以前设置的闹钟时间的余留秒数
其中,参数seconds的值是秒数,经过了所指定的seconds秒后会产生信号SIGALRM
。要了解
的是,经过了指定秒后,信号是由系统产生的,由于进程调度的延迟,进程得到控
制能够处
理该信号还需一段时间。
早期的Unix版本曾警告,这种信号可能比予定值提前1秒发送。POSIX1则不允许
这样做。

每个进程只能有一个闹钟时间。如果在调用alarm时,以前已为该进程设置过闹钟
时间,而
且它还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的值返回。以前
登记的闹
钟时间则被新值代换。
如果有以前登记的尚未超过的闹钟时间,而且seconds值是0,则取消以前的闹钟时
间。前闹
间。前闹
钟时间的余留值仍作为函数的返回值。
虽然SIGALRM的默认动作是终止进程,但是大多数使用闹钟的进程捕捉此信号。如
果此时进
程要终止,则在终止之前它可以执行所需的清除操作。
pause函数使调用进程挂起直至捕捉到一个信号。
#include<unistdh>
int pause(void);
返回:-1 errno设置为EINTR
只有执行了一个信号处理程序并从其返回时,pause再返回。在这种情况下,paus
e返回-1
,errno设置为EINTR。
实例
使用alarm和pause,进程可使自己睡眠一段指定的时间。程序104中的sleep函数
提供这种
功能。
程序104 sleep1的简化但并不完整的实现
sleep1函数看起来与将在1019节中说明的sleep函数类似,但这种简化的实现有
下列问题
。
1如果调用者已设置了闹钟,则它被sleepl函数中的第一次alarm调用擦去。
可用下列方法更正这一点:检查第一次调用alarm的返回值,如其小于本次调用al
arm的参数
arm的参数
值,则只应等到该前次放置的闹钟时间超时。如果前次设置的闹钟时间的超时时刻
后于本次
设置值,则在sleepl函数返回之前,要再设置闹钟时间,使其在予定时间再发生超
时。
2该程序中修改了对SIGALRM的配置。如果编写了一个函数供其它函数调用,则在
该函数被
调用时先要保存原配置,在本函数返回前再恢复原配置。
更改这一点的方法是:保存signal函数的返回值,在返回前恢复设置原配置。
3不调用alarm和pause之间有一个竟态条件。在一个很繁忙的系统中,可能alar
m在调用pa
use之前超时,并调用了信号处理程序。如果发生了这种情况,则在进程调用paus
e后,如果
没有捕捉到其它信号它就永远被挂起。
早期的sleep实现与程序104类似,但更正了问题1和2。有两种方法可以更正问题
3。第一
种方法是使用setjmp,下面立即说明这种方法。另一种方法是使用sigprocmask和
sigsusp
end,在1019节中将说明这种方法。
实例
SVR2中的Sleep实现使用了setjmp和longjmp(见710节)以避免问题3中所说明的竟
态条件
。此函数的一个简化版本,称为sleep2,示于程序105中(为了缩短这一实际的长
。此函数的一个简化版本,称为sleep2,示于程序105中(为了缩短这一实际的长
度,在程
序中没有处理上面所说的问题1和2。)
〖HT5"SS〗程序105 Sleep的另一个不完善的实现〖HT5SS〗
在此函数中,程序104具有的竟态条件已被避免。即使pause从未执行,在发生S
IGALRM时
,sleep2函数也返回。
但是,sleep2函数中却有另一个难于察觉的问题,它涉及到与其它信号的互相作用
。如果SI
GALRM中断了某个其它信号处理程序,则调用longjmp就会提早终止该信号处理程序
。程序10
5显示了这种情况。在SININT处理程序中的for循环语句其执行时间在作者所用的
系统上会超过5秒钟,也就是大于sleep2的参数值,这正是我们所想要的。整型变量
j说明为volatile,这样就阻止了优化编译程序除去循环语句。执行程序106得到:
$ aout
^? 键入中断字符
sig-int starting
sleep2 returned:0
〖HT5"SS〗程序106 在一个捕捉其它信号的程序中调用sleep2〖HT5
SS〗
从中可见sleep2函数所引起的longjmp使另一个信号处理程序sig-int提早终止。如
果将SVR2的Sleep函数与其它信号处理程序一起使用,你就可能碰到这种情况。见
练习10?。sleep1和sleep2函数这两个实例的目的是告诉我们在涉及到信号时需要
练习10?。sleep1和sleep2函数这两个实例的目的是告诉我们在涉及到信号时需要
有精细而周到的考虑。下面几节将说明解决这些问题的方法,使我们能够可靠地,
在不影响其它代码段的情况下处理信号。
实例
除了用来实现sleep函数外,alarm还常用于对可能阻塞的操作设置一个时间上限值
。例如,程序中有一个读低速设备的会阻塞的操作(见105),我们希望它超过一
定时间量后就一定
终止。程序107实现了这一点,它从标准输入读一行,然后将其写到标准输出上
。
〖HT5"SS〗程序107 带时间限制调用read〖HT5SS〗
这种代码序列在很多Unix应用程序中都能见到,但是这种程序有两个问题:
1程序107有程序104中的同样问题:在第一个alarm调用和read之间有一个竞
态条件。
如果系统核在这两个函数调用之间使进程不能占用处理机运行,而其时间长度又超
过闹钟时
间,则read可能永远阻塞。这种类型的大多数操作使用较长的闹钟时间,例如1分
钟或更长
一点,使这种问题不会发生,但无论如何这是一个竞态条件。
2如果系统调用是自动再起动的,则当从SIGALRM信号处理程序返回时,read并不
被终止。
在这种情形下,设置时间限制不会起作用。
在这里我们确实需要终止慢速系统调用。但是,POSIX1并未提供一种可移植的方
法实现这
法实现这
一点。
实例
让我们用longjmp再实现前面的实例。使用这种方法我们就无需担心一个慢速的系
统调用是
否被中断。
程序108 使用longjmp,带时间限制调用read
不管系统是否重新起动系统调用,该程序都会如所予期的那样工作。但是要理解,
该程序仍
旧有与程序105一样的与其它信号处理程序相互作用的问题。
如果要对I/O操作设置时间限制,则如上所示可以使用longjmp,当然也要理解它
可能有与

它信号处理程序相互作用的问题。另一种选择是使用select或poll函数,在125
1和12
52将对它们进行说明。
1011〓信号集
我们需要有一个能表示多个信号〖CD2〗信号集的数据类型。将在sigprocmask(下
一节中说
明)这样的函数中使用这种数据类型,以告诉系统核不允许发生核信号集中的信号
。如同前
面已提过的,信号种类数可能超过一个整型量所包含的二进位数,所以一般而言,
不能用一
不能用一

整型量中的一个二进位代表一种信号。POSIX1定义数据类型sigset-t以包含一个
信号集,
也定义了下列五个处理信号集的函数。
#include <signalh>
int sigemptyset(sigset 迹茫模*常病絫 *set);
int sigfillset(sigset 迹茫模*常病絫 *set);
int sigaddset(sigset 迹茫模*常病絫 *set,int signo);
int sigdelset(sigset 迹茫模*常病絫 *set,int signo);
4个函数都返回:若成功为0,出错为-1
int sigismember(const sigset 迹茫模*常病絫 *set,int signo);
返回:若真为1,若假为0
函数sigemptyset初始化由set指向的信号集,使排除其中所有信号。函数sigfill
set初始化
由set指向的信号集,使包括其中所有信号。所有应用程序在使用信号集前,要对
该信号集
调用sigemptyset或sigfillset一次。这是因为C编译程序将不赋初值的外部和静态
度量都初
始化为0,而这是否与给定系统上信号集的实现相对应并不清楚。
一旦我们已经初始化了一个信号集,以后就可在该信号集中增、删特定的信号。函
数sigadd
set将一个信号添加到一个现存集中,sigdelset则从一个信号集中删除一个信号。
实现
如果系统所实现的信号数少于一个整型量所包含的两进位数,则可用一个二进制位
代表一个
信号的方法实现信号集。例如,大多数43+BSD实现中有31种信号,和32位字长整
型。sige
mptyset和sigfillset这两个函数可以在<signalh>头文件中实现为宏:
#define sigemptyset(ptr) (*(ptr)=0)
#define sigfillset(ptr) (*(ptr)=~(sigset 迹茫模*常病絫)0,0)
注意,除了设置对应信号集中各信号的位外,sigfillset必须返回0,所以我们使
用逗号算
符,它将逗号算符后的值作为表达式的值返回。
使用这种实现的sigaddset打开一位,sigdelset则关闭一位。sigismember测试一
指定位。
因为没有信号编号值为0,所以从信号编号中减1以得到要处理的二进制位的位编号
数。程序
109实现这些功能。
程序109 sigaddset、sigdelset和sigismember的实现
我们也可将这三个函数在<signalh>中实现为各一行的宏,但是POSIX1要求检
查信号编
号参数的有效性,如果无效则设置errno。在宏中实现这一点比函数要难。

--
※ 来源:·BBS 水木清华站 smth.org·[FROM: 162.105.8.213]

--
☆ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: bbs@192.168.28.23]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店