实际上所有的C语言实现中都包括signal库函数,将其作为捕获异步事件的一种方式。
要调用该库函数,需要在源文件中加上:
#include <signal.h>
以引入相关的声明。要处理一个特定的signal(信号),可以这样调用signal函数:
signal(signal type, handler function);
signal type代表系统头文件signal.h中定义的某些变量,这些常量用来标识signal函数将要捕获的信号类型。handler function是当指定的事件发生时,将要加以调用的事件处理函数。
在许多C语言实现中,信号是真正意义上的“异步”。从理论上说,一个信号可能在C程序执行期间的任何时刻发生。需要特别强调的是,信号甚至可能出现在某些复杂库函数(如malloc)的执行过程中。因此,从安全的角度考虑,信号的处理函数不应该调用上述类型的库函数。例如,假设malloc函数的执行过程被一个信号中断。此时,malloc函数可以用来跟踪内存的数据结构很可能部分被更新,如果signal处理函数再调用malloc函数,结果可能是malloc函数用到的数据结构完全崩溃,后果不堪设想!
基于同样的原因,从signal处理函数中使用longjmp退出,通常情况下也是不安全的:因为信号可能发生在malloc或者其他库函数开始更新某个数据结构,但又没有最后完成的过程中。因此,signal处理函数能够做的安全的事情,似乎就只有设置一个标志然后返回,期待以后主程序能够检查这个标志,发现一个信号已经发生。
然而,就算这样做也并不总是安全的。当一个算术运算错误(例如溢出或者零作除数)引发一个信号时,某些机器在signal处理函数返回后还将重新执行失败的操作。而当这个算术运算重新执行时,我们并没有一个可移植的办法来改变操作数。在这种情况下,最可能的结果就是马上又引发一个同样的信号。因此,对于算术运算错误,signal处理函数的唯一安全、可移植的操作就是打印一条出错信息,然后使用longjmp或exit立即退出程序。
我们得到的结果是:信号非常复杂棘手,而且具有一些从本质上而言不可移植的特性。要解决这个问题,我们最好采取“守势”,让signal处理函数尽可能简单,并将它们组织在一起。这样,当需要适应一个新系统时,我们可以很容易进行修改。
/*
** signal。
*/
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
jmp_buf restart;
void handler( int );
int main( void ){
int a, b;
int quot;
int result;
signal( SIGFPE, handler );
a = 7;
b = 0;
result = setjmp( restart );
if( result == 0 ){
quot = a / b;
printf( "a = %d, b = %d, quot = %d\n", a, b, quot );
} else{
printf( "divisor can't be zero!\n" );
printf( "a = %d, b = %d\n", a, b );
}
return EXIT_SUCCESS;
}
void handler( int sig ){
/*
if( sig == SIGFPE ){
printf( "an arithmetric error happens!\n" );
exit( EXIT_FAILURE );
}
*/
if( sig == SIGFPE ){
printf( "an arithmetric error happens!\n" );
longjmp( restart, 1 );
}
}
输出: