# Signal ## Intro Signal是什麼? - 軟體中斷(處理非同步事件) - 告訴行程某特殊事件發生了 - 非同步事件? - from 硬體 - 存取非法記憶體位置 - 除以0 - from 使用者輸入 - Ctrl + C - from another process - To sum up - 信號 = 行程間的通信 ## 6.1 信號的完整生命週期 - signal 的本質 - 行程間約定好,如果某件事情T發生了(trigger),就向目標行程(destination process)發送訊號(X),當 destination process看到X,就會意識到事件T發生了,就會執行相應的動作A(action) - life cycle - kernel接收到了信號,會向目標行程發送信號(在目標行程的task_struct裡加上這筆紀錄),並在適當的時間deliver給目標行程。 - 在收到信號但還沒送給目標行程之前,這個信號就會是處在掛起的狀態,此信號此時被稱作掛起信號或是未決信號(pending signal)。 - 當信號 deliver 給目標行程後,行程就會暫停,並執行該信號註冊/安裝好的信號處理函數。 ## 6.2 信號的產生 * 硬體異常 * 終端相關的訊號 * 軟體事件相關的訊號 ### 6.2.1 硬體異常 P.236 表 6-1 - SIGBUS - Access to an undefined portion of a memory object - Scenario: - 存取資料時要對齊變數位址 - ex. int 要有4個 bytes,若存取時未對齊,便會觸發 - mmap映射檔案 - ex. 將檔案映射進記憶體,如果檔案大小被其他行程截短,則是在存取超過檔案大小的記憶體,就會觸發 - SIGFPE - Erroneous arithmetic operation - Scenario: - 算術運算錯誤 - ex. 整數除以0 - SIGILL - Illegal instruction - Scenario - P.237 - ![](https://i.imgur.com/rx6TG0U.png) - 新架構指令集編譯的程式碼執行在舊架構的機器上 - SIGSEGV - Invalid memory reference - Scenario - 存取未初始化的指標或是NULL指到的address - 在user space想存取kernel space 的記憶體 - 企圖修改read only的記憶體位址 #### 以上四種信號會使行程中止並產生core dump ### 6.2.2 終端相關的信號 #### stty -a ![](https://i.imgur.com/lYUW3O9.png) - Ctrl + C: SIGINT - Ctrl + \: SIGQUIT - Ctrl + Z: SIGSTP #### Terminal SSH 斷線後,shell 接收由 kernel 傳來的 **SIGHUP** 信號,但在終止前會向由這個 shell 建立的前景行程組和背景行程組發送 **SIGHUP** 信號,預設的行為就是終止行程。 如何避免以上事件? ##### nohup ```c= nohup command // stdin 導至 /dev/null // stdout & stderr 導至 nohup.out // 使行程不再回應 SIGHUP signal ``` ##### setsid ```c= setsid command // 建立完全不屬於這個 shell 的 session 的 process // p.239 中間 ``` ##### disown ![](https://i.imgur.com/lywBjm4.png) ![](https://i.imgur.com/LQnFOSw.png) ### 6.2.3 軟體事件相關的信號 - child process 退出,kernel 可能會向 parent process 發出 **SIGCHLD** 訊號 - ![](https://i.imgur.com/R7WCuJu.png) - Parent process 退出,kernel 可能會向 child process 發送信號 - 每個 task_struct 裡有一個member - ```int pdeath_signal``` - 可用 ```prctl(PR_SET_PDEATHSIG, sig)``` 決定parent process 退出時,要向 call 此 function 的 child process 發送哪種 signal - ![](https://i.imgur.com/TF8iOUx.png) - 計時器到期,給 process 發送 signal - p. 240 表 6-2 ## 6.3 信號的預設處理函數 #### 行程接收到 Signal 會有什麼反應? | 標記 | 描述 | | -------- | -------- | | ignore | 直接丟棄信號,對行程不會有任何影響 | |terminate |終止(kill)行程| |core|終止行程並產生 core dump file for debug| |stop|停止行程(使行程暫停,並將其狀態設為TASK_STOPPED)| |continue|使停止的行程恢復執行| 詳閱 P.242 P.243 #### 指定信號處理的函數而非預設:信號的安裝 - signal - sigaction ## 6.4 信號的分類 ![](https://i.imgur.com/0f0Wjxz.png) - 不可靠信號 - [1, 31] - 繼承自UNIX - 存在一些弊端 - 可靠信號 - [SIGRTMIN, SIGRTMAX] ##### 信號可不可靠與安裝方式(signal/sigaction)及發送方式(kill/tkill/sigqueue)都無關,端看信號的值 ##### 那可靠與不可靠到底差在哪? - 不可靠信號 - kernel 用串列紀錄該信號是否已掛起。如果收到不可靠信號的時候,kenel 發現在 pending 的信號中有相同的信號,就丟棄該信號 - 可靠信號 - kenel 使用佇列來維護,如果收到信號,會放到相應的佇列中。(但也存在上限) ##### 我們可以說在一定的範圍程度下,可靠信號不會被丟棄。 ## 6.5 傳統信號的特點 ### 6.5.1 ONESHOT 特性 - 在傳統的 System V 風格的 singal,註冊完的信號處理函數只能執行一次,且在執行完後會將信號處理函數變成預設的 **SIG_DFL**,在 kernel 中會用 SA_ONESHOT 這個 macro 來代表這件事情 - kernel會將 signal 送給行程時檢查 - 信號處理函數不是 SIG_DFL - SA_ONSHOT flag 是不是被設定了 - 若以上都有,則將信號處理函數變成預設的 **SIG_DFL** - BSD 與 glibc 風格的 signal 則都沒有 ONESHOT 問題 ### 6.5.2 執行時遮蔽自身 在執行信號處理函數期間,接收到其他的信號(甚至是一樣的訊號) - System V - 不會遮蔽相同的傳入訊號->重入 - ```rt_sigaction(SIGINT, {0x8048756, [], SA_INTERRUPT|SA_NODEFER|SA_RESETHAND}, {SIG_DFL, [], 0}, 8) = 0``` - BSD - 遮蔽相同的信號傳入 - ```rt_sigaction(SIGINT, {0x8048736, [INT], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0``` - 中括號就是需要暫時遮蔽的信號 - 在安裝信號時,會將自身這個自定義的信號加入遮蔽集合中,所以在處理A信號(執行A信號處理函數)期間就不會再被傳來的A信號打斷 #### 原理 Kernel 傳遞訊號時會呼叫```get_signal_to_deliver``` ,用以在掛起的訊號裡選擇一個信號並送給行程,接著會呼叫```handler_signal``` ![](https://i.imgur.com/22nFpSx.png) ref p.251 上半段 - 如果有加 SA_NODEFER flag,正在處理的信號處理韓式就不會被阻塞(System V) ### 6.5.3 中斷system call的重啟特性 - 一些 system call會需要比較久的時間處理(read, write 等 ),因此被signal 中斷的機會變大大提升,因此在設計程式時,若希望被中斷的 system call 可以重啟,就要注意判斷 errno 時的值,如果是EINTR,system call 就重啟 - ```p.252``` 上方範例 - 但每個function都要注意的話就累死人了... - BSD 風格的 signal 提供另一種方法 - 若 system call 被信號中斷,則在處理完信號處理函數後自動重啟 system call - 使用 **SA_RESTART** flag 來告訴 kernel,該 system call 被信號中斷後是否要重啟 - ```rt_sigaction(SIGINT, {0x8048736, [INT], SA_RESTART}, {SIG_DFL, [], 0}, 8) = 0``` - 但是!!也不是所有的 system call 都能在被設定了**SA_RESTART** flag 後能自動重啟 - 可以:p.253 表6-9 - 不行:p.253 表6-10 - ```man 7 signal``` ##### 整理:表6-11 ## 6.6 信號的可靠性 不可靠信號丟失的問題 ### 6.6.1 實驗 比較發送信號的次數與信號處理函數的次數來說明信號丟失問題 p.254 ### 6.6.2 信號可靠性差異的根源 - 不可靠信號會不會丟棄信號 與 到底丟了多少信號,會由接收到訊號與信號送給行程的時序決定 - 紀錄掛起訊號的資料結構: ![](https://i.imgur.com/qWZcE9P.png) - 共有 64 種訊號,在64-bit OS 中,一個 unsigned long 就夠用了 ![](https://i.imgur.com/sd1lb0H.png) - kernel 收到信號時,會分配一個結構體,並加入list開頭的串列(struct sigpending 的 member) - ![](https://i.imgur.com/D0druvi.png) - 如果收到不可靠信號,會檢查串列中對應的位置使否已存在(已被設為1),若已存在不可靠信號,就會丟棄該信號 - 如果收到可靠信號,不管是不是該信號是不是已經掛起,都會分配一個struct sigqueue,並加入 sigpending 中,因此不會丟失 - 但也不能無限加入 - ![](https://i.imgur.com/Lr12LsL.png) - ![](https://i.imgur.com/g262ARX.png) - 也可用```ulimit -i unlimited```修改 ## 6.7 信號的安裝 先前提到的BSD風格的安裝signal的函數僅能針對SIGINT做遮蔽,如果同時要遮蔽其他信號,例如SIGUP, SIGUSR1就沒辦法了(SIGKILL & SIGSTOP無法遮蔽) 因此需要sigaction,得以更精確的安裝訊號 ![](https://i.imgur.com/9szuUb9.png) ![](https://i.imgur.com/8XMjxWb.png) - 利用 sa_flags 可以控制以下 flag (用bitwise OR of zero or more of following) - SA_NOCLDSTOP - SA_NOCLDWAIT - SA_ONESHOT and SA_RESETHAND - SA_NODEFER and SA_NOMASK - SA_RESTART - SA_SIGINFO - 額外的參數 - 發送此信號的行程的 PID UID 及額外資訊 - P.263 example