曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 6642|回复: 0
打印 上一主题 下一主题

setjmp()/longjmp() 与 sigsetjmp()/siglongjmp() 区别

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34395
跳转到指定楼层
楼主
发表于 2012-8-7 18:19:34 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
setjmp()/longjmp()  和 sigsetjmp()/siglongjmp()  都是非局部跳转函数(goto 是局部跳转语句,只能在函数内跳转),两者功能类似,不同的地方是 sigsetjmp() 使用的第一个参数类型是 sigjmp_buf (setjmp() 使用的是 jmp_buf )。此外,sigsetjmp() 还使用了第 2 个参数 savesigs ,如果该参数非零,那么在调用 sigsetjmp() 时所设置的信号掩码会保存在 env 参数中,这样使用 siglongjmp() 返回时使用相同的 env 参数就能还原为之前的信号掩码。如果 savesigs 为 0,那么信号掩码就既不会保存也不会还原。

注意,longjmp() 和 siglongjmp() 并不是异步信号安全函数。如果一个信号处理将正在更新数据结构的主函数打断,然后以非局部跳转(nonlocal goto)退出信号处理函数,那么主函数里的更新动作就没法完成,从而导致被操作的数据结构遭到破坏。解决这个问题的一个办法是使用 sigprocmask()  函数,可以在更新一些敏感数据时用它临时阻塞相应信号。

下面程序演示上述两个函数对的区别:
[C++] 纯文本查看 复制代码
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <signal.h>


static volatile sig_atomic_t can_jump = 0;


void print_signal_set (FILE *of, char *prefix, const sigset_t *sigset)
{
        int sig, cnt;
        cnt = 0;


        for (sig = 1; sig < NSIG; sig++) {
            if (sigismember(sigset, sig)) {
                cnt++;
                fprintf(of, "%s%d  (%s)\n", prefix, sig, strsignal(sig));
            }
        }
        if (cnt == 0)
          fprintf (of, "%s<empty signal set>\n", prefix);
}


int print_sigmask(FILE *of, const char *msg)
{
        sigset_t curr_mask;
        if (msg != NULL)
           fprintf (of, "%s", msg);


        if (sigprocmask(SIG_BLOCK, NULL, &curr_mask) == -1)
           return -1;


        print_signal_set(of, "\t\t", &curr_mask);


        return 0;
}




#ifdef USE_SIGSETJMP
static sigjmp_buf senv;
#else
static jmp_buf env;
#endif


static void handler(int sig)
{
        printf ("Received signal %d (%s), signal mask is:\n", sig, strsignal(sig));
        print_sigmask (stdout, NULL);
        if (!can_jump) {
           printf ("'env' buffer not yet set, doing a simple return\n");
           return;
        }


        #ifdef USE_SIGSETJMP
           siglongjmp(senv, 1);
        #else
           longjmp(env, 1);
        #endif
}
int main(int argc, char *argv[])
{
        struct sigaction sa;
        
        print_sigmask(stdout, "Sinal mask at startup:\n");


        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        sa.sa_handler = handler;
        if (sigaction(SIGINT, &sa, NULL) == -1) {
           perror("sigaction");
           exit (EXIT_FAILURE);
        }
        
#ifdef USE_SIGSETJMP
        printf ("Calling sigsetjmp()\n");
        if (sigsetjmp(senv, 1) == 0)
#else
        printf ("Calling setjmp()\n");
        if (setjmp(env) == 0)
#endif
           can_jump = 1;
        else
           print_sigmask(stdout, "After jump from handler, signal mask is:\n");


        for(;;)
           pause();
}

在上面程序中,使用宏 USE_SIGSETJMP 来控制是使用setjmp()/longjmp()  还是使用  sigsetjmp()/siglongjmp()
下面先测试不定义 USE_SIGSETJMP ,即使用 setjmp() 和 longjmp() 时的情况:
[beyes@beyes   signal]$ ./sigmask_longjmp
Sinal mask at startup:
        <empty signal set>
Calling setjmp()
^CReceived signal 2 (Interrupt), signal mask is:
        2  (Interrupt)
After jump from handler, signal mask is:
        2  (Interrupt)
^C^C^C^C^C^C^
由上面输出可见,在 longjmp() 跳回主函数后,Ctrl + c (发送 SIGINT 信号)已经不会再被捕捉。如果在程序中定义 USE_SIGSETJMP ,那么可以看到:
[beyes@beyes   signal]$ ./sigmask_longjmp
Sinal mask at startup:
        <empty signal set>
Calling sigsetjmp()
^CReceived signal 2 (Interrupt), signal mask is:
        2  (Interrupt)
After jump from handler, signal mask is:
        <empty signal set>
^CReceived signal 2 (Interrupt), signal mask is:
        2  (Interrupt)
After jump from handler, signal mask is:
        <empty signal set>
^CReceived signal 2 (Interrupt), signal mask is:
        2  (Interrupt)
After jump from handler, signal mask is:
        <empty signal set>
由输出可以看到,程序每次都能捕捉 SIGINT 信号,并且在使用 siglongjmp() 跳回主函数中时,信号屏蔽码都是被清空的。

从上面的两种输出可以知道,在捕捉到一个信号并进入信号捕捉函数时,当前信号会被自动的加到进程的信号屏蔽字中,这样就阻止了后来的这种信号所引发的处理程序的执行,形象的说一个医生在接待了一个患者后,如果后面再有相同的病患过来,他是不会接待的。而在使用 longjmp() 返回主函数后,信号屏蔽字并没有被恢复,因此后面来再多的同样的信号也不会被处理。但是使用 siglongjmp() 的情况则不同,它是能够恢复信号屏蔽字的。上面要被恢复的信号屏蔽字为 sa.sa_mask,它在调用 sigaction() 函数前被 sigemptyset() 清空,因此在 siglongjmp() 出来后恢复的也是同样的被清空状态,所以信号处理函数能够再次得到执行。

此外,还需要注意到,一个信号在任何一个时间点都是能送达的,因此它完全可能在 sigsetjmp() 或者 setjmp() 设置之前就到达,那么在这种情况下进入信号处理函数,并在里面调用了 siglongjmp() 或者 longjmp() ,那么就会使用一个未初始化的 senv 或 env 缓冲。为了防止这种情况,在程序中引入了一个监控变量 can_jump ,如果 can_jump 没有被设置时,信号处理函数只是简单的退出,而不会进行非局部跳转;也只有在 sigsetjmp() 或 setjmp() 设置完后,才会将该变量设为 1 (sigsetjmp() 和 setjmp() 在不是 siglongjmp() 或 longjmp() 的返回下,它们的返回值为 0,否则不为 0)。当然,另外一个办法是也可以在建立信号处理函数之前就调用 sigsetjmp() 或 setjmp()  --- 实际上,在一个复杂的程序里,很难保证说一定能让这两个步骤按此顺序去执行 --- 因此较为简单的方法还是像上面使用一个监控变量。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-5-4 16:43 , Processed in 0.072298 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表