IPC--信號(1)--基本知識

信號本質

信號是在軟件層次上對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣的。

信號是異步的,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。

信號是進程間通信機制中唯一的異步通信機制,可以看作是異步通知,通知接收信號的進程有哪些事情發生了。

信號機制經過POSIX實時擴展後,功能更加強大,除了基本通知功能外,還可以傳遞附加信息。

信號來源

信號事件的發生有兩個來源:

硬件來源(比如我們按下了鍵盤或者其它硬件故障);

軟件來源,最常用發送信號的系統函數是kill, raise, alarm和setitimer以及sigqueue函數,軟件來源還包括一些非法運算等操作。

信號種類

    第一種:

    可靠信號與不可靠信號

    1. 信號值小於SIGRTMIN (Red hat 7.2中,SIGRTMIN=32,SIGRTMAX=63) 的信號都是不可靠信號;

    2. 不可靠信號的問題(主要只信號可能丟失):

       A. 進程每次處理信號後,就將對信號的響應設置爲默認動作。在某些情況下,將導致對信號的錯誤處理;因此,用戶如果不希望這樣的操作,

           那麼就要在信號   處理函數結尾再一次調用signal(),重新安裝該信號。( 已經改善)

       B. 信號可能丟失,後面將對此詳細闡述。

    3. 可靠信號支持排隊,不會丟失。

    第二種:

    實時信號與非實時信號:

    1. 非實時信號都不支持排隊,都是不可靠信號;

    2. 實時信號都支持排隊,都是可靠信號。

 

進程對信號的響應

     1. 忽略信號,即對信號不做任何處理,其中,有兩個信號不能忽略:SIGKILL及 SIGSTOP;

     2. 執行缺省操作,Linux對每種信號都規定了默認操作。注意,進程對實時信號的缺省反應是進程終止。

     3. 捕捉信號。定義信號處理函數,當信號發生時,執行相應的處理函數;


信號阻塞與信號信號的未決

信號的“未決”是一種狀態,指的是從信 號的產生到信號被處理前的這一段時間;
信號的“阻塞”是一個開關動作,指的是阻止信號被處理,但不是阻止信號產生。


僞demo:

 進程捕獲信號過程

1. 用戶輸入命令,在Shell下啓動一個前臺進程。
2. 用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。
3. 如果CPU當前正在執行這個進程的代碼,則該進程的用戶空間代碼暫停執行,CPU從用戶態切換到內核態處理硬件中斷。
4. 終端驅動程序將Ctrl-C解釋成一個SIGINT信號,記在該進程的PCB中(也可以說發送了一個SIGINT信號給該進程)。
5. 當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,首先處理PCB中記錄的信號,發現有一個SIGINT信號待處理,而這個信號的默認處理動作是終止進程,所以直接終止進程而不再返回它的用戶空間代碼執行。

  內核如何實現信號的捕捉

    如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個函數,這稱爲捕捉信號。

由於信號處理函數的代碼是在用戶空間的,處理過程比較複雜,舉例如下:
1. 用戶程序註冊了SIGQUIT信號的處理函數sighandler。
2. 當前正在執行main函數,這時發生中斷或異常切換到內核態。
3. 在中斷處理完畢後要返回用戶態的main函數之前檢查到有信號SIGQUIT遞達。
4. 內核決定返回用戶態後不是恢復main函數的上下文繼續執行,而是執行sighandler函數,sighandler和main函數使用不同的堆棧空間,它們之間不存在調用和被調用的關係,是兩個獨立的控制流程。
5. sighandler函數返回後自動執行特殊的系統調用sigreturn再次進入內核態。
6. 如果沒有新的信號要遞達,這次再返回用戶態就是恢復main函數的上下文繼續執行了。

 如下圖:


信號在內核中的表示(refer to :linux c 一站式學習)

以上我們討論了信號產生(Generation)的各種原因,而實際執行信號的處理動作稱爲信號遞達(Delivery),信號從產生到遞達之間的狀態,稱爲信號未決(Pending)。進程可以選擇阻塞(Block)某個信號。被阻塞的信號產生時將保持在未決狀態,直到進程解除對此信號的阻塞,才執行遞達的動作。注意,阻塞和忽略是不同的,只要信號被阻塞就不會遞達,而忽略是在遞達之後可選的一種處理動作。信號在內核中的表示可以看作是這樣的:

圖 33.1. 信號在內核中的表示示意圖

每個信號都有兩個標誌位分別表示阻塞和未決,還有一個函數指針表示處理動作。信號產生時,內核在進程控制塊中設置該信號的未決標誌,直到信號遞達才清除該標誌。在上圖的例子中,
1. SIGHUP信號未阻塞也未產生過,當它遞達時執行默認處理動作。
2. SIGINT信號產生過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒有解除阻塞之前不能忽略這個信號,因爲進程仍有機會改變處理動作之後再解除阻塞。
3. SIGQUIT信號未產生過,一旦產生SIGQUIT信號將被阻塞,它的處理動作是用戶自定義函數sighandler。
如果在進程解除對某信號的阻塞之前這種信號產生過多次,將如何處理?POSIX.1允許系統遞送該信號一次或多次。Linux是這樣實現的:常規信號在遞達之前產生多次只計一次,而實時信號在遞達之前產生多次可以依次放在一個隊列裏。本章不討論實時信號。從上圖來看,每個信號只有一個bit的未決標誌,非0即1,不記錄該信號產生了多少次,阻塞標誌也是這樣表示的。因此,未決和阻塞標誌可以用相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,這個類型可以表示每個信號的“有效”或“無效”狀態,在阻塞信號集中“有效”和“無效”的含義是該信號是否被阻塞,而在未決信號集中“有效”和“無效”的含義是該信號是否處於未決狀態。下一節將詳細介紹信號集的各種操作。阻塞信號集也叫做當前進程的信號屏蔽字(Signal Mask),這裏的“屏蔽”應該理解爲阻塞而不是忽略。

3.2. 信號集操作函數

sigset_t類型對於每種信號用一個bit表示“有效”或“無效”狀態,至於這個類型內部如何存儲這些bit則依賴於系統實現,從使用者的角度是不必關心的,使用者只能調用以下函數來操作sigset_t變量,而不應該對它的內部數據做任何解釋,比如用printf直接打印sigset_t變量是沒有意義的。

函數sigemptyset初始化set所指向的信號集,使其中所有信號的對應bit清零,表示該信號集不包含任何有效信號。函數sigfillset初始化set所指向的信號集,使其中所有信號的對應bit置位,表示該信號集的有效信號包括系統支持的所有信號。注意,在使用sigset_t類型的變量之前,一定要調用sigemptyset或sigfillset做初始化,使信號集處於確定的狀態。初始化sigset_t變量之後就可以在調用sigaddset和sigdelset在該信號集中添加或刪除某種有效信號。這四個函數都是成功返回0,出錯返回-1。sigismember是一個布爾函數,用於判斷一個信號集的有效信號中是否包含某種信號,若包含則返回1,不包含則返回0,出錯返回-1。

3.3. sigprocmask
調用函數sigprocmask可以讀取或更改進程的信號屏蔽字。

返回值:若成功則爲0,若出錯則爲-1
如果oset是非空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是非空指針,則更改進程的信號屏蔽字,參數how指示如何更改。如果oset和set都是非空指針,則先將原來的信號屏蔽字備份到oset裏,然後根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字爲mask,下表說明了how參數的可選值。

表 33.1. how參數的含義

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

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

下面用剛學的幾個函數做個實驗。程序如下:


程序運行時,每秒鐘把各信號的未決狀態打印一遍,由於我們阻塞了SIGINT信號,按Ctrl-C將會使SIGINT信號處於未決狀態,按Ctrl-\仍然可以終止程序,因爲SIGQUIT信號沒有阻塞。


sigsuspend 函數

sigsuspend(const sigset_t *mask))用於在接收到某個信號之前, 臨時用mask替換進程的信號掩碼, 並暫停進程執行,直到收到信號爲止。sigsuspend 返回後將恢復調用之前的信號掩碼。信號處理函數完成後,進程將繼續執行。該系統調用始終返回-1,並將errno設置爲EINTR。

注意:

sigsuspend的整個原子操作過程爲:
(1) 設置新的mask阻塞當前進程,有些地方的講解說恢復原先的mask在第三步
(2) 收到信號,恢復原先mask;執行,實際上按照輸出結果,恢復原先的mask
(3) 調用該進程設置 的信號處理函數,應該在調用進程的信號處理函數之前進行。請注意
(4) 待信號處理函數返回後,sigsuspend 返回。
 

實例:

功能描述:
sigsuspend 函數將進程的信號屏蔽字設置爲 sigmask 指向的值。在捕捉到一個信號或發生了一個會終止該進程的信號之前,該進程被掛起。如果捕捉到一個信號而且從該信號處理程序返回,則sigsuspend返回,在返回之前,將進程的信號屏蔽字設置爲調用sigsuspend之前的值。


用 法:
# include < signal . h>
int sigsuspend ( const sigset_t * sigmask) ;

參 數:
sigmask:  指向信號集的指針,裏面設置了屏蔽的信號


返回說明:
函數沒有成功返回值。如果它返回到調用者,則總是返回-1,並將 errno 設置爲EINTR(表示一個被中斷的系統調用)。

demo:


  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <signal.h>  
  4. volatile sig_atomic_t quitflag; /* set nonzero by signal handler */  
  5.   
  6.  /* one signal handler for SIGINT and SIGQUIT */  
  7. static void sig_handler(int signo)  
  8. {  
  9.     if (signo == SIGINT)  
  10.                 printf("ninterruptn");  
  11.         else if (signo == SIGQUIT)  
  12.                 quitflag = 1; /* set flag for main loop */  


  13. int main(int argc, char *argv[])  
  14. {  
  15.         sigset_t newmask, oldmask, zeromask;  
  16.         if (signal(SIGINT, sig_handler) == SIG_ERR)  
  17.                 perror("signal(SIGINT) error");  
  18.         if (signal(SIGQUIT, sig_handler) == SIG_ERR)  
  19.                 perror("signal(SIGQUIT) error");  
  20.         sigemptyset(&newmask);  
  21.         sigemptyset(&zeromask);  
  22.         sigaddset(&newmask, SIGQUIT);  
  23.         /* Block SIGQUIT and save current signal mask */  
  24.         if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) != 0)  
  25.                 perror("sigprocmask SIG_BLOCK error");  
  26.         //while(1);   
  27.         while (quitflag == 0)  
  28.     {
  29.        printf(“start sigsuspend…\n”);
  30.                 sigsuspend(&zeromask);  
  31.        printf(“end sigsuspend…\n”);
  32.     }
  33.         quitflag = 0;  
  34.         /* Reset signal mask which unblocks SIGQUIT */  
  35.         if (sigprocmask(SIG_SETMASK, &oldmask, NULL) != 0)  
  36.                 perror("sigprocmask SIG_SETMASK error");  
  37.         exit(0);  
  38. }  

我們運行程序(註釋while(1);版本):

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+\         程序終止

接着我們把程序中的while(1); 解除註釋,運行程序

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+c   :    輸出 :  interrept

輸入: ctrl+\         沒有反應

分析:

程序停在while(1)的時候,這裏屏蔽了sigquit的信號,而程序在sigsuspend阻塞時候,把屏蔽信號設置成zeromask,不阻塞任何信號,所以能接受到ctrl+\
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章