文章目錄
- 概述
- 信號類型
- 可靠信號與不可靠信號
- Fatal信號與Non Fatal信號
- 不可捕獲/忽略信號
- 信號工作機制
- 信號處理方式
- 信號嵌套處理
- 信號使用
- 信號發送
- kill命令
- 注冊信號處理函數
- 信號安全與函數可重入性
- 可重入函數
- 線程安全與可重入性
- 相關參考
概述
Linux信號機制是進程間通信的一種方式,用于在不同進程之間傳遞信息,它通過向目標進程發送一個特定的信號,來觸發目標進程執行相應的處理操作。信號本質上是在軟件層次上對中斷機制的一種模擬,可以認為是用戶態下的中斷機制,它為用戶進程提供了一種處理異步事件的方法;用戶進程可以注冊自定義的信號處理函數,在進程響應外部信號時,會自動調用回調進行處理。
信號類型
Linux定義了64種信號類型,每個信號都有唯一的編號進行標識。在系統中,通過kill -l命令可以查看所有的信號類型。
可靠信號與不可靠信號
Linux信號可以分為不可靠信號和可靠信號。
- 不可靠信號:又稱非實時信號,是指在信號傳遞過程中可能丟失或產生不可預測行為的信號,這意味著當一個進程接收到該信號時,無法確保該信號一定會被進程處理。1-31號信號為不可靠信號。
- 可靠信號:又稱實時信號,是保證傳遞和處理的信號。當一個進程接收到可靠信號時,系統會確保該信號不會丟失,并且會等待進程處理完該信號后再繼續執行其他操作,Linux使用隊列來保存待處理的信號,保證它們按照接收的順序被進程處理。34-64號信號為可靠信號。
在日常開發及維護時,見到的基本是不可靠信號:
Fatal信號與Non Fatal信號
對于每一個信號,系統都有默認的處理行為,根據信號的默認處理行為,可以將信號分為 Fatal(致命)信號和 Non-Fatal(非致命)信號。
- Fatal信號:Fatal 信號是指那些默認處理行為會導致進程終止的信號。當進程收到這類信號且沒有注冊自定義處理函數時,進程會被終止。
- Non Fatal信號:Non-Fatal 信號是指那些默認處理行為不會導致進程終止的信號。這些信號通常用于控制進程狀態或通知特定事件。
不可捕獲/忽略信號
在Linux系統中,用戶通常可以捕獲信號,并自定義處理信號處理行為;但有兩個信號比較特殊,它們既不能忽略,也不能捕獲,只能執行默認處理:
- SIGKILL (9):強制終止進程
- SIGSTOP (19):暫停進程
信號工作機制
與中斷的實時響應(CPU執行完一條指令后,就會響應中斷請求)不同,進程對信號的處理有一定的滯后性。原因在于,當應用進程接收到其它進程發送的信號時,不會立即做出響應,只有等當前進程陷入到內核空間時,才會進行信號檢測。Linux系統中,應用程序處理信號的流程示意如下:
可以看到,系統對信號的檢測與響應總是發生在內核態,只有當前進程由于系統調用、中斷或異常而進入系統空間以后,從系統空間返回到用戶空間的前夕,內核才會進行信號的處理。
檢測到信號后,內核在用戶棧創建新層,將返回地址指向信號處理函數,確保函數在用戶態執行,避免權限泄露;信號處理程序執行完成后,會執行sigreturn
系統調用再次切換到內核態,再由恢復原系統調用或代碼執行點。
信號處理方式
在進程接收到一個信號時,可以告訴內核按照下列三種方式之一進行處理:
- 忽略信號:對信號不做任何處理(但 SIGKILL 和 SIGSTOP 不能被忽略);
- 捕獲信號:注冊自定義的信號處理函數;
- 默認處理:執行系統定義的默認動作。Linux信號的默認處理行為可以分為以下幾類:
- Terminate:終止進程;
- Coredump:終止進程并生成核心轉儲文件;
- Ignore:忽略信號;
- Stop:暫停進程;
- Continue:繼續運行進程。
信號嵌套處理
默認情況下,信號處理函數運行期間,如果再次接收到相同信號,信號會被自動阻塞,直到當前處理函數執行完畢。
信號使用
在日常開發或維護過程中,所涉及到信號的使用方式,主要是如何發送信號以及自定義程序的信號的處理行為。
信號發送
Linux系統使用kill系統調用向指定進程發送信號,原型定義如下:
#include <sgnal.h>
int kill(int pid, int sign);
kill函數的pid參數根據取值有多重含義:
- pid>0:將此信號發送給進程ID為pid的進程;
- pid=0:將此信號發送給進程組ID和該進程相同的進程;
- pid<0:將此信號發送給進程組內進程ID為pid的進程;
- pid=-1:將此信號發送給系統所有的進程。
kill命令
在Linux系統中,kill命令用于向進程發送信號,以終止或控制進程。默認情況下,kill命令發送的是SIGTERM(終止信號),這通常會讓進程自行清理資源并優雅地終止。如果進程沒有響應SIGTERM,可以使用SIGKILL(殺死信號)強制終止進程。
kill [-signum] [PID]
注冊信號處理函數
Linux系統下可以通過signal
或sigaction
函數可以注冊信號處理函數。signal原型如下:
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
sigaction是 signal的增強版,提供了更精細的控制,包括:
* 自定義信號處理函數
* 設置信號屏蔽字(阻塞其他信號)
* 指定信號處理標志(如是否自動重置處理函數)
sigaction函數原型定義如下:
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
信號安全與函數可重入性
信號安全是指線程在信號處理函數當中,不管以任何方式調用你的函數如果不死鎖不修改數據,那就是信號安全的。在信號處理函數中應避免調用不可重入的函數(如 printf、malloc),否則可能導致程序崩潰。
可重入函數
一個函數或代碼段若能在被中斷后安全地再次調用(如信號處理函數中調用),則稱為可重入的。不可重入的代碼會導致數據損壞或死鎖。
可重入代碼的條件:
- 不使用全局或靜態變量:依賴局部變量或通過參數傳遞狀態。
- 不調用不可重入函數:如malloc()、printf()、非線程安全的庫函數。
- 避免鎖的嵌套:信號處理函數中不應獲取鎖(可能引發死鎖)。
線程安全與可重入性
可重入函數是線程安全函數的一個真子集。可重入函數一定是線程安全的。盡管線程安全和可重入有時會被不正確地用作同義詞,但它們之間有清晰的技術差別。線程安全函數可能使用同步機制(如鎖)來保護共享數據,而可重入函數則完全不使用共享數據。
相關參考
- 《Unix環境高級編程》
- Linux signal 圖文詳解(一)信號簡介、信號注冊
- 干貨】linux內核信號的處理過程