高级进程控制和信号

  作者:FinalBSD 出处:www.chinaunix.net 更新时间: 2006年05月12日
第四章 高级进程控制和信号



翻译: gvim@chinaunix/bsd





一些说明:
1 很高兴有这个机会参与到这项活动中来。
2 由于英语语言的习惯,文中有不少复杂长句,按照字面翻译出来并不适合中国人的阅读习惯。我在保留文章原意的基础上作了一些语言组织上的调整,将大部分长句子组织成中文擅长的短句型,所以你会发现翻译品一部分内容不是按照原文字面翻译的。
3 考虑到该书的入门引导作用,在原文中一些比较少见的词后面我添加了少许译注,希望减少入门兄弟的负担。
4 由于2中的语言调整和3中的译注,是我的个人行为,可能会有我对 原文理解模糊,概念不清,或是语言组织不顺畅的地方,还请大家一定多多指出来。谢谢。
个人声明:
本翻译品受控于chinaunix BSD翻译小组。如需单独转载,请保留“翻译:gvim@chinaunix/bsd”和以上几点说明。
4.1 高级进程控制和信号
信号:4 a:一种对象,用于传输或负载人类声音之外的信息。

到 目前为止,我们已经讨论了进程的创建和其他系统调用。现在是讨论下面这些问题的时候了:你要在多个进程间通讯以获得更好的进程控制粒度,或者要其他程序或 操作者用信号通知你的程序。例如,你可能希望你的程序重新读取它的配置文件。或者,你的数据库程序需要在退出之前将事务从主存写入后备存储器,然后再退 出。这两个例子可能只是使用信号的很小一部分。虽然已经有套接字,先入先出队列,管道,信号量等多种方式来完成类似的任务,但是我们将把讨论的焦点放在信 号和其它进程控制机制上。在现实中,信号和进程控制机制可以提供大部分你所需要的特性和功能。

4.2 信号
信号与硬件中断很相似。当设备需要中断服务的时候它可以产生一个硬件中断来通知CPU。与硬件中断类似的,当进程需要将一些事件通知给其他进程的时候可以使用信号来完成。

大 多数Unix系统管理员会比较熟悉SIGHUP信号。当你通过kill命令向后台服务进程发出SIGHUP信号后,大多数进程要么重新读入他们的配置文件 要么重新启动。这些信号之中,一些与硬件有直接关系,如SIGFPE(浮点异常),SIGILL(非法指令);其它则是与软件相关,如SIGSYS(未实 现的系统调用被调用)。

一旦进程接收到信号之后,该信号的行为与信号本身和进程对它的使用目的两个因素有关。一些信号可以被阻塞,忽 略,或者捕获,而另外一些则不可以。如果进程需要捕获一个信号并履行一些相关操作,你可以为进程设定这个特定信号的信号处理句柄。处理句柄仅仅是一个函 数,在这个信号被进程接收之后调用。或者更确切的说,处理句柄是一个函数调用,你可以对它进行指派(specify)。

当信号没有指定处理句柄时,将会执行操作系统默认的行为。这些缺省行为可以是从终止进程到完全核心转储等不同的操作。注意,有两个信号不能被捕获或忽略:SIGSTOP和SIGKILL,下面会解释。

在BSD 系统中定义的有许多信号;我们讨论在/usr/include/sys/signals.h(译注:在我的FB5.2.1中是signal.h)文件中定 义的标准信号。注意, NetBSD系统中定义的信号数量稍微多一点,并且我们没有将它们的讨论放在这里。所以如果需要使用某个下面没有涉及到的信号的时候,请查阅你的系统的头 文件。



#define SIGHUP       1     /* hangup */
SIGHUP是Unix系统管理员很常用的一个信号。许多后台服务进程在接受到该信号后将会重新读取它们的配置文件。然而,该信号的实际功能是通知进程它的控制终端被断开。缺省行为是终止进程。



#define SIGINT       2     /* interrupt */
对于Unix使用者来说,SIGINT是另外一个常用的信号。许多shell的CTRL-C组合使得这个信号被大家所熟知。该信号的正式名字是中断信号。缺省行为是终止进程。



#define SIGQUIT       3     /* quit */
SIGQUIT信号被用于接收shell的CTRL-/组合。另外,它还用于告知进程退出。这是一个常用信号,用来通知应用程序从容的(译注:即在结束前执行一些退出动作)关闭。缺省行为是终止进程,并且创建一个核心转储。



#define SIGILL       4     /* illegal instr. (not reset when caught) */
如 果正在执行的进程中包含非法指令,操作系统将向该进程发送SIGILL信号。如果你的程序使用了线程,或者pointer functions,那么可能的话可以尝试捕获该信号来协助调试。(注意:原文这句为:“If your program makes use of use of threads, or pointer functions, try to catch this signal if possible for aid in debugging.”。中间的两个use of use of,不知是原书排版的瑕疵还是我确实没有明白其意义;另外,偶经常听说functions pointer,对于pointer functions,google了一下,应该是fortran里面的东西,不管怎样,还真不知道,确切含义还请知道的兄弟斧正。)缺省行为是终止进程, 并且创建一个核心转储。



#define SIGTRAP       5     /* trace trap (not reset when caught) */
SIGTRAP这个信号是由POSIX标准定义的,用于调试目的。当被调试进程接收到该信号时,就意味着它到达了某一个调试断点。一旦这个信号被交付,被调试的进程就会停止,并且它的父进程将接到通知。缺省行为是终止进程,并且创建一个核心转储。



#define SIGABRT       6     /* abort() */
SIGABRT提供了一种在异常终止(abort)一个进程的同时创建一个核心转储的方法。然而如果该信号被捕获,并且信号处理句柄没有返回,那么进程不会终止。缺省行为是终止进程,并且创建一个核心转储。



#define SIGFPE       8     /* floating point exception */
当进程发生一个浮点错误时,SIGFPE信号被发送给该进程。对于那些处理复杂数学运算的程序,一般会建议你捕获该信号。缺省行为是终止进程,并且创建一个核心转储。



#define SIGKILL       9     /* kill (cannot be caught or ignored) */
SIGKILL 是这些信号中最难对付的一个。正如你在它旁边的注释中看到的那样,这个信号不能被捕获或忽略。一旦该信号被交付给一个进程,那么这个进程就会终止。然而, 会有一些极少数情况SIGKILL不会终止进程。这些罕见的情形在处理一个“非中断操作”(比如磁盘I/O)的时候发生。虽然这样的情形极少发生,然而一 旦发生的话,会造成进程死锁。唯一结束进程的办法就只有重新启动了。缺省行为是终止进程。



#define SIGBUS       10     /* bus error */
如同它的名字暗示的那样,CPU检测到数据总线上的错误时将产生SIGBUS信号。当程序尝试去访问一个没有正确对齐的内存地址时就会产生该信号。缺省行为是终止进程,并且创建一个核心转储。



#define SIGSEGV       11     /* segmentation violation */
SIGSEGV是另一个C/C++程序员很熟悉的信号。当程序没有权利访问一个受保护的内存地址时,或者访问无效的虚拟内存地址(脏指针,dirty pointers,译注:由于没有和后备存储器中内容进行同步而造成。关于野指针,可以参见http://en.wikipedia.org/wiki/Wild_pointer 的解释。)时,会产生这个信号。缺省行为是终止进程,并且创建一个核心转储。



#define SIGSYS       12     /* non-existent system call invoked */
SIGSYS信号会在进程执行一个不存在的系统调用时被交付。操作系统会交付该信号,并且进程会被终止。缺省行为是终止进程,并且创建一个核心转储。



#define SIGPIPE       13     /* write on a pipe with no one to read it */
管道的作用就像电话一样,允许进程之间的通信。如果进程尝试对管道执行写操作,然而管道的另一边却没有回应者时,操作系统会将SIGPIPE信号交付给这个讨厌的进程(这里就是那个打算写入的进程)。缺省行为是终止进程。



#define SIGALRM       14     /* alarm clock */
在进程的计时器到期的时候,SIGALRM信号会被交付(delivered)给进程。这些计时器由本章后面将会提及的setitimer和alarm调用设置。缺省行为是终止进程。



#define SIGTERM       15     /* software termination signal from kill */
SIGTERM信号被发送给进程,通知该进程是时候终止了,并且在终止之前做一些清理活动。SIGTERM信号是Unix的kill命令发送的缺省信号,同时也是操作系统关闭时向进程发送的缺省信号。缺省行为是终止进程。



#define SIGURG       16     /* urgent condition on IO channel */
在进程已打开的套接字上发生某些情况时,SIGURG将被发送给该进程。如果进程不捕获这个信号的话,那么将被丢弃。缺省行为是丢弃这个信号。



#define SIGSTOP       17     /* sendable stop signal not from tty */
本信号不能被捕获或忽略。一旦进程接收到SIGSTOP信号,它会立即停止(stop),直到接收到另一个SIGCONT信号为止。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。



#define SIGTSTP       18     /* stop signal from tty */
SIGSTP与SIGSTOP类似,它们的区别在于SIGSTP信号可以被捕获或忽略。当shell从键盘接收到CTRL-Z的时候就会交付(deliver)这个信号给进程。缺省行为是停止进程,直到接收到一个SIGCONT信号为止。



#define SIGCONT       19     /* continue a stopped process */
SIGCONT 也是一个有意思的信号。如前所述,当进程停止的时候,这个信号用来告诉进程恢复运行。该信号的有趣的地方在于:它不能被忽略或阻塞,但可以被捕获。这样做 很有意义:因为进程大概不愿意忽略或阻塞SIGCONT信号,否则,如果进程接收到SIGSTOP或SIGSTP的时候该怎么办?缺省行为是丢弃该信号。



#define SIGCHLD       20     /* to parent on child stop or exit */
SIGCHLD 是由Berkeley Unix引入的,并且比SRV 4 Unix上的实现有更好的接口。(如果信号是一个没有追溯能力的过程(not a retroactive process),那么BSD的SIGCHID信号实现会比较好。在system V Unix的实现中,如果进程要求捕获该信号,操作系统会检查是否存在有任何未完成的子进程(这些子进程是已经退出(exit)的子进程,并且在等待调用 wait的父进程收集它们的状态)。如果子进程退出的时候附带有一些终止信息(terminating information),那么信号处理句柄就会被调用。所以,仅仅要求捕获这个信号会导致信号处理句柄被调用(译注:即是上面说的“信号的追溯能 力”),而这是却一种相当混乱的状况。)

一旦一个进程的子进程状态发生改变,SIGCHLD信号就会被发送给该进程。就像我在前面章节提 到的,父进程虽然可以fork出子进程,但没有必要等待子进程退出。一般来说这是不太好的,因为这样的话,一旦进程退出就可能会变成一个僵尸进程。可是如 果父进程捕获SIGCHLD信号的话,它就可以使用wait系列调用中的某一个去收集子进程状态,或者判断发生了什么事情。当发送SIGSTOP, SIGSTP或SIGCONF信号给子进程时,SIGCHLD信号也会被发送给父进程。缺省行为是丢弃该信号。



#define SIGTTIN       21     /* to readers pgrp upon background tty read */
当一个后台进程尝试进行一个读操作时,SIGTTIN信号被发送给该进程。进程将会阻塞直到接收到SIGCONT信号为止。缺省行为是停止进程,直到接收到SIGCONT信号。



#define SIGTTOU       22     /* like TTIN if (tp->t_local&LTOSTOP) */
SIGTTOU 信号与SIGTTIN很相似,不同之处在于SIGTTOU信号是由于后台进程尝试对一个设置了TOSTOP属性的tty执行写操作时才会产生。然而,如果 tty没有设置这个属性,SIGTTOU就不会被发送。缺省行为是停止进程,直到接收到SIGCONT信号。



#define SIGIO       23     /* input/output possible signal */
如果进程在一个文件描述符上有I/O操作的话,SIGIO信号将被发送给这个进程。进程可以通过fcntl调用来设置。缺省行为是丢弃该信号。



#define SIGXCPU       24     /* exceeded CPU time limit */
如果一旦进程超出了它可以使用的CPU限制(CPU limit),SIGXCPU信号就被发送给它。这个限制可以使用随后讨论的setrlimit设置。缺省行为是终止进程。



#define SIGXFSZ       25     /* exceeded file size limit */
如果一旦进程超出了它可以使用的文件大小限制,SIGXFSZ信号就被发送给它。稍后我们会继续讨论这个信号。缺省行为是终止进程。



#define SIGVTALRM     26     /* virtual time alarm */
如果一旦进程超过了它设定的虚拟计时器计数时,SIGVTALRM信号就被发送给它。缺省行为是终止进程。



#define SIGPROF       27     /* profiling time alarm */
当设置了计时器时,SIGPROF是另一个将会发送给进程的信号。缺省行为是终止进程。



#define SIGWINCH     28     /* window size changes */
当进程调整了终端的行或列时(比如增大你的xterm的尺寸),SIGWINCH信号被发送给该进程。缺省行为是丢弃该信号。



#define SIGUSR1       29     /* user defined signal 1 */
#define SIGUSR2       30     /* user defined signal 2 */
SIGUSR1和SIGUSR2这两个信号被设计为用户指定。它们可以被设定来完成你的任何需要。换句话说,操作系统没有任何行为与这两个信号关联。缺省行为是终止进程。(译注:按原文的意思翻译出来似乎这两句话有点矛盾。)

4.3 系统调用
那 么,你该如何使用信号呢?有时候甚至拿不准是否应该使用信号。例如,当信号被交付的时候,一方面你可以在行为发生之前,分析当前情况,找出信号发生的原 因,或者找到这些信号是从哪里发出来的;另一方面,其他一些时候你也可以只是希望简单的退出程序,并且在清除之后创建一个核心转储文件。参见最后部分的简 单代码可以获得这些函数的较详细的例子。

Kill函数
kill函数对于那些经常在命令行使用kill命令杀死进程的人来说是再熟悉不过的。基本语法是:



int   kill(pid_t pid, int sig);
Kill函数将指定的信号发送给进程号为pid的进程。只有当进程符合下面几点情况的时候信号才会被交付:


•     发送与接收进程有相同的有效用户ID(UID);
•     发送进程有适当的权限(例如:setuid命令);
•     发送进程有超级用户(root)的UID。
注意:SIGCONT信号是一个特例,它可以由当前进程发送给任何一个该进程的派生进程。

使用不同的调用参数使得kill函数的行为差别非常大。这些行为如下所述:
(译注:下面的PID应该指的是上面kill函数原型中的那个pid,我在这里做出说明并保留原文)


•     如果PID大于0,并且发送进程有适当的权限,那么参数sig指定的信号将被交付。
•     如果PID等于0,那么sig信号将被交付给所有那些与发送进程有相同组ID的进程。(发送进程同样需要满足权限需求。)
•     如果PID是 -1,那么信号将被发送给所有那些与发送进程有相同有效用户ID的进程(不包含发送进程在内)。然而,如果发送进程的有效用户ID与超级用户(root) 的相同,那么信号被交付给除了系统进程(由它们的proc结构中的p_flag域是否是P_SYSTEM来定义)之外的所有进程。在这个特殊的例子中,如 果某些进程不能被发送(could not be sent)sig信号,kill函数并不返回一个错误。
•     如果sig是0,kill函数只检查错误(例如,无效权限,不存在的进程等)。该用法有时候用来检查一个指定进程是否存在。
•     如果成功的话kill函数返回0,否则返回-1。kill调用失败时会在errno全局变量中设置相应的错误值。
kill的另一个版本是raise函数:



int   raise(int sig);
raise 函数会向当前进程发送sig信号。该函数用处不是很大,因为它只能够给当前进程发送信号。raise函数调用成功时返回0,否则返回-1。调用失败时会在 errno全局变量中设置相应的错误值,效果和signal函数的返回类似:(译注:原文只有两个单词“as in:”,我并不知道作者把signal列在这里所要表达的意思,所以我按照我的理解+猜测来翻译的。如果大家有什么建议,或是需要纠正的话,请一定告诉 我。)



void (*signal(int sig, void (*func)(int)))(int);
4.4 信号处理
现在我们知道何时会产生信号,也知道如何发送信号,那么我们怎么处理它们呢?

signal函数

signal系统函数调用提供了一种最简单的范例。然而,由于C原形声明的缘故使它看起来比实际复杂。signal函数将一个给定的函数和一个特定的信号联系。这里是FreeBSD中的定义(和一个typedef一起):


typedef void (*sig_t) (int);

sig_t   signal(int sig, sig_t func);
第一个参数是目标信号,可以是上面列举的所有信号中的任何一个。func参数是一个指针,指向某个处理该信号的函数。这个处理信号函数带有一个int型参数,并应返回void。signal函数中的func参数也可以设定为下面的一些值:


SIG_IGN: 如果func参数被设置为SIG_IGN,该信号将被忽略。

SIG_DFL: 如果func参数被设置为SIG_DFL,该信号会按照确定行为处理。
sigaction函数

sigaction函数是一个比signal更通用的方案。第一个参数是目标信号。下一个名为act的参数(指向)sigaction结构,该结构包含一些用于信号处理的信息。最后一个参数oact是一个指针,指向一个可以存储上一次设置信号处理的信息的地方。


int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
sigaction结构有下面这些个成员:


void   (*sa_handler)(int);
这个结构成员是一个指向函数的指针,该函数带有一个简单的整形参数,并返回(void)。这与signal函数的func参数相同,也可以被设置为SIG_IGN和SIG_DFL,并且与调用signal得到的效果也一样。


void   (*sa_sigaction)(int, siginfo_t *, void *);
该结构成员是一个指向函数的指针,返回(void)并需要三个参数。这些参数依次为:一个整形参数指定信号发送;一个指向siginfo_t结构的指针用来保存关于信号的信息;最后一个也是一个指针,指向信号交付时的特定上下文(context)空间。


sigset_t sa_mask;
该 结构成员是一个位掩码(bitwise mask),用来指示信号交付时哪些信号会被阻塞。阻塞SIGKILL和SIGSTOP信号的做法会被忽略。接下来,被阻塞的信号将被推迟,直到它们被开 启(unblock)。参见sigprocmask获得更多关于全局掩码(global masks)的信息。


int     sa_flags;
该数据成员是一个拥有下面这些标志的位掩码:



SA_NOCLDSTOP: 如果SA_NOCLDSTOP位被置位并且目标信号是SIGCHLD,除非子进程退出,而在子进程停止(stop)时父进程将不会收到通知。

SA_NOCLDWAIT: SA_NOCLDWAIT标志会阻止子进程成为僵尸进程。在目标信号是SIGCHLD的时候使用。如果进程设置了这个标志,接着调用某个wait系统调 用,进程将被阻塞直到子进程全部终止,最后返回-1(译注:此处在APUE2ed中的解释是返回1),设置errno全局变量为ECHILD。

SA_ONSTACK: 一些时候需要在特定的堆栈上进行信号的处理。sigaction系统调用提供了这个方式。如果该位被置位,那么信号将会被交付到指定的堆栈上。

SA_NODEFER: 如果SA_NODEFER位被置位,那么当前信号正被处理时,系统不会屏蔽该信号以后的交付。

SA_RESETHAND: 如果SA_RESETHAND被置位,一旦信号被交付,信号处理句柄将被置为SIG_DEF。

SA_SIGINFO: 被置位时,由结构体sigaction 的成员sa_sigaction指向的函数被使用。注意:使用SIG_IGN或SIG_DFL时不应该设置这个标志。成功调用sigaction之后,返回0或-1,并且将error设置成相关错误值。
4.5信号掩码(阻塞与开启信号)

进 程可以阻塞或设置某个信号。一旦该信号被阻塞,关于它的交付将被推迟,直到进程重新开启它。在这样的情况下是非常有用的:进程进入代码中某个部分,不能被 中断但仍希望可以接受、处理可能丢失的信号。可靠交付信号的能力直到4.2BSD引入之后(不久被SVR3采用),操作系统才拥有该能力。

随着可靠信号的出现,信号的生命和交付(life and delivery)都有所改变。信号可以在之前产生和交付。现在,一旦信号是挂起的(pending),进程可以在接收它之前决定怎么处理。进程可能会去处理它,也可能设置为缺省行为,或者丢弃信号

注意:如果许多信号都挂起,系统将会首先交付会改变进程状态的信号,例如SIGBUS。

sigprocmask

任何进程可以使用sigprocmask函数来阻塞信号。语法如下:



int   sigprocmask(int how, const sigset_t *set, sigset_t *oset);
sigprocmask函数会修改或检查(modify or examine)当前信号掩码。当set参数不是null的时候,sigprocmask的行为和第一个参数how有关。函数行为和相关意义列举如下:



SIG_BLOCK: 在set参数中指定的信号被阻塞,并且添加进阻塞信号列表。

SIG_UNBLOCK: 在set参数中指定的信号会从信号掩码中移除。

SIG_SETMASK: set参数将完全替代当前信号掩码。如果oset参数不为null,则会被设置为前一个信号掩码。如果set值是null,how参数被忽略并且信号掩码 保持不变。所以,为了检查信号掩码,我们可以将传入set null值,oset为非null值来调用sigprocmask函数。一旦掩码得到之后,你可能需要对他进行检查或操作。可以使用下面的过程 (routine)。注意当前这些过程是宏的实现。




int   sigemptyset(sigset_t *set)
如果调用这个过程,set参数将被初始化指向一个空信号集。



int   sigfillset(sigset_t *set)
如果调用这个过程,set参数将被初始化指向一个包括所有信号的信号集。



int   sigaddset(sigset_t *set, int signo)
如果调用这个过程,signo指定的信号将被添加进set参数指定的信号集。



int   sigdelset(sigset_t *set, int signo)
如果调用这个过程,signo指定的信号将从set参数指定的信号集中移除。



int   sigismember(const sigset_t *set, int signo)
如果调用这个过程,如果由signo指定的信号存在于set参数指定的信号集中时,返回1,否则返回0。



int   sigpending(sigset_t *set);
进程可以使用sigpending函数去查出当前那些信号被挂起。sigpending函数会返回一个包含所有挂起信号的掩码。该掩码可以使用上面介绍的过程去检查。sigpending成功时返回0,否则返回-1,并且设置errno为相应错误值。

4.6 自定义行为
一些时候程序要求信号处理句柄运行在一个特定的堆栈上。为了实现这个目的,一个备用(alternate)堆栈区间必须用signaltstack函数指出来。这个函数使用的数据结构为signaltstack:



int   sigaltstack(const struct sigaltstack *ss, struct sigaltstack *oss);
它的结构成员解释如下。



char   *ss_sp;
该成员指向一个被用作堆栈的区域。系统中有个MINSIGSTKSZ常量,它定义了进行信号处理时所需的最小内存空间。系统中还有一个SIGSTKSZ常量,它定义了通常情况下处理时所需内存空间。该内存空间需要在调用signaltstack函数之间分配。



size_t ss_size;
数据成员ss_size指出新堆栈的大小。如果这个值是错误的(inaccurate),当信号处理句柄执行时,它的行为就变得不可预知(你不能明确知道系统怎样处理这个信号)。



int   ss_flags;
根 据调用环境(calling circumstances),数据成员ss_flags可以具有少数几个不同的值。首先,当进程希望停用备用堆栈的时候,ss_flags会被设为 SS_DISABLE。在这个情况下,ss_sp和ss_size被忽略,备用堆栈被禁止。注意,备用堆栈只能在当前句柄没有处理时禁止。

如果使用一个non-null值作为oss的实参去调用signaltstack,ss_flags将包含指示当前状态的信息。它们是:



SS_DISABLE: 备用堆栈被停用。
SS_ONSTACK: 备用堆栈当前正在被使用,并且现在不可以停用。
如 果调用signaltstack 的oss实参不是null,会返回当前状态。调用成功返回0,否则返回-1。如果调用失败,errno也会相应的被设置。因为信号可以在任何一点被交付, 所以很难被预测。出于这个原因,4.2BSD的缺省行为是:重新开始被中断的系统调用,重新提供还没有被转送的数据。在大多数时候这个行为是很不错的,并 且也是所有BSD系统采用的缺省行为。可是,也有一些罕见的情况,你可能需要将这个特性关掉。你可以使用siginterrupt函数完成需求。使用很简 单:



int   siginterrupt(int sig, int flag);
将sig参数设置为目标信号,并且设置flag为真(在这个情况下是1)。如果flag参数被设为假(在这个情况下是0),那么缺省行为是重新启动系统调用。

4.7 等待信号


sigsuspend 函数可以暂时将当前阻塞信号集改变为由sigmask指定的信号集。改变后,sigsuspend会等待,直到一个信号被交付。一旦一个信号被交付后,原 先的信号集被恢复。由于sigsuspend调用在信号交付之后总是被终止,它的返回值总是-1,errno总是EINTR。下面是它的语法:



int   sigsuspend(const sigset_t *sigmask);
sigwait 函数用set参数指定的信号集作为信号掩码。它会检查包含在这个特定集合内的是否有任何挂起信号,如果有的话,它将清除这个挂起的信号,并在sig参数中 返回这个被清除信号的数值。如果没有信号挂起,sigwait将一直等待直到指定信号集合中的任何一个信号产生。下面是它的语法:



int   sigwait(const sigset_t *set, int *sig);
当 信号被交付给进程(该进程安装了相应信号的处理句柄)时,进程将会切换到信号处理句柄中执行。例如,假设你的程序监听一个由配置文件设定的端口。你的进程 安装了一个捕获SIGHUP信号的处理句柄来重新读取配置文件。一旦SIGHUP信号被交付给你的程序,进程将会执行信号处理句柄来重新读取配置文件。这 里存在一个问题,你没有办法知道在进程执行过程中信号被交付的确切地点。你虽然可以使用一些下面列出的函数来将范围缩小,但是如果碰到像打开套接字,打开 链接,或者其它那些首先需要清理后才能在新端口上监听 这些情况的时候呢?你怎样确定清理活动在那里开始,什么时候开始?如果你的程序正在等到输入,并且没有数据被传进来的时候,系统调用将被重启 (system call will be restarted),所以从SIGHUP的返回将会继续等待。

这是使用setjmp和longjmp函数的一些情况,这些函数提供非本地分支(non-local branching)。为了使用这些setjmp函数,需要提供一个evn参数,如下:



jmp_buff env;
int   sigsetjmp(sigjmp_buf env, int savemask);
void   siglongjmp(sigjmp_buf env, int val);
int   setjmp(jmp_buf env);
void   longjmp(jmp_buf env, int val);
int   _setjmp(jmp_buf env);
void   _longjmp(jmp_buf env, int val);
void   longjmperror(void);
首 先,调用setjmp的返回为0,当前的环境将被保存在env中。接着,你可以在信号处理句柄内部调用对应的longjmp。一旦调用了longjmp, 它将把执行环境恢复为env中保存的环境,并返回到最初setjmp被调用时的环境中。最初的setjmp调用返回那个传递给longjmp的va参数的 值。

关于setjmp和longjmp函数一些说明:首先,这两个是不能混杂使用。也就是说,调用setjmp时保存的env变量不能传递给_longjmp调用。另外,调用setjmp的函数返回后,接下来调用longjmp将会发生错误。
The different calls have specific actions that they take. These actions are listed below:
不同的调用有特定的行为。这些行为在下面列出来:



jmp和longjmp: 他们会保存(恢复)信号掩码,寄存器组和堆栈。




_setjmp和_longjmp: 他们只保存(恢复)寄存器组和堆栈。




sigsetjmp和siglongjmp: 只要savemask参数不是0,他们就保存(恢复)寄存器组,堆栈和信号掩码。
由 于一些原因,如果env参数保存的东西被破坏,或者调用setjmp的函数返回了,longjmp函数将会调用longjmperror函数。如果 longjmperror也返回了,程序将被异常终止。你可以使用与longjmperror有相同原形的函数来自定义longjmperror函数。缺 省的longjmperror会在标准错误上输出”longjmp botch”,然后返回。
4.8 Alarms



unsigned int   alarm(unsigned int seconds);
alarm 函数基本上是一个简单的闹钟时钟(alarm clock),同时也是一个很有用的函数。它允许进程在经过指定秒数之后收到一个通知。一旦闹钟时间到,进程将收到一个SIGALRM信号。任何随后的 alarm调用都会覆盖原先的调用设定。alarm不像sleep函数,它不会被阻塞。

它有一些返回值需要值得你注意:首先,如果进程没有设定定时器,那么返回值是0。其次,如果有一个定时器被设定但还没有超时的话,那么会返回前一个调用到现在还有的剩余时间。

现在可以设定的最大时间是100,000,000秒 --- 已经是相当长的时间了。



int getitimer(int which, struct itimerval *value);
getitimer函数会检索由第一个参数(which参数)描述的itimerval结构。第一个参数可选的选项将在下面说明:



int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
setitmer比先前的alarm调用提供了更稳定的接口。在BSD系统上,每个进程可以提供三种不同时间间隔的定时器。他们在下面讲述:



#define ITIMER_REAL     0
实 时时钟实时的递减而不管进程在CPU上的实际花销时间(换句话说,它追踪自然时间natural time)。这允许进程设置一个基于自然实时时间(based on atural real time)的定时器。当实时定时器超时的时候,进程会收到SIGALRM信号。



#define ITIMER_VIRTUAL   1
虚拟定时器仅只递减进程在CPU上的执行时间,允许进程设定一个基于CPU使用率的定时器。当虚拟定时器超时的时候,进程收到SIGVTALRM信号



#define ITIMER_PROF     2
Profile 定时器递减在CPU上的执行时间和代表进程执行的系统调用的时间。这对于那些要求静态剖析的解释程序是很有帮助的。当profile定时器超时的时候,进 程收到SIGPROF信号。然而并不像实时和虚拟定时器那样,SIGPROF可以在系统调用的时候被发送;进程应该准备好重新执行被中断的系统调用。

本章将焦点放在了信号库上。这些信号及他们的使用方法对于系统编程是很重要的。信号允许系统管理员通知应用程序重新读取配置文件,从而使程序更稳定。其它重要的信号处理挂起在打开的文件描述符上的I/O操作。下一章讲述怎样利用这些I/O相关的信号。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值