资讯详情

unix环境高级编程-10(信号)

第十章:信号 101〓引言 信号是软件中断。许多更重要的应用程序需要处理信号。信号提供了处理异常的方法 步骤方法:如果终端用户输入中断键,将通过信号机构停止程序.Unix的早期 有信号机构,但这些系统,如Version7提供的信号模型是不可或缺的 靠。信号可能被丢失,而且在执行临界区代码时,进程难于关闭所选择的信号. 4.3BSD和SVR三是改变了信号模型,增加了可靠的信号机制。但这两种变化并存 不兼容。幸运的是POSIX.本章规定了可靠信号例程的标准化.本章 首先总结信号机制,说明每个信号的一般用法。然后分析早期实现的问题。 分析存在的问题之后再说明解决这些问题的方法,这样有助于加深对改进机制的理解. 本章还包含了许多不是100%正确的例子,旨在讨论其不确定性。 102〓信号的概念 首先,每个信号都有一个名字。这些名字有三个字符SIG开头。SIGABRT是夭折 当过程调用时,信号abort这种信号在函数中产生。SIGALRM当由是闹钟信号alarm函数设 这个信号在放置时间超过后产生。Version7有十五种不同的信号;SVR4和4.3BSD两者 有31种不同的信号。 在头文件<signal.h>这些信号被定义为正整数(信号编号)。没有信号编号 号为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),则该过程的行为是最终定义的。 2.捕获信号。为了做到这一点,请通知系统在发生信号时调用用户函数。 在用户函数中,用户希望处理此类事件。例如,如果我们编写命令解释器, 当用户使用键盘产生中断信号时,我们可能希望返回程序的主循环,终止系统正在这样做 用户执行的命令。如果捕获SIGCHLD信号表示子过程已经终止,因此该信号的捕获 可调用函数Waitpid以获得子过程的过程ID以及它的终止状态。例如,如果过程创建 如果我们建立了临时文件,我们可能不得不SIGTERM编写信号捕获函数以清除临时文件 (kill命令传送的系统默认信号是终止信号。) 3.执行系统默认动作。.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实现版本特征.3 BSD产生名为core.prog的文 件,其中prog是被执行程序名的前16个字符。core文件给出了一些标志,所以是 一种改进特证。.1中说明列, "硬件故障 "对应实现定义的硬件故障。这些名字 字中有很多取自于Unix早先在PPP-11上的实现。请查看您使用的系统手册,以确切 确定这些信号对应的错误类型。 图10.1Unix信号 下面详细说明这些信号。 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标志。它告诉终端驱动程序忽略所有 调制解调器的状态。注意如何在第十一章中设置此标志) 对话期的第一个过程可能在后台,例如见图97.这与通常由终端生成的不同 信号(中断、退出和悬挂)总是传递给前台进程组。如果对话期前的过程 终止也会产生这个信号。在这种情况下,信号发送给前台进程组的每个过程。 通常用这个信号通知精灵过程(第十三章),然后阅读它们的配置文件。为此选择SIGHUP 原因是精灵过程没有控制终端,通常永远不会接收到这个 信号。SIGKILL该信号指示过程已执行非法硬件指示。.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现用于这些情况。  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不能被捕促或忽略。  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)写进程所属进程组是孤儿进程组。  在这两种  情况下不产生此信号,写操作出错返回,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的设置-窗口-大小命令更改了窗口大小,则系统核将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    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参数。  本节开头所示的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);  这样处理后,仅当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)  pause(); /* 睡眠,等待信号 */  …  }  sig 迹茫模*常病絠nt()  {  signal(SIGINT,sig 迹茫模*常病絠nt);/*为下一次信号重新设置处理程序*/  sig 迹茫模*常病絠nt 迹茫模*常病絝lag=1; /* 设置标志供main循环检查 */    其中,进程调用pause函数使其睡眠,直到捕捉到一个信号。当此信号被捕捉到后  ,信号处  理程序将标志sig-int-flag设置为非0。在接获信号后系统将该进程唤醒,在信号  处理程  序返回之后,它检测到该标志为非0,然后执行它所需做的。但是这里也有一个时  间窗口,  这使操作可能错误。如果在测试sig-int-flag之后,调用paust之前发生信号,则  此进程可  能会一直睡眠(假定此信号不再次产生)。于是,这次发生的信号也就丢失了。这是  另一个例  子,某种代码并不正确,但是大多数时间,它能正常工作。查找并排除这种类型的  问题是很  困难的。  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; /*一个中断的系统调用 */  /* 处理其它出错 */  }  为了帮助应用程序使其不必处理被中断的系统调用,42BSD引进了某些被中断的  系统调用  的自动再起动。自动再起动的系统调用包括:ioctl、read、readv、write、writ  er、wait  和waitpid。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中  断。而wai  t和waitpid在捕捉到信号时总是被中断。某些应用程序并不希望这些函数被中断后  再起动,  于是这种自动再起动的处理方式也会带来问题,为此43BSD允许进程在每个信号  个别处理  的基础上不使用此功能。  POSIX11允许实现再起动系统调用,但这并不是要求的。系统V的默认工作方式是  不起动系  统调用。但是SVR4使用sigaction时(1014节),可以指定SA-RESTART选项以再起  动由该信  号中断的系统调用。  在43+BSD中,系统调用的再起动依赖于调用了那一个函数设置信号处理方式配置  。较老式  的43BSD兼容的sigvec函数使被该信号中断的系统调用自动再起动。但是,使用  较新的POS  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会返回-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下编译并运行此程序,  则其输出  是不断重复 "SIGCLD received n "。最后进程用完其栈空间并异常终止。  此程序的问题是:在信号处理程序的开始处调用signal,按照上述第二项,系统核  检查是否  有需要等待的子进程(因为我们正在处理一个SIGCLD,所以确实有这种子进程),所  以它产生  另一个对信号处理程序的调用。信号处理程序调用signal,整个过程再次重复。  为了解决这一问题,应当在调用wait取了子进程的终止状态之后再调用signal。此  时仅当其  它子进程终止,系统核再会再次产生此种信号。  如果为SIGCHLD建立了一个信号处理程序,又存在一个已终止的但尚未等待的进程  ,则是否  会产生信号?POSIX1对此没有作说明。这样就允许前面所述的工作方式。但是,  因为POSIX  1在信号发生时并没有将信号配置复置为其默认值(假定我们正用POSIX1的sig  action函  数设置其配置),于是在SIGCHLD处理程序中也就不必再为该信号指定一个信号处理  程序。  务必了解你所用的系统中SIGCHLD信号的语义。也应了解在某些系统中#define SI  GCHLD为SI  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参数有四种不同的情况。  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函数可以设置一个时间值(闹钟时间),在将来的某个时刻该时间值会被  超过。当  所设置的时间值被超过后,产生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的参数  值,则只应等到该前次放置的闹钟时间超时。如果前次设置的闹钟时间的超时时刻  后于本次  设置值,则在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中(为了缩短这一实际的长  度,在程  序中没有处理上面所说的问题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函数这两个实例的目的是告诉我们在涉及到信号时需要  有精细而周到的考虑。下面几节将说明解决这些问题的方法,使我们能够可靠地,  在不影响其它代码段的情况下处理信号。  实例  除了用来实现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指向的信号集,使包括其中所有信号。所有应用程序在使用信号集前,要对  该信号集  调

标签: 21静态电压继电器sy

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台