Linux處理序間通訊——使用訊號

來源:互聯網
上載者:User
一、什麼是訊號用過Windows的我們都知道,當我們無法正常結束一個程式時,可以用工作管理員強制結束這個進程,但這其實是怎麼實現的呢?同樣的功能在Linux上是通過產生訊號和捕獲訊號來實現的,運行中的進程捕獲到這個訊號然後作出一定的操作並最終被終止。訊號是UNIX和Linux系統響應某些條件而產生的一個事件,接收到該訊號的進程會相應地採取一些行動。通常訊號是由一個錯誤產生的。但它們還可以作為處理序間通訊或修改行為的一種方式,明確地由一個進程發送給另一個進程。一個訊號的產生叫產生,接收到一個訊號叫捕獲。二、訊號的種類訊號的名稱是在標頭檔signal.h中定義的,訊號都以SIG開頭,常用的訊號並不多,常用的訊號如下:更多的訊號類型可查看附錄表。三、訊號的處理——signal函數程式可用使用signal函數來處理指定的訊號,主要通過忽略和恢複其預設行為來工作。signal函數的原型如下:
#include <signal.h>void (*signal(int sig, void (*func)(int)))(int);
這是一個相當複雜的聲明,耐心點看可以知道signal是一個帶有sig和func兩個參數的函數,func是一個類型為void (*)(int)的函數指標。該函數返回一個與func相同類型的指標,指向先前指定訊號處理函數的函數指標。準備捕獲的訊號的參數由sig給出,接收到的指定訊號後要調用的函數由參數func給出。其實這個函數的使用是相當簡單的,通過下面的例子就可以知道。注意訊號處理函數的原型必須為void
func(int),或者是下面的特殊值:    SIG_IGN:忽略訊號    SIG_DFL:恢複訊號的預設行為說了這麼多,還是給出一個例子來說明一下吧,源檔案名稱為signal1.c,代碼如下:
#include <signal.h>#include <stdio.h>#include <unistd.h>void ouch(int sig){printf("\nOUCH! - I got signal %d\n", sig);//恢複終端中斷訊號SIGINT的預設行為(void) signal(SIGINT, SIG_DFL);}int main(){//改變終端中斷訊號SIGINT的預設行為,使之執行ouch函數//而不是終止程式的執行(void) signal(SIGINT, ouch);while(1){printf("Hello World!\n");sleep(1);}return 0;}
運行結果如下:
可以看到,第一次按下終止命令(ctrl+c)時,進程並沒有被終止,面是輸出OUCH! - I got signal 2,因為SIGINT的預設行為被signal函數改變了,當進程接受到訊號SIGINT時,它就去調用函數ouch去處理,注意ouch函數把訊號SIGINT的處理方式改變成預設的方式,所以當你再按一次ctrl+c時,進程就像之前那樣被終止了。四、訊號處理——sigaction函數前面我們看到了signal函數對訊號的處理,但是一般情況下我們可以使用一個更加健壯的訊號介面——sigaction函數。它的原型為:
#include <signal.h>int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
該函數與signal函數一樣,用於設定與訊號sig關聯的動作,而oact如果不是null 指標的話,就用它來儲存原先對該訊號的動作的位置,act則用於設定指定訊號的動作。sigaction結構體定義在signal.h中,但是它至少包括以下成員:void (*) (int) sa_handler;處理函數指標,相當於signal函數的func參數。sigset_t sa_mask; 指定一個。訊號集,在調用sa_handler所指向的訊號處理函數之前,該訊號集將被加入到進程的訊號屏蔽字中。訊號屏蔽字是指當前被阻塞的一組訊號,它們不能被當前進程接收到int sa_flags;訊號處理修改器;sa_mask的值通常是通過使用訊號集合函式來設定的,關於訊號集合函式,我將會在我的下一篇文章——Linux處理序間通訊——訊號集合函式,詳細講述。sa_flags,通常可以取以下的值:此外,現在有一個這樣的問題,我們使用signal或sigaction函數來指定處理訊號的函數,但是如果這個訊號處理函數建立之前就接收到要處理的訊號的話,進程會有怎樣的反應呢?它就不會像我們想像的那樣用我們設定的處理函數來處理了。sa_mask就可以解決這樣的問題,sa_mask指定了一個訊號集,在調用sa_handler所指向的訊號處理函數之前,該訊號集將被加入到進程的訊號屏蔽字中,設定訊號屏蔽字可以防止訊號在它的處理函數還未運行結束時就被接收到的情況,即使用sa_mask欄位可以消除這一競態條件。承接上面的例子,下面給出用sigaction函數重寫的例子代碼,源檔案為signal2.c,代碼如下:
#include <unistd.h>#include <stdio.h>#include <signal.h>void ouch(int sig){printf("\nOUCH! - I got signal %d\n", sig);}int main(){struct sigaction act;act.sa_handler = ouch;//建立空的訊號屏蔽字,即不屏蔽任何資訊sigemptyset(&act.sa_mask);//使sigaction函數重設為預設行為act.sa_flags = SA_RESETHAND;sigaction(SIGINT, &act, 0);while(1){printf("Hello World!\n");sleep(1);}return 0;}
運行結果與前一個例子中的相同。注意sigaction函數在預設情況下是不被重設的,如果要想它重設,則sa_flags就要為SA_RESETHAND。五、發送訊號上面說到的函數都是一些進程接收到一個訊號之後怎麼對這個訊號作出反應,即訊號的處理的問題,有沒有什麼函數可以向一個進程主動地發出一個訊號呢?我們可以通過兩個函數kill和alarm來發送一個訊號。1、kill函數先來看看kill函數,進程可以通過kill函數向包括它本身在內的其他進程發送一個訊號,如果程式沒有發送這個訊號的許可權,對kill函數的調用就將失敗,而失敗的常見原因是目標進程由另一個使用者所擁有。想一想也是容易明白的,你總不能控制別人的程式吧,當然超級使用者root,這種上帝般的存在就除外了。kill函數的原型為:
#include <sys/types.h>#include <signal.h>int kill(pid_t pid, int sig);
它的作用把訊號sig發送給進程號為pid的進程,成功時返回0。kill調用失敗返回-1,調用失敗通常有三大原因:1、給定的訊號無效(errno = EINVAL)2、發送許可權不夠( errno = EPERM )3、目標進程不存在( errno = ESRCH )2、alarm函數這個函數跟它的名字一樣,給我們提供了一個鬧鐘的功能,進程可以調用alarm函數在經過預定時間後向發送一個SIGALRM訊號。alarm函數的型如下:
#include <unistd.h>unsigned int alarm(unsigned int seconds);
alarm函數用來在seconds秒之後安排發送一個SIGALRM訊號,如果seconds為0,將取消所有已設定的鬧鐘請求。alarm函數的傳回值是以前設定的鬧鐘時間的餘留秒數,如果返回失敗返回-1。馬不停蹄,下面就給合fork、sleep和signal函數,用一個例子來說明kill函數的用法吧,源檔案為signal3.c,代碼如下:
#include <unistd.h>#include <sys/types.h>#include <stdlib.h>#include <stdio.h>#include <signal.h>static int alarm_fired = 0;void ouch(int sig){alarm_fired = 1;}int main(){pid_t pid;pid = fork();switch(pid){case -1:perror("fork failed\n");exit(1);case 0://子進程sleep(5);//向父進程發送訊號kill(getppid(), SIGALRM);exit(0);default:;}//設定處理函數signal(SIGALRM, ouch);while(!alarm_fired){printf("Hello World!\n");sleep(1);}if(alarm_fired)printf("\nI got a signal %d\n", SIGALRM);exit(0);}
運行結果如下:
在代碼中我們使用fork調用複製了一個新進程,在子進程中,5秒後向父進程中發送一個SIGALRM訊號,父進程中捕獲這個訊號,並用ouch函數來處理,變改alarm_fired的值,然後退出迴圈。從結果中我們也可以看到輸出了5個Hello World!之後,程式就收到一個SIGARLM訊號,然後結束了進程。註:如果父進程在子進程的訊號到來之前沒有事情可做,我們可以用函數pause()來掛起父進程,直到父進程接收到訊號。當進程接收到一個訊號時,預設好的訊號處理函數將開始運行,程式也將恢複正常的執行。這樣可以節省CPU的資源,因為可以避免使用一個迴圈來等待。以本例子為例,則可以把while迴圈改為一句pause();下面再以一個小小的例子來說明alarm函數和pause函數的用法吧,源檔案名稱為,signal4.c,代碼如下:
#include <unistd.h>#include <sys/types.h>#include <stdlib.h>#include <stdio.h>#include <signal.h>static int alarm_fired = 0;void ouch(int sig){alarm_fired = 1;}int main(){//關聯訊號處理函數signal(SIGALRM, ouch);//調用alarm函數,5秒後發送訊號SIGALRMalarm(5);//掛起進程pause();//接收到訊號後,恢複正常執行if(alarm_fired == 1)printf("Receive a signal %d\n", SIGALRM);exit(0);}
運行結果如下:進程在5秒後接收到一個SIGALRM,進程恢複運行,列印資訊並退出。六、訊號處理函數的安全問題試想一個問題,當進程接收到一個訊號時,轉到你關聯的函數中執行,但是在執行的時候,進程又接收到同一個訊號或另一個訊號,又要執行相關聯的函數時,程式會怎麼執行?也就是說,訊號處理函數可以在其執行期間被中斷並被再次調用。當返回到第一次調用時,它能否繼續正確操作是很關鍵的。這不僅僅是遞迴的問題,而是可重新進入的(即可以完全地進入和再次執行)的問題。而反觀Linux,其核心在同一時期負責處理多個裝置的插斷服務常式就需要可重新進入的,因為優先順序更高的中斷可能會在同一段代碼的執行期間“插入”進來。簡言之,就是說,我們的訊號處理函數要是可重新進入的,即離開後可再次安全地進入和再次執行,要使訊號處理函數是可重新進入的,則在資訊處理函數中不能調用不可重新進入的函數。下面給出可重新進入的函數在列表,不在此表中的函數都是不可重新進入的,可重新進入函數表如下:七、附錄——訊號表如果進程接收到上面這些訊號中的一個,而事先又沒有安排捕獲它,進程就會終止。還有其他的一些訊號,如下:

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.