APUE學習筆記:Linux下signal和sigaction的使用區別

《UNIX環境高級編程》第三版,圖10-20在Linux環境下的運行結果與書中所述有所不同,信號處理函數執行期間未自動屏蔽該信號,詳見:
信號之sigsetjmp和siglongjmp函數

其原因在於:

  • glibc 2之後,如果系統定義了_BSD_SOURCE宏或_GNU_SOURCE宏的話,那麼系統將通過調用sigaction函數的方式去實現signal函數,否則signal函數將遵循System V語義

signal函數在System V下的語義:

  • 信號處理函數被調用後,該信號的處理方式被重置爲默認處理方式,因此在信號處理函數中要用signal函數對信號重新註冊
  • 信號處理函數執行期間,該信號未被自動屏蔽

因此書中例程可修改爲


#include "apue.h"
#include <setjmp.h>
#include <time.h>
#include <errno.h>

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

int main(void)
{
#ifdef _BSD_SOURCE
    if(signal(SIGUSR1, sig_usr1) == SIG_ERR)
        err_sys("signal(SIGUSR1) error");
    if(signal(SIGALRM, sig_alrm) == SIG_ERR)
        err_sys("signal(SIGALRM) error");

#else
    struct sigaction sa_usr1, osa_usr1, sa_alrm, osa_alrm;
    struct sigaction *sa_usr1_p, *osa_usr1_p, *sa_alrm_p, *osa_alrm_p;
    sa_usr1_p = &sa_usr1;
    osa_usr1_p = &osa_usr1;
    sa_alrm_p = &sa_alrm;
    osa_alrm_p = &osa_alrm;

    sa_usr1_p->sa_handler = sig_usr1;
    sigemptyset(& sa_usr1_p->sa_mask);
    sa_usr1_p->sa_flags = 0;

    sa_alrm_p->sa_handler = sig_alrm;
    sigemptyset(& sa_alrm_p->sa_mask);
    sa_alrm_p->sa_flags = 0;

    if(sigaction(SIGUSR1, sa_usr1_p, osa_usr1_p) == -1)
        err_sys("sigaction(SIGUSR1,,) error");
    if(sigaction(SIGALRM, sa_alrm_p, osa_alrm_p) == -1)
        err_sys("sigaction(SIGALRM,,) error");
#endif

    pr_mask("starting main: ");

    if(sigsetjmp(jmpbuf, 1))
    {
        pr_mask("ending main: ");
        exit(0);
    }
    canjump = 1;

    for(;;)
        pause();
}

static void sig_usr1(int signo)
{
    time_t  starttime;

    if(canjump == 0)
        return;

    pr_mask("starting sig_usr1: ");

    alarm(3);
    starttime = time(NULL);
    for(;;)
        if(time(NULL) > starttime + 5)
            break;

    pr_mask("finishing sig_usrr1: ");

    canjump = 0;
    siglongjmp(jmpbuf, 1);
}

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

或者可以使用以下兩種方法:
關於__GNU_SOURCE 這個宏
gcc中一個編譯選項 -D_GNU_SOURCE

程序運行結果:
運行結果

以下引自《Linux程序員手冊》:

  1. 只有在信號的處理方式被設爲SIG_DFL和SIG_IGN的情況下,signal函數纔是可移植的,其他情況下,signal的語義取決於系統的具體實現。
  2. 在早先的UNIX系統中,使用signal函數安裝信號處理函數,等同於以sa.sa_flags = SA_RESETHAND | SA_NODEFER的方式調用sigaction函數,同時在信號處理函數執行期間,該信號的傳遞未被自動屏蔽
  3. BSD對此做了優化,等同於以sa.sa_flags = SA_RESTART的方式調用sigaction函數,同時引入了信號的自動屏蔽機制
  4. Linux則遵循以下語義:
    (1)若signal爲內核的系統調用函數,則遵循System V語義
    (2)glic2及以後,signal函數不再執行內核系統調用,如果_BSD_SOURECE功能測試宏已定義,那麼遵循BSD語義,若_GNU_SOURCE被定義,則_BSD_SOURECE被隱式定義
  5. 推薦使用sigaction而非signal函數註冊信號!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章