天天看點

UNIX環境進階程式設計之信号

1、信号本質與來源

信号是在軟體層次上對中斷機制的一種模拟,在原理上,一個程序收到一個信号與處理器收到一個中斷請求可以說是一樣的。信号是異步的,一個程序不必通過任何操作來等待信号的到達,事實上,程序也不知道信号到底什麼時候到達。信号是程序間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信号的程序有哪些事情發生了。信号機制經過POSIX實時擴充後,功能更加強大,除了基本通知功能外,還可以傳遞附加資訊。

信号事件的發生有兩個來源:硬體來源(比如我們按下了鍵盤或者其它硬體故障);軟體來源,最常用發送信号的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟體來源還包括一些非法運算等操作。

2、信号的種類

可以從兩個不同的分類角度對信号進行分類:(1)可靠性方面:可靠信号與不可靠信号;(2)與時間的關系上:實時信号與非實時信号。在《Linux環境程序間通信(一):管道及有名管道》的附1中列出了系統所支援的所有信号。

2.1可靠信号與不可靠信号

2.1.1  "不可靠信号"

Linux信号機制基本上是從Unix系統中繼承過來的。早期Unix系統中的信号機制比較簡單和原始,後來在實踐中暴露出一些問題,是以,把那些建立在早期機制上的信号叫做"不可靠信号",信号值小于SIGRTMIN(Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63)的信号都是不可靠信号。這就是"不可靠信号"的來源。它的主要問題是:

程序每次處理信号後,就将對信号的響應設定為預設動作。在某些情況下,将導緻對信号的錯誤處理;是以,使用者如果不希望這樣的操作,那麼就要在信号處理函數結尾再一次調用signal(),重新安裝該信号。

信号可能丢失,後面将對此詳細闡述。

是以,早期unix下的不可靠信号主要指的是程序可能對信号做出錯誤的反應以及信号可能丢失。

Linux支援不可靠信号,但是對不可靠信号機制做了改進:在調用完信号處理函數後,不必重新調用該信号的安裝函數(信号安裝函數是在可靠機制上的實作)。是以,Linux下的不可靠信号問題主要指的是信号可能丢失。

2.1.2 "可靠信号"

随着時間的發展,實踐證明了有必要對信号的原始機制加以改進和擴充。是以,後來出現的各種Unix版本分别在這方面進行了研究,力圖實作"可靠信号"。由于原來定義的信号已有許多應用,不好再做改動,最終隻好又新增加了一些信号,并在一開始就把它們定義為可靠信号,這些信号支援排隊,不會丢失。同時,信号的發送和安裝也出現了新版本:信号發送函數sigqueue()及信号安裝函數sigaction()。POSIX.4對可靠信号機制做了标準化。但是,POSIX隻對可靠信号機制應具有的功能以及信号機制的對外接口做了标準化,對信号機制的實作沒有作具體的規定。

可靠信号克服了信号可能丢失的問題。Linux在支援新版本的信号安裝函數sigation()以及信号發送函數sigqueue()的同時,仍然支援早期的signal()信号安裝函數,支援信号發送函數kill()。

但應注意的是:信号的可靠與不可靠隻與信号值有關,與信号的發送及安裝函數無關。

信号值小于SIGRTMIN的信号為不可靠信号,信号值在SIGRTMIN及SIGRTMAX之間的信号為可靠信号。

2.2   實時信号與非實時信号

早期Unix系統隻定義了32種信号,Ret hat7.2支援64種信号,編号0-63(SIGRTMIN=31,SIGRTMAX=63),将來可能進一步增加,這需要得到核心的支援。前32種信号已經有了預定義值,每個信号有了确定的用途及含義,并且每種信号都有各自的預設動作。如按鍵盤的CTRL ^C時,會産生SIGINT信号,對該信号的預設反應就是程序終止。後32個信号表示實時信号,等同于前面闡述的可靠信号。這保證了發送的多個實時信号都被接收。實時信号是POSIX标準的一部分,可用于應用程序。

非實時信号都不支援排隊,都是不可靠信号;實時信号都支援排隊,都是可靠信号。

3   程序對信号的響應

程序可以通過三種方式來響應一個信号:(1)忽略信号,即對信号不做任何處理,其中,有兩個信号不能忽略:SIGKILL及SIGSTOP;(2)捕捉信号。定義信号處理函數,當信号發生時,執行相應的處理函數;(3)執行預設操作,Linux對每種信号都規定了預設操作,詳細情況請參考[2]以及其它資料。注意,程序對實時信号的預設反應是程序終止。Linux究竟采用上述三種方式的哪一個來響應信号,取決于傳遞給相應API函數的參數。

4   信号的發送

發送信号的主要函數有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。這裡僅給出kill()、raise()。

4.1   kill()

#i nclude <sys/types.h>

#i nclude <signal.h>

int kill(pid_t pid,int signo)

參數pid的值  信号的接收程序

---------------------------------------

 pid>0  程序ID為pid的程序

 pid=0  同一個程序組的程序

 pid<0 pid!=-1 程序組ID為 -pid的所有程序

 pid=-1  除發送程序自身外,所有程序ID大于1的程序

Sinno是信号值,當為0時(即空信号),實際不發送任何信号,但照常進行錯誤檢查,是以,可用于檢查目标程序是否存在,以及目前程序是否具有向目标發送信号的權限(root權限的程序可以向任何程序發送信号,非root權限的程序隻能向屬于同一個session或者同一個使用者的程序發送信号)。

Kill()最常用于pid>0時的信号發送,調用成功傳回 0; 否則,傳回 -1。注:對于pid<0時的情況,對于哪些程序将接受信号,各種版本說法不一,其實很簡單,參閱核心源碼kernal/signal.c即可,上表中的規則是參考red hat 7.2。

4.2   raise()

#i nclude <signal.h>

int raise(int signo)

向程序本身發送信号,參數為即将發送的信号值。調用成功傳回 0;否則,傳回 -1。

#i nclude <stdlib.h>

void abort(void);

向程序發送SIGABORT信号,預設情況下程序會異常退出,當然可定義自己的信号處理函數。即使SIGABORT被程序設定為阻塞信号,調用abort()後,SIGABORT仍然能被程序接收。該函數無傳回值。

5 信号的安裝(設定信号關關聯作)

如果程序要處理某一信号,那麼就要在程序中安裝該信号。安裝信号主要用來确定信号值及程序針對該信号值的動作之間的映射關系,即程序将要處理哪個信号;該信号被傳遞給程序時,将執行何種操作。

linux主要有兩個函數實作信号的安裝:signal()、sigaction()。其中signal()在可靠信号系統調用的基礎上實作, 是庫函數。它隻有兩個參數,不支援信号傳遞資訊,主要是用于前32種非實時信号的安裝;而sigaction()是較新的函數(由兩個系統調用實作:sys_signal以及sys_rt_sigaction),有三個參數,支援信号傳遞資訊,主要用來與 sigqueue() 系統調用配合使用,當然,sigaction()同樣支援非實時信号的安裝。sigaction()優于signal()主要展現在支援信号帶有參數。

5.1 signal()

#i nclude <signal.h>

void (*signal(int signum, void (*handler))(int)))(int);

如果該函數原型不容易了解的話,可以參考下面的分解方式來了解:

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler));

第一個參數指定信号的值,第二個參數指定針對前面信号值的處理,可以忽略該信号(參數設為SIG_IGN);可以采用系統預設方式處理信号(參數設為SIG_DFL);也可以自己實作處理方式(參數指定一個函數位址)。

如果signal()調用成功,傳回最後一次為安裝信号signum而調用signal()時的handler值;失敗則傳回SIG_ERR。

5.2   sigaction()

#i nclude <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

sigaction函數用于改變程序接收到特定信号後的行為。該函數的第一個參數為信号的值,可以為除SIGKILL及SIGSTOP外的任何一個特定有效的信号(為這兩個信号定義自己的處理函數,将導緻信号安裝錯誤)。第二個參數是指向結構sigaction的一個執行個體的指針,在結構sigaction的執行個體中,指定了對特定信号的處理,可以為空,程序會以預設方式對信号處理;第三個參數oldact指向的對象用來儲存原來對相應信号的處理,可指定oldact為NULL。如果把第二、第三個參數都設為NULL,那麼該函數可用于檢查信号的有效性。第二個參數最為重要,其中包含了對指定信号的處理、信号所傳遞的資訊、信号處理函數執行過程中應屏蔽掉哪些函數等等。

       信号是軟體中斷。很多比較重要的應用程式都需要處理信号。信号提供了一種處理異步事件的方法.每個信号都有一個名字,這些名字都以三個字元SIG開頭。在頭檔案<signal.h>中,這些信号都被定義為正整數(信号編号)。

很多條件可以産生一個信号。

1.當使用者按某些終端鍵時,産生信号。如:使用者按ctrl+c 産生中斷信号,中止目前程序。

2.硬體異常産生信号。如:除數為0,無效的存儲通路等等。這些條件通常由硬體檢測到,并将其通知核心。然後核心為該條件發生時正在運作的程序産生适當的信号。

3.程序用kill(1) 指令将信号發送給另一個程序或程序組。自然,有些限制:按收信号程序和發送信号程序的所有這者必須相同,或發送信号的所有者必須是超級使用者。

4.使用者可用kill(1) 指令将信号發送給其他程序。此程式是kill函數的界面,常用此命名終止一個失控的背景程序。

5.當檢測到某種軟體條件已經發生,并将其通知有關程序時也産生信号。

在Redhat上kill -l 得到:

      1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL

      5) SIGTRAP      6) SIGABRT      7) SIGBUS       8) SIGFPE

      9) SIGKILL     10) SIGUSR1     11) SIGSEGV     12) SIGUSR2

     13) SIGPIPE     14) SIGALRM     15) SIGTERM     17) SIGCHLD

     18) SIGCONT     19) SIGSTOP     20) SIGTSTP     21) SIGTTIN

     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ

     26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO

     30) SIGPWR      31) SIGSYS      32) SIGRTMIN    33) SIGRTMIN+1

     34) SIGRTMIN+2  35) SIGRTMIN+3  36) SIGRTMIN+4  37) SIGRTMIN+5

     38) SIGRTMIN+6  39) SIGRTMIN+7  40) SIGRTMIN+8  41) SIGRTMIN+9

     42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13

     46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14

     50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10

     54) SIGRTMAX-9  55) SIGRTMAX-8  56) SIGRTMAX-7  57) SIGRTMAX-6

     58) SIGRTMAX-5  59) SIGRTMAX-4  60) SIGRTMAX-3  61) SIGRTMAX-2

     62) SIGRTMAX-1  63) SIGRTMAX

二、不可靠信号安裝和發送函數。

1.名稱: signal

   功能:信号安裝(設定信号關關聯作)

   頭檔案:#i nclude <signal.h>

   函數原形:typedef void (*sighandler_t)(int);

  sighandler_t signal(int signum,sighandler_t handler);

  參數:

  signum  信号名

  handler  操作方式

  傳回值: 成功則為以前的信号處理配置,若出錯則為SIG_ERR

signum參數是信号名,handler的值是:1。常數SIG_IGN:表示忽略此信号。2。SIG_DFL:表示接到此信号後的動作是系統預設動作。3。函數位址:表示我們捕捉此信号,并且調用使用者設定的信号處理程式。當調用signal設定信号處理程式時,第二個參數是指向該函數的指針。

***************************************

  #i nclude <stdio.h>

  #i nclude <unistd.h>

  #i nclude <signal.h>

  main(){

  signal(SIGINT,SIG_IGN);

  printf("hello!n");

  sleep(10);

  printf("hellon");

  }

***************************************

上面的代碼忽略了SININT信号.

此程式執行會在螢幕上先列印一個“hello!”,然後睡眠10分鐘。在此期間使用者按ctrl+c沒有任何反應,因為signal函數已将SIGINT信号(按ctrl+c會産生)設為忽略。

然後看下面的程式:

**************************************

    #i nclude <stdio.h>

  #i nclude <unistd.h>

  #i nclude <signal.h>

  void catch(int sig);

  main(){

  signal(SIGINT,catch);

  printf("hello!n");

  sleep(10);

  printf("hello!n");

  }

  void catch(int sig){

  printf("catch signaln");

}

***************************************

  當使用者按下ctrl+c時,程序被中斷,catch()被執行.中斷處理函數處理完畢後,轉回斷點執行下面的指令.

  當編寫自己的中斷處理函數時,注意下面兩點:

    1.信号不能打斷系統調用.

    2.信号不能打斷信号處理函數.

2.

名稱: pause

功能:等待信号

頭檔案:#i nclude <unistd.h>

函數原形:int pause(void);

參數: 無

傳回值: -1,errno設定為EINTR

pause函數使調用程序挂起直至捕捉到一個信号,pause才傳回。在這種情況下,pause傳回-1,errno設定為EINTR..

下面是一個例子:

******************************************

#i nclude <signal.h>

static void sig_usr(int signo);

int main()

{

    if(signal(SIGUSR1,sig_usr)==SIG_ERR)

    perror(SIGUSR1);

    if(signal(SIGUSR2,sig_usr)==SIG_ERR)

       perror(SIGUSR2);

    while(1)

    pause();

}

static void sig_usr(int signo)

{

if(signo==SIGUSR1)

    printf(“received SIGUSR1\n”);

else if(signo==SIGUSR2)

    printf(“received SIGUSR2\n”);

else

    printf(“received signal %d\n”,signo);

}

******************************************

pause();函數使調用程序挂起,直至捕捉到一個信号。

下面我們來運作一下:

****************************************************

#./10_1 &                在背景運作程序

[3] 18864 

#.kill -USR1 18864       向該程序發送SIGUSR1

received SIGUSR1

# kill –USR2 18864      向該程序發送SIGUSR1

received SIGUSR2

# kill 18864             向該程序發送SIGTERM

[3]+ Terminated          ./signal

可以看到當使用者kill -USR1 18864的時候産生了SIGUSR1信号,signal 定義了處理此信号要調用sig_usr函數,是以就在螢幕上列印出received SIGUSR1。

       shell自動将背景程序對中斷和退出信号的處理方式設定為忽略。于是當按中斷鍵時就不會影響到背景程序。如果沒有執行這樣的處理,那麼當按中斷鍵時,它不但會終止前台程序,還會終止是以的背景程序。

       我們還應注意的是,我們不能在信号處理程式中調用某些函數,這些函數被稱為不可重入函數,例如malloc,getpwnam..那是因為當發生中斷的時候系統有可能正在執行這些函數,在中斷中調用這些函數可能會覆寫原來的資訊,因而産生錯誤。

3.

名稱: kill/raise

功能: 信号發送函數

頭檔案: #i nclude <signal.h>

函數原形: int kill(pid_t pid,int signo);

int raise(int signo);

參數:

 pid     程序id

 signo   信号

傳回值: 若成功傳回0,若出錯傳回-1

kill函數将信号發送給程序或程序組。raise函數則允許程序向自己發送信号。

raise(signo)等價于kill(getpid(),signo);

kill的pid函數有4種不同的情況:

pid>0 将信号發送給程序ID為pid的程序。

pid==0将信号發送給與發送程序同一組的所有程序。

pid<0 将該信号發送給其程序組id等于pid絕對值。

pid==-1将信号發送給程序有權向它發送信号的系統上的所有程序。

       程序将信号發送給其他程序需要許可權。超級使用者可将信号發送另一個程序。對于非超級使用者,其基本規則是發送者的實際或有效使用者ID必須等于接收者的實際或有效使用者ID。

***************************************************

#i nclude <signal.h>

#i nclude <stdio.h>

void sig_usr(int);

main()

{

if(signal(SIGINT,sig_usr)==SIG_ERR)

    perror(“error”);

while(1);

}

void sig_usr(int signo)

{

if(signo==SIGINT)

    printf(“received SIGINT\n”);

kill(getpid(),SIGKILL);

}

***************************************************

程式運作後,當使用者按ctrl+c後,程式調用信号處理函數輸出received SIGINT,然後調用kill函數中止程序。此程式的kill(getpid(),SIGKILL);也可以寫成raise(SIGKILL);

4.

名稱: alarm

功能:set an alarm clock for delivery of a signal

頭檔案:#i nclude <unistd.h>

函數原形: unsigned int alarm(unsigned int seconds);

參數:seconds 時間

傳回值: 0或以前設定時間的剩餘數

使用alarm函數可以設定一個時間值(鬧鐘時間),在将來的某個時刻時間值會被超過。當所設時間值被超過後,産生SIGALRM信号。如果不忽略或不捕捉此信号,則其預設動作是終止該程序。

******************************************************

#i nclude <signal.h>

#i nclude <unistd.h>

main()

{

 unsigned int i;

 alarm(1);

 for(i=0;1;i++)

 printf("I=%d",i);

}

*****************************************************

下面這個函數會有什麼結果呢?

SIGALRM的預設操作是結束程序,是以程式在1秒之後結束,你可以看看你的最後I值為多少,來比較一下大家的系統性能差異(我的是40300)。

5.

名稱:abort

功能: 信号發送函數

頭檔案: #i nclude <stdlib.h>

函數原形:void abort(void);

參數:無

傳回值:無

此函數将SIGABRT信号發送給調用程序。程序不應該忽略此信号。

<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

三、可靠信号安裝和發送函數。

可靠信号的處理函數和不可靠信号的處理函數基本原理是一樣的,隻不過是可靠信号的處理函數支援排隊,信号不會丢失。

6.

名稱: sigaction

功能: 可靠信号的安裝函數

頭檔案: #i nclude <signal.h>

函數原形: int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);

參數: 

傳回值: 若成功傳回0,若出錯傳回-1。

sigaction結構的原形為:

struct sigaction {

void (*sa_handler)(int signo);

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

sigset_t sa_mask;

int sa_flags;

void (*sa_restore)(void);

}

這個函數和結構看起來是不是有點恐怖呢。不要被這個吓着了,其實這個函數的使用相當簡單的。我們先解釋一下各個參數的含義。 signo很簡單就是我們要處理的信号了,可以是任何的合法的信号。有兩個信号不能夠使用(SIGKILL和SIGSTOP)。 act包含我們要對這個信号進行如何處理的資訊。oact更簡單了就是以前對這個函數的處理資訊了,主要用來儲存資訊的,一般用NULL就OK了。

信号結構有點複雜。不要緊我們慢慢的學習。

sa_handler是一個函數型指針,這個指針指向一個函數,這個函數有一個參數。這個函數就是我們要進行的信号操作的函數。 sa_sigaction,sa_restore和sa_handler差不多的,隻是參數不同罷了。這兩個元素我們很少使用,就不管了。

sa_flags用來設定信号操作的各個情況。一般設定為0好了。sa_mask用來設定信号屏蔽字,将在後面介紹。

在使用的時候我們用sa_handler指向我們的一個信号操作函數,就可以了。sa_handler有兩個特殊的值:SIG_DEL和SIG_IGN。SIG_DEL是使用預設的信号操作函數,而SIG_IGN是使用忽略該信号的操作函數。

這個函數複雜,我們使用一個執行個體來說明。下面這個函數可以捕捉使用者的CTRL+C信号。并輸出一個提示語句。

**********************************************

#i nclude <stdio.h>

#i nclude <signal.h>

#define PROMPT "catch the signal of ‘ctrl+c’\nplease enter ‘ctrl+z’ to exit\n"

char *prompt=PROMPT;

void ctrl_c_op(int signo)

{

 write(STDERR_FILENO,prompt,strlen(prompt));

}

int main()

{

 struct sigaction act;

 act.sa_handler=ctrl_c_op;

 act.sa_flags=0;

 if(sigaction(SIGINT,&act,NULL)<0)

 {

  preeor(“error”);

  exit(1);

 }

 while(1);

}

**********************************************

運作程式後,當使用者按ctrl+c(會産生SIGINT信号)後螢幕上會列印。

catch the signal of ‘ctrl+c’

please enter ‘ctrl+z’ to exit

也就是說當使用者按ctrl+c時我們調用我們自己寫的函數來進行中斷。

7.

名稱: sigqueue

功能: 可靠信号的發送函數

頭檔案: #i nclude <signal.h>

函數原形: int sigqueue(pid_t pid,int sig,const union sigval value);

參數:

傳回值: 若成功傳回0,若出錯傳回-1。

typedef union sigval

{

 int sival_int;

 void *sival_ptr;

}sigval_t; 

sigqueue()是比較新的發送信号系統調用,主要是針對實時信号提出的(當然也支援前32種),支援信号帶有參數,與函數sigaction()配合使用。sigqueue的第一個參數是指定接收信号的程序ID,第二個參數确定即将發送的信号,第三個參數是一個聯合資料結構union sigval,指定了信号傳遞的參數,即通常所說的4位元組值。 

sigqueue()比kill()傳遞了更多的附加資訊,但sigqueue()隻能向一個程序發送信号,而不能發送信号給一個程序組。如果sig為0,将會執行錯誤檢查,但實際上不發送任何信号,0值信号可用于檢查pid的有效性以及目前程序是否有權限向目标程序發送信号。

在調用sigqueue時,sigval_t指定的資訊會拷貝到3參數信号處理函數的siginfo_t結構中,這樣信号處理函數就可以處理這些資訊了。由于sigqueue系統調用支援發送帶參數信号,是以比kill()系統調用的功能要靈活和強大得多。

注:sigqueue()發送非實時信号時,第三個參數包含的資訊仍然能夠傳遞給信号處理函數; sigqueue()發送非實時信号時,仍然不支援排隊,即在信号處理函數執行過程中到來的所有相同信号,都被合并為一個信号。

四、信号屏蔽字:

有時候我們希望程序正确的執行,而不想程序受到信号的影響,比如我們希望上面那個程式在1秒鐘之後不結束。這個時候我們就要進行信号的操作了。

信号操作最常用的方法是信号屏蔽。信号屏蔽要用到下面的幾個函數。

sigemptyset,sigfillset,sigaddset,sigdelset,sigismember,sigprocmask。下面對他們分别進行講解。

8.

名稱: sigemptyset/sigfillset/sigaddset/sigdelset/sigismember

功能: 處理信号集

頭檔案: #i nclude <signal.h>

函數原形:

 int sigemptyset(sigset_t *set);

 int sigfillset(sigset_t *set);

 int sigaddset(sigset_t *set,int signum);

 int sigdelset(sigset_t *set,int signum);

 int sigismember(const sigset_t *set,int signum);

 參數:

   set 信号集

   signum 信号

傳回值:若成功傳回0,若出錯傳回-1。

        若真傳回1,若假傳回0,若出錯傳回-1。

 我們需要有一個能表示多個信号—信号集的資料類型。我們将在諸如sigprocmask之類的函數中使用這種資料類型,以便告訴核心不允許發生該信号集中的信号。上面的5個函數可以對信号集進行處理。

函數sigemptyset 初始化由set指向的信号集,清除其中所有信号。函數sigfillset初始化由set指向的信号集,使其包含所有信号。是以信号在使用信号集前,要對信号集調用sigemptyset或sigfillset一次。

函數sigaddset 将一個信号添加到現有集中,sigdelset則從信号集中删除一個信号。對所有以信号集作為參數的函數,我們總是以信号集位址作為其傳送的參數。

gismember查詢信号是否在信号集合之中。

下面的例子:

*******************************************

#i nclude <stdio.h>

#i nclude <signal.h>

main()

{

 sigset_t *set;

 set=(sigset_t*)malloc(sizeof(set));

 sigemptyset(set);

 sigaddset(set,SIGUSR1);

 sigaddset(set,SIGINT);

 if((sigismember(set,SIGUSR1))==1)

     printf(“SIGUSR1\n”);

 if((sigismember(set,SIGUSR2))==1)

     printf(“SIGUSR2\n”);

 if((sigismember(set,SIGINT))==1)

     printf(“SIGINT\n”);

}

*******************************************

下面是執行結果:

# ./10_7

SIGUSR1

SIGINT

程式先初始化信号集,清除其中所有信号,然後把SIGUSR1和SIGINT添加到信号集中,然後測試SIGUSR1,SIGUSR2,SIGINT信号是否在信号集中。因為SIGUSR2不在信号集中,是以程式并不列印SIGUSR2。

9.

名稱: sigprocmask

功能: 檢測或更改信号屏蔽字

頭檔案: #i nclude <signal.h>

函數原形: int sigprocmask(int how,const sigsett_t *set,sigset_t *oldset);

參數: how  操作方式

      set  信号集

      oldest 

傳回值: 若成功傳回0,若出錯傳回-1。

每個程序都有一個用來描述哪些信号遞送到程序時将被阻塞的信号集,該信号集中的所有信号在遞送到程序後都将被阻塞。

sigprocmask是最為關鍵的一個函數。在使用之前要先設定好信号集合set。這個函數的作用是将指定的信号集合set加入到程序的信号阻塞集合之中去,如果提供了oldset那麼目前的程序信号阻塞集合将會儲存在oldset裡面。參數how決定函數的操作方式。

SIG_BLOCK:增加一個信号集合到目前程序的阻塞集合之中。

SIG_UNBLOCK:從目前的阻塞集合之中删除一個信号集合。

SIG_SETMASK:将目前的信号集合設定為信号阻塞集合。

我們把10_7.c稍微修改一下,看看sigprocmask函數的功能。

**********************************************

#i nclude <stdio.h>

#i nclude <signal.h>

main()

{

 sigset_t *set;

 set=(sigset_t*)malloc(sizeof(set));

 sigemptyset(set);

 sigaddset(set,SIGINT);

 sigprocmask(SIG_SETMASK,set,NULL);

 wile(1);

}

***********************************************

程式先定義信号集set,然後把信号SIGINT添加到set信号集中,最後把set設定為信号阻塞集合。當我們運作程式時,程序進入死循環。我們按“ctrl+c”系統并沒有中斷程式,因為我們已經把SIGINT信号屏蔽掉了。我們可以按”ctrl+z”來結束程式。

sigeprocmask函數通常和sigemptyset/sigfillset/sigaddset/sigdelset/sigismember函數配合使用,主要有兩種用途:

 1.我們不希望某些不太重要的信号來影響我們的程序,我們就可以把這些信号添加到信号屏蔽集中。使它們不打擾程序的執行。

 2.如果系統現在很忙,沒有時間及時相應信号,程序可以先把信号阻塞掉,等系統有空閑時間在去相應,這也保證了信号的可靠性。下面的函數可以做到這一點。

10.

名稱: sigpending

功能: 傳回信号集

頭檔案: #i nclude <signal.h>

函數原形: int sigpending(sigset_t *set);

參數:

傳回值: 若成功傳回0,若出錯傳回-1。

我們要注意的是,阻塞信号并不是丢棄信号,它們被儲存在一個程序的信号阻塞隊列裡,sigpending可以獲得目前已遞送到程序,卻被阻塞的所有信号,在set指向的信号集中傳回這些信号。

下面是一個例子:

*************************************************

#i nclude <stdio.h>

#i nclude <signal.h>

#i nclude <stdlib.h>

int main(void)

{

 sigset_t newmask,oldmask,pendmask;

 sigemptyset(&newmask); 

 sigaddset(&newmask,SIGINT); 

 if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0) 

      perror(“error”);

 sleep(5);

 if(sigpending(&pendmask)<0) 

  perror(“error”);

 if(sigismember(&pendmask,SIGINT)) 

      printf(“\nSIGINT pending\n”);

 exit(0);

}

*************************************************

程式開始運作,如果我們在5秒鐘内按”ctrl+c”,SIGINT信号會被傳遞到程序,但是由于程序設定了信号阻塞集,信号SIGINT在這個集合中,所有這個信号被阻塞。并由sigpend儲存到程序的信号阻塞隊列pendmask裡.然後我們用sigismember檢測SIGINT是否在信号阻塞隊列pendmask裡。

11.

名稱: sigsuspend

功能:

頭檔案: #i nclude <signal.h>

函數原形: int sigsuspend(const sigset_t *sigmask);

參數: sigmask 要替換的程序信号屏蔽字。

傳回值: -1,errno設定為EINTR.

sigsuspend用于在接收到某個信号之前, 臨時用sigmask替換程序的信号掩碼, 并暫停程序執行,直到收到信号為止。sigsuspend 傳回後将恢複調用之前的信号掩碼。信号處理函數完成後,程序将繼續執行。該系統調用始終傳回-1,并将errno設定為EINTR。

       但要注意的是,sigsuspend的整個操作都是原子的,也就不允許被打斷的。sigsuspend的整個原子操作過程為:

              (1) 設定新的mask阻塞目前程序;

              (2) 收到信号,恢複原先mask;

              (3) 調用該程序設定的信号處理函數;

              (4) 待信号處理函數傳回後,sigsuspend傳回。

       這種技術的功能有:

       1.可以保護不希望由信号中斷的代碼臨界區。

       2.等待一個信号處理程式設定一個全局變量。

       3.實作父子程序之間的同步。     

       這個函數的執行步驟有點難了解,但我們仔細分析一下就會明白,sigsuspend函數是先用我們定義的sigmask來暫時替代信号屏蔽集,然後阻塞目前程序,收到信号時調用信号處理函數函數對信号進行處理,處理後傳回。

下面是函數的一個例子:

*******************************************

#i nclude <signal.h>

#i nclude <stdio.h>

#i nclude <stdlib.h>

static void sig_int(int);

int main(void)

{

 sigset_t newmask,oldmask,zeromask;

 if(signal(SIGINT,sig_int)==SIG_ERR)

     perror(“signal error”);

 sigemtyset(&zeromask);

 sigemtyset(&newmask);

 if(sigprocmask(SIG_BLICK,&newmask,&oldmask)<0)

     perror(“SIG_BLOCK error”);

 printf(“In critical region: SIGINT\n”);

 if(sigsuspend(&zeromask)!=-1)

      perror(“sigsuspend error”);

 printf(“After return from sigsuspend: SIGINT\n”);

 sleep(5);

 exit(0);

}

static void sig_int(int signo)

#i nclude <linux/sched.h>

#i nclude <linux/kernel.h>

#i nclude <asm/segment.h>

#i nclude <signal.h>

volatile void do_exit(int error_code);

int sys_sgetmask()

{

 return current->blocked;

}

int sys_ssetmask(int newmask)

{

 int old=current->blocked;

 current->blocked = newmask & ~(1<<(SIGKILL-1));

 return old;

}

static inline void save_old(char * from,char * to)

{

 int i;

 verify_area(to, sizeof(struct sigaction));

 for (i=0 ; i< sizeof(struct sigaction) ; i++) {

  put_fs_byte(*from,to);

  from++;

  to++;

 }

}

static inline void get_new(char * from,char * to)

{

 int i;

 for (i=0 ; i< sizeof(struct sigaction) ; i++)

  *(to++) = get_fs_byte(from++);

}

int sys_signal(int signum, long handler, long restorer)

{

 struct sigaction tmp;

 if (signum<1 || signum>32 || signum==SIGKILL)

  return -1;

 tmp.sa_handler = (void (*)(int)) handler;

 tmp.sa_mask = 0;

 tmp.sa_flags = SA_ONESHOT | SA_NOMASK;

 tmp.sa_restorer = (void (*)(void)) restorer;

 handler = (long) current->sigaction[signum-1].sa_handler;

 current->sigaction[signum-1] = tmp;

 return handler;

}

int sys_sigaction(int signum, const struct sigaction * action,

 struct sigaction * oldaction)

{

 struct sigaction tmp;

 if (signum<1 || signum>32 || signum==SIGKILL)

  return -1;

 tmp = current->sigaction[signum-1];

 get_new((char *) action,

  (char *) (signum-1+current->sigaction));

 if (oldaction)

  save_old((char *) &tmp,(char *) oldaction);

 if (current->sigaction[signum-1].sa_flags & SA_NOMASK)

  current->sigaction[signum-1].sa_mask = 0;

 else

  current->sigaction[signum-1].sa_mask |= (1<<(signum-1));

 return 0;

}

void do_signal(long signr,long eax, long ebx, long ecx, long edx,

 long fs, long es, long ds,

 long eip, long cs, long eflags,

 unsigned long * esp, long ss)

{

 unsigned long sa_handler;

 long old_eip=eip;

 struct sigaction * sa = current->sigaction + signr - 1;

 int longs;

 unsigned long * tmp_esp;

 sa_handler = (unsigned long) sa->sa_handler;

 if (sa_handler==1)

  return;

 if (!sa_handler) {

  if (signr==SIGCHLD)

   return;

  else

   do_exit(1<<(signr-1));

 }

 if (sa->sa_flags & SA_ONESHOT)

  sa->sa_handler = NULL;

 *(&eip) = sa_handler;

 longs = (sa->sa_flags & SA_NOMASK)?7:8;

 *(&esp) -= longs;

 verify_area(esp,longs*4);

 tmp_esp=esp;

 put_fs_long((long) sa->sa_restorer,tmp_esp++);

 put_fs_long(signr,tmp_esp++);

 if (!(sa->sa_flags & SA_NOMASK))

  put_fs_long(current->blocked,tmp_esp++);

 put_fs_long(eax,tmp_esp++);

 put_fs_long(ecx,tmp_esp++);

 put_fs_long(edx,tmp_esp++);

 put_fs_long(eflags,tmp_esp++);

 put_fs_long(old_eip,tmp_esp++);

 current->blocked |= sa->sa_mask;

}

繼續閱讀