unix高级编程10.pdf
下载下载第1 0章信号10.1 引言信号是软件中断。很多比较重要的应用程序都需处理信号。信号提供了一种处理异步事件的方法:终端用户键入中断键,则会通过信号机构停止一个程序。U N I X的早期版本,就已经有信号机构,但是这些系统,例如 V 7所提供的信号模型并不可靠。信号可能被丢失,而且在执行临界区代码时,进程很难关闭所选择的信号。4.3 B S D和S V R 3对信号模型都作了更改,增加了可靠信号机制。但是这两种更改之间并不兼容。幸运的是P O S I X.1对可靠信号例程进行了标准化,这正是本章所说明的。本章先对信号机制进行综述,并说明每种信号的一般用法。然后分析早期实现的问题。在分析存在的问题之后再说明解决这些问题的方法,这样有助于加深对改进机制的理解。本章也包含了很多并非1 0 0%正确的实例,这样做的目的是为了对其不足之处进行讨论。10.2 信号的概念首先,每个信号都有一个名字。这些名字都以三个字符 S I G开头。例如,S I G A B RT是夭折信号,当进程调用a b o r t函数时产生这种信号。S I G A L R M是闹钟信号,当由a l a r m函数设置的时间已经超过后产生此信号。V 7有1 5种不同的信号,S V R 4和4.3+B S D均有3 1种不同的信号。在头文件中,这些信号都被定义为正整数(信号编号)。没有一个信号其编号为0。在1 0.9节中将会看到k i l l函数,对信号编号0有特殊的应用。P O S I X.1将此种信号编号值称为空信号。很多条件可以产生一个信号。当用户按某些终端键时,产生信号。在终端上按D E L E T E键通常产生中断信号(S I G I N T)。这是停止一个已失去控制程序的方法。(第11章将说明此信号可被映射为终端上的任一字符。)硬件异常产生信号:除数为0、无效的存储访问等等。这些条件通常由硬件检测到,并将其通知内核。然后内核为该条件发生时正在运行的进程产生适当的信号。例如,对执行一个无效存储访问的进程产生一个S I G S E G V。进程用k i l l(2)函数可将信号发送给另一个进程或进程组。自然,有些限制:接收信号进程和发送信号进程的所有者必须相同,或发送信号进程的所有者必须是超级用户。用户可用k i l l(1)命令将信号发送给其他进程。此程序是 k i l l函数的界面。常用此命令终止一个失控的后台进程。当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。这里并不是指硬件产生条件(如被 0除),而是软件条件。例如 S I G U R G(在网络连接上传来非规定波特率的数据)、S I G P I P E(在管道的读进程已终止后一个进程写此管道),以及S I G A L R M(进程所设置的闹钟时间已经超时)。信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能只是测试一个变量(例如e r r n o)来判别是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。可以要求系统在某个信号出现时按照下列三种方式中的一种进行操作。(1)忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:S I G K I L L和S I G S TO P。这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是未定义的。(2)捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。例如,若编写一个命令解释器,当用户用键盘产生中断信号时,很可能希望返回到程序的主循环,终止系统正在为该用户执行的命令。如果捕捉到S I G C H L D信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用 w a i t p i d以取得该子进程的进程I D以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要为S I G T E R M信号编写一个信号捕捉函数以清除临时文件(k i l l命令传送的系统默认信号是终止信号)。(3)执行系统默认动作。表1 0-1给出了对每一种信号的系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。表1 0-1列出所有信号的名字,哪些系统支持此信号以及对于信号的系统默认动作。在P O S I X.1列中,表示要求此种信号。j o b表示这是作业控制信号(仅当支持作业控制时,才要求此种信号)。表10-1 UNIX信号名字说明ANSI C P O S I X.1S V R 44.3+B S D缺 省 动 作S I G A B R T异常终止(a b o r t)终止w/c o r eS I G A L R M超时(a l a r m)终止S I G B U S硬件故障终止w/c o r eS I G C H L D子进程状态改变作业忽略S I G C O N T使暂停进程继续作业继续/忽略S I G E M T硬件故障终止w/c o r eS I G F P E算术异常终止w/c o r eS I G H U P连接断开终止S I G I L L非法硬件指令终止w/c o r eS I G I N F O键盘状态请求忽略S I G I N T终端中断符终止S I G I O异步I/O终止/忽略S I G I O T硬件故障终止w/c o r eS I G K I L L终止终止S I G P I P E写至无读进程的管道终止S I G P O L L可轮询事件(p o l l)终止S I G P R O F梗概时间超时(s e t i t i m e r)终止S I G P W R电源失效/再起动忽略S I G Q U I T终端退出符终止w/c o r eS I G S E G V无效存储访问终止w/c o r eS I G S T O P停止作业暂停进程第1 0章信号1 9 9下载(续)名字说明ANSI C P O S I X.1S V R 44.3+B S D缺 省 动 作S I G S Y S无效系统调用终止w/c o r eS I G T E R M终止终止S I G T R A P硬件故障终止w/c o r eS I G T S T P终端挂起符作业停止进程S I G T T I N后台从控制t t y读作业停止进程S I G T T O U后台向控制t t y写作业停止进程S I G U R G紧急情况忽略S I G U S R 1用户定义信号终止S I G U S R 2用户定义信号终止S I G V T A L R M虚拟时间闹钟(s e t i t i m e r)终止S I G W I N C H终端窗口大小改变忽略S I G X C P U超过C P U限制(s e t r l i m i t)终止w/c o r eS I G X F S Z超过文件长度限制(s e t r l i m i t)终止w/c o r e在系统默认动作列,“终止w/c o r e”表示在进程当前工作目录的c o r e文件中复制了该进程的存储图像(该文件名为c o r e,由此可以看出这种功能很久之前就是 U N I X功能的一部分)。大多数U N I X调试程序都使用c o r e文件以检查进程在终止时的状态。在下列条件下不产生 c o r e文件:(a)进程是设置-用户-I D,而且当前用户并非程序文件的所有者,或者(b)进程是设置-组-I D,而且当前用户并非该程序文件的组所有者,或者(c)用户没有写当前工作目录的许可权,或者(d)文件太大(回忆7.11节中的R L I M I T _ C O R E)。c o r e文件的许可权(假定该文件在此之前并不存在)通常是用户读写,组读和其他读。c o r e文件的产生不是P O S I X.1所属部分,而是很多U N I X版本的实现特征。U N I X第6版没有检查条件(a)和(b),并且其源代码中包含如下说明:“如果你正在找寻保护信号,那么当设置-用户-I D命令执行时,将可能产生大量的这种信号”。4.3+B S D产生名为c o r e.p ro g的文件,其中p ro g是被执行的程序名的前 1 6个字符。它对c o r e文件给予了某种标识,所以是一种改进特征。表1 0-1“硬件故障”对应于实现定义的硬件故障。这些名字中有很多取自U N I X早先在P D P-11上的实现。请查看你所使用的系统的手册,以确切地确定这些信号对应于哪些错误类型。下面比较详细地说明这些信号。SIGABRT 调用a b o r t函数时(见1 0.1 7节)产生此信号。进程异常终止。SIGALRM 超过用a l a r m函数设置的时间时产生此信号。详细情况见 1 0.1 0节。若由s e t i t i m e r(2)函数设置的间隔时间已经过时,那么也产生此信号。SIGBUS 指示一个实现定义的硬件故障。SIGCHLD 在一个进程终止或停止时,S I G C H L D信号被送给其父进程。按系统默认,将忽略此信号。如果父进程希望了解其子进程的这种状态改变,则应捕捉此信号。信号捕捉函数中通常要调用w a i t函数以取得子进程I D和其终止状态。2 0 0U N I X环境高级编程下载系统V的早期版本有一个名为 S I G C L D(无H)的类似信号。这一信号具有非标准的语义,S V R 2的手册页警告在新的程序中尽量不要使用这种信号。应用程序应当使用标准的 S I G C H L D信号。1 0.7节将讨论这两个信号。SIGCONT 此作业控制信号送给需要继续运行的处于停止状态的进程。如果接收到此信号的进程处于停止状态,则系统默认动作是使该进程继续运行,否则默认动作是忽略此信号。例如,v i编辑程序在捕捉到此信号后,重新绘制终端屏幕。关于进一步的情况见 1 0.2 0节。SIGEMT 指示一个实现定义的硬件故障。E M T这一名字来自P D P-11的emulator trap 指令。SIGFPE 此信号表示一个算术运算异常,例如除以0,浮点溢出等。SIGHUP 如果终端界面检测到一个连接断开,则将此信号送给与该终端相关的控制进程(对话期首进程)。见图9-11,此信号被送给s e s s i o n结构中s _ l e a d e r字段所指向的进程。仅当终端的C L O C A L标志没有设置时,在上述条件下才产生此信号。(如果所连接的终端是本地的,才设置该终端的C L O C A L标志。它告诉终端驱动程序忽略所有调制解调器的状态行。第 11章将说明如何设置此标志。)注意,接到此信号的对话期首进程可能在后台,作为一个例子见图9-7。这区别于通常由终端产生的信号(中断、退出和挂起),这些信号总是传递给前台进程组。如果对话期前进程终止,则也产生此信号。在这种情况,此信号送给前台进程组中的每一个进程。通常用此信号通知精灵进程(见第1 3章)以再读它们的配置文件。选用S I G H U P的理由是,因为一个精灵进程不会有一个控制终端,而且通常决不会接收到这种信号。SIGILL 此信号指示进程已执行一条非法硬件指令。4.3 B S D由a b o r t函数产生此信号。S I G A B RT现在被用于此。SIGINFO 这是一种4.3+B S D信号,当用户按状态键(一般采用C t r l-T)时,终端驱动程序产生此信号并送至前台进程组中的每一个进程(见图 9-8)。此信号通常造成在终端上显示前台进程组中各进程的状态信息。SIGINT 当用户按中断键(一般采用D E L E T E或C t r l-C)时,终端驱动程序产生此信号并送至前台进程组中的每一个进程(见图9-8)。当一个进程在运行时失控,特别是它正在屏幕上产生大量不需要的输出时,常用此信号终止它。SIGIO 此信号指示一个异步I/O事件。在1 2.6.2节中将对此进行讨论。在表1 0-1中,对S I G I O的系统默认动作是终止或忽略。不幸的是,这依赖于系统。在S V R 4中,S I G I O与S I G P O L L相同,其默认动作是终止此进程。在 4.3+B S D中(此信号起源于4.2 B S D),其默认动作是忽略。SIGIOT 这指示一个实现定义的硬件故障。I O T这个名字来自于P D P-11对于输入输出TRAP(input/output TRAP)指令的缩写。系统V的早期版本,由a b o r t函数产生此信号。S I G A B RT现在被用于此。SIGKILL 这是两个不能被捕捉或忽略信号中的一个。它向系统管理员提供了一种可以第1 0章信号2 0 1下载杀死任一进程的可靠方法。SIGPIPE 如果在读进程已终止时写管道,则产生此信号。1 4.2节将说明管道。当套接口的一端已经终止时,若进程写该套接口也产生此信号。SIGPOLL 这是一种S V R 4信号,当在一个可轮询设备上发生一特定事件时产生此信号。1 2.5.2节将说明p o l l函数和此信号。它与4.3+B S D的S I G I O和S I G U R G信号接近。SIGPROF 当s e t i t i m e r(2)函数设置的梗概统计间隔时间已经超过时产生此信号。SIGPWR 这是一种S V R 4信号,它依赖于系统。它主要用于具有不间断电源(U P S)的系统上。如果电源失效,则U P S起作用,而且通常软件会接到通知。在这种情况下,系统依靠蓄电池电源继续运行,所以无须作任何处理。但是如果蓄电池也将不能支持工作,则软件通常会再次接到通知,此时,它在1 53 0秒内使系统各部分都停止运行。此时应当传递S I G P W R信号。在大多数系统中使接到蓄电池电压过低的进程将信号S I G P W R发送给i n i t进程,然后由i n i t处理停机操作。很多系统V的i n i t实现在i n i t t a b文件中提供了两个记录项用于此种目的;p o w e r f a i l以及p o w e r w a i t。目前已能获得低价格的U P S系统,它用R S-2 3 2串行连接能够很容易地将蓄电池电压过低的条件通知系统,于是这种信号也就更加重要了。SIGQUIT 当用户在终端上按退出键(一般采用C t r l-)时,产生此信号,并送至前台进程组中的所有进程(见图9-8)。此信号不仅终止前台进程组(如 S I G I N T所做的那样),同时产生一个c o r e文件。SIGSEGV 指示进程进行了一次无效的存储访问。名字S E G V表示“段违例(segmentation violation)”。SIGSTOP 这是一个作业控制信号,它停止一个进程。它类似于交互停止信号(S I G T S T P),但是S I G S TO P不能被捕捉或忽略。SIGSYS 指示一个无效的系统调用。由于某种未知原因,进程执行了一条系统调用指令,但其指示系统调用类型的参数却是无效的。SIGTERM 这是由k i l l(1)命令发送的系统默认终止信号。SIGTRAP 指示一个实现定义的硬件故障。此信号名来自于P D P-11的T R A P指令。SIGTSTP 交互停止信号,当用户在终端上按挂起键(一般采用C t r l-Z)时,终端驱动程序产生此信号。SIGTTIN 当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。(见9.8节中对此问题的讨论。)在下列例外情形下不产生此信号,此时读操作返回出错,e r r n o设置为E I O:(a)读进程忽略或阻塞此信号,或(b)读进程所属的进程组是孤儿进程组。SIGTTOU 当一个后台进程组进程试图写其控制终端时产生此信号。(见9.8节对此问题的讨论。)与上面所述的S I G T T I N信号不同,一个进程可以选择为允许后台进程写控制终端。第11章将讨论如何更改此选择项。如果不允许后台进程写,则与S I G T T I N相似也有两种特殊情况:(a)写进程忽略或阻塞此信2 0 2U N I X环境高级编程下载不幸的是,术语停止(s t o p)有不同的意义。在讨论作业控制和信号时我们需提及停止和继续作业。但是终端驱动程序一直用术语停止表示用C t r l-S和C t r l-Q字符停止和起动终端输出。因此,终端驱动程序将产生交互停止信号和字符称之为挂起字符而非停止字符。号,或(b)写进程所属进程组是孤儿进程组。在这两种情况下不产生此信号,写操作返回出错,e r r n o设置为E I O。不论是否允许后台进程写,某些除写以外的下列终端操作也能产生此信号:t c s e t a t t r,tcsendbreak,tcdrain,tcflush,tcflow 以及t c s e t p g r p。第11章将说明这些终端操作。SIGURG 此信号通知进程已经发生一个紧急情况。在网络连接上,接到非规定波特率的数据时,此信号可选择地产生。SIGUSR1 这是一个用户定义的信号,可用于应用程序。SIGUSR2 这是一个用户定义的信号,可用于应用程序。SIGVTALRM 当一个由s e t i t i m e r(2)函数设置的虚拟间隔时间已经超过时产生此信号。SIGWINCH SVR4和4.3+B S D内核保持与每个终端或伪终端相关联的窗口的大小。一个进程可以用i o c t l函数(见11.1 2节)得到或设置窗口的大小。如果一个进程用 i o c t l的设置-窗口-大小命令更改了窗口大小,则内核将S I G W I N C H信号送至前台进程组。SIGXCPU SVR4和4.3+B S D支持资源限制的概念(见 7.11节)。如果进程超过了其软C P U时间限制,则产生此信号。SIGXFSZ 如果进程超过了其软文件长度限制(见 7.11节),则S V R 4和4.3+B S D产生此信号。10.3 signal函数U N I X信号机制最简单的界面是s i g n a l函数。#include void(*signal(int s i g n o,void(*f u n c)(int)(int);返回:成功则为以前的信号处理配置,若出错则为 S I G _ E R Rs i g n a l函数由ANSI C定义。因为ANSI C不涉及多进程、进程组、终端 I/O等,所以它对信号的定义非常含糊,以致于对 U N I X系统而言几乎毫无用处。确实,ANSI C对信号的说明只用了2页,而P O S I X.1的说明则用了1 5页。S V R 4也提供s i g n a l函数,该函数可提供老的S V R 2不可靠信号语义(1 0.4节将说明这些老的语义)。提供此函数主要是为了向下兼容要求此老语义的应用程序,新应用程序不应使用它。4.3+B S D也提供s i g n a l函数,但是它是用s i g a c t i o n函数实现的(1 0.1 4节将说明s i g a c t i o n函数),所以在4.3+B S D之下使用它提供新的可靠的信号语义。在讨论s i g a c t i o n函数时,提供了使用该函数的 s i g n a l的一个实现。本书中的所有实例均使用程序1 0-1 2中给出的s i g n a l函数。s i g n o参数是表1 0-1中的信号名。f u n c的值是:(a)常数S I G _ I G N,或(b)常数S I G _ D F L,或(c)当接到此信号后要调用的函数的地址。如果指定 S I G _ I G N,则向内核表示忽略此信号。(记住有两个信号S I G K I L L和S I G S TO P不能忽略。)如果指定S I G _ D F L,则表示接到此信号后的动作是系统默认动作(见表1 0-1中的最后1列)。当指定函数地址时,我们称此为捕捉此信号。我们称此函数为信号处理程序(signal handler)或信号捕捉函数(signal-catching function)。第1 0章信号2 0 3下载s i g n a l函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(v o i d)。第一个参数s i g n o是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。用一般语言来描述也就是要向信号处理程序传送一个整型参数,而它却无返回值。当调用s i g n a l设置信号处理程序时,第二个参数是指向该函数(也就是信号处理程序)的指针。s i g n a l的返回值则是指向以前的信号处理程序的指针。很多系统用附加的依赖于实现的参数来调用信号处理程序。1 0.2 1节将说明可选择的S V R 4和4.3+B S D参数。本节开头所示的s i g n a l函数原型太复杂了,如果使用下面的 t y p e d e fPlauger 1992,则可使其简单一些。typedef void Sigfunc(int);然后,可将s i g n a l函数原型写成:Sigfunc*signal(int,Sigfunc*);我们已将此t y p e d e f包括在o u r h d r.h文件中(见附录B),并随本章中的函数一起使用。如果查看系统的头文件,则可能会找到下列形式的说明:#define SIG_ERR(void(*)()-1#define SIG_DFL(void(*)()0#define SIG_IGN(void(*)()1这些常数可用于表示“指向函数的指针,该函数要一个整型参数,而且无返回值”。s i g n a l的第二个参数及其返回值就可用它们表示。这些常数所使用的三个值不一定要是 1,0和1。它们必须是三个值而决不能是任一可说明函数的地址。大多数U N I X系统使用上面所示的值。实例程序1 0-1显示了一个简单的信号处理程序,它捕捉两个用户定义的信号并打印信号编号。1 0.1 0节将说明p a u s e函数,它使调用进程睡眠。程序10-1 捕捉S I G U S R 1和S I G U S R 2的简单处理程序2 0 4U N I X环境高级编程下载我们使该程序在后台运行,并且用k i l l(1)命令将信号送给它。注意,在U N I X中,杀死(k i l l)这个术语是不恰当的。k i l l(1)命令和k i l l(2)函数只是将一个信号送给一个进程或进程组。该信号是否终止该进程则取决于该信号的类型,以及该进程是否安排了捕捉该信号。$a.out&在后台启动进程1 4720作业控制s h e l l打印作业号和进程I D$kill-USR1 4720向该进程发送S I G U S R 1received SIGUSR1$kill-USR2 4720向该进程发送S I G U S R 2received SIGUSR2$kill 4720向该进程发送S I G T E R M1+Terminated a.out&当向该进程发送S I G T E R M信号后,该进程就终止,因为它不捕捉此信号,而对此信号的系统默认动作是终止。10.3.1 程序起动当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为系统默认动作,除非调用e x e c的进程忽略该信号。比较特殊的是,e x e c函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就自然地不能再捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。我们经常会碰到的一个具体例子是一个交互 s h e l l如何处理对后台进程的中断和退出信号。对于一个非作业控制s h e l l,当在后台执行一个进程时,例如:cc main.c&s h e l l自动将后台进程中对中断和退出信号的处理方式设置为忽略。于是,当按中断键时就不会影响到后台进程。如果没有这样的处理,那么当按中断键时,它不但终止前台进程,也终止所有后台进程。很多捕捉这两个信号的交互程序具有下列形式的代码:int sig_int(),sig_quit();if(signal(SIGINT,SIG_IGN)!=SIG_IGN)signal(SIGINT,sig_int);if(signal(SIGQUIT,SIG_IGN)!=SIG_IGN)signal(SIGQUIT,sig_quit);这样处理后,仅当S I G I N T和S I G Q U I T当前并不忽略,进程才捕捉它们。从s i g n a l的这两个调用中也可以看到这种函数的限制:不改变信号的处理方式就不能确定信号的当前处理方式。我们将在本章的稍后部分说明使用 s i g a c t i o n函数可以确定一个信号的处理方式,而无需改变它。第1 0章信号2 0 5下载10.3.2 进程创建当一个进程调用f o r k时,其子进程继承父进程的信号处理方式。因为子进程在开始时复制了父进程存储图像,所以信号捕捉函数的地址在子进程中是有意义的。10.4 不可靠的信号在早期的U N I X版本中(例如V 7),信号是不可靠的。不可靠在这里指的是,信号可能会被丢失一个信号发生了,但进程却决不会知道这一点。那时,进程对信号的控制能力也很低,它能捕捉信号或忽略它,但有些很需要的功能它却并不具备。例如,有时用户希望通知内核阻塞一信号不要忽略该信号,在其发生时记住它,然后在进程作好了准备时再通知它。这种阻塞信号的能力当时并不具备。4.2 B S D对信号机构进行了更改,提供了被称之为可靠信号的机制。然后,S V R 3也修改了信号机制,提供了另一套系统 V可靠信号机制。P O S I X.1选择了B S D模型作为其标准化的基础。早期版本中的一个问题是在进程每次处理信号时,随即将信号动作复置为默认值(在前面运行程序1 0-1时,我们通过只捕捉每种信号各一次避免了这一点)。以下是早期版本中关于如何处理中断信号的经典实例的代码:由于早期的C语言版本不支持ANSI C的v o i d数据类型,所以将信号处理程序说明为i n t类型。这种代码段的一个问题是:在信号发生之后到信号处理程序中调用 s i g n a l函数之间有一个时间窗口。在此段时间中,可能发生另一次中断信号。第二个信号会造成执行默认动作,而对中断信号则是终止该进程。这种类型的程序段在大多数情况下会正常工作,使得我们认为它们正确,而实际上却并不是如此。这些早期版本的另一个问题是:在进程不希望某种信号发生时,它不能关闭该信号。进程能做的就是忽略该信号。有时希望通知系统“阻止下列信号发生,如果它们确实产生了,请记住它们。”这种问题的一个经典实例是下列程序段,它捕捉一个信号,然后设置一个表示该信号已发生的标志:2 0 6U N I X环境高级编程下载其中,进程调用p a u s e函数使自己睡眠,直到捕捉到一个信号。当信号被捕捉到后,信号处理程序将标志s i g _ i n t _ f l a g设置为非0。在信号处理程序返回之后,内核将该进程唤醒,它检测到该标志为非0,然后执行它所需做的。但是这里也有一个时间窗口,可能使操作错误。如果在测试s i g _ i n t _ f l a g之后,调用p a u s t之前发生信号,则此进程可能会一直睡眠(假定此信号不再次产生)。于是,这次发生的信号也就丢失了。还有另一个例子,某段代码并不正确,但是大多数时间却能正常工作。要查找并排除这种类型的问题很困难。10.5 中断的系统调用早期U N I X系统的一个特性是:如果在进程执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其 e r r n o设置为E I N T R。这样处理的理由是:因为一个信号发生了,进程捕捉到了它,这意味着已经发生了某种事情,所以是个好机会应当唤醒阻塞的系统调用。在这里,我们必须区分系统调用和函数。当捕捉到某个信号时,被中断的是内核中执行的系统调用。为了支持这种特性,将系统调用分成两类:低速系统调用和其他系统调用。低速系统调用是可能会使进程永远阻塞的一类系统调用,它们包括:在读某些类型的文件时,如果数据并不存在则可能会使调用者永远阻塞(管道、终端设备以及网络设备)。在写这些类型的文件时,如果不能立即接受这些数据,则也可能会使调用者永远阻塞。打开文件,在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,它要等待直到所连接的调制解调器回答了电话)。pause(按照定义,它使调用进程睡眠直至捕捉到一个信号)和w a i t。某种i o c t l操作。某些进程间通信函数(见第1 4章)。在这些低速系统调用中一个值得注意的例外是与磁盘 I/O有关的系统调用。虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间),但是除非发生硬件错误,I/O操作总会很快返回,并使调用者不再处于阻塞状态。可以用中断系统调用这种方法来处理的一种情况是:一个进程起动了读终端操作,而使用该终端设备的用户却离开该终端很长时间。在这种情况下进程可能处于阻塞状态几个小时甚至数天,除非系统停机,否则一直如此。与被中断的系统调用相关的问题是必须用显式方法处理出错返回。典型的代码序列(假定第1 0章信号2 0 7下载进行一个读操作,它被中断,我们希望重新起动它)可能如下列样式:a g a i n:if(n=read(fd,buff,BUFFSIZE)0)if(errno=EINTR)goto again;/*just an interrupted system call*/*handle other errors*/为了帮助应用程序使其不必处理被中断的系统调用,4.2 B S D引进了某些被中断的系统调用的自动再起动。自动再起动的系统调用包括:i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和w a i t p i d。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中断。而 w a i t和w a i t p i d在捕捉到信号时总是被中断。某些应用程序并不希望这些函数被中断后再起动,因为这种自动再起动的处理方式也会带来问题,为此 4.3 B S D允许进程在每个信号各别处理的基础上不使用此功能。P O S I X.1允许实现再起动系统调用,但这并不是必需的。系统V的默认工作方式是不再起动系统调用。但是 S V R 4使用s i g a c t i o n时(见1 0.1 4节),可以指定S A _ R E S TA RT选择项以再起动由该信号中断的系统调用。在4.3+B S D中,系统调用的再起动依赖于调用了哪一个函数设置信号处理方式配置。早期的与4.3 B S D兼容的s i g v e c函数使被该信号中断的系统调用自动再起动。但是,使用较新的与 P O S I X.1兼容的s i g a c t i o n则不使它们再起动。但如同在S V R 4中一样,在s i g a c t i o n中可以使用S A _ R E S TA RT选择项,使内核再起动由该信号中断的系统调用。4.2 B S D引进自动再起动功能的一个理由是:有时用户并不知道所使用的输入、输出设备是否是低速设备。如果我们编写的程序可以用交互方式运行,则它可能读、写终端低速设备。如果在程序中捕捉信号,而系统却不提供再起动功能,则对每次读、写系统调用就要进行是否出错返回的测试,如果是被中断的,则再进行读、写。表1 0-2列出了几种实现所提供的信号功能及它们的语义。表10-2 几种信号实现所提供的功能函数系统信号处理程阻塞信号被中断系统调序仍被安装的能力用的自动再起动s i g n a l,V7,SVR2,决不SVR3,SVR4s i g s e t,s i g h o l d,s i g r e l s eSVR3,SVR4决不s i g i g n o r e,s i g p a u s es i g n a l,s i g v e c,s i g b l o c k4.2 B S D总是s i g s e t m a s k,s i g p a u s e4.3BSD,4.3+BSD默认P O S I X.1未说明s i g a c t i o n,s i g p r o c m a s kS V R 4可选s i g p e n d i n g,s i g s u s p e n d4.3+B S D可选2 0 8U N I X环境高级编程下载应当了解,其他厂商提供的 U N I X系统可能会有不同于表 1 0-2中所示的处理情况。例如,SunOS 4.1.2中的s i g a c t i o n其默认方式是再起动被中断的系统调用,这与 S V R 4和4.3+B S D都不同。程序1 0-1 2提供了我们自己的 s i g n a l函数版本,它试图重新起动被中断的系统调用(除S I G A L R M信号外)。程序1 0-1 3则提供了另一个函数s i g n a l _ i n t r,它不进行再起动。在所有程序实例中,我们都有目的地显示了信号处理程序的返回(如果它返回的话),这种返回可能会中断一个系统调用。1 2.5节说明s e l e c t和p o l l函数时还将涉及被中断的系统调用。10.6 可再入函数进程捕捉到信号并继续执行时,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用 e x i t或l o n g j m p),则继续执行在捕捉到信号时进程正在执行的正常指令序列(这类似于硬件中断发生时所做的)。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。如果进程正在执行m a l l o c,在其堆中分配另外的存储空间,而此时由于捕捉到信号插入执行该信号处理程序,其中又调用 m a l l o c,这时会发生什么?又例如若进程正在执行g e t p w n a m(见6.2节)这种将其结果存放在静态存储单元中的函数,而插入执行的信号处理程序中又调用这样的函数,这时又会发生什么呢?在m a l l o c例中子,可能会对进程造成破坏,因为m a l l o c通常为它所分配的存储区保持一个连接表,而插入执行信号处理程序时,进程可能正在更改此连接表。在 g e t p w n a m的例子中,正常返回给调用者的信息可能由返回至信号处理程序的信息覆盖。P O S I X.1说明了保证可再入的函数。表1 0-3列出了这些可再入函数。图中四个带*号的函数并没有按P O S I X.1说明为是可再入的,但SVR4 SVID AT&T 1989则将它们列为是可再入的。表10-3 信号处理程序中可以调用的可再入函数_ e x i tf o r kp i p es t a ta b o r t*f s t a tr e a ds y s c o n fa c c e s sg e t e g i dr e n a m et c d r a i na l a r mg e t e u i dr m d i rt c f l o wc f g e t i s p e e dg e t g i ds e t g i dt c f l u s hc f g e t o s p e e dg e t g r o u p ss e t p g i dt c g e t a t t rc f s e t i s p e e dg e t p g r ps e t s i dt c g e t p g r pc f s e t o s p e e dg e t p i ds e t u i dt c s e n d b r e a kc h d i rg e t p p i ds i g a c t i o nt c s e t a t t rc h m o dg e t u i ds i g a