国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 服務器 > Web服務器 > 正文

Linux進程間通信--使用信號

2024-09-01 13:51:35
字體:
來源:轉載
供稿:網友

一、什么是信號

用過Windows的我們都知道,當我們無法正常結束一個程序時,可以用任務管理器強制結束這個進程,但這其實是怎么實現的呢?同樣的功能在Linux上是通過生成信號和捕獲信號來實現的,運行中的進程捕獲到這個信號然后作出一定的操作并最終被終止。

信號是UNIX和Linux系統響應某些條件而產生的一個事件,接收到該信號的進程會相應地采取一些行動。通常信號是由一個錯誤產生的。但它們還可以作為進程間通信或修改行為的一種方式,明確地由一個進程發送給另一個進程。一個信號的產生叫生成,接收到一個信號叫捕獲。

二、信號的種類

信號的名稱是在頭文件signal.h中定義的,信號都以SIG開頭,常用的信號并不多,常用的信號如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

更多的信號類型可查看附錄表。

三、信號的處理——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; } 

運行結果如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

可以看到,第一次按下終止命令(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如果不是空指針的話,就用它來保存原先對該信號的動作的位置,act則用于設置指定信號的動作。

sigaction結構體定義在signal.h中,但是它至少包括以下成員:

void (*) (int) sa_handler;處理函數指針,相當于signal函數的func參數。

sigset_t sa_mask; 指定一個。信號集,在調用sa_handler所指向的信號處理函數之前,該信號集將被加入到進程的信號屏蔽字中。信號屏蔽字是指當前被阻塞的一組信號,它們不能被當前進程接收到

int sa_flags;信號處理修改器;

sa_mask的值通常是通過使用信號集函數來設置的,關于信號集函數,我將會在我的下一篇文章——Linux進程間通信——信號集函數,詳細講述。

sa_flags,通常可以取以下的值:

linux進程通信信號,進程間通信,信號,linux,進程通信

此外,現在有一個這樣的問題,我們使用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); } 

運行結果如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

在代碼中我們使用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秒后發送信號SIGALRM  alarm(5);  //掛起進程  pause();  //接收到信號后,恢復正常執行  if(alarm_fired == 1)   printf("Receive a signal %d/n", SIGALRM);  exit(0); } 

運行結果如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

進程在5秒后接收到一個SIGALRM,進程恢復運行,打印信息并退出。

六、信號處理函數的安全問題

試想一個問題,當進程接收到一個信號時,轉到你關聯的函數中執行,但是在執行的時候,進程又接收到同一個信號或另一個信號,又要執行相關聯的函數時,程序會怎么執行?

也就是說,信號處理函數可以在其執行期間被中斷并被再次調用。當返回到第一次調用時,它能否繼續正確操作是很關鍵的。這不僅僅是遞歸的問題,而是可重入的(即可以完全地進入和再次執行)的問題。而反觀Linux,其內核在同一時期負責處理多個設備的中斷服務例程就需要可重入的,因為優先級更高的中斷可能會在同一段代碼的執行期間“插入”進來。

簡言之,就是說,我們的信號處理函數要是可重入的,即離開后可再次安全地進入和再次執行,要使信號處理函數是可重入的,則在信息處理函數中不能調用不可重入的函數。下面給出可重入的函數在列表,不在此表中的函數都是不可重入的,可重入函數表如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

七、附錄——信號表

linux進程通信信號,進程間通信,信號,linux,進程通信

如果進程接收到上面這些信號中的一個,而事先又沒有安排捕獲它,進程就會終止。

還有其他的一些信號,如下:

linux進程通信信號,進程間通信,信號,linux,進程通信

以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持VEVB武林網!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 克拉玛依市| 蒲城县| 潍坊市| 和龙市| 包头市| 成安县| 屏边| 湘潭市| 筠连县| 饶河县| 福贡县| 永顺县| 宁晋县| 信宜市| 桂林市| 社旗县| 万盛区| 高安市| 上思县| 泊头市| 施甸县| 云和县| 凤翔县| 平阳县| 自治县| 靖江市| 淮滨县| 益阳市| 特克斯县| 陆丰市| 安福县| 河源市| 宜春市| 抚顺县| 新化县| 梁平县| 正定县| 静乐县| 通城县| 六安市| 元阳县|