APUE——alarm與pause、sigsetjmp、siglongjmp、sigsuspend

1、alarm與pause

  1. 每個進程只能有一個alarm,如果之前有個alarm,在本次alarm執行完畢的時候如果還沒有結束,則本次alarm返回上次alarm剩餘的值。如果本次alarm執行的結果爲0,則取消原來的鬧鐘。
  2. SIGALARM的默認執行動作是終止進程,但是大多數鬧鐘會捕獲該信號,如果要捕獲該信號,應該在alarm執行前,註冊信號處理函數!
    在這裏插入圖片描述
    在這裏插入圖片描述

1.1 例子分析

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

2、sigsetjmp、siglongjmp

  1. sigsetjmp與siglongjmp是爲了解決,當捕捉信號進入信號處理函數時後,該信號被進入信號屏蔽集,但是如果跳轉後,該信號不會自動從信號屏蔽集中清除的問題,而正常的處理函數返回時,會自動從屏蔽集裏,清除該信號。
#include <setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);
返回值:若直接調用則返回0,若從siglongjmp調用返回則返回非0void siglongjmp(sigjmp_buf env, int val);
函數說明:sigsetjmp()會保存目前堆棧環境,然後將目前的地址作一個記號,
而在程序其他地方調用siglongjmp()時便會直接跳到這個記號位置,然後還原堆棧,繼續程序的執行。 
參數env爲用來保存目前堆棧環境,一般聲明爲全局變量 
參數savemask若爲非0則代表擱置的信號集合也會一塊保存 
當sigsetjmp()返回0時代表已經做好記號上,若返回非0則代表由siglongjmp()跳轉回來。 
返回:若直接調用則爲0,若從siglongjmp調用返回則爲非0
在信號處理程序中經常調用longjmp函數以返回到程序的主循環中,而不是從該處理程序返回。

但是,調用longjmp有一個問題。當捕捉到一個信號時,進入信號捕捉函數,此時當前信號被自動地加到進程的信號屏蔽字中。這阻止了後來產生的這種信號中斷該信號處理程序。(僅當從信號捕捉函數返回時再將進程的信號屏蔽字復位爲原先值:鏈接)如果用longjmp跳出信號處理程序,那麼,對此進程的信號屏蔽字會發生什麼呢()?(setjmp和longjmp保存和恢復信號屏蔽字,還是不保存和恢復,不同的實現各有不同。)

爲了允許兩種形式的行爲並存,POSIX.1並沒有說明setjmp和longjmp對信號屏蔽字的作用,而是定義了兩個新函數sigsetjmp和siglongjmp。在信號處理程序中進行非局部轉移時使用這兩個函數。

  1. 下面例子中在執行完sigsetjmp後,將canjump 賦1,爲了避免信號因爲隨時可能發生,導致先siglongjmp的情況
#include "apue.h"
#include <setjmp.h>
#include <time.h>

static void            sig_usr1(int), sig_alrm(int);
static sigjmp_buf        jmpbuf;
static volatile sig_atomic_t     canjump;

int
main(void)
{
    if (signal(SIGUSR1, sig_usr1) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");

    if (signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");
    
    pr_mask("starting main: ");

    if (sigsetjmp(jmpbuf, 1))
    {
        pr_mask("ending main: ");
        exit(0);
    }
    canjump = 1;    /* now sigsetjmp() is OK */

    for(; ;)
        pause();
}

static void
sig_usr1(int signo)
{    
    time_t starttime;

    if (canjump == 0)
        return;        /* unexpected signal, ignore */

    pr_mask("starting sig_usr1: ");
    alarm(3);        /* SIGALRM in 3 seconds */
    starttime = time(NULL);
    for(; ;)        /* busy wait for 5 seconds */
        if (time(NULL) > starttime + 5)
            break;
    pr_mask("finishing sig_usr1: ");
    
    canjump = 0;
    siglongjmp(jmpbuf, 1);    /* jump back to main, don't return */
}


static void 
sig_alrm(int signo)
{
    pr_mask("in sig_alrm: ");
}

3、sigsuspend

相關鏈接
問題引入:
如果在信號阻塞時將其發送給進程,那麼該信號的傳遞就被推遲直到對它解除了阻塞。對應用程序而言,該信號好像發生在解除對SIGINT的阻塞和pause之間。如果發生了這種情況,或者如果在解除阻塞時刻和pause之間確實發生了信號,那麼就產生了問題。因爲我們可能不會再見到該信號,所以從這種意義上而言,在此時間窗口(解除阻塞和pause之間)中發生的信號丟失了,這樣就使pause永遠阻塞。

爲了糾正此問題,需要在一個原子操作中先恢復信號屏蔽字,然後使進程休眠。這種功能是由sigsuspend函數提供的。

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 */
#include <signal.h>

int sigsuspend( const sigset_t *sigmask );
返回值:-1,並將errno設置爲EINTR
  1. 在sigprocmask和pause之間發生了信號,進程還未pause,那麼如果後續沒有信號後,進程一直在pause,永久掛起
  2. sigsuspend是爲了解決前面阻塞了一個信號後,解除阻塞,並且掛起的操作如果進程切換會永久掛起的問題。將其變成原子操作
  3. sigsuspend是臨時替換進程信號屏蔽集,並將進程掛起,如果接收到信號,且執行完信號處理函數後,會繼續執行進程,且將臨時信號屏蔽集替換爲原信號屏蔽集!
    在這裏插入圖片描述
  4. 將進程的信號屏蔽字設置爲由sigmask指向的值。在捕捉到一個信號或發生了一個會終止該進程的信號之前,該進程被掛起。如果捕捉到一個信號而且從該信號處理程序返回,則sigsuspend返回,並且將該進程的信號屏蔽字設置爲調用sigsuspend之前的值。
  5. 此函數沒有成功返回值。如果它返回到調用者,則總是返回-1,並將errno設置爲EINTR(表示一個被中斷的系統調用)。
    sigsuspend細述

3.1 例子1

#include "apue.h"

static void sig_int(int);

void fun();
void pr_mask(const char *str)
{
    sigset_t    sigset;
    int        errno_save;
    
    //errno_save = errno;    /* we can be called by signal handlers */
    if (sigprocmask(0, NULL, &sigset) < 0)
        printf("sigprocmask error");

    printf("%s,%d", str,getpid());
    
    if (sigismember(&sigset, SIGINT))    printf("SIGINT ");
    if (sigismember(&sigset, SIGQUIT))    printf("SIGQUIT ");
    if (sigismember(&sigset, SIGUSR1))    printf("SIGUSR1 ");
    if (sigismember(&sigset, SIGALRM))    printf("SIGALRM ");

    /* remaining signals can go here */

    printf("\n");
    //errno = errno_save;
}
int main(void)
{
    sigset_t     newmask, oldmask, waitmask;
    
    pr_mask("program start: ");

    if(signal(SIGINT, sig_int) == SIG_ERR)
        printf("signal(SIGINT) error");
    sigemptyset(&waitmask);
    sigaddset(&waitmask, SIGUSR1);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGINT);
    // if(signal(SIGUSR2, fun) == SIG_ERR)
    //     printf("signal(SIGUSR2) error");
    signal(SIGUSR2, fun);
    /*
    * Block SIGINT and save current signal mask.
    */
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("SIG_BLOCK error: ");

    /*
    * Critical region of code.
    */
    pr_mask("in critical region: ");

    /*
    * Pause, allowing all signals except SIGUSR1.
    */
    if(sigsuspend(&waitmask) != -1)
        printf("sigsuspend error");
    
    pr_mask("after return from sigsuspend: ");

    /*
    * Reset signal mask which unblocks SIGINT.
    */
    if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        printf("SIG_SETMASK error");

    /*
    * And continue processing...
    */
    pr_mask("program exit: ");

    exit(0);
}

static void
sig_int(int signo)
{
    pr_mask("\nin sig_int: ");
}
void fun(int signo)
{
    pr_mask("USR2\n");
}

執行結果如下1

lxz@lxz-VirtualBox:~/liuxz/testapue$ gcc sigsuspend.c -o sigsuspend
lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend 
program start: ,2597
in critical region: ,2597SIGINT //表示SIGINT在sigprocmask後被阻塞
USR2
,2597SIGUSR1 
after return from sigsuspend: ,2597SIGINT //表示在sigsuspend執行完畢後,SIGINT恢復阻塞
program exit: ,2597  //解除阻塞

執行結果如下2

lxz@lxz-VirtualBox:~/liuxz/testapue$ ./sigsuspend 
program start: ,2642
in critical region: ,2642SIGINT 
^C                                          // 執行INT,發現sigsuspend解除了INT的阻塞
in sig_int: ,2642SIGUSR1 
after return from sigsuspend: ,2642SIGINT 
program exit: ,2642

3.2 例子2

#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);   //這裏要注意對於老版本的linux,執行完信號處理函數後,信號處理方式變成SIG_DFL
    							//但是新版本不會,而需要在sigaction,會SA_RESETHAND
    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)
        printf("signal(SIGINT) error");
    if(signal(SIGQUIT, sig_int) == SIG_ERR)
        printf("signal(SIGQUIT) error");

    sigemptyset(&zeromask);
    sigemptyset(&newmask);
    sigaddset(&newmask, SIGQUIT);

    /*
    * Block SIGQUIT and save current signal mask.
    */
    if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        printf("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)
        printf("SIG_SETMASK error");
    
    exit(0);
}

結果1,按照上述寫法,老版linux

^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt
^C
interrupt

結果2,沒有在信號處理函數中重新寫signal,老版linux
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章