最近换了一种方式看APUE,直接看source code,如果这个程序看完没有疑问,并且执行结果与预想中的一样就跳过,看到第10章信号的时候源码中居然有setjmp和longjmp,之前的理解是这两个函数不就是加强版的goto吗?前几天写了篇文章段错误segment fault分析,函数调用会涉及到函数信息入栈出栈,所以这看似简单的两个函数,程序执行背后有着复杂的函数栈切换过程。
apue信号章节中引入setjmp和longjmp函数是为了解决例子sleep1.c中存在的第三个问题。

#include    <signal.h>
#include    <unistd.h>

static void
sig_alrm(int signo)
{
    /* nothing to do, just return to wake up the pause */
}

unsigned int
sleep1(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return(seconds);
    alarm(seconds);     /* start the timer */
      /*alarm和pause之间可能有频繁的cpu切换,导致执行到pause前alarm已超时*/
    pause();            /* next caught signal wakes us up */
    return(alarm(0));   /* turn off timer, return unslept time */
}

上面代码在一个繁忙的系统中,可能alarm在调用pause之前超时,并调用了信号处理函数。如果发生了这种情况,在调用pause后,如果没有再捕获到其他信号,调用者将永远被挂起。
使用setjmp和longjmp可以避免该问题:

#include    <setjmp.h>
#include    <signal.h>
#include    <unistd.h>

static jmp_buf  env_alrm;

static void
sig_alrm(int signo)
{
    longjmp(env_alrm, 1);
}

unsigned int
sleep2(unsigned int seconds)
{
    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        return(seconds);
    if (setjmp(env_alrm) == 0) {
        alarm(seconds);     /* start the timer */
        pause();            /* next caught signal wakes us up */
    }
    return(alarm(0));       /* turn off timer, return unslept time */
}

在上面代码中即使alarm执行后,在执行pause前已经触发了信号处理函数,也不会导致程序卡到pause处,因为从longjmp跳到setjmp处返回值为1,不会走到返回值为0的逻辑。
维基百科setjmp中的例子非常之巧妙,介绍了基本用法、异常处理、信号处理,本篇笔记中的处理信号的例子作为其例子补充。