7.10 节说明了用于非局部转移的 setjmplongjmp 函数。在信号处理程序中经常调用longjmp函数以返回到程序的主循环中,而不是从该处理程序返回。图10-8和图10-11中已经出现了这种情况。

但是,调用longjmp有一个问题。当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。如果用longjmp跳出信号处理程序,那么,对此进程的信号屏蔽字会发生什么呢?

在FreeBSD 8.0和Mac OS X 10.6.8中,setjmplongjmp保存和恢复信号屏蔽字。但是, Linux 3.2.0和Solaris 10并不执行这种操作,虽然Linux支持提供BSD行为的选项。FreeBSD 8.0和Mac OS X 10.6.8提供函数_setjmp_longjmp,它们也不保存和恢复信号屏蔽字。

为了允许两种形式并存,POSIX.1并没有指定setjmplongjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmpsiglongjmp。在信号处理程序中进行非局部转移时应当使用这两个函数。

#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savemask);
//返回值:若直接调用,返回0;若从siglongjmp调用返回,则返回非0
void siglongjmp(sigjmp_buf env, int val);

这两个函数和setjmplongjmp 之间的唯一区别是sigsetjmp 增加了一个参数。如果savemask非0,则sigsetjmpenv中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0 savemasksigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。

实例

图10-20中的程序演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地包括刚被捕捉到的信号。此程序也示例说明了如何使用sigsetjmpsiglongjmp函数。

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

static void                        sig_usr1(int);
static void                        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: ");        /* {Prog prmask} */

    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: ");
}

图10-20 信号屏蔽、sigsetjmp和siglongjmp实例

此程序演示了另一种技术,只要在信号处理程序中调用 siglongjmp 就应使用这种技术。仅在调用sigsetjmp之后才将变量canjump设置为非0值。在信号处理程序中检测此变量,仅当它为非0值时才调用siglongjmp。这提供了一种保护机制,使得在jmpbuf(跳转缓冲)尚未由sigsetjmp 初始化时,防止调用信号处理程序。(在本程序中,siglongjmp 之后程序很快就结束,但是在较大的程序中,在 siglongjmp 之后的较长一段时间内,信号处理程序可能仍旧被设置)。在一般的C代码中(不是信号处理程序),对于longjmp并不需要这种保护措施。但是,因为信号可能在任何时候发生,所以在信号处理程序中,需要这种保护措施。

在程序中使用了数据类型sig_atomic_t,这是由ISO C标准定义的变量类型,在写这种类型变量时不会被中断。这意味着在具有虚拟存储器的系统上,这种变量不会跨越页边界,可以用一条机器指令对其进行访问。这种类型的变量总是包括ISO类型修饰符volatile,其原因是:该变量将由两个不同的控制线程——main 函数和异步执行的信号处理程序访问。图10-21显示了此程序的执行时间顺序。可将图10-21分成三部分:左面部分(对应于main),中间部分(sig_usr1)和右面部分(sig_alrm)。在进程执行左面部分时,信号屏蔽字是 0(没有信号是阻塞的)。而执行中间部分时,其信号屏蔽字是SIGUSR1。执行右面部分时,信号屏蔽字是SIGUSR1|SIGALRM

图10-21 处理两个信号的实例程序的时间顺序

AMY:个人不推荐用这个东西,如此简单的一个demo如果没有图解的话就已经非常让人难以理解,如果正常开发中使用这种会给同事带来极大的麻烦,当然也分什么团队了。。

执行图10-20程序,得到下面的输出:

$ ./a.out &             在后台启动进程
starting main:
[1]  531              作业控制shell打印其进程ID
$ kill -USR1 531          向该进程发送SIGUSR1
starting sig_usr1: SIGUSR1
$ in sig_alrm: SIGUSR1 SIGALRM
finishing sig_usr1: SIGUSR1
ending main:
#键入回车
[1] + Done     ./a.out &

该输出与我们所期望的相同:当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中。当从信号处理程序返回时,恢复原来的屏蔽字。另外,siglongjmp 恢复了由sigsetjmp所保存的信号屏蔽字。

如果在Linux中将图10-20程序中的sigsetjmpsiglongjmp分别替换成setjmplongjmp(在FreeBSD中,则替换成_setjmp_longjmp),则最后一行输出变成:

ending main: SIGUSR1

这意味着在调用 setjmp之后执行 main 函数时,其 SIGUSR1 是阻塞的。这多半不是我们所希望的。

results matching ""

    No results matching ""