#include <setjmp.h> int sigsetjmp(sigjmp_buf, int savemask); /* 返回值:若直接调用,返回 0;若从 siglongjmp 调用返回,则返回非 0 */ void siglongjmp(sigjmp_buf env, int val);
这两个函数与 setjmp、longjmp 之间的唯一区别是 sigsetjmp 增加了一个参数:如果 savemask 非 0,则 sigsetjmp 在 env 中保存进程的当前信号屏蔽字,以便调用 siglongjmp 时从其中恢复保存的信号屏蔽字。
下面这个程序演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地包括被捕捉到的信号。
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <setjmp.h> #include <errno.h> // extern void pr_mask(const char *); void pr_mask(const char *str){ sigset_t sigset; int errno_save; errno_save = errno; // we can be called by signal handlers if(sigprocmask(0, NULL, &sigset) < 0){ printf("sigprocmask error\n"); }else{ printf("%s", str); if(sigismember(&sigset, SIGINT)) printf(" SIGINT"); if(sigismember(&sigset, SIGQUIT)) printf(" SIGQUIT"); if(sigismember(&sigset, SIGUSR1)) printf(" SIGUSR1"); if(sigismember(&sigset, SIGALRM)) printf(" SIGALRM"); /* remaining signals can go here */ printf("\n"); } errno = errno_save; // restroe errno } static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump; static void sig_usr1(int signo){ if(canjump == 0) return; // unexpected signal, ignore pr_mask("starting sig_usr1:"); alarm(3); // SIGALRM in 3 seconds time_t starttime = time(NULL); for(;;) // busy wait for 5 seconds if(time(NULL) > starttime + 5) break; pr_mask("finishing sig_usr1:"); canjump = 0; siglongjmp(jmpbuf, 1); // jump back to main, don't return } static void sig_alrm(int signo){ pr_mask("in sig_alrm:"); } int main(void){ if(signal(SIGUSR1, sig_usr1) == SIG_ERR) printf("signal(SIGUSR1) error\n"); if(signal(SIGALRM, sig_alrm) == SIG_ERR) printf("signal(SIGALRM) error\n"); pr_mask("starting main:"); if(sigsetjmp(jmpbuf, 1)){ pr_mask("ending main:"); exit(0); } canjump = 1; // now sigsetjmp() is OK for(;;) pause(); }
此程序中演示了另一种只要在信号处理程序中调用 siglongjmp 就应使用的技术:仅在调用 sigsetjmp 之后才将变量 canjump 设置为非 0,在信号处理程序中检测此变量,仅当它为非 0 时才调用 siglongjmp。这提供了一种保护机制,以免 jmpbuf 还未初始化时就调用信号处理程序,因为信号可能在任何时候发生。
另外,程序中使用了数据类型 sig_atomic_t,在写这种类型的变量时不会被中断。这意味着这种变量在具有虚拟存储器的系统上不会跨越页边界,可以用一条机器指令对其进行访问。这种类型的变量总是包括类型修饰符 volatile,其原因是:该变量将由两个不同的控制线程 main 函数和异步执行的信号处理程序访问。
下图显示了此程序的执行时间顺序。在进程执行左面部分时(对应 main),信号屏蔽字是 0(无信号被阻塞);执行中间部分是(对应 sig_usr1),其信号屏蔽字是 SIGUSR1;执行右面部分时(对应 sig_alrm),其信号屏蔽字是 SIGUSR1 和 SIGALRM。
运行该程序的结果如下。
$ ./sigjump.out & # 在后台启动程序 [1] 16011 $ starting main: # 这是 main 函数的输出 $ $ kill -USR1 16011 # 向该后台进程发送 SIGUSR1 信号 starting sig_usr1: SIGUSR1 in sig_alrm: SIGUSR1 SIGALRM finishing sig_usr1: SIGUSR1 ending main: [1]+ Done ./sigjump.out
可见该输出与所期望的相同:当调用一个信号处理程序时,被捕捉到的信号会自动加到进程的当前信号屏蔽字中。当从信号处理程序返回时,siglongjmp 恢复了 sigsetjmp 所保存的信号屏蔽字。
如果在 Linux 中将该程序中的 sigsetjmp 和 siglongjmp 分别替换成 setjmp 和 longjmp,则最后一行输出将变成:
ending main: SIGUSR1
这意味着在调用 setjmp 之后执行 main 函数时,其 SIGUSR1 是阻塞的,这多半不是所期望的。