函数sigsetjmp和siglongjmp
在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。
但是,调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。(仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值)如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢?(setjmp和longjmp保存和恢复信号屏蔽字,还是不保存和恢复,不同的实现各有不同。)
为了允许两种形式的行为并存,POSIX.1并没有说明setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmp和siglongjmp。在信号处理程序中进行非局部转移时使用这两个函数。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接调用则返回0,若从siglongjmp调用返回则返回非0值
void siglongjmp(sigjmp_buf env, int val);
这两个函数与setjmp和longjmp之间的唯一区别是sigsetjmp增加了一个参数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带 非0 savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
示例代码
程序清单10-14演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如果自动地包括刚被捕捉到的信号。该程序也通过实例说明了如何使用sigsetjmp和siglongjmp函数。
使用setjmp和longjmp
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include <setjmp.h>
#include <time.h>
#include <errno.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
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
}
int
main(void)
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
perror("signal(SIGUSR1) error");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
perror("signal(SIGALRM) error");
pr_mask("starting main: ");
// if (sigsetjmp(jmpbuf, 1))
if(setjmp(jmpbuf))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for(; ;)
pause();
}
static void
sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
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 */
longjmp(jmpbuf,1);
}
static void
sig_alrm(int signo)
{
pr_mask("sig alarm");
}
使用sigsetjmp和siglongjmp
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
#include <setjmp.h>
#include <time.h>
#include <errno.h>
static void sig_usr1(int), sig_alrm(int);
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t canjump;
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
}
int
main(void)
{
if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
perror("signal(SIGUSR1) error");
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
perror("signal(SIGALRM) error");
pr_mask("starting main: ");
if (sigsetjmp(jmpbuf, 1))
// if(setjmp(jmpbuf))
{
pr_mask("ending main: ");
exit(0);
}
canjump = 1; /* now sigsetjmp() is OK */
for(; ;)
pause();
}
static void
sig_usr1(int signo)
{
time_t starttime;
if (canjump == 0)
return; /* unexpected signal, ignore */
pr_mask("starting sig_usr1: ");
alarm(3); /* SIGALRM in 3 seconds */
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 */
// longjmp(jmpbuf,1);
}
static void
sig_alrm(int signo)
{
pr_mask("sig alarm");
}
程序分析
执行starting main时,信号屏蔽字是 0(无信号被阻塞);执行starting sig_usr1,其信号屏蔽字是 SIGUSR1;执行in sig_alrm),其信号屏蔽字是 SIGUSR1 和 SIGALRM。
可见该输出与所期望的相同:当调用一个信号处理程序时,被捕捉到的信号会自动加到进程的当前信号屏蔽字中。当从信号处理程序返回时,siglongjmp 恢复了 sigsetjmp 所保存的信号屏蔽字。
如果在 Linux 中将该程序中的 sigsetjmp 和 siglongjmp 分别替换成 setjmp 和 longjmp,则最后一行输出将变成:ending main: SIGUSR1
这意味着在调用 setjmp 之后执行 main 函数时,其 SIGUSR1 是阻塞的,这多半不是所期望的。
此程序演示了另一种技术,只要在信号处理程序中调用siglongjmp,就应使用这种技术:仅在调用sigsetjmp之后才将变量canjump设置为非0值。在信号处理程序中检测此变量,仅当它为非0值时才调用siglongjmp。这提供了一种保护机制,使得在jmpbuf(跳转缓冲)尚未由sigsetjmp初始化时,防止调用信号处理程序。在一般的C代码中(不是信号处理程序),对于longjmp并不需要这种保护措施。但是,因为信号可能在任何时候发生,所以在信号处理程序中,需要这种保护措施。
在程序中使用了数据类型sig_atomic_t,这是由ISO C标准定义的变量类型,在写这种类型的变量时不会被中断。这种类型的变量总是包括ISO类型修饰符voaltile。