# 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