# Linux Signal 解說 ## [signal(7)](https://man7.org/linux/man-pages/man7/signal.7.html) ### signal disposition 代表行程接收到 signal 後會產生什麼行為,並且每個行程有各自的 signal disposition ,基本的預設行為有下列幾種 * **Term** : 預設行為是終止行程 * **Ign** : 預設行為是忽略該 signal * **Core** : 預設行為是終止行程並 dump core * **Stop** : 預設行為是暫停該行程 * **Cont** : 預設行為是將暫停的行程繼續運行 行程可以透過 [sigaction(2)](https://man7.org/linux/man-pages/man2/sigaction.2.html) 改變 signal disposition ,有以下幾種行為可以選擇 * 維持預設行為 * 忽略該 signal * 透過 signal handler (programmer-defined function) 來處理該 signal 如果利用 signal handler 來處理該 signal ,預設是將該 signal handler 指向的函式的函式資訊框 (function frame,簡稱 fp) 加在原本行程的堆疊當中,也可用 [signalstack(2)](https://man7.org/linux/man-pages/man2/sigaltstack.2.html) 使該函式資訊框使用其他的堆疊。 ### Execution of signal handlers Linux 核心在每次轉移回使用者模式時,都會檢查是否有正在等待的 signal 並檢查是否有對應的 signal handler,如果有的話會進行以下動作 * 執行 signal handler 的前置作業 * 從等待的 signal set 中移除該 signal * 若該 signal handler 有指定使用另外的堆疊空間,則建立新的堆疊空間給它 * 把和該 signal 相關的資訊存入堆疊上的特定的資訊框當中 * 所有在註冊該 signal handler 時使用 `sigprocmask()` 來建立在 `act->sa_mask` 當中的 signal 都會被加到該執行緒的 signal mask 當中。 * Linux 核心會在堆疊空間上為該 signal handler 建立一個資訊框,並把對應執行緒的 program counter 指向 signal handler function 的第一個指令,而回傳位址則是指向 signal trampoline (詳見 [sigreturn(2)](https://man7.org/linux/man-pages/man2/sigreturn.2.html)) * signal trampoline 會呼叫 `sigreturn()` ,恢復執行緒的狀態回到呼叫 signal handler 之前,然後 Linux 核心把控制權轉回給使用者模式,繼續執行 signal handler 被呼叫之前的指令。 如果 signal handler 因為 `siglongjmp()` 或使用 `execve()` 啟動新的程式而沒有回傳,則不會回傳 signal trampoline 也不會呼叫 `sigreturn()` ,此時這是開發者的責任要去恢復 signal mask 的狀態。 以 Linux 核心的角度來看,它不知道 signal handler 跟其他使用者層級的程式碼之間的差別,自然也不知道目前執行的是誰,所有 signal handler 需要的相關資訊都儲存在使用者層級的暫存器和堆疊中,所以巢狀呼叫的 signal handler 數量上限是被使用者層級的堆疊大小所限制。 ## [sigaction(2)](https://man7.org/linux/man-pages/man2/sigaction.2.html) ### 系統呼叫 sigaction() 系統呼叫可以改變行程接收到特定 signal 時的行為。 `signum` 是用來指定該 signal 的數字(不能指定 `SIGKILL` 和 `SIGSTOP` )。 ### 結構體 `struct sigaction` 定義如下 ```c struct sigaction { void (*sa_handler)(int); void (*sa_sigaction)(int, siginfo_t *, void *); sigset_t sa_mask; int sa_flags; void (*sa_restorer)(void); }; ``` 特別注意到 `sa_restorer` 並非是給使用者使用的,詳見 [sigreturn(2)](https://man7.org/linux/man-pages/man2/sigreturn.2.html) 。 如上節所說的, `sa_handler` 是行程接收到 `signum` 這個 signal 時會採取的行為 * `SIG_DFL` 代表採取預設行為 * `SIG_IGN` 代表忽略該 signal * 若在 `sa_flags` 中設定 `SA_SIGINFO` ,則 `sa_sigaction` 就指向處理 `signum` 的 signal-hadling function signal handler 的函式定義如下 ```c void handler(int sig, siginfo_t *info, void *ucontext); ``` 以下解說參數 * `sig` : 觸發此 handler function 的 signal number (和 `signum` 相同) * `info` : 指向 `siginfo_t` 的指標,該結構體包含 signal 的詳細資訊。詳見 [sigaction(2)](https://man7.org/linux/man-pages/man2/sigaction.2.html) 。 * `ucontext` : a pointer to a `ucontext_t` structure, cast to `void *` 。此指標指向的結構體包含了 Linux 核心 存在 userspace stack 中的 signal context information 。通常此參數不會被使用到,詳見 [getcontext(3)](https://man7.org/linux/man-pages/man3/getcontext.3.html) 。 `sa_mask` 則代表在 signal handler 執行期間會被阻塞著的 signals ,並且觸發 signal handler 的 signal 本身也會被阻塞。 `sa_flags` 代表改變該 signal 行為的標籤集合,有數種標籤詳見 [sigaction(2)](https://man7.org/linux/man-pages/man2/sigaction.2.html) ## userspace context 在 [泛 Sytem V](https://en.wikipedia.org/wiki/UNIX_System_V) 環境當中,我們可以透過以下幾種結構體和函式來控制行程當中不同執行緒的 user-level context switch 。 * `mcontext_t` : machine-dependent 且不開放給使用者操作。 * `ucontext_t` : 定義如下 ```c typedef struct ucontext_t { struct ucontext_t *uc_link; sigset_t uc_sigmask; stack_t uc_stack; mcontext_t uc_mcontext; ... } ucontext_t; ``` * `uc_link` : 指向在目前 context 被終止後會接續執行的 context 。 * `uc_sigmask` : 被此 context 阻塞的 signals 集合。 * `uc_stack` : 此 context 使用的堆疊空間。 * `uc_mcontext` : 被儲存的 context 的 machine-specific 表示法,包含 calling thread's machine registers 。 `getcontext()` 會將 `ucp` 指向的結構體初始化並指向目前執行的 context 。 `setcontext()` 會繼續執行 `ucp` 指向的 user context ,此 context 有三種方式獲取 * 呼叫 `getcontext()` * 呼叫 `makecontext()` * signal handler 的第三個參數 最早實現 user-level context switch 此機制的方式是透過 `setjmp()`/`longjmp()` ,不過這樣的方式沒有定義如何處理 signal context ,之後 `sigsetjmp()`/`siglongjmp()` 讓我們可以處理 signal context 。當 signal 觸發某個 signal handler 時,目前的 user context 會被暫存而 Linux 核心 會為 signal handler 建立一個新的 user context 。我們不該使用 `longjmp()` 函式來離開 signal handler ,這樣的行為是未定義的,取而代之應該使用 `siglongjmp()` 或 `setcontext()` 。 注意到除非我們自己設置額外儲存裝置進行記錄,不然我們無從得知 `getcontext()` 的回傳行為是從首次呼叫 `getcontext()` 還是透過 `setcontext()` 來回傳。因為 register 會被重新恢復,所以利用 register variable 來記錄是無效的。