信號之sigsuspend函數


更改進程的信號屏蔽字可以阻塞所選擇的信號,或解除對它們的阻塞。使用這種技術可以保護不希望由信號中斷的代碼臨界區。如果希望對一個信號解除阻塞,然后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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM