處理序間通訊(五)—訊號,處理序間通訊訊號

來源:互聯網
上載者:User

處理序間通訊(五)—訊號,處理序間通訊訊號

我會用幾篇部落格總結一下在Linux中進程之間通訊的幾種方法,我會把這個開頭的摘要部分在這個系列的每篇部落格中都打出來

進程之間通訊的方式

  • 管道
  • 訊息佇列
  • 訊號
  • 訊號量
  • 共用儲存區
  • 通訊端(socket)

處理序間通訊(四)—共用儲存區傳送門:http://www.cnblogs.com/lenomirei/p/5651995.html

處理序間通訊(三)—訊號量傳送門:http://www.cnblogs.com/lenomirei/p/5649792.html

處理序間通訊(二)—訊息佇列傳送門:http://www.cnblogs.com/lenomirei/p/5642575.html

處理序間通訊(一)—管道傳送門:http://www.cnblogs.com/lenomirei/p/5636339.html

我感覺這麼寫下去越來越不像進程間的通訊了,更像是進程間的打招呼。。。訊號就是這樣的,某某(這個某某有很多可能性)給進程一個訊號,進程就會在適當的情況下處理這個訊號這說明進程可能不會立即處理訊號),什麼是適當的時候呢?比如說中斷返回的時候,或者核心態返回使用者態的時候(這個情況出現的比較多)等等(推薦本書《Linux核心設計與實現》,裡面講過)。。。這個也不是本篇的主題就不多述了。

首先來說訊號是怎麼產生的

  • 由硬體產生,別入從鍵盤敲入按鍵組合發送一個訊號,常用的Ctrl+C就可以給前台進程發送。
  • 由進程發送(或者說是由軟體產生),比如我們可以在shell進程下輸入kill命令給一個進程發送訊號(命令:kill -訊號標號 PID)
  • 異常,當異常的發生的時候肯定是會發送訊號的

然後說訊號的處理方式,誰來處理訊號?肯定是作業系統來,難不成還是程式員麼。文章一開頭就說了,號不一定會被立即處理,作業系統不會為了處理一個訊號而把當前正在啟動並執行進程掛起(切換進程)或者殺掉(肯定不會殺掉啊,難道看見一個訊號就殺害一個無辜群眾麼),掛起(進程切換)的話消耗太大了,如果不是緊急訊號,可能是不會立即處理的。作業系統多選擇在核心態切換回使用者態的時候處理訊號,這樣就利用兩者的切換來處理了(不用單獨進行進程切換以免浪費時間)。

總歸是不能避免的,因為很有可能在睡眠的進程就接收到訊號,作業系統肯定不願意切換當前正在happy地跑著的進程,於是就得把訊號儲存啊,因為是進程收到的訊號,所以把訊號儲存在進程唯一的PCB(就是task_struct)當中。struct sigpending pending;欄位就是存放訊號的訊號表,之後會解釋pending。

 訊號的處理過程

所有的訊號可以通過kill -l 命令查看

需要注意的幾點

  • 沒有0號訊號
  • 沒有32,33號訊號
  • 從31號訊號分開,前面的是普通訊號,後面的是即時訊號,本篇介紹的是普通訊號
  • 訊號和前面的標號是一致的,可以使用標號,也可以使用宏名稱

訊號的處理方式有三種

  • 忽略(就是這麼6,你發啥我都不理你)
  • 預設處理方式,作業系統設定的預設處理方式,會終止一個進程,就是說接到訊號就把這個進程幹掉了
  • 自訂處理方式,想幹什麼還是得我說了算,你需要一個signal函數
    • 函數原型:sighandler_t signal(int signum, sighandler_t handler);
    • 標頭檔:#include <signal.h>
    • 參數解析:
      • 第一個是訊號標號,給數字就可以啦
      • 關鍵是第二個參數這裡,sighandler_t是一個typedef來的,主要是為了可讀性,原型是void (*)(int)函數指標啦,int的參數會被設定成signum
      • 我有用到這個函數,可以在我的測試案例中看一下用法

 

  • 相關函數解析

這次就沒有建立函數什麼的了,主要是訊號相關的一些操作函數,但是由於比較多和繁雜,不好每一個都寫測試案例看輸出結果,最後的測試程式只用了一部分函數,並沒有全部使用到

前面說了用kill命令可以給進程發訊號,可是我想用C語言編寫程式發,別怕!你需要下面的函數(raise函數只能給進程本身發訊號

  • 函數原型:int raise(int signo);
  • 標頭檔:#include <signal.h>
  • 參數解析:
    • 就一個參數就是訊號了,可以用標號,也可以用宏名

或者你說不想光給自己發訊號,光自己是很沒意思,那麼看下面這個函數

  • 函數原型:int kill(pid_t pid, int signo);
  • 標頭檔:#include <signal.h>
  • 參數解析:
    • 第一個參數是pid進程號,你得讓作業系統知道你要給哪個進程發訊號,給自己發也是可以的哦
    • 第二個參數是訊號。。。呃,準確的說是哪個訊號,可以用標號,也可以用宏名

看這個訊號(6) SIGABRT,這個訊號可以讓進程異常終止,他有一個對應的函數

  • 函數原型:void abort(void);
  • 標頭檔:#include <stdlib.h>
  • 參數解析:
    • 這根本就沒有參數嘛!那就寫點其他的。
    • 該函數沒有傳回值,因為該函數執行絕對不會失敗(為什嗎?殺個進程而已,談什麼失敗(一刀不行就兩刀!!(玩笑))),和exit函數一樣絕對不會失敗(重要!說兩遍!)和exit函數不同的地方是,exit會設定退出碼。

看這個訊號(14) SIGALRM,這個訊號是鬧鐘訊號,它可以由這個函數發送

  • 函數原型:unsigned int alarm(unsigned int seconds);
  • 標頭檔:#include <unistd.h>
  • 參數解析:
    • 參數都寫的這麼清楚了!等待多少秒之後就會發送SIGALRM訊號給當前進程。

接下來說明一下阻塞訊號,就是(pending)了

  • 阻塞訊號(pending signal)

之前提到過,可以忽略一個訊號,那麼我說,阻塞和忽略是不一樣的阻塞是進程收不到該訊號,忽略是進收到該訊號,卻不做出任何反應

實際執行訊號的處理動作稱為訊號遞達(Delivery),訊號從產生到遞達之間的狀態,稱為訊號未決(Pending)。進程可以選擇阻塞(Block )某個訊號。被阻塞的訊號產生時將保持在未決狀態,直到進程解除對此訊號的阻塞,才執行遞達的動作。

task_struct中有類似欄位,之前說的訊號可能不會立即被執行就會儲存在pending表裡面

其中一旦訊號被block,當有訊號產生的時候會一直pending,因為未決就是未處理的意思,block到不了就處理不了,pending會一直有該位

以為PCB中使用32位來表示中的block和pending表,所以非即時訊號就算髮送多個,也只顯示一個。即時訊號會排隊,有幾個來就有幾個排隊

block表:某位為0表示該位對應標號的訊號未被阻塞,為1表示阻塞

pending表:某位為0表示訊號還未產生或者已經被處理

中2號訊號兩個表同時為1表示產生了2號訊號,但因為2號訊號被阻塞所以一直未決。

介紹幾個操作這兩張表的函數

  • 函數原型:

    int sigemptyset(sigset_t *set);用於初始化一個訊號集(新建立出來的sigset_t對象都要先執行這個函數再去操作)
    int sigfillset(sigset_t *set);初始化訊號集,所有位置位1,表示支援所有訊號(就好像添加了所有的訊號)
    int sigaddset(sigset_t *set, int signo);把感興趣的(想要操作)訊號添加到訊號集

    int sigdelset(sigset_t *set, int signo);把感興趣的(想要操作)訊號從訊號集踢出去
    int sigismember(const sigset_t *set, int signo);判斷你傳入的signo是否是set集合中的一員

  • 標頭檔:#include <signal.h>
  • 參數解析:
    • sigset_t結構體的參數表示訊號集訊號操作的時候都是以訊號集合的方式進行操作,需要事先建立一個該結構體的對象,然後把想要操作的訊號添加到訊號集合對象當中去
    • signo就是訊號的標號了

如何阻塞一個訊號?用下面的函數

  • 函數原型:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
  • 標頭檔:#include <signal.h>
  • 參數解析:
    • 先說how,有三個宏
      • SIG_BLOCK添加到block表當中去
      • SIG_UNBLOCK從block表中刪除
      • SIG_SETMASK設定block表(跟第一個SIG_BLOCK不同,這個是直接讓當前進程和set完全相等,不是把set的添加到當前block表中去了)  
    • set表示要設定的集合,比如你集合裡面有1234,就這些,那麼這個函數就把這幾個根據第一個參數how進行設定
    • oset表示old set就是設定之前儲存一下之前的block表資訊,可以給NULL

如何擷取當前pending表中的資訊?你需要它~

  • 函數原型:int sigpending(sigset_t *set);
  • 標頭檔:#include <signal.h>
  • 參數解析
    • 這是一個輸出型參數,會把當前進程的pending表列印到傳入的set集中

 事已至此,基本操作就說完了,廢話少說,show me the code

功能看結果圖:先貼結果圖,一開始沒有任何訊號,所以pending表中全是0,我通過Ctrl+C傳入2號訊號,看到pending表中有2號被置位了,經過10秒取消阻塞,2號訊號被處理(經過我自訂的函數)

我的程式只有一個檔案server.c

 1 #include <stdio.h> 2 #include <sys/signal.h> 3 #include <sys/types.h> 4 #include <signal.h> 5  6  7  8 void func(int num) 9 {10   printf("catch signal number is %d",num);11 12 }13 14 15 void printfpendingsignal(sigset_t *set)16 {17   int i;18   for(i=1;i<32;++i)19   {20     if(sigismember(set,i))21     {22       printf("1");23 24     }25     else26     {27       printf("0");28     }29   }30   printf("\n");31 }32 33 34 int main()35 { 36   sigset_t s,p,o;37   signal(SIGINT,func);38   sigemptyset(&s);39   sigemptyset(&p);40   sigemptyset(&o);41   sigaddset(&s,SIGINT);42   sigprocmask(SIG_SETMASK,&s,&o);43   int count=0;44   while(1)45   {46     sigpending(&p);47     printfpendingsignal(&p);48     sleep(1);49     if(count++==10)50     {51       printf("recover!\n");52       sigprocmask(SIG_SETMASK,&o,NULL);53     }54   }55   return 0;56 }

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.