細說linux信號 一

l         什麼是信號

信號是一個消息,用來進行進程間的相互通信,這和人們用e_mail相互通信類似。所以信號的處理一定是在某一進程中進行的。

信號的發生是由於某一事件而引起的,這些事件可以是:1、由硬件產生的硬件異常(比如除零操作),2、由軟件產生信號(比如先前設置的鬧鐘時間到時),3、從終端產生的信號,4、使用kill函數。

Linux下有三十多種的信號,不同的信號其默認的處理是不一樣的,對於一些無關緊要的信號,進程在接收後不做任何處理(比如:當子進程的狀態發生改變時向父進程發送的SIGCHLD信號),另外一些信號可能導致進程停止當前的操作轉而執行其它的操作(比如:當你按下CTRLC時,SIGINT信號被送往當前進程,進程在接收到該信號後立刻終止運行)。

很多時候我們並希望使用系統默認的信號方式(比如:在一款遊戲軟件中,我們希望當進程接收到SIGINT信號後並不直接退出,而是先保存當前的數據然後在退出,這樣當用戶下次執行遊戲時就能從上次被停止的地方繼續執行),linux提供了許多和信號處理相關的函數,這些函數使得進程對信號的處理變得相當的靈活。

l         最簡單的信號函數

首先解釋下什麼叫做信號句柄(signal handler):函數和變量一樣都是有地址的,我們說函數的地址是指該函數在內存中的起始位置,編譯器把這個地址告訴cpu,以便cpu找到該函數並執行。所謂信號句柄就是當進程接收到某一信號後執行那個函數的地址。它告訴進程當這個信號到來是應該運行那個函數。

void (*signal (int signo, void (*func)(int))) (int )

如果該函數調用成功將返回前次設置的信號句柄,否則返回SIG_ERR

signal函數設置當進程接收到某一信號(singno)時所執行動作。func 可以是a) 常量SIG_IGN(忽略該信號), b) 常量SIG_DEF(使用系統默認的處理方法),c)一個函數的地址(當信號到達時系統將轉而執行該函數)。

當進程調用exec家族的函數後,所有信號都將被置爲SIG_DEL或者SIG_IGN(這是由於exec改變了進程中代碼段和數據段中的內容,所以原先設置的信號句柄在這時可能有其它的意義了)。

當進程調用fork函數產生子進程時,原先對信號的設置將被子進程所繼承(因爲子進程原樣複製了父親進程的代碼段和數據段中的數據,或者它們共享相同的地址空間(COW),這取決與操作系統的實現)。

此外從<signal.h>頭文件我們可以看到如下定義:

#define SIG_ERR (void (*) ())-1

#define SIG_DEL (void (*) ()) 0

#define SIG_IGN (void (*) ()) 1

這些宏告訴我們如何將一個常量轉換爲函數指針

l         不可靠的信號(unreliable signal

早期的操作系統對信號的處理存在着一個問題:當某信號發生是,系統將還原對該信號的設置。於是有人寫下面這段代碼:

int sig_int()/* my signal function*/

main(){

signal(SIG_INT,sig_int);

}

sig_int(int signo){

       signal(SIG_INT,sig_int);

      

}

乍看起來這裏是不會有問題的,因爲當進程第一次收到SIG_INT並執行sig_int句柄後雖然系統還原了對SIG_INT的默認處理(結束進程),但是由於進程再次調用了signal函數而使得當下個SIG_INT到來時進程遞歸進入下個sig_int句柄(而不是退出該進程)。

不幸的是這段代碼是錯誤的,原因是忽略了窗口時間(window of time ),所謂窗口時間是指從信號發生到在信號句柄內(sig_int函數)調用signal函數前的這段時間。如果這時恰好進程又收到一個SIG_INT信號,由於信號系統默認的SIG_INT是終止進程,於是該進程將被終止。

                     早期系統存在的另外一個問題是我們無法關閉一個信號(阻止該信號出現), 能做的只有忽略(SIG_IGN)該信號。

l         可重入函數(reentrant function

在信號句柄中我們無法告訴進程該信號到來前一刻程序究竟運行到了哪個位置。假設信號到來前進程正通過malloc分配一個地址空間,並且信號句柄中我們再次調用了malloc,這時會發生什麼事呢?由於malloc會修改一個保存空閒地址的鏈表,而且這個鏈表是全局數據類型,於是當第一個malloc正在修改這個鏈表時(修改未完成),由於一個信號到來,進程轉而去執行信號句柄,而信號句柄中又調用malloc函數,第二個malloc也修改同一個鏈表,這樣就產生了錯誤的數據。類似malloc的就是不可重入的函數,其它的就叫做可重入函數。

       判斷一個函數是否爲一個不可重入函數一幫按照下面標準:1、使用靜態數據結構,2、有調用mallocfree函數,3、是標準I/O庫的一部分(因爲大部分的標準I/O庫使用了全局數據結構)。

       通過調用sigsetjmp函數(以後將會說明)可以避免不可重入函數所產生的問題。

 

 

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