在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的異常終止。