1,原型
前面提到过,setjmp和longjmp函数可以实现非局部跳转,实现goto语句都不易实现的功能。
上一节又提到过,在进入信号处理程序的时候,我们总是先屏蔽当前的信号,以避免在执行这个信号处理程序的时候又收到了一模一样的信号。当信号处理程序返回时,要先回复信号屏蔽字,解除屏蔽。
现在问题来了,如果在信号处理程序中调用setjmp和longjmp,实现非局部跳转,那信号屏蔽字会恢复吗??
不同的操作系统对这个问题的回答是不同的。
所以在信号处理程序中我们应当使用一套特殊的setjmp和longjmp,即sigsetjmp好siglongjmp.它有输入参数,让你可以控制是否恢复屏蔽字。原型如下:
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
vodi siglongjmp(sigjmp_buf env, int val);
其原型和用法都与setjmp和longjmp非常接近。只是多了一个savemask参数。其为0代表不保存、不恢复屏蔽字;为非0代表需要保存、恢复屏蔽字。屏蔽字也同样保存在env中。所以我们有了个新的类型sigjmp_buf.
2,例子
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <setjmp.h>
static sigjmp_buf jmpbuf;//信号处理程序中的跳转环境
static volatile sig_atomic_t canjump;//是否可以跳转的标志
void sig_usr_1(int signo);//信号处理程序
void pr_mask(const char *str); //打印当前屏蔽字
int main(){
//10.15 sigsetjmp和siglongjmp
// 1,注册信号
if(signal(SIGUSR1, sig_usr_1) == SIG_ERR)
{
printf("signal error!\n");
exit(1);
}
// 2,打印当前屏蔽字
pr_mask("starting main: ");
// 3,设置跳转的位置
if (sigsetjmp(jmpbuf, 1)) //1代表需要恢复屏蔽字
{
pr_mask("ending main: ");
exit(0);
}
// 4,跳转标注
canjump = 1;
// 5,死循环
while(1)
{
pause;
}
}
//定义信号处理程序
void sig_usr_1(int signo)
{
if (canjump)
{
printf("recived SIGUSR, signo = %d\n",signo);
pr_mask("sig_usr1: ");
canjump = 0;
siglongjmp(jmpbuf, 1); //跳转到sigsetjmp处,其使其返回1
}
}
//打印当前屏蔽字
void pr_mask(const char *str)
{
sigset_t sigset;
//获取当前屏蔽字
if (sigprocmask(0, NULL, &sigset) < 0)
{
printf("sigprocmask error !\n");
exit(1);
}
//判断一些在不在当前屏蔽字中
printf("%s", str);
if (sigismember(&sigset, SIGUSR1))
printf(" SIGUSR1");
if (sigismember(&sigset, SIGUSR2))
printf(" SIGUSR2");
if (sigismember(&sigset, SIGALRM))
printf(" SIGALRM");
printf("\n");
}
注意,这里用到了canjump这个变量。因为我们的跳转发生在信号处理程序中。而信号实际上是随时随地都可能触发的。如果在注册了信号之后,尚未设置跳转就触发了信号,此时jmpbuf还没有设置正确的值,跳转会发生错误。所以我们通过canjump这个变量告诉程序是不是可以跳转了。由于在主线程和异步的信号处理程序里都需要访问、修改这个变量,所以我们将其设为sig_atomic_t类型。这个类型保证其读写在一个机器指令内完成,因而不会被打断。sig_atomic_t不能是结构体,只能是数字类型,通常就是int。sig_atomic_t总是与volatile合用。这样,只有在我们设置完跳转之后,才进行跳转。
程序执行的结果如下:
➜ code ./study_Linux &
[1] 303
starting main: #主线程里没有屏蔽任何信号
➜ code kill -USR1 303 #发送USR1信号
recived SIGUSR, signo = 10 #进入了信号处理程序
sig_usr1: SIGUSR1 #信号处理程序中已经屏蔽了USR1
ending main: #回到了主函数,已经没有任何屏蔽