52 读书笔记:第10章 信号 (5)

10.15 sigsetjmp和siglongjmp函数

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

        在FreeBSD和Mac中,setjmp和longjmp保存和恢复信号屏蔽字。但是在Linux中并不执行这种操作。

        POSIX.1并没有说明setjmp和longjmp对信号屏蔽字的作用,而是定义了两个新函数sigsetjmp和siglongjmp。在信号处理程序中进行非局部转移时应当使用这两个函数。

       #include <setjmp.h>

       int sigsetjmp(sigjmp_buf env, int savesigs);
        // 返回值:若直接调用则返回0,若从siglongjmp调用则返回非0值
       void siglongjmp(sigjmp_buf env, int val);

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

        程序清单10-14演示了在信号处理程序被调用时,系统所设置的信号屏蔽字如何自动地包括刚被捕捉的信号。该程序也通过实例说明了如何使用sigsetjmp和siglongjmp函数。

        《UNIX环境高级编程》P266:程序清单10-14 信号屏蔽字、sigsetjmp和siglongjmp实例(有改动)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <signal.h>
#include <errno.h>
#include <setjmp.h>

static void sig_usr1(int signo);
static void sig_alrm(int signo);
void pr_mask(const char *str);

static sigjmp_buf                jmpbuf;
static volatile    sig_atomic_t    canjump;

int main(void)
{
    if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
        fprintf(stderr, "signal(SIGUSR1) error\n");
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        fprintf(stderr, "signal(SIGALRM) error\n");
    pr_mask("starting main: ");

    if (sigsetjmp(jmpbuf, 1)) {            // 将会保存进程中的信号屏蔽字
        pr_mask("ending main: ");
        exit(0);
    }

    canjump = 1;                        // sigsetjmp() 就绪

    for ( ; ; )
        pause();
}

static void sig_usr1(int signo)
{
    time_t    starttime;

    if (canjump == 0)
        return;

    pr_mask("starting sig_usr1: ");
    alarm(3);
    starttime = time(NULL);                    // 获取当前时间
    for ( ; ; )
        if (time(NULL) > starttime + 5)        // 运行5秒
            break;

    pr_mask("finishing sig_usr1: ");
    canjump = 0;
    siglongjmp(jmpbuf, 1);                    // 跳转至main函数
}

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

// 输出信号屏蔽字信息
void pr_mask(const char *str)
{
    sigset_t    sigset;
    int            errno_save;

    errno_save = errno;
    if (sigprocmask(0, NULL, &sigset) < 0)
        fprintf(stderr, "sigprocmask error\n");

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

    printf("\n");
    errno = errno_save;
}

        此程序演示了另一种技术,只要在信号处理程序中调用siglongjmp,就应使用这种技术。仅在调用sigsetjmp之后才将变量canjump设置为非0值。在信号处理程序中检测此变量,仅当它为非0值时才调用siglongjmp。这提供了一种保护机制,使得在jmpbuf(跳转缓冲)尚未由sigsetjmp初始化时,防止调用信号处理程序。因为信号可能在任何时候发生,所以在信号处理程序中,需要这种保护措施。

        在程序中使用了数据类型sig_atomic_t,这是由ISO C标准定义的变量类型,在写这种类型的变量时不会被中断。它意味着在具有虚拟存储器的系统上这种变量不会跨越页边界,可以用一条机器指令对其进行访问。这种类型的变量总是包括ISO类型修饰符volatile,其原因是:该变量由两个不同的控制线程——main函数和异步的信号处理程序访问。

        运行该程序:

$ ./14 &                                在后台启动进程
[1] 4924                                作业控制shell打印其进程ID
$ starting main: 

$ kill -USR1 4924                       向该进程发送SIGUSR1
starting sig_usr1: SIGUSR1 
$ in sig_larm: SIGUSR1 SIGALRM 
finishing sig_usr1: SIGUSR1 
ending main: 
                                        键入回车
[1]+  完成                  ./14

        当调用一个信号处理程序时,被捕捉到的信号加到进程的当前信号屏蔽字中。当从信号处理程序返回时,恢复原来的屏蔽字。另外,siglongjmp恢复了有sigsetjmp保存的信号屏蔽字。

        如果在Linux中将程序清单10-14中的sigsetjmp和siglongjmp分别替换称setjmp和longjmp,输出变为:

$ ./a.out &                                 在后台启动进程
[1] 4954                                    作业控制shell打印其进程ID
$ starting main: 

$ kill -USR1 4954                           向进程发送SIGUSR1
starting sig_usr1: SIGUSR1 
$ in sig_larm: SIGUSR1 SIGALRM 
finishing sig_usr1: SIGUSR1 
ending main: SIGUSR1 
                                            键入回车
[1]+  完成                  ./a.out

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

你可能感兴趣的:(读书笔记,《UNIX环境高级编程》)