apue程序清單10_6中信號處理程序提早終止的問題

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



發佈了15 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章