Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始,信号值小于SIGRTMIN的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是信号可能丢失。
随着时间的发展,实践证明了有必要对信号的原始机制加以改进和扩充。由于原来定义的信号已有许多应用,不好再做改动,最终只好又新增加了一些信号,并在一开始就把它们定义为可靠信号,这些信号支持排队,不会丢失。
信号值位于SIGRTMIN和SIGRTMAX之间的信号都是可靠信号,可靠信号克服了信号可能丢失的问题。Linux在支持新版本的信号安装函数sigation()以及信号发送函数sigqueue()的同时,仍然支持早期的signal()信号安装函数,支持信号发送函数kill()。
信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。目前linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。
对于目前linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。
早期Unix系统只定义了32种信号,前32种信号已经有了预定义值,每个信号有了确定的用途及含义,并且每种信号都有各自的缺省动作。如按键盘的CTRL ^C时,会产生SIGINT信号,对该信号的默认反应就是进程终止。后32个信号表示实时信号,等同于前面阐述的可靠信号。这保证了发送的多个实时信号都被接收。
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
一些IO系统调用执行时,如 read 等待输入期间,如果收到一个信号,系统将中断read,转而执行信号处理函数. 当信号处理返回后,系统遇到了一个问题: 是重新开始这个系统调用,还是让系统调用失败?早期UNIX系统的做法是,中断系统调用,并让系统调用失败,比如read返回 -1,同时设置 errno 为 EINTR中断了的系统调用是没有完成的调用,它的失败是临时性的,如果再次调用则可能成功,这并不是真正的失败,所以要对这种情况进行处理,典型的方式为:
while (1) { n = read(fd, buf, BUFSIZ); if (n == -1 && errno != EINTR) { printf("read error\n"); break; } if (n == 0) { printf("read done\n"); break; } }
这样做逻辑比较繁琐,事实上,我们可以从信号的角度来解决这个问题,安装信号的时候,设置 SA_RESTART属性,那么当信号处理函数返回后,被该信号中断的系统调用将自动恢复。
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
参数的how:
假设现在阻塞的信号表是{SIGSEGV,SIGSUSP},我们执行了以下代码:
sigset_t x,y; sigemtyset(&x); sigemtyset(&y); sigaddset(&x,SIGUSR1); sigprocmask(SIG_BLOCK,&x,&y);
则新的阻塞表是{SIGSEGV,SIGSUSP,SIGUSR1},y中保存的是{SIGSEGV,SIGSUSP}
如果我接着执行sigprocmask(SIG_UNBLOCK,&x,NULL),
则新的阻塞表是{SIGSEGV,SIGSUSP}
如果我接着执行sigprocmask(SIG_SET,&x,NULL);
则新的阻塞表是{SIGUSR1}。
注册信号处理函数。
typedef void (*sigaction_func)(int, siginfo_t*, void*); static void signal_handler(int signo, siginfo_t* siginfo, void* context) { …… } static sigaction_func signal_install_handler(void) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_sigaction = signal_handler; act.sa_flags = 0; act.sa_flags |= SA_SIGINFO; // 使用3个参数的信号处理函数 act.sa_flags |= SA_RESTART; // 信号返回时,重新调研系统函数。 if (sigaction(RES_SIGNAL, &act, &oact) < 0) { errmsg("not able to set up signal action handler"); } /* Return previously installed handler */ return oact.sa_sigaction; }
sigset_t intMask; sigfillset(&intMask); sigdelset(&intMask, SIGSEGV); // SIGSEGV = 11
sigfillset(&intMask); 执行后,intMask值如下,注意,第32和33 bit位为0,这两个位没有使用:
(gdb) p/t intMask $3 = {__val = {1111111111111111111111111111111001111111111111111111111111111111, 1111111111111111111111111111111111111111111111111111111111111111 <repeats 15 times>}}
sigdelset(&intMask, SIGSEGV); 执行后,intMask值如下,可见,第11位被清零:
(gdb) p/t intMask $6 = {__val = {1111111111111111111111111111111001111111111111111111101111111111, 1111111111111111111111111111111111111111111111111111111111111111 <repeats 15 times>}}
相关函数:longjmp, siglongjmp, setjmp
表头文件:#include <setjmp.h>
函数定义:int sigsetjmp(sigjmp_buf env, int savesigs)
函数说明:sigsetjmp()会保存目前堆栈环境,然后将目前的地址作一个记号,
而在程序其他地方调用siglongjmp()时便会直接跳到这个记号位置,然后还原堆栈,继续程序的执行。
参数env为用来保存目前堆栈环境,一般声明为全局变量。
参数savesigs若为非0则代表搁置的信号集合也会一块保存。
当sigsetjmp()返回0时代表已经做好记号上,若返回非0则代表由siglongjmp()跳转回来。
返回:若直接调用则为0,若从siglongjmp调用返回则为非0。
#include <stdio.h> #include <signal.h> #include <setjmp.h> #include <unistd.h> #include <sys/time.h> sigjmp_buf jmp_env; static void connect_alarm(int) { siglongjmp(jmp_env, 1); } int main() { // 当超时时间sec_timeout大于等于运行时间run_time时会跳过printf("running...\n"); int sec_timeout = 3; int run_time = 2; printf("timeout = %d, run time = %d\n", sec_timeout, run_time); if (sec_timeout) { // 超过用alarm函数设置的时间时产生此信号,调用connect_alarm函数 signal(SIGALRM, connect_alarm); alarm(sec_timeout); printf("set timeout\n"); if (sigsetjmp(jmp_env, 1)) { printf("timeout\n"); goto out; } } sleep(run_time); printf("running...\n"); out: if (sec_timeout) { // 取消先前设置的闹钟 alarm(0); printf("cancel timeout\n"); } return 0; }