最近换了一种方式看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中的例子非常之巧妙,介绍了基本用法、异常处理、信号处理,本篇笔记中的处理信号的例子作为其例子补充。