Linux--信號

一、信號

  信號用來通知進程發生了異步事件。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。注意,信號只是用來通知某進程發生了什麼事件,並不給該進程傳遞任何數據。

       **用kill-l 命令查看系統定義的信號列表

二、信號的產生方式

  ① 通過鍵盤組合鍵向前臺發送信號,(一個命令後面加 & 可以發到後臺運行)

  a.信號的默認動作是終止進程,SIGQUIT的默認處理動作是終止進程並且Core Dump,(core dump是進程異常終止時,可以選擇把進程的用戶空間內存數據全部保存到磁盤上,文件名通常是core,這就叫做 core dump,通常就是程序有了BUG,可以用調試器檢查core文件以查清錯誤原因,)默認是不允許產生core文件的,因爲core文件中可能包含用戶密碼,不安全,在開發調試時可以用 ulimit命令改變這個限制,允許產生core文件。

      用命令 ulimit -c 1024

  ulimit命令改變了Shell進程的Resource Limit,test進程的PCB由Shell進程複製而來,所以也具有和Shell進程相同的Resource Limit值,這樣就可以產生Core Dump

  ② 通過調用系統函數想進程發送信號

  a.kill命令是調用kill函數實現的。kill函數可以給一個指定的進程發送指定的信號.raise函數可以給當前進程發送指定的信號(自己給自己發信號).     

        int kill(pid_t pid, int signo);

        int raise(int signo);

  都是成功返回0,錯誤返回-1;

  abort函數使當前進程接收到SIGABRT信號而異常終止。

      void abort(void);

   像exit函數一樣,abort函數總會成功的,所以沒有返回值。

  ③ 由軟件條件產生的信號

  a.alarm函數 和SIGALRM信號

  unsigned int alarm(unsigned int seconds);調用alarm函數可以設定一個鬧鐘,就是告訴內核在senconds秒後給當前進程發SIGALRM信號,默認動作是終止當前進程,函數的返回值是0或者是以前設定的鬧鐘時間還餘下的秒數。

三、處理信號的方式

  ① 忽略此信號

  ② 執行信號的默認處理動作,一般是終止進程

  ③ 捕捉信號

四、信號的遞達和阻塞

  ①、阻塞

  實際執行信號的處理動作稱爲信號遞達(Delivery),信號從產生到遞達之間的狀態,稱爲信號未決(Pending)。進程可以選擇阻塞(Block )某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。阻塞和忽略是不一樣的只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。

 信號在內核中的表示

wKiom1csjbeyqP0zAADkGzD9dTs551.jpg

  每個信號都有兩個標誌位分別表示阻塞(block)和未決(pending),還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌

 如果在進程解除對某信號的阻塞之前這種信號產生過多次,在Linux中常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列裏。每個信號只有一 個bit的未決標誌,非0即1,不記錄該信號產生了多少次,阻塞標誌也是這樣表示的。(blank是一種狀態,pending表示的是有無)未決和阻塞標誌可以用相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這裏的“屏蔽”應該理解爲阻塞而不是忽略。

  ②、信號集操作函數      

        #include <signal.h>

        int sigemptyset(sigset_t *set);//初始化對應的信號集bit位爲0

        int sigfillset(sigset_t *set);//初始化對象的信號集bit位爲1

        int sigaddset(sigset_t *set, int signo);//添加有效信號    

        int sigdelset(sigset_t *set, int signo);//刪除有效信號

        int sigismember(const sigset_t *set, int signo);//判斷一個信號集的有效信號中是否包含某種信號,包含返回1,不包含返回0。

  ③、sigprocmask

    調用函數sigprocmask可以讀取或更改進程的信號屏蔽字(阻塞信號集)。

    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);成功則爲0,出錯則爲-1;

    如果調用sigprocmask解除了對當前若干個未決信號的阻塞,則在sigprocmask返回前,至少將其中一個信號遞達.

  how參數的含義

    SIG_BLOCK set包含了我們希望添加到當前信號屏蔽字的信號,

    SIG_UNBLOCK set包含了我們希望從當前信號屏蔽在字中解除阻塞的信號,

    SIG_SETMASK 設置當前信號屏蔽字爲set所指向的值,

  ④、sigpending  

        int sigpending(sigset_t *set);

    sigpending讀取當前進程的未決信號集,通過set參數傳出。調用成功則返回0,出錯則返回-1。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

用程序說明

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<signal.h>
  4 void printsigset(sigset_t* sig)
  5 {
  6     int i=0;
  7     for(;i<31;i++)
  8     {
  9         if(sigismember(sig,i))//判斷指定信號是否在目標集合中
 10         {
 11             printf("1");
 12         }
 13         else
 14         {
 15             printf("0");
 16         }
 17     }
 18     printf("\n");
 19 }
 20 int main()
 21 {
 22     sigset_t s,p;//定義信號集
 23     sigemptyset(&s);//初始化
 24     sigemptyset(&p);
 25     sigaddset(&s,SIGINT);//設置信號ctrl+C
 26     sigprocmask(SIG_BLOCK,&s,NULL);//設置阻塞信號集,阻塞 SIGINT信號
 27     while(1)
 28     {
 29         sigpending(&p);//獲取未決信號集
 30         printsigset(&p);
 31         sleep(1);
 32     }
 33     return 0;
 34 }

wKiom1cslofTUwnJAAG2QcWrr-M220.jpg

結果分析:

    程序運行時,每秒鐘把各信號的未決狀態打印一遍,直到按Ctrl-C將會使SIGINT信號處於未決狀態,

五、捕捉信號

  a.內核如何實現信號的捕捉

  如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱爲捕捉信號,處理過程如下,舉例來說明

    1. 用戶程序註冊了SIGQUIT信號的處理函數sighandler。

    2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。

    3. 在中斷處理完畢後要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。

    4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函       數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程。

    5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。

    6. 如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了

**(先從用戶態—>內核態->返回用戶態之前檢查有信號遞達,返回用戶態處理信號->處理完成後再進入內核態->如果沒有新的信號遞達,返回用戶態恢復上下文繼續執行)

  b.sigaction       

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

sigaction函數可以讀取和修改與指定信號相關聯的處理動作。調用成功則返回0,出錯則返回- 1

    將sa_handler賦值爲常數SIG_IGN傳給sigaction表示忽略信號,賦值爲常數SIG_DFL表示執行系統默認動作,賦值爲一個函數指針表示用自定義函數捕捉信號,或者說向內核註冊了一個信號處理函 數,該函數返回值爲void,可以帶一個int參數,通過參數可以得知當前信號的編號,這樣就可以用同一個函數處理多種信號。顯然,這也是一個回調函數,不是被main函數調用,而是被系統所調用。

   當某個信號的處理函數被調用時,內核自動將當前信號加入進程的信號屏蔽字,當信號處理函數返回時自動恢復原來的信號屏蔽字,這樣就保證了在處理某個信號時,如果這種信號再次產生,那麼它會被阻塞到當前處理結束爲止。    

    如果在調用信號處理函數時,除了當前信號被自動屏蔽之外,還希望自動屏蔽另外一些信號,則用sa_mask字段說明這些需要額外屏蔽的信號,當信號處理函數返回時自動恢復原來的信號屏蔽字。

 c.pause

    int pause(void);  

    pause函數使調用進程掛起直到有信號遞達。如果信號的處理動作是終止進程,則進程終止,pause函數沒有機會返回;如果信號的處理動作是忽略,則進程繼續處於掛起狀態,pause不返回;如果信號的處理動作是捕捉,則調用了信號處理函數之後pause返回-1,

用alarm和pause實現sleep(3)的函數

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<signal.h>
 4 void sig_alarm(int signo)
 5 {
 6        //do nothing
 7 }
 8 unsigned int  my_sleep(unsigned int times)
 9 {
 10     struct sigaction new ,old;
 11     unsigned int unslept=0;
 12     new.sa_handler=sig_alarm;
 13     sigemptyset(&new.sa_mask);
 14     sigemptyset(&old.sa_mask);
 15     new.sa_flags=0;
 16     sigaction(SIGALRM,&new,&old);//註冊信號處理函數
 17     alarm(times); //設置鬧鐘
 18     pause();
 19     unslept=alarm(0);//取消鬧鐘
 20     sigaction(SIGALRM,&old,NULL);//恢復默認信號處理動作
 21     return unslept;
 22 }
 23 int main()
 24 {
 25     while(1)
 26     {
 27         my_sleep(5);
 28         printf("5 senconds pass\n");
 29     }
 30     return 0;
 31 }

 六、可重入函數  

    當捕捉到信號時,不論進程的主控制流程當前執行到哪兒,都會先跳到信號處理函數中執行,從信號處理函數返回後再繼續執行主控制流程。信號處理函數是一個單獨的控制流程,因爲它和主控制流程是異步的,二者不存在調用和被調用的關係,並且使用不同的堆棧空間。引入了信號處理函數使得一個進程具有多個控制流程,如果這些控制流程訪問相同的全局資源(全局變量、硬件資源等),就有可能出現衝突。

  


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章