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 規範規定了在<signal.h>中定義的訊號有：

備註：打星號的部分表示這是 X/Open System Interfaces (XSI) 擴充的部分。使用引號的文字是參照自 SUS [1] （頁面存檔備份，存於網際網路檔案館）。

**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`+`Z`。

### 預設行為

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

**可移植編號**

對於大多數訊號，相應的訊號編號由實現定義。此列列出了 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

## 參見

* `<signal.h>`