Linux-信號機制詳解(一)

      之前有寫過SystemV的信號量機制,現在是信號。這裏的信號和前面的信號量是不同的。這裏的信號是進程給操作系統或進程的某種信息,讓操作系統或者其他進程做出某種反應。

      信號是進程間通信機制中唯一的異步通信機制,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。信號機制除了基本通知功能外,還可以傳遞附加信息。


在這裏舉幾個常見的信號的例子:

1. 用戶輸入命令,在Shell下啓動一個前臺進程。

2. 用戶按下Ctrl-C,這個鍵盤輸入產生一個硬件中斷。

3. 如果CPU當前正在執行這個進程的代碼,則該進程的用戶空間代碼暫停執行,CPU從用

戶態 切換到內核態處理硬件中斷。

4. 終端驅動程序將Ctrl-C解釋成一個SIGINT信號,記在該進程的PCB中(也可以說發送了

一 個SIGINT信號給該進程)。

5. 當某個時刻要從內核返回到該進程的用戶空間代碼繼續執行之前,需先處理PCB中記

錄的信號,發現有一個SIGINT信號待處理,用這個信號的默認處理動作是終止進程,所

以直接終止進程而不再返回它的用戶空間代碼執行。


    前臺進程在運行過程中用戶隨時可能按下Ctrl-C鍵產生一個信號,也就是說該進 程的用戶空間代碼執行到任何地值都有可能收到SIGINT信號而終止,所以信號相對於進程的控制流 程來說是異步(Asynchronous)的。


一。信號種類:

Linux的signal.h中定義了很多信號,使用命令kill -l  可以查看系統定義的信號列表:



可以發現其實沒有32,33號信號。1-31號信號叫做普通信號,34-64號信號叫做實時信號。

通過指令      kill  信號序號  進程號   可以向一個進程發送信號


二。信號產生的條件:

1. 用戶在終端按下某些鍵時,終端驅動程序會發送信號給前臺進程,例如Ctrl-C產生SIGINT信號,Ctrl-\產生SIGQUIT信號,Ctrl-Z產生SIGTSTP信號(可使前臺進程停止)

      SIGINT的默認處理動作是終止進程,SIGQUIT的默認處理動作是終止進程並且Core Dump,所以我們可以寫一個死循環,讓後通過對這個進程發送SIGINT(ctrl-C)SIGQUIT(ctrl-\)信號來終止這進程。



可以看到進程被終止了,ctrl-C好像不行。。。。


解釋一下CoreDump

當一個進程要異常終止時,可以選擇把進程的用戶空間內存數據全部 保存到磁盤上,文件名通常是core,這叫做Core Dump。進程異常終止通常是因爲有Bug,例如非法內存訪問致段錯誤,事後可以用調試器檢查core文件以查清錯誤原因,這叫做Post-mortem Debug。這個進程允許產生多個的core問件取決於進程的Resource Limit(這個信息保存 在PCB中)。默認是不允許產生core問件的,因爲core文件中可能包含用戶密碼等敏感信息,不安全。在開發調試階段可以用ulimit命令改變這個限制,允許產生core文件。


2. 硬件異常產生信號,這些條件由硬件檢測到並通知內核,然後內核向當前進程發送適當的信號。例如當前進程執行了除以0的令,CPU的運算單元會產生異常,內核將這個異常解釋 爲SIGFPE信號發送給進程。再如當前進程訪問了非法內存地址,,MMU會產生異常,內核將這個異常解釋爲SIGSEGV信號發送給進程。

       首先在後臺執行死循環程序,然後用kill命令給它發SIGSEGV信號。通過在可執行文件後面加一個&,將進程放到後臺運行


可以看到進程被殺死。

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

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);</span>

這兩個函數都是成功返回0,錯誤返回-1。
abort函數使當前進程接收到SIGABRT信號時異常終止。

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <stdlib.h>
void abort(void);</span>

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


3. 一個進程調用kill(2)函數可以發送信號給另一個進程。 可以用kill(1)命令發送信號給某個進程,kill(1)命令也是調用kill(2)函數實現的,如果不明確指定信號則發送SIGTERM信號,該信號的默認處理動作是終止進程。 當內核檢測到某種軟件條件發生時也可以通過信號通知進程,例如鬧鐘超時產生SIGALRM信號,向讀端已關閉的管道寫數據時產生SIGPIPE信號。 如果不想按默認動作處理信號,用戶程序可以調用sigaction(2)函數告訴內核如何處理某種信號

     可選的處理方式有三種:

          (一)忽略此信號。

           (二)執行該信號的默認處理動作。

            (三)提供一個自定義的信號處理函數要求內核在處理該信號時切換到用戶態執行這個處理函數,這種方 式稱爲捕捉(Catch)一個信號。


例:

SIGPIPE是一種由軟件條件產生的信號

<span style="font-family:Microsoft YaHei;font-size:14px;">#include <unistd.h>
unsigned int alarm(unsigned int seconds);</span>

      調用alarm函數可以設定一個鬧鐘,也就是告訴內核在seconds秒之後給當前進程發SIGALRM信號, 該信號的默認處理動作是終止當前進程。 這個函數的返回值是0或者是以前設定的鬧鐘時間還餘下 的秒數。打個比方,某人要睡覺,設定鬧鐘爲30分鐘之後響,20分鐘後被吵醒了,還想多睡 一會兒,於是重新設定鬧鐘爲15分鐘之後響,“以前設定的鬧鐘時間還餘下的時間”就是10分鐘。如果seconds值爲0,表⽰取消以前設定的鬧鐘,函數的返回值仍然是以前設定的鬧鐘時間還餘下的秒數。 


      這個程序的作用是1秒鐘之內不停地數數,1秒鐘到了就被SIGALRM信號終止。


三。阻塞信號

       實際執⾏信號的處理動作稱爲信號遞達(Delivery),信號從產⽣到遞達之間的狀態,稱爲信號未決(Pending)。進程可以選擇阻塞(Block )某個信號。被阻塞的信號產⽣時將保持在未決狀態,直到進程解除對此信號的阻塞,才 執⾏遞達的動作。 注意,阻塞和忽略是不同的只要信號被阻塞就不會遞達,⽽忽略是在遞達之後 可選的⼀種處理動作。信號在內核中的表⽰可以看作是這樣的:


結合處理信號的三種方式:

1. SIGHUP信號未阻塞也未產⽣過,當它遞達時執⾏默認處理動作。

2. SIGINT信號產⽣過,但正在被阻塞,所以暫時不能遞達。雖然它的處理動作是忽略,但在沒 有解除阻塞之前不能忽略這個信號,因爲進程仍有機會改變處理動作之後再解除阻塞。

3. SIGQUIT信號未產⽣過,⼀旦產⽣SIGQUIT信號將被阻塞,它的處理動作是⽤戶⾃定義函數sighandler。


常規信號在遞達之前產⽣多次只計⼀次,⽽實時信號在遞達之前產⽣多次可以依次放在⼀個隊列⾥。因此,未決和阻塞標誌可以⽤相同的數據類型sigset_t來存儲,sigset_t稱爲信號集,這個類型可以表⽰每個信號的“有效”或“⽆效”狀態,在阻塞信號集中“有效”和“⽆效”的含義是該信號是否被阻塞,⽽在未決信號集中“有效”和“⽆效”的含義是該信號是否處於未決狀態。

(一)信號集操作函數

調⽤以下函數來操作sigset_t變量

#include <signal.h>
int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signo);

int sigdelset(sigset_t *set, int signo);

int sigismember(const sigset_t *set, int signo);
(二)信號屏蔽字

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

#include <signal.h>

int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

返回值:若成功則爲0,若出錯則爲-1


如果oset是⾮空指針,則讀取進程的當前信號屏蔽字通過oset參數傳出。如果set是⾮空指針,則 更改進程的信號屏蔽字,參數how指⽰如何更改。如果oset和set都是⾮空指針,則先將原來的信號 屏蔽字備份到oset⾥,然後根據set和how參數更改信號屏蔽字。假設當前的信號屏蔽字爲mask,下表說明了how參數的可選值。


如果調⽤sigprocmask解除了對當前若⼲個未決信號的阻塞,則在sigprocmask返回前,⾄少將其中 ⼀個信號遞達


(三)未決信號集

#include <signal.h>
int sigpending(sigset_t *set);

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

  

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