目錄
1 信號的本質
軟中斷信號(signal,又簡稱爲信號)用來通知進程發生了異步事件。在軟件層次上是對中斷機制的一種模擬,在原理上,一個進程收到一個信號與處理器收到一箇中斷請求可以說是一樣的。信號是進程間通信機制中唯一的異步通信機制,一個進程不必通過任何操作來等待信號的到達,事實上,進程也不知道信號到底什麼時候到達。進程之間可以互相通過系統調用kill發送軟中斷信號。內核也可以因爲內部事件而給進程發送信號,通知進程發生了某個事件。信號機制除了基本通知功能外,還可以傳遞附加信息。
收到信號的進程對各種信號有不同的處理方法。處理方法可以分爲三類:
- 類似中斷的處理程序,對於需要處理的信號,進程可以指定處理函數,由該函數來處理。
- 忽略某個信號,對該信號不做任何處理,就象未發生過一樣。
- 對該信號的處理保留系統的默認值,這種缺省操作,對大部分的信號的缺省操作是使得進程終止。進程通過系統調用signal來指定進程對某個信號的處理行爲。
2 信號列表
自己的UNIX操作系統支持多少種信號,可以在命令行中通過kill -l指令查看。一般來說MAC支持31種信號,Linux支持64種信號。不同平臺支持的信號都差不多,因爲畢竟都是按照POSIX標準來的。
SIGHUP | 1 | A | 終端掛起或者控制進程終止 |
SIGINT 2 | 2 | A | 鍵盤中斷(如break鍵被按下) |
SIGQUIT | 3 | C | 鍵盤的退出鍵被按下 |
SIGILL | 4 | C | 非法指令 |
SIGABRT | 6 | C | 由abort(3)發出的退出指令 |
SIGFPE | 8 | C | 浮點異常 |
SIGKILL | 9 | AEF | Kill信號 |
SIGSEGV | 11 | C | 無效的內存引用 |
SIGPIPE | 13 | A | 往一個寫端關閉de socket中連續寫入數據 |
SIGALRM | 14 | A | 由alarm(2)發出的信號 |
SIGTERM | 15 | A | 終止信號 |
SIGUSR1 | 30,10,16 | A | 用戶自定義信號1 |
SIGUSR2 | 31,12,17 | A | 用戶自定義信號2 |
SIGCHLD | 20,17,18 | B | 子進程結束信號 |
SIGCONT | 19,18,25 | 進程繼續(曾被停止的進程) | |
SIGSTOP | 7,19,23 | DEF | 終止進程 |
SIGTSTP | 18,20,24 | D | 控制終端(tty)上按下停止鍵 |
SIGTTIN | 21,21,26 | D | 後臺進程企圖從控制終端讀 |
SIGTTOU | 22,22,27 | D | 後臺進程企圖從控制終端寫 |
處理動作一項中的字母含義如下:
- A 缺省的動作是終止進程
- B 缺省的動作是忽略此信號,將該信號丟棄,不做處理
- C 缺省的動作是終止進程並進行內核映像轉儲(dump core),內核映像轉儲是指將進程數據在內存的映像和進程在內核結構中的部分內容以一定格式轉儲到文件系統,並且進程退出執行,這樣做的好處是爲程序員提供了方便,使得他們可以得到進程當時執行時的數據值,允許他們確定轉儲的原因,並且可以調試他們的程序。
- D 缺省的動作是停止進程,進入停止狀況以後還能重新進行下去,一般是在調試的過程中(例如ptrace系統調用)
- E 信號不能被捕獲
- F 信號不能被忽略
3 信號發送時機
信號發送時機主要有兩種,一種是內核自動給進程發送信號。另一種是進程主動給進程發送信號,此時可以是當前進程給當前進程發信號,也可以是進程A給進程B發信號。下面分別解釋:
3.1 內核自動給進程發送信號
這裏單獨列出內核給進程發送信號其實有點牽強,因爲內核本身就屬於進程地址空間的一部分,只不過這部分地址空間是所有進程共享的。這裏只講一個信號,SIGALARM。
此信號和alarm系統調用有關,alarm()系統調用是給調用進程設置一個告警時間值,到達那個告警時間值內核自動給進程發一個SIGALARM信號,其實現過程是這樣的:進程調用alarm()系統調用會傳一個時間參數(以秒爲單位),該系統調用會在調用進程的task_struct.alarm字段上加上指定的秒然後退出系統調用。內核調度程序schedule()每次執行的時候會遍歷一遍進程數組列表裏面的所有進程,只要發現有進程當前時間的值已經大於task_struct.alarm的值,就給該進程發一個SIGALARM信號,並重置該進程的alarm=0。我們姑且認爲這種信號發送機製爲內核自動給進程發送的。
3.2 進程給進程發送信號
進程給進程發送信號分爲進程給自己發信號,進程給其他進程發信號兩種。大部分應用場景都是進程給其他進程發信號。
不管如何進程給進程發信號都要通過系統調用kill(pid , sig)來實現,注意這裏kill不僅僅代表殺死進程的意思,雖然大多數信號都是殺死進程。這裏有一個限制,發送進程的euid必須和接受進程的euid相同,或者發送進程具有超級用戶權限。該函數的參數說明如下(pid標誌接收信號的進程,sig標誌要發送的信號):
- pid > 0 , pid代表進程號,即給某單個進程發信號,該單個進程由pid來唯一標誌。
- pid = 0 , 信號被髮送給當前進程的進程組中的所有進程,這裏的一個隱含條件是發送信號的進程必須是進程組的組長。
- pid = -1 , 信號被髮送給除0號進程進程外的所有進程。
- pid < -1 , 信號被髮送給進程組中的所有進程(進程組號=-pid)。
如果是進程自己給進程自己發信號,則一般是在進程執行程序中調用kill(pid,sig)。
如果是進程自己給其他進程發信號,可選擇的方式就很多了,可以在進程代碼執行過程中發送,也可以用命令行發送。
用命令行發送信號的格式一般是 kill -sig pid。其實用命令行發送信號本質也是進程給進程發信號。
4 信號處理時機
信號在被進程從內核態轉到用戶態的時候執行。常見的執行態切換有 系統調用返回, 時鐘中斷返回。這兩種本質上都是中斷處理返回。Linux當中系統調用的返回值放在eax寄存器中,接着內核代碼判斷進程狀態,如果進程狀態不是0,則去執行調度程序。如果進程時間片到期,則也去執行調度程序。
接着開始檢查當前進程的task_struct.signal & ~task_struct.blocked , 如果有收到未被屏蔽的信號,則按照信號從低位到高位的方式依次調用do_signal信號處理函數。
從這裏可以看出,信號的處理過程是異步的,並不是說給進程發了信號,進程就立刻馬上執行完當前指令就去執行信號處理程序。而是選擇在從內核態返回到用戶態的時候檢查處理。一個進程在執行過程中可能沒有系統調用(第一次創建fork(),最後一次退出exit()除外),或者系統調用的頻率非常低,所以我們發的信號有可能很長時間得不到處理。但是時鐘中斷髮生的頻率非常頻繁,並且是勻速發生的,所以在這裏從時鐘中斷的角度來說的話,可以認爲信號的處理是準實時進行的。
參考: