Unix信號

在電腦科學中，信號（Signals）是Unix、類Unix以及其他POSIX兼容的作業系統中進程間通訊的一種有限制的方式。它是一種異步的通知機制，用來提醒進程一個事件已經發生。當一個信號發送給一個進程，作業系統中斷了進程正常的控制流程，此時，任何非原子操作都將被中斷。如果進程定義了信號的處理函數，那麼它將被執行，否則就執行默認的處理函數。

信號類似於中斷，不同之處在於中斷由處理器調解並由內核處理，而信號由內核調解(可能通過系統調用)並由進程處理。內核可以將中斷作為信號傳遞給導致中斷的進程（典型的例子有SIGSEGV、SIGBUS、SIGILL和SIGFPE）。

信號起源於20世紀70年代的貝爾實驗室Unix，最近在POSIX標準中有所規定。

嵌入式程序可能會發現信號對於進程間通信很有用，因為信號的計算和記憶體占用很小。

## 發送信號

- 在一個運行的程序的控制終端鍵入特定的組合鍵可以向它發送某些信號：
- Ctrl-C發送INT信號（SIGINT）；默認情況下，這會導致進程終止。
- Ctrl-Z發送TSTP信號（SIGTSTP）；默認情況下，這會導致進程掛起。
- Ctrl-\發送QUIT信號（SIGQUIT）；默認情況下，這會導致進程終止並且將記憶體中的資訊轉儲到硬碟（核心轉儲）。
- （這些組合鍵可以通過stty命令來修改。）
- kill系統調用會在權限允許的情況下向進程發送特定的信號，類似地，kill命令允許用戶向進程發送信號。`raise(3)`庫函數可以將特定信號發送給當前進程。
- 像除數為零、段錯誤這些異常也會產生信號（這裡分別是SIGFPE和SIGSEGV，默認都會導致進程終止和核心轉儲）。
- 內核可以向進程發送信號以告知它一個事件發生了。例如當進程將數據寫入一個已經被關閉的管道時將會收到SIGPIPE信號，默認情況下會使進程關閉。

## 處理信號

信號處理函數可以通過`signal()`系統調用來設置。如果沒有為一個信號設置對應的處理函數，就會使用默認的處理函數，否則信號就被進程截獲並調用相應的處理函數。在沒有處理函數的情況下，程序可以指定兩種行為：忽略這個信號（SIG_IGN）或者用默認的處理函數（SIG_DFL）。但是有兩個信號是無法被截獲並處理的：SIGKILL和SIGSTOP。

### 風險

因為競態條件的存在，信號的處理是有弱點的。因為信號是異步的，所以在處理一個信號的過程中，進程可能收到另一個信號（甚至是相同的信號）。`sigprocmask()`系統調用可以用來阻塞和恢復信號的傳遞。信號可以造成進程中系統調用的中斷，並在信號處理完後重新開始未完成的系統調用。信號處理函數應該沒有任何不想要的副作用，比如，errno的改變、信號掩碼的改變、信號處理方法的改變，以及其他全局進程性質的改變。在信號處理函數內使用不可重入函數，如malloc和printf，也是不安全的。

## 與硬體異常的關係

進程的運行也可能導致硬體異常，例如，將一個數除以零，或者出現TLB不命中。在類Unix系統中，這會自動運行內核的異常處理程序。對於某些異常如頁缺失，內核有足夠的資訊來處理完並恢復進程的運行。但是對於另外一些異常，內核不能處理而只能通過發送信號把異常交給進程自己處理。例如在x86架構的CPU上，如果一個進程嘗試將一個數除以零，將會產生divide error異常，並使內核向出錯的進程發送SIGFPE信號。相似地，如果一個進程嘗試訪問虛擬地址空間以外的記憶體，內核將向進程發送SIGSEGV信號。異常與信號的具體對應關係在不同的CPU架構上是不同的。

## 信號列表

單一UNIX規範規定了在中定義的信號有：

備註：打星號的部分表示這是X/Open System Interfaces (XSI)擴充的部分。使用引號的文字是引用自SUS。

SIGABRT 和 SIGIOT

SIGABRT 和 SIGIOT 信號能讓程序異常終止（abort）。該信號通常是由進程自身調用 C標準函數庫 的 `abort()` 函數來觸發，但它也可以像其它信號一樣由外部發送給進程。

SIGALRM, SIGVTALRM 和 SIGPROF

如果你用 setitimer 這一類的報警設置函數設置了一個時限，到達時限時進程會接收到 SIGALRM, SIGVTALRM 或者 SIGPROF。但是這三個信號量的含義各有不同，SIGALRM 計時的是真實時間，SIGVTALRM計時的是進程使用了多少CPU時間，而 SIGPROF 計時的是進程和代表該進程的內核用了多少時間。

SIGBUS

總線發生錯誤時，進程接收到一個SIGBUS信號。舉例來說，存儲器訪問對齊或者或不存在對應的物理地址都會產生SIGBUS信號。

SIGCHLD

當子進程終止、被中斷或被中斷後恢復時，SIGCHLD信號被發送到進程。該信號的一個常見用法是指示作業系統在子進程終止後清理其使用的資源，而不顯式調用等待系統調用。

SIGCONT

SIGCONT信號指示作業系統繼續(重啟)先前由SIGSTOP或SIGTSTP信號暫停的進程。Unix 殼的作業控制是該信號的一個重要應用。

SIGFPE

當進程執行了一個錯誤的算術運算時，例如被零除，信號被發送到一個進程。這可能包括整數被零除，以及整數在除結果中溢出（在C中只有INT_MIN/-1、INT64_MIN/-1和%-1會觸發該行為）。注意該信號與浮點數溢出無關。

SIGHUP

檢測到控制中斷掛起或者控制進程死亡時，進程會收到 SIGHUP。現在作業系統，該信號通常意味著使用的 虛擬終端 已經被關閉。許多 守護進程 在接收到該信號時，會重載他們的設置和重新打開日誌文件（logfiles），而不是去退出程序。nohup 命令用於無視該信號。

SIGILL

當進程試圖執行非法、格式錯誤、未知或特權指令時，SIGILL信號被發送到該進程。

SIGINT

當使用者希望中斷進程時，SIGINT信號由使用者的控制終端發送到進程。這通常通過按下Ctrl+C來發送，但是在某些系統中，可以使用「DELETE」鍵或「BREAK」鍵。

SIGKILL

發送SIGKILL信號到一個進程可以使其立即終止（KILL）。與SIGTERM和SIGINT相不同的是，這個信號不能被捕獲或忽略，接收過程在接收到這個信號時不能執行任何清理。以下例外情況適用：

- 殭屍進程不能被殺死，因為它們已經死了，正在等待它們的父進程來收穫它們。
- 處於阻塞狀態的進程不會死亡，直到它們再次醒來。
- init 進程是特殊的： init不接收任何它不打算處理的信號，因此它會忽略SIGKILL。這條規則有一個例外，Linux 上的 init 如果被 ptrace 了，那麼它是可以接收 SIGKILL 並被殺死的。
- 處於不可中斷的睡眠的進程即使發送了SIGKILL，也有可能不會終止（並釋放其資源）。這是少數 Unix 系統必須重新啟動才能解決臨時軟體問題的幾種情況之一。

當在大多數系統關閉程序中終止進程時，如果進程沒有響應 SIGTERM 而自動退出，SIGKILL 是最後的手段。為了加快電腦關機過程，Mac OS X 10.6向標記自己為「clean」的進程發送SIGKILL，從而加快關機時間，而且可能不會產生任何不良影響。在 Linux 中執行 `killall -9` 命令具有類似不過更危險的效果；它不讓程序保存未保存的數據。

SIGPIPE

當一個進程試圖寫入一個沒有連接到另一端進程的管道時，SIGPIPE信號會被發送到該進程。

SIGPOLL

當一個事件發生在一個正在顯式監視的文件描述符上時，就會發送SIGPOLL信號。有效使用這種用法可以進行異步 I/O，因為內核將代替調用者輪詢描述符。它提供了主動輪詢的替代方案。

SIGRTMIN 到 SIGRTMAX

SIGRTMIN至SIGRTMAX信號用於使用者自定義的目的。它們是實時信號。

SIGTTIN 和 SIGTTOU

當進程在後台試圖分別從tty讀取或寫入時，SIGTTIN和SIGTTOU信號會被發送到該進程。通常，這些信號僅由作業控制下的進程接收；守護進程沒有控制終端，因此永遠不會接收這些信號。

SIGQUIT

當使用者在進程的控制終端請求退出進程並進行核心轉儲時，SIGQUIT信號會被發送到該進程。

SIGSEGV

當進程試圖訪問無效記憶體引用時發生存儲器區段錯誤，SIGSEGV信號會被發送到該進程。

SIGSTOP

當作業系統暫停進程的運行時，會產生SIGSTOP信號。SIGSTOP信號無法被捕獲或無視。

SIGSYS

當系統調用時傳入非法的參數，會產生SIGSYS信號。實際上，SIGSYS信號很少會出現，因為應用程式依賴庫調用系統調用。SIGSYS可以被違反Linux Seccomp安全規則的應用程式捕獲。SIGSYS也可用於模擬外部系統調用，例如：在Linux上模擬 Windows系統調用。

SIGTERM

當使用者請求終止進程時，會產生SIGTERM信號。SIGTERM信號可以被捕獲或無視。這允許該進程在結束前釋放掉所占用的資源並保存其狀態。SIGINT和SIGTERM非常相似。

SIGTSTP

當使用者在進程的控制終端請求退出進程時，會產生SIGTSTP信號。SIGTSTP信號可以被捕獲或無視。SIGTSTP信號的產生通常是由於使用者按下Ctrl。

### 默認行為

一個進程可以自定義如何處理傳入的POSIX信號。如果一個進程沒有定義一個信號處理程序，那麼這個信號的默認處理程序將被使用。下表列出了一些與POSIX兼容的UNIX系統的默認操作，例如FreeBSD、OpenBSD和Linux。

信號｜可移植代號｜默認行為｜描述

SIGABRT｜6｜終止 (核心轉儲)｜進程終止信號

SIGALRM｜14｜終止｜計時器告警

SIGBUS｜不適用｜終止 (核心轉儲)｜訪問記憶體對象未定義區域

SIGCHLD｜不適用｜忽略｜子進程終止、暫停、繼續

SIGCONT｜不適用｜繼續｜如果被暫停，重新繼續執行

SIGFPE｜8｜終止 (核心轉儲)｜錯誤的算術運算

SIGHUP｜1｜終止｜掛起

SIGILL｜4｜終止 (核心轉儲)｜非法的指令

SIGINT｜2｜終止｜終端中斷信號

SIGKILL｜9｜終止｜殺死 (無法被捕獲或忽略的信號)

SIGPIPE｜13｜終止｜寫入一個沒有連接另一端的管道

SIGPOLL｜不適用｜終止｜可輪詢事件

SIGPROF｜不適用｜終止｜性能調優定時器超時

SIGQUIT｜3｜終止 (核心轉儲)｜終端退出信號

SIGSEGV｜11｜終止 (核心轉儲)｜非法的記憶體引用

SIGSTOP｜不適用｜暫停｜暫停執行（無法被捕獲或忽略的信號）

SIGSYS｜不適用｜終止 (核心轉儲)｜錯誤的系統調用

SIGTERM｜15｜終止｜終止信號

SIGTRAP｜5｜終止 (核心轉儲)｜追蹤/斷點陷阱

SIGTSTP｜不適用｜暫停｜終端中止信號

SIGTTIN｜不適用｜暫停｜後台進程嘗試讀

SIGTTOU｜不適用｜暫停｜後台進程嘗試寫

SIGUSR1｜不適用｜終止｜使用者自定義信號1

SIGUSR2｜不適用｜終止｜使用者自定義信號2

SIGURG｜不適用｜忽略｜Out-of-band data is available at a socket

SIGVTALRM｜不適用｜終止｜虛擬定時器超時

SIGXCPU｜不適用｜終止 (核心轉儲)｜超出CPU時間限制

SIGXFSZ｜不適用｜終止 (核心轉儲)｜超出文件大小限制

SIGWINCH｜不適用｜忽略｜終端窗口大小已變化

可移植編號

對於大多數信號，相應的信號編號由實現定義。此列列出了POSIX標準中指定的數字。

行為釋義：終止 – 進程異常終止。進程終止的結果和調用 _exit 是一樣的，除了終止可以向 wait 和 waitpid 返回導致進程終止的信號。

終止（核心轉儲） – 進程異常終止。這種進程中止的過程根據實現有所不同，一般會創建一個核心文件。

忽略 – 進程忽略該信號。

暫停 – 進程被暫停（不是終止）。

繼續 – 進程恢復執行。

## 外部連結

- Introduction to Unix Signals Programming
- Another Introduction to Unix Signals Programming
- UNIX and Reliable POSIX Signals by Baris Simsek
- Signal Handlers by Henning Brauer