分类: 系统运维
2012-03-30 18:54:27
UNIX系统的信号特性的最简单的接口是signal函数。
signal函数由ISO C定义,它不包含多进程、进程组、终端I/O、等等。因此,它对信号的定义是足够模糊的,对于UNIX系统的几乎无用的。
从UNIX系统V继承的实现支持signal函数,但是它支持老的不可靠信号的语义(我们将在10.4节讨论这些更老的语义。)这个函数提供了需要更老语义的应用的向后兼容。新的应用不应该使用这些不可靠的信号。
4.4BSD也提供了signal函数,但是它以sigaction函数的方式定义(我们在10.4节讨论),所以在4.4BSD下使用它提供了更新的可靠信号语义。FreeBSD和Mac OS X跟随了这种策略。
Solaris有系统V和BSD两者的根,但是它对于signal函数选择了跟随系统V的语义。
在Linux上,signal的语义可以跟随BSD或系统V,取决于C库的版本和你如何编译你的应用。
因为signal的语义在实现间不同,所以最好使用sigaction函数。当我们在10.14节描述sigactoin函数时,我们提供一个使用它的signal的实现。
signo 参数只是上一节的信号名。func的值为a、常量SIG——IGN,b、常量SIG_DFL,或c、当信号发生时要调用的函数的地址。如果我们指定 SIG_IGN,那么我们告诉系统忽略这个信号。(记住我们不能忽略SIGKILL和SIGSTOP)。当我们指定SIG_DFL时,我们设置信号相关的 动作为默认值。当我们指定信号发生时所调用的函数的地址时,我们是尝试捕获这个信号。我们称这个函数为系统处理器或信号捕获函数。
signal 函数的原型指定函数需要两个参数并返回一个无返回的函数的指针。signal函数的第一个参数,signo是一个整型。第二个参数是一个接受一个整型参数 而无返回的函数的指针。signal返回的指针指向接受单个整型参数(信号号)而无返回的函数。当我们调用signal来建立信号处理器时,第二个参数是 一个函数指针。signal的返回值是前一个信号处理器的指针。
许多系统用额外的,系统相关的参数来调用信号处理器。我们在10.14节讨论。
本节开头展示的令人疑惑的signal函数原型可以通过typedef变得更简单:typedef void Sigfunc(int);
然后原型变为:Sigfun *signal(int, Sigfunc *);
如果我们检查系统的头文件
/* Fake signal functions. */
#define SIG_ERR ((__sighandler_t) -1) /* Error return. */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
这些常量被用来代替“指向带一个整型参数而无返回的函数的指针”,signal的第二个参数和signal的返回值。这三个值不必是-1、0、1。它们必须是三个不可能是任何声明的函数的地址的值。多数UNIX系统使用了展示的值。
下面的代码展示了一个简单的信号处理器,它捕获两个用户定义的信号的任一个并打印信号号:
我们在后台运行这个程序,然后使用kill命令向它发送信号。注意在UNIX系统的术语“kill”是一个不恰当的名字。kill命令和kill函数只是向一个进程或进程组发送一个信号。信号是否终止进程取决于哪个信号被发送和进程是否捕获这个信号。运行结果为:
$ ./a.out &
[1] 3594
$ kill -USR1 3594
received SIGUSR1
$ kill -USR2 3594
received SIGUSR2
$ kill 3594
[1]+ 已终止 ./a.out
当我们发送SIGTERM信号时,进程被终止,因为它不捕获这个信号,而这个信号的默认动作是终止。
程序启动
当 一个程序执行时,所有信号的状态都是默认或忽略。通常,所有信号被设置被它们的默认动作,除非调用exec的进程正忽略这个信号。确切地说,exec函数 改变任何被被捕获的的信号的布署为它们的默认动作,而不改变其它信号的状态。(自然地,一个被一个调用exec的进程捕获的信号不能被新程序的相同函数捕 获,因为调用者的信号处理函数很可能在新的被执行的程序文件里没有意义。)
一个确切的例子是一个交互外壳如何为后台进程处理中断和退出信 号。对于一个不支持工作控制的外壳,当我们在后台执行一个进程时,比如cc main.c &,外壳自动把后台进程的中断和退出信号的布署设置为忽略。这样如果我们输入中断字符,它不会影响后台进程。如果不这么做而我们输入中断字符,那 么它不会只终止前台进程,还有所有的后台进程。
许多捕获这两个信号的交互程序有如下的代码:
void sig_int(int), sig_quit(int);
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
signal(SIGINT, sig_int);
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
signal(SIGQUIT, sig_quit);
这样做,进程只捕获当前没有被忽略的信号。
这两个signal的调用也显示了signal函数的局限:我们不能不改变布署来决定当前的布署。我们在本章后面看到sigaction函数如何允许我们不改变它也能决定一个信号的布署。
进程创建
当一个进程调用fork时,子进程继承了父进程的信号布署。这里,既然子进程从父进程的内存映像的一份拷贝开始,那么信号处理函数的地址在子进程里是有意义的。