sigsetjmp和siglongjmp函数

sigsetjmp和siglongjmp函数

在APUE 7.10节,我们描述过setjmp和longjmp函数,它曾被用于非本地分支跳转。longjmp函数常被信号处理函数调用,用于返回程序的主循环,而不是返回到句柄(handler)。我们已经在FIGURE10.8和10.11见过了。

在调用longjmp过程中有个问题。当信号被捕获时,使用该信号进入信号处理函数(signal-catching function),并自动的把当前信号添加到进程的信号掩码里。这是为了防止后续产生相同的信号中断了当前信号处理函数的执行。如果我们的longjmp跳出信号处理函数,进程的信号掩码会怎样???

在FreeBSD 5.2.1和MacOS X 10.3中,setjmplongjmp保存和恢复信号掩码。Linux 2.4.22和Solaris 9,并不这么做。FreeBSD和Mac OS X提供了_setjmp_longjmp函数,它们并不保存和恢复信号掩码。

POSIX.1并没有指定允许这两种形式的行为在setjmplongjmp函数上(保存和恢复信号掩码)。而是用两个新函数来代替——sigsetjmpsiglongjmp函数,这两个函数被定义在POSIX.1中。当从信号处理函数中跳转分支时应该一直使用这两个函数(因为它们可以保存和恢复信号掩码)。

#include <setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);

Returns: 0 if called directly, nonzero if returning from a call to siglongjmp

void siglongjmp(sigjmp_buf env, int val);

不同于setjmplongjmp函数的地方是sigsetjmp有一个额外的参数。如果savemask是非零值,那么sigsetjmp同时在env中保存当前进程的信号掩码。当siglongjmp被调用时,如果env参数中已经保存了sigsetjmp保存的值,那么siglongjmp恢复保存的信号掩码。

例子

Figure 10.20中的程序演示了当一个信号处理函数自动被调用时信号掩码是如何被系统使用的。这个程序同样演示了sigsetjmpsiglongjmp函数的用法。

这个程序演示当siglongjmp在信号处理函数中的任意时刻被调用的技术。我们在只当调用了sigsetjmp后设置canjump变量值为一个非零值。这个变量在信号处理函数中同样被检查,并且siglongjmp函数仅在canjump为非零值时才被调用。这是为了防止当jmpbuf还没被sigsetjmp初始化信号处理函数被过早或过晚的调用。(在这个没有具体意义的示例中,我们在siglongjmp调用后快速的终止该程序,但是在一个大型程序中,信号处理程序在调用siglongjmp后还会保留很久。)这类检查通常不使用在longjmp函数的调用中。因为一个可能在任何时间发生,无论如何我们都要在信号处理函数中增加一些保护。

在这我们使用数据类型sig_atomic_t,它由ISO C标准定义,用来定义那些没被中断才能写的变量(variable that can be written without being interrupted.)。这意味着该类变量不能扩展到使用虚拟内存的系统的不同页面中,并且只能在单机器指令系统中被访问。我们一直为这类数据类型使用ISO的限定符volatile,因为这个变量被两个不同的线程控制访问:main函数和异步执行的信号处理函数。Figure 10.21显示了该程序的时间线。

我们可以把Figure10.21分成三个部分:左边(属于main),中间(sig_usr1),和右边(sig_alrm)。当进程在左边执行时,它的信号掩码是0(没有信号被阻塞)。当执行到中间时,它的信号掩码是SIGUSR1。当执行到右边时,它的信号掩码是SIGUSR1|SIGALRM

当FIGURE10.20被执行后它的输出如下:

   $ ./a.out &                      start process in background
   starting main:
   [1]   531                        the job-control shell prints its process ID
   $ kill -USR1 531                 send the process SIGUSR1
   starting sig_usr1: SIGUSR1
   $ in sig_alrm: SIGUSR1 SIGALRM
   finishing sig_usr1: SIGUSR1
   ending main:
                                    just press RETURN
   [1] + Done          ./a.out &

输出就如我们所希望的那样:当我们的信号处理函数被调用时,信号被捕获并添加到当前进程的信号掩码中。当信号处理函数返回时原始的掩码被恢复。同样,siglongjmp也恢复了由sigsetjmp保存的信号掩码。

如果我们在linux平台上改变Figure10.20的程序,把调用的sigsetjmpsiglongjmp函数用setjmplongjmp替换。最后一行的输出结果会变成:

 ending main: SIGUSR1

这意味着main函数在调用setjmp后的执行过程中信号掩码是SIGUSR1。这应该不是我们所希望的。

Figure 10.20. Example of signal masks, sigsetjmp, and siglongjmp

#include "apue.h"
#include <setjmp.h>
#include <time.h>

static void                         sig_usr1(int), sig_alrm(int);
static sigjmp_buf                   jmpbuf;
static volatile sig_atomic_t        canjump;

int
main(void)
{
    if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");
    pr_mask("starting main: ");     /* Figure 10.14 */

    if (sigsetjmp(jmpbuf, 1)) {
        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 */
}

static void
sig_alrm(int signo)
{
    pr_mask("in sig_alrm: ");
}

figure10-21

发表回复