更改進程的信號屏蔽字可以阻塞所選擇的信號,或解除對它們的阻塞。使用這種技術可以保護不希望由信號中斷的代碼臨界區。如果希望對一個信號解除阻塞,然后pause等待以前被阻塞的信號發生,則又將如何呢?假定信號時SIGINT,實現這一點的一種不正確的方法是:
sigset_t newmask, oldmask; sigemptyset(&newmask); sigaddset(&newmask, SIGINT); /* block SIGINT and save current signal mask */ if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); /* critical region of code */ if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); /* window is open */ pause(); /* wait for signal to occur */ /* continue processing */
如果在信號阻塞時將其發送給進程,那么該信號的傳遞就被推遲直到對它解除了阻塞。對應用程序而言,該信號好像發生在解除對SIGINT的阻塞和pause之間。如果發生了這種情況,或者如果在解除阻塞時刻和pause之間確實發生了信號,那么就產生了問題。因為我們可能不會再見到該信號,所以從這種意義上而言,在此時間窗口(解除阻塞和pause之間)中發生的信號丟失了,這樣就使pause永遠阻塞。
為了糾正此問題,需要在一個原子操作中先恢復信號屏蔽字,然后使進程休眠。這種功能是由sigsuspend函數提供的。
#include <signal.h> int sigsuspend( const sigset_t *sigmask ); 返回值:-1,並將errno設置為EINTR
將進程的信號屏蔽字設置為由sigmask指向的值。在捕捉到一個信號或發生了一個會終止該進程的信號之前,該進程被掛起。如果捕捉到一個信號而且從該信號處理程序返回,則sigsuspend返回,並且將該進程的信號屏蔽字設置為調用sigsuspend之前的值。
注意,此函數沒有成功返回值。如果它返回到調用者,則總是返回-1,並將errno設置為EINTR(表示一個被中斷的系統調用)。
實例
程序清單10-15顯示了保護臨界區,使其不被特定信號中斷的正確方法。
程序清單10-15 保護臨界區不被信號中斷
#include "apue.h" static void sig_int(int); int main(void) { sigset_t newmask, oldmask, waitmask; pr_mask("program start: "); if(signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); sigemptyset(&waitmask); sigaddset(&waitmask, SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask, SIGINT); /* * Block SIGINT and save current signal mask. */ if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error: "); /* * Critical region of code. */ pr_mask("in critical region: "); /* * Pause, allowing all signals except SIGUSR1. */ if(sigsuspend(&waitmask) != -1) err_sys("sigsuspend error"); pr_mask("after return from sigsuspend: "); /* * Reset signal mask which unblocks SIGINT. */ if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); /* * And continue processing... */ pr_mask("program exit: "); exit(0); } static void sig_int(int signo) { pr_mask("\nin sig_int: "); }
運行該程序的結果如下:
此結果與《UNIX環境高級編程》中的結果不一致,不同之處在於,in sig_int:SIGINT SIGUSR1。(在處理某一信號的時候會不會把該信號暫時阻塞直到對該信號的信號處理函數返回。)這里的問題與http://www.cnblogs.com/nufangrensheng/p/3516134.html中最后的問題其實是一個性質的問題。我想可能是由於實現版本不同造成的吧。
實例
sigsuspend的另一種應用是等待一個信號處理程序設置一個全局變量。程序清單10-16用於捕捉中斷信號和退出信號,但是希望僅當捕捉到退出信號時,才喚醒主例程。
程序清單10-16 用sigsuspend等待一個全局變量被設置
#include "apue.h" volatile sig_atomic_t quitflag; /* set nonzero by signal handler */ static void sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */ { signal(SIGINT, sig_int); signal(SIGQUIT, sig_int); if (signo == SIGINT) printf("\ninterrupt\n"); else if (signo == SIGQUIT) quitflag = 1; /* set flag for main loop */ } int main(void) { sigset_t newmask, oldmask, zeromask; if(signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); if(signal(SIGQUIT, sig_int) == SIG_ERR) err_sys("signal(SIGQUIT) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); /* * Block SIGQUIT and save current signal mask. */ if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); while(quitflag == 0) { sigsuspend(&zeromask); } /* * SIGQUIT has been caught and is now blocked; do whatever. */ quitflag = 0; /* * Reset signal mask which unblocks SIGQUIT. */ if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); exit(0); }
注意,程序清單10-16中標記為紅色字體的兩行在《UNIX環境高級編程》中本來是沒有的,但是不加這兩行,在我的運行環境下(Red Hat Linux 2.6.18)得不到跟書中一樣的運行結果:
書中的運行結果(可以反復鍵入中斷字符):
我的運行結果(鍵入一次中斷字符后,第二次鍵入中斷字符就退出了):
而在程序中加入上面帶紅色標記的語句后,則可以得到與書中同樣的結果。
出現上面現象的原因是:對於書中的UNIX版本:一旦對給定的信號設置了一個動作,那么在調用sigaction(signal函數的實現其實就是調用sigaction)顯式地改變它之前,該設置就一直有效。而對於我的版本:在進程每次接到信號對其進行處理時,隨即將該信號動作復位為默認值(早期版本)。詳情可參考:http://www.cnblogs.com/nufangrensheng/p/3515945.html
實例
可以用信號實現父、子進程之間的同步,這是信號應用的另一個實例。程序清單10-17包含了http://www.cnblogs.com/nufangrensheng/p/3510306.html中提到的五個例程的實現,它們是:TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT和WAIT_CHILD。
程序清單10-17 父子進程可用來實現同步的例程
#include "apue.h" static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */ static sigset_t newmask, oldmask, zeromask; static void sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */ { sigflag = 1; } void TELL_WAIT(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("signal(SIGUSR1) error"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("signal(SIGUSR2) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGUSR1); sigaddset(&newmask, SIGUSR2); /* * Block SIGUSR1 and SIGUSR2, and save current signal mask. */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); } void TELL_PARENT(pid_t pid) { kill(pid, SIGUSR2); /* tell parent we're done */ } void WAIT_PARENT(void) { while(sigflag == 0) sigsuspend(&zeromask); /* and wait for parent */ sigflag = 0; /* * Reset signal mask to original value. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); } void TELL_CHILD(pid_t pid) { kill(pid, SIGUSR1); } void WAIT_CHILD(void) { while(sigflag == 0) sigsuspend(&zeromask); /* and wait for child */ sigflag = 0; /* * Reset signal mask to original value. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); }
其中使用了兩個用戶定義的信號:SIGUSR1由父進程發送給子進程,SIGUSR2由子進程發送給父進程。
如果在等待信號發生時希望去休眠,則使用sigsuspend函數是非常合適的,但是如果在等待信號期間希望調用其他系統函數,那么將會怎樣呢?不幸的是,在單線程環境下對此問題沒有妥善的解決方法。如果可以使用多線程,則可專門安排一個線程處理信號。
如果不使用線程,那么我們能盡力做到最好的是,當信號發生時,在信號捕捉程序中對一個全局變量置1.
本篇博文內容摘自《UNIX環境高級編程》(第二版),僅作個人學習記錄所用。關於本書可參考:http://www.apuebook.com/。