信號

本章函數都是定義在<signal.h>linux

1.可靠的信號和不可靠的信號
ubuntu

1.1不可靠的信號api

信號可能會丟失,可是進程並不知道此事情,這是早期信號的弊端,而且早期實現對信號的控制也是蠻差的,列如用戶但願內核阻塞某個信號,可是不能忽略他,在合適的時候進行釋放,當時不具有此種能力    網絡

1.2可靠的信號
數據結構

1.2.1遞送和未決:當一個信號產生時,內核一般在進程表中以某種形式設置一個標誌,此動做叫作遞送,在信號產生和遞送的時間間隔內,此時間段內信號是未決的,而且只有在遞送了一個信號的時候,才決定進程對信號的處理方式多線程

1.2.2在可靠的信號中,能夠對信號進行阻塞,阻塞是把信號阻塞在未決的狀態,因此很據1.2.1所述,只要阻塞在未決狀態,信號沒有進行遞送,就能夠在次狀態中改變信號的處理方式。函數

1.2.2若是信號在阻塞期間發生屢次怎麼辦性能

由於是可靠的信號,因此,在這種狀況下,應該對信號進行阻塞,可是,當解除阻塞的時候,被阻塞的信號會根據選擇,進行遞送一次或者屢次,若是是進行遞送了屢次,則說明信號發生了排隊,可是此種發生排隊的現象,必須支持POSIX.1的實時擴展spa

2.系統的中斷調用線程

定義:若是進程在執行一個低速系統調用而阻塞期間捕捉到一個信號,則系統調用就被中斷不在繼續執行,該系統調用返回出錯,error被設置成EINTR,這樣處理是由於一個信號發生了,進程捕捉到他,這意味着某個重要的事情發生了,在此時,不該該進程阻塞在低速系統調用中,而是應該喚醒阻塞進程,對信號進行處理

2.1關於全局變量errno的理解

常常在調用linux 系統api 的時候會出現一些錯誤,比方說使用open() write() creat()之類的函數有些時候會返回-1,也就是調用失敗,這個時候每每須要知道失敗的緣由。這個時候使用errno這個全局變量就至關有用了。
在程序代碼中包含 #include <errno.h>,而後每次程序調用失敗的時候,系統會自動用用錯誤代碼填充errno這個對象(下面介紹)

errno這個全局變量在<errno.h>頭文件中聲明以下:extern int errno;


errno是一個由POSIX和ISO C標準定義的符號,看(用)起來就好像是一個整形變量。當系統調用或庫函數發生錯誤的時候,好比以只讀方式打開一個不存在的文件時,它的值將會被改變,根 據errno值的不一樣,咱們就能夠知道本身的程序發生了什麼錯誤,而後進行相應的處理。

爲何,要強調errno看起來好像是一個整形變量呢?由於有的標準(如ISO C)只規定了errno的做用,而沒有規定它的實現方式,它可能被定義成一個變量,也有可能被定義成一個宏,這個具體要看編譯器本身的實現。早些時 候POSIX.1曾把errno定義成extern int errno這種形式,但如今這種方式比較少見了。由於以這種形式來實現errno,在多線程環境下errno變量是被多個線程共享的,這樣可能線程A發生 某些錯誤改變了errno的值,線程B雖然沒有發生任何錯誤,可是當它檢測errno的值的時候,線程B會覺得本身發生了錯誤。因此如今errno在 Linux中被實現成extern int * __errno_location(void): #define errno (*__errno_location()),這樣每一個線程都有本身的errno,不會再發生混亂了。

2.2低速系統調用

系統中有兩種系統調用,一種低速系統調用和其餘系統調用,低速系統調用是會讓進程永遠阻塞的一類系統調用

(1)在讀某些類型的文件時(這種系統調用是讀操做,讀操做,讀操做!!!)若是數據並不存在則可能會使

調用者永遠阻塞(管道、終端設備以及網絡設備)。

(2)在寫這些類型(指的是寫類型的系統調用)指的是管道,終端設備,網絡設備)的文件時,若是不能當即

接受這些數據,則也可能會使調用者永遠阻塞。

(3)打開文件(一些打開文件的系統調用),在某種條件發生以前也可能會使調用者阻塞(例如,打開終端設備,

它要等待直到所鏈接的調制解調器應答)。

(4)pause(按照定義,它使調用進程睡眠直至捕捉到一個信號 )和wait

(5)某種ioctl操做。

(6)某些進程間通訊函數(見第 1 4章)

在這些低速系統調用中,有一個比較值得注意的是與磁盤IO有關的系統調用

雖然讀、寫一個磁盤文件可能暫時阻塞調用者(在磁盤驅動程序將請求排入隊列,而後在適當時間執行請求期間)

,可是除非發生硬件錯誤, I / O操做總會很快返回,並使調用者再也不處於阻塞狀態。

2.3對於如何處理read和write系統調用,當被中斷的時候處理方案(POSIX 2001)

(1)若是在read函數時發生中斷,可是沒有收到所有的信息,系統能夠認爲是失敗的,而且將errno設置爲

EINTR

(2)系統液能夠認爲調用是成功的,而且返回已經處理完的數據

2.4當被中斷的系統調用出錯返回的時候,咱們有可能但願他從新啓動

爲了幫助應用程序使其沒必要處理被中斷的系統調用, 4 . 2 B S D引進了某些被中斷的系統調

用的自動再起動。自動再起動的系統調用包括: i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和

w a i t p i d。正如前述,其中前五個函數只有對低速設備進行操做時纔會被信號中斷。而 w a i t和

w a i t p i d在捕捉到信號時老是被中斷。某些應用程序並不但願這些函數被中斷後再起動,由於這

種自動再起動的處理方式也會帶來問題,爲此 4 . 3 B S D容許進程在每一個信號各別處理的基礎上

不使用此功能。

當sigaction指定爲SA_RESTART進行中斷的系統調用從新啓動,signal,被中斷的系統調用是默認啓動的,

可是在signal中各類平臺處理不同,應該本身定義signal,能夠增強可移植性能

3.可重入函數

注意:當進程正在執行執行正常的指令的時候,若是此時有信號發生,則應該首先執行信號處理程序,執行信號處理的過程應該能返回發生信號的地方,而後按照正常的程序執行流程執行

3.1什麼叫作可重入:

好比說進程正在執行malloc函數,此時正好發生一個信號,進入信號處理,在信號處理的過程當中,再一次執行malloc函數,此時,由於在堆上就會發生錯誤,這就叫作不可重入的(在執行某一個函數的時候,信號處理程序發生,處理程序中又有執行此函數,可是此函數會對進程產生破壞)

3.2哪些函數是不可重入的

(1)使用了靜態數據結構(2)調用了malloc或者free(3)他們是標準的IO函數(由於標準的IO函數使用了全局數據結構)

3.3errno變量

要注意系統調用中的errno,爲了保證函數的可重入性,應該在調用信號處理程序的時候保存errno的值,而後在返回的時候發送

4.signal:

函數原型是 void (*signal(int signo,void (*func) (int))) (int);

此函數由於聲明過於複雜,因此使用了typedef進行簡化typedef void Sigfunc(int);

Sigfunc *signal(int ,Sigfunc*);

在ubuntu中,signal信號默認不阻塞本信號(若是在信號處理程序中,再次發生了本信號,不進行本次信號的阻塞),而且在有本次信號到來的時候恢復系統的默認動做

5.sigaction函數:

int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact)

5.1signo是信號的編號

5.2sigaction解釋

struct sigaction

{

    void (*sa_handler)(int);

    sigset_t sa_mask;

    int sa_flags;

    void (*sa_sigaction)(int,siginfo_t*,void *);

}

5.2.1sa_mask信號屏蔽字,當進程在調用sa_handler以前,sa_mask加入進程的信號屏蔽字中,當從信號處理程序返回的時候,而後恢復原來的信號屏蔽字,注意在信號處理程序中,正在處理的信號被自動加入信號屏蔽字中,若在發生此信號,應該對其進行阻塞,若是一個信號發生屢次,通常不將其放入信號隊列中,最後,解阻塞的時候,屢次相同的信號,只發生一次

5.2.2sa_handler是信號的處理程序,在再次改變信號處理程序的時候,該設置一直有效

5.2.3sa_flags:

SA_NODEFER:

當捕捉到此信號時,在執行其信號捕捉函數時,系統不自動阻塞此信號。注意,此種類型的操做對應於早期的不可

靠信號

SA_RESETHAND:

若是加入此標誌,在進入信號處理程序的時候將信號的處理方式設置爲SIG_DFL,而且清除SA_SIGINFO,而且此種標誌對應之前不可靠的信號!!!對於SIGILL和SIGTRAP信號,此設置是無效的

SA_INTERRUPT:

由此信號中斷的系統調用不自動重啓動

SA_RESTART:

由此信號中斷的系統調用自動重啓動

SA_SIGINFO:

由此選項對信號處理程序提供了附加信息,一個指向siginfo結構的指針以及一個指向程序上下文標識符的指針

當設置了此標誌,調用如下信號處理程序

void handler(int signo,siginfo_t *info,void *context)


6.sigsuspend函數

1)頭文件:#include <signal.h>

2)一個保護臨界區代碼的錯誤實例:(sigprocmask()和pause()實現)

#include <unistd.h>

#include <signal.h>

#include <stdio.h> 

void handler(int sig)    //信號處理函數的實現

{

   printf("SIGINT sig");

}

int main()

{

    sigset_t new,old;

    struct sigaction act;

    act.sa_handler = handler;  //信號處理函數handler

    sigemptyset(&act.sa_mask);

    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);  //準備捕捉SIGINT信號

    sigemptyset(&new);

    sigaddset(&new, SIGINT);

    sigprocmask(SIG_BLOCK, &new, &old);  //將SIGINT信號阻塞,同時保存當前信號集

    printf("Blocked");

    sigprocmask(SIG_SETMASK, &old, NULL);  //取消阻塞

    pause();

    return 0;

}

上面實例的問題是:原本指望pause()以後,來SIGINT信號,能夠結束程序;但是,若是當「取消阻塞」和「pause」之間,正好來了SIGINT信號,結果程序由於pause的緣由會一直掛起。。。

解決的方式,固然是sigsuspend()函數了。

 

3)使用sigsuspend()的程序

#include <unistd.h>

#include <signal.h>

#include <stdio.h>

void handler(int sig)   //信號處理程序

{

   if(sig == SIGINT)

      printf("SIGINT sig");

   else if(sig == SIGQUIT)

      printf("SIGQUIT sig");

   else

      printf("SIGUSR1 sig");

}

 

int main()

{

    sigset_t new,old,wait;   //三個信號集

    struct sigaction act;

    act.sa_handler = handler;

    sigemptyset(&act.sa_mask);

    act.sa_flags = 0;

    sigaction(SIGINT, &act, 0);    //能夠捕捉如下三個信號:SIGINT/SIGQUIT/SIGUSR1

    sigaction(SIGQUIT, &act, 0);

    sigaction(SIGUSR1, &act, 0);

   

    sigemptyset(&new);

    sigaddset(&new, SIGINT);  //SIGINT信號加入到new信號集中

    sigemptyset(&wait);

    sigaddset(&wait, SIGUSR1);  //SIGUSR1信號加入wait

    sigprocmask(SIG_BLOCK, &new, &old);       //將SIGINT阻塞,保存當前信號集到old中

   

    //臨界區代碼執行    

  

    if(sigsuspend(&wait) != -1)  //程序在此處掛起;用wait信號集替換new信號集。即:過來SIGUSR1信  號,阻塞掉,程序繼續掛起;過來其餘信號,例如SIGINT,則會喚醒程序。執行sigsuspend的原子操做。注意:若是「sigaddset(&wait, SIGUSR1);」這句沒有,則此處不會阻塞任何信號,即過來任何信號均會喚醒程序。

        printf("sigsuspend error");

    printf("After sigsuspend");

    sigprocmask(SIG_SETMASK, &old, NULL);

    return 0;

}

sigsuspend的原子操做是:

(1)設置新的mask阻塞當前進程(上面是用wait替換new,即阻塞SIGUSR1信號)

(2)收到SIGUSR1信號,阻塞,程序繼續掛起;收到其餘信號,繼續運行sigsupsend。

(3)調用該進程設置的信號處理函數(程序中若是先來SIGUSR1信號,而後過來SIGINT信號,則信號處理函數會調用兩次,打印不一樣的內容。第一次打印SIGINT,第二次打印SIGUSR1,由於SIGUSR1是前面阻塞的)

(4)待信號處理函數返回,sigsuspend返回了。(sigsuspend將捕捉信號和信號處理函數集成到一塊兒了)恢復原先的mask(即包含SIGINT信號的)












相關文章
相關標籤/搜索