在apue10.10节中,sleep2函数为避免alarm和pause之间的竞争条件,使用了setjmp和longjmp,原始实现如下:
#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 nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
alarm(nsecs); /* start the timer */
pause(); /* next caught signal wakes us up */
return(alarm(0)); /* turn off timer, return unslept time */
}
上述sleep1函数有一个问题,设置alarm和调用pause之间有时间间隔,即alarm可能在调用pause函数之前超时,进程随后调用pause而被挂起,由于没有alarm唤醒进程,它将被永远挂起。
解决上述问题可以使用setjmp和longjmp,修改后的sleep2函数版本如下:
#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 nsecs)
{
if (signal(SIGALRM, sig_alrm) == SIG_ERR)
return(nsecs);
if (setjmp(env_alrm) == 0) {
alarm(nsecs); /* start the timer */
pause(); /* next caught signal wakes us up */
}
return(alarm(0)); /* turn off timer, return unslept time */
}
上述代码中信号处理程序返回时保证不在执行pause函数,这样就避免了alarm和pause的竞争条件。但是这样的实现也会带来一个问题,即调用longjmp会提早终止其他的信号处理程序。在linux系统中,当一个信号在执行他的信号处理程序时,它只会阻塞同类型的信号,但是其他类型的信号就可以打断当前正在执行的信号处理程序。设想以下情景:当进程正在执行一个SIGINT的信号处理程序时,一个alarm信号超时,新的信号会中断SIGINT的信号处理程序转而去执行alarm的handler,而在alarm的handler中我们调用longjmp返回,这样就会跳过SIGINT信号处理程序的栈帧而直接返回到了main黄色函数,相当于longjmp提早终止了SIGINT的信号处理程序。一个例子如下:
#include "apue.h"
unsigned int sleep2(unsigned int);
static void sig_int(int);
int
main(void)
{
unsigned int unslept;
if (signal(SIGINT, sig_int) == SIG_ERR)
err_sys("signal(SIGINT) error");
unslept = sleep2(5);
printf("sleep2 returned: %u\n", unslept);
exit(0);
}
static void
sig_int(int signo)
{
int i, j;
volatile int k;
/*
* Tune these loops to run for more than 5 seconds
* on whatever system this test program is run.
*/
printf("\nsig_int starting\n");
for (i = 0; i < 300000; i++)
for (j = 0; j < 4000; j++)
k += i * j;
printf("sig_int finished\n");
}
sig_int 中的程序执行时间会超过5秒钟,即大于我们设置的alarm时间,这样alarm就会在sig_int返回前超时,从而打断sig_int的信号处理程序。执行程序得到:
Rev-1-0:~/文档$ ./test10_6
^C
sig_int starting
sleep2 returned: 0
从中可见longjmp使sig_int的信号处理程序提前终止。没有执行longjmp返回时栈中空间是这样的:
(栈的底部,高地址)
main的栈帧
sleep2的栈帧
sig_int的栈帧
sig_alrm的栈帧
执行longjmp返回之后栈空间是这样的:
(栈的底部,高地址)
main的栈帧
sleep2的栈帧
即longjmp直接跳过了sig_int的栈帧,从而导致了sig_int的异常终止。