# What is deadlock? 在一個 task 想要向 OS 索取 resource (資源) 的時候,如果這些 resource 並非能夠馬上使用的,就會使這個 task 進入 wait (等待) 的狀態,如果這些 resource 又被其他正在 wait 的 task 所持有,將會使這個正在 wait 的 task 永遠無法改變其 wait 的狀態,這個現象就是 **deadlock (死結)** > 這邊的 task 指的是一個抽象的執行單位,可能是 process 或是 thread,姜媽的講義似乎都以 thread 表示 :::info 對死結的最佳說明要回溯到美國堪薩斯州在 20 世紀初所制定的一條法律來詮釋,部分內容為: 當兩輛火車同時到達一交叉路口時,雙方都必須完全停止,待一輛駛去之後,另一輛再通過 ::: # 系統模型 一個 OS 包含可分配給 task 的**有限** resource。這些 resource 可區分成許多 class (類型),每種都會含有一至數個相同類型的 instance (實例) 一個 task 在使用一 resource 前必須先進行 **request (請求)**,並在使用完後 **release (釋放)** 在正常運作模式下,一個 task 都只能依下方順序去使用資源: 1. request > 若此請求無法馬上被認可,例如這個 resource 正在被使用,則 task 進入 wait 的狀態等待此 resource 2. use > task 得以使用此 resource 3. release > task 釋放此 resource,告知 OS 這個 resource 變回可用的狀態 :::info 例如在撰寫 C 程式時,使用 `malloc()` 去 request 一塊動態的記憶體空間跟使用完後使用 `free()` release 這一段記憶體空間 ::: :::warning 一個 task 所要求的 resource 不可超過 OS 可用的 resource 總數 ::: # 範例: mutex (互斥鎖) 的 deadlock 以下程式碼範例初始化了倆 mutex ```c pthread_mutex_t first_mutex; pthread_mutex_t second_mutex; pthread_mutex_init(&first_mutex, NULL); pthread_mutex_init(&second_mutex, NULL); ``` 接下來,產生 `thread_one` 跟 `thread_two` 分別執行 `do_work_one()` 跟 `do_work_two()` 兩個 function ```c /* thread one runs in this function */ void *do_work_one(void *param) { pthread_mutex_lock(&first_mutex); pthread_mutex_lock(&second_mutex); /** * Do some work */ pthread_mutex_unlock(&second_mutex); pthread_mutex_unlock(&first_mutex); pthread_exit(0); } /* thread two runs in this function */ void *do_work_two(void *param) { pthread_mutex_lock(&second_mutex); pthread_mutex_lock(&first_mutex); /** * Do some work */ pthread_mutex_unlock(&first_mutex); pthread_mutex_unlock(&second_mutex); pthread_exit(0); } ``` `thread_one` 嘗試以 1. `first_mutex` 2. `second_mutex` 取得 mutex `thread_two` 嘗試以 1. `second_mutex` 2. `first_mutex` 取得 mutex 當 `thread_two` 獲得 `second_mutex` 時,若 `thread_one` 獲得 `first_mutex`,則**有可能造成 deadlock** # 造成 deadlock 的四個"必要"條件 > 全部**同時成立**,才會造成 deadlock ## Mutual exclusion 互斥 一次只能有一 task 使用此 resource,意即 Non-shareable (不可共享的) ## Hold and Wait 佔用與等待 存在一已 hold (佔用) resource 的 task,且正在 wait 被其他 task 佔用的 resource ## No Preemption 不可搶先 resource 無法被 preemption (搶先),意即這個 resource 只能被 hold 的 task 去 release ## Circular wait 循環等待 存在一等待彼此的 task 集合,假設有 {A, B, C} 三個 tasks,A 等 B,B 等 C,C 等 A,形成迴圈就是 circular wait :::info 四個條件事實上息息相關,像 - 處於 circular wait 的狀態時,也包含了 hold and wait - 又或是 task 0 在等待 task 1 時,若 task 0 能夠搶奪 task 1 的資源,就不會發生等待,所以一定也有 no preemtion 這個條件 ::: # Resource-Allocation Graph (資源分配圖) deadlock 現象可用 system resource-allocation graph (系統資源配置圖) 表示 - 由一組 vertices (頂點) V 和一組 edges (邊) E 組成 - V 又具有兩種類型 - T 表示正在執行之 task 集合,$T = \{ T_1, T_2, ..., T_n \}$ - R 表示 OS 所有 resource class 的集合,$R = \{R_1, R_2, ..., R_n\}$ - E 也有兩種 - request edge (請求邊),由 T 指向 R,意即 task T 已要求 class R 的 resource instance,且正在 wait,通常這樣表示 $T_i \rightarrow R_i$ - assignment edge (分配邊),由 R 指向 T,意即 class R 的某個 resource instance 已被 task T 持有,處於 hold 的狀態,通常這樣表示 $R_i \rightarrow T_i$ ## 例圖 ![Untitled Diagram](https://hackmd.io/_uploads/BJBRRA2Lp.png) - 集合 $T$ $R$ $E$ - $T = \{T_1, T_2, T_3\}$ - $R = \{R_1, R_2, R_3, R_4\}$ - $E = \{T_1 \rightarrow R_1 , T_2 \rightarrow R_3, R_1 \rightarrow T_2, R_2 \rightarrow T_2, R_2 \rightarrow T_1, R_3 \rightarrow T_3\}$ - resource instance (黑點點) - class $R_1$ 的 resource 有 1 個 instance - class $R_2$ 的 resource 有 2 個 instance - class $R_3$ 的 resource 有 1 個 instance - class $R_4$ 的 resource 有 3 個 instance - task 狀態 - $T_1$ 正在 hold $R_2$ 的一個 instance,並在等待一個 $R_1$ 的 instance - $T_2$ 正在 hold $R_1$ 以及 $R_2$ 的各一個 instance,並在等待一個 $R_3$ 的 instance - $T_3$ 正在 hold $R_3$ 的一個 instance :::info - 如果此圖中沒有 cycle (循環) 存在,則此系統中不會發生 deadlock - 若此圖中具有 cycle 且所有 class 的 resource 都只有一個 instance 就會發生 deadlock - 若此圖中具有 cycle 且所有 class 的 resource 有多個 instance 則**有可能** (不一定) 發生 deadlock ::: # 處理 deadlock 的方式 一般來說,可以從以下三種方法之一來處理 deadlock: - 使用某種 protocol (協議) 以**預防**或是**防止** deadlock - deadlock prevention - deadlock avoidance - 允許系統進入 deadlock 的狀態,**偵測**到後再想辦法**恢復** - deadlock detection - deadlock recovery - ignore (忽視) deadlock,假裝系統從沒發生 deadlock :::info 第三種方法被大部分的 OS 所採用,包含 UNIX-like 以及 Windows,因此處理 deadlock 通常由系統程式開發人員自行處理 ::: # Deadlock prevention (預防) 提供一組方法,確保之前提及的四個產生 deadlock 的必要條件,至少有一項不被滿足,以下分別進行說明 ## Mutual exclusion 若 mutual exclusion 成立,則代表至少有一項 resource 是不可共享的,反之,可共享的 resource 就不必以 mutex 的方式進行存取,所以就不會產生 deadlock 假設有很多 task 都想要開啟一個 read-only (唯讀) 的檔案,則可以讓它們同時存取此檔案 :::info 但實務上來說,幾乎不可能透過排除 mutual exclusion 來預防 deadlock,因為許多 resource **本質上就是無法共用**的 ::: ## Hold and Wait 為了使此條件不成立,需要保證一個 task 在 request 一項 resource 時,不可 hold 其他 resource,以下介紹兩種方式 - 每個 task 在執行前,都必須先 request 並被 OS 成功分配所需的 resource - 只允許 task 在未 hold 任何 resource 的情況下才能夠 request resource,意即一個 task 在需要某些 resource 時,在進行 request 前必須先 release 掉所有正 hold 的 resource 這邊假設有一 task,要從 DVD 複製資料至一 disk file,然後對其進行排序,最後將結果由印表機印出 如果使用第一種方法,此 task 就必須一開始就 request DVD、disk file 跟印表機,雖在最後才使用到印表機,但這個 task 整個執行過程中都 hold 著印表機 第二種方法允許這個 task 一開始只 request DVD 跟 disk file,等此 task 將 DVD 的資料複製完畢後,就要把 DVD 跟 disk file 都 release 掉,然後再要求 disk file 跟印表機,待列印完成後再 release 掉這兩項 resource :::info 這兩種方式有兩個主要的缺點: - 資源使用率 (resource utilization) 有可能很低,因為大部分時間許多 resource 可能只是被分配而未實際使用 - 有可能產生 starvation (飢餓) 現象,當一個 task 需要用到的許多 resource,其中一樣被其他 task 使用,這個 task 將會永遠處於 wait 狀態 ::: ## No Preemption 要避免此條件,有以下幾種方法: - 如果某 task 已經 hold 著一些 resource,而它要 request 一個無法立即獲得的 resource (也就是它會處於 wait 的狀態),則此 task hold 的 resource 應該要給其他 task 搶先使用,意即處於 wait 狀態後會 implicitly (隱含地) release 所有 resource - 如果某 task 需要幾項 resource,須先檢查 resource 是否可被使用,如果可以就直接分配,如果不是,就再往下檢查說這些 resource 是否被一些其他正在 wait 的 task hold 著,如果是的話就從這些正在 wait 的 task 把 resource 搶先分配給此 task,如果真的無法立即分配此 task 就會進入 wait 的狀態,同理它原本 hold 的 resource 就能夠被其他 task 搶先 > 上方提及的方式,被搶先或者處於 wait 的 task,只有能夠重新獲得原本 hold 的 resource 再加上正 request 的 resource 時,才會繼續執行 :::info 此些做法,通常適用於 resource 的狀態能夠輕易被保存及恢復的情況下,像是 memory 以及 CPU register,不適用於 mutex 或是 semaphore 這類的 resource ::: ## Circular wait 確保不會發生此條件的方法是: 對所有 resource class 強迫安排一線性的順序,task 在 request resource 時必須依照數字大小,遞增的提出 request 為了方便說明,假設 $R = \{R_1, R_2, R_3, ..., R_m\}$ 為 resource class 的集合,然後定義一函數 $F: R \rightarrow N$ 來產生一對一的正整數,舉例來說,若 resource class 集合 $R$ 中有包含 DVD drive、disk drive 還有印表機則可能表示如下 - $F(DVD) = 1$ - $F(disk) = 5$ - $F(printer) = 12$ 然後接下來可以考慮以下做法來預防 deadlock: - 每一 task 只能依遞增的方式去 request resource,以上方的例子來說,若有一 task 需要同時使用 DVD 跟印表機,則必須先要求 DVD 才能再要求印表機 (因為 $F(printer)$ > $F(DVD)$) - 若一 task 要求 resource class $R_j$ 時,並定要先 release 所有的資源 $R_i$,其中 $R_j \ge R_i$ :::info 上方提及的預防 deadlock 的方式,都會有一些副作用,就是 - low device utilization (降低資源使用率) - reduced system throughput (降低系統效能) ::: # Deadlock avoidance (避免) 避免 deadlock 主要的概念就是,要讓 task 提供多一些關於 request resource 的相關資訊,其中最簡單的且最有用的方式就是,每個 task 都需要宣告其所需每種 class 的 resource 之最大數量 使用這類資訊就能建立出一演算法去保證系統不會進入 deadlock ,這種演算法會動態檢查 resource-allocation state (資源分配狀態),以確保不會出現 circular wait :::info resource-allocation state 會被三個元素決定: - available (可被使用的) resources 數量 - allocated (已分配的) resources 數量 - task 對 resource 的 maximum demands (最大需求) ::: ## safe state (安全狀態) 如果 OS 能以某種順序將 resource 分配給各個 task (符合其最大值),且不會產生 deadlock,就稱為 safe state 或者說一個 OS 只有在 safe sequence 存在時,才算處於 safe state > safe sequence 是指一群 task 的執行順序,此順序能夠使這些 task 能夠在不產生 deadlock 的情況下順利執行完畢 :::info - 如果 OS 處於 safe state 則**不會**發生 deadlock - 如果 OS 處於 unsafe state 則**有可能**發生 deadlock - deadlock avoidance 的主要方式就是確保 OS 永遠不會進入 unsafe state ::: ## Resource-Allocation Graph Algorithm 如果有一 resource 分配系統,其中所有 resource class 都各**只有一個 instance**,則之前提及的 resource-allocation graph 可用來避免 deadlock 這邊要再額外加上一個新的 claim edge (申請邊),此種邊與 request edge 相似,都是由 T 指向 R,但以**虛線**表示,意義是 T 有可能會需要 R,當 T request R 時,要將 claim edge 轉為 request edge,然後在 T release R 時,要將 assignment edge 再轉回 claim edge ### 例圖 ![Untitled Diagram](https://hackmd.io/_uploads/H1sPSi6L6.png) 這邊假設 $T_2$ 想要 request $R_2$,雖然此時 $R_2$ 可用,但不能將 $R_2$ 分配給 $T_2$,因為將會在圖中產生 cycle,而系統就會處於 unsafe state,這時如果 $T_1$ request $R_2$ 且 $T_2$ 要求 $R_1$ 就會產生 deadlock :::info - resource-allocation graph algorithm 不適用於有多個 instance 的 resource 分配系統 - 使用 cycle detection 去判斷系統是否處於 safe state ::: ## Banker's Algorithm - 如果是在所有 resource class 有很多 instance 的狀況,banker's algorithm 就會是個較好的選擇 - 當一新的 task 進入系統時,必須先宣告可能需要的每種 resource class 的最大數量,此數量不能超過系統所有資源的總數 - 當一 task request 一組 resource 時,就要馬上評估是否會因此進入 unsafe state,若仍能處於 safe state,就能將這些 resource 分配給此 task,否則此 task 就必須 wait 直到其他 task release 足夠的 resource :::info 此名稱是出自銀行系統中,確保可用資金都能滿足客戶需求的分配方式 ::: ### 例題 - 有 5 個 task - $T_0$ - $T_1$ - $T_2$ - $T_3$ - $T_4$ - 有 3 種 resource class - A (10 個 instance) - B (5 個 instance) - C (7 個 instance) 目前的狀態如下 - Allocation (已分配的 resource) | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 0 | 1 | 0 | | $T_1$ | 2 | 0 | 0 | | $T_2$ | 3 | 0 | 2 | | $T_3$ | 2 | 1 | 1 | | $T_4$ | 0 | 0 | 2 | - Max (最大需求) | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 7 | 5 | 3 | | $T_1$ | 3 | 2 | 2 | | $T_2$ | 9 | 0 | 2 | | $T_3$ | 2 | 2 | 2 | | $T_4$ | 4 | 3 | 3 | 試求出 safe sequence ### 解法 1. 先求出 Available ``` A 可從題目中條件知道有 10 個 instance 所以去剪掉 Allocation 中已被分配出去的部分即可得出: A = 10 - (0 + 2 + 3 + 2 + 0) = 3 B, C 同理 B = 5 - (1 + 0 + 0 + 1 + 0) = 3 C = 7 - (0 + 0 + 2 + 1 + 2) = 2 ``` - Available (可用資源) | A | B | C | |:---:|:---:|:---:| | 3 | 3 | 2 | 2. 再求出 Need ``` 求出 Need 的公式為 Max - Allocation T0 = (7, 5, 3) - (0, 1, 0) = (7, 4, 3) T1 = (3, 2, 2) - (2, 0, 0) = (1, 2, 2) T2 = (9, 0, 2) - (3, 0, 2) = (6, 0, 0) T3 = (2, 2, 2) - (2, 1, 1) = (0, 1, 1) T4 = (4, 3, 3) - (0, 0, 2) = (4, 3, 1) ``` - Need | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 7 | 4 | 3 | | $T_1$ | 1 | 2 | 2 | | $T_2$ | 6 | 0 | 0 | | $T_3$ | 0 | 1 | 1 | | $T_4$ | 4 | 3 | 1 | 3. 判定目前是否有 safe sequence ``` 目前已知 Available = (3, 3, 2) 檢查看看目前 T0 ~ T4 的 Need 當中是否有可以被成功分配的 (就是要 <= (3, 3, 2)) 1. T1 (1, 2, 2) <= (3, 3, 2) 2. 成功執行後釋放資源 Available (3, 3, 2) 從 T1 獲得 Allocation (2, 0, 0) 3. 所以 Available 變為 (3, 3, 2) + (2, 0, 0) = (5, 3, 2) 4. 重新開始找尋剩下的 task 中是否有可以成功分配的 最終可得 T1, T3, T4, T2, T0 這個 safe sequence ``` ### 如果原始狀態中,$T_1$ 額外 request (1, 0, 2) 的 resource 呢? 1. 首先先檢查 (1, 0, 2) 有沒有超過 Need 且 Available 夠用 ``` 原始狀態中 T1 的 Need = (1, 2, 2),(1, 0, 2) <= (1, 2, 2) 所以條件成立 原始狀態中 Available = (3, 3, 2),(1, 0, 2) <= (3, 3, 2) 所以能夠分配 ``` 2. 狀態改變 ``` 因應這額外的分配,需要調整相應的狀態 Available = (3, 3, 2) - (1, 0, 2) = (2, 3, 0) T1 的 Allocation = (2, 0, 0) + (1, 0, 2) = (3, 0, 2) T1 的 Need = (3, 2, 2) - (3, 0, 2) = (0, 2, 0) ``` 得出狀態如下 - Allocation (已分配的 resource) | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 0 | 1 | 0 | | $T_1$ | 3 | 0 | 2 | | $T_2$ | 3 | 0 | 2 | | $T_3$ | 2 | 1 | 1 | | $T_4$ | 0 | 0 | 2 | - Need | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 7 | 4 | 3 | | $T_1$ | 0 | 2 | 0 | | $T_2$ | 6 | 0 | 0 | | $T_3$ | 0 | 1 | 1 | | $T_4$ | 4 | 3 | 1 | - Available (可用資源) | A | B | C | |:---:|:---:|:---:| | 2 | 3 | 0 | 3. 判定目前是否有 safe sequence ``` 參考上方做法,可得出 safe sequence: T1, T3, T4, T0, T2 ``` 所以這個 request 是可行的 :::info - 在此新狀態中,若 $T_4$ 想要再額外 request (3, 3, 0) 就是不可行的,因為 Available 已經不夠用了 - 但在此新狀態中,$T_0$ 再額外 request (0, 2, 0),就算 Available 夠用也不能答應,因為會找不到 safe sequence,意即進入 unsafe state ::: # Deadlock detection (偵測) 假設 OS 沒有 deadlock prevention 跟 deadlock avoidance 的機制,此時系統就須提供 - 檢察系統狀態判斷是否有 deadlock 產生的演算法 - 從 deadlock 恢復的機制 ## Wait-for Graph - 適用於所有 resource class 都各**只有一個 instance** 的系統 - 同樣變化自 resource-allocation graph - 也同樣是判斷是否有 cycle 的存在 - 簡單來說就是拿掉所有 resource 並把等待的邊連起來 假設有一 resource-allocation graph 如下 ![Untitled Diagram](https://hackmd.io/_uploads/HkMnRATUa.png) 則 wait-for graph 如下 ![Untitled Diagram](https://hackmd.io/_uploads/BJpSyyCLT.png) :::info - 若 wait-for graph 中有 cycle,則系統必有 deadlock - 為了偵測 deadlock,系統必須一直維護此圖 ::: ## Deadlock Detection Algorithm - 適用於所有 resource class 都**有多個 instance** 的系統 ### 例題 - 有 5 個 task - $T_0$ - $T_1$ - $T_2$ - $T_3$ - $T_4$ - 有 3 種 resource class - A (7 個 instance) - B (2 個 instance) - C (13 個 instance) 目前的狀態如下 - Allocation | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 0 | 1 | 0 | | $T_1$ | 2 | 0 | 0 | | $T_2$ | 3 | 0 | 3 | | $T_3$ | 2 | 1 | 1 | | $T_4$ | 0 | 0 | 2 | - Request | | A | B | C | |:-----:|:---:|:---:|:---:| | $T_0$ | 0 | 0 | 0 | | $T_1$ | 2 | 0 | 2 | | $T_2$ | 0 | 0 | 0 | | $T_3$ | 1 | 0 | 0 | | $T_4$ | 0 | 0 | 2 | - Available | A | B | C | |:---:|:---:|:---:| | 0 | 0 | 0 | 1. 找尋 safe sequence ``` 1. 看看 Available (0, 0, 0) 能否執行任何一個 task 2. T0 的 Request (0, 0, 0) <= Avaiable (0, 0, 0),可以執行 3. T0 release resource 給 Available (0, 0, 0) + (0, 1, 0) = (0, 1, 0) 4. 看看 Available (0, 1, 0) 能否執行任何一個剩下的 task,並依此類推 最終可得 T0, T2, T3, T1, T4 ``` :::info - 假設在初始狀態,$T_2$ 額外進行請求 (0, 0, 1),將會導致 deadlock 發生,因為沒有足夠的 resource 進行任何 task ::: ### Detection-Algorithm 的使用 需要考慮下方兩種因素,去判斷什麼時候該使用 detection-algorithm - 多久會發生一次 deadlock - 有多少個 task 被 deadlock 影響 若經常發生 deadlock,則演算法的執行將會變得頻繁,但在解決 deadlock 前,系統又會不斷地把 resource 分配給 task,因此受到 deadlock 影響的 task 可能會越來越多 # Recovery (恢復) from deadlock 當偵測到 deadlock 之後,可以有兩種結束 deadlock 的方式 - 直接終止一或多個 task - 搶先一或多個陷入 deadlock 的 task 所持有的 resource ## 終止 task 藉由中止 task 來消除 deadlock 有兩種方法 - 直接終止所有在 deadlock 中的 task - 雖然這樣可以消除 deadlock,但代價會很大,因為這些 task 有可能已經執行了很長時間,而這些計算結果將會被拋棄,並且極有可能要重新計算 - 一次終止一個 task 直到 deadlock 消失 - 這樣會有不小的系統開銷,因為在每終止一個 task 之後,都必須重新執行 detection-algorithm 去判斷 deadlock 是否還存在 :::info 如果使用第二種方法,就必須決定要終止哪些 task 來消除 deadlock,但許多元素都可以決定要選擇哪個 task - task 的優先權 - task 工作了多久? 還需要多少時間? - task 使用了多少 resource,都是哪些 class - task 還需要多少 resource,才能夠完成 - 有多少 task 需要被終止 - task 的類型 ::: ## 搶先陷入 deadlock 的 resource 為了透過 preemption 來消除 deadlock,需要考慮以下三個議題: - select a victim (選擇犧牲者) - 以最小開銷的方式去做選擇 - rollback (回溯) - 因 resource 被搶先,所以 task 已經不能正常執行,要能夠讓被搶先的 task 能夠回溯到某個能正確執行的狀態 - starvation (飢餓問題) - 保證 task 不會一直被搶先 --- # 考古題 - **Deadlock prevention** provides a set of methods to ensure that at least one of the necessary conditions cannot hold. - **Deadlock avoidance** requires that OS be given additional information in advance concerning which resources a thread will request and use during its lifetime. - **low device utilization** and **reduced system throughput** are possible side effects of Preventing Deadlocks. - When a thread requests an available resource, system must decide if immediate allocation leaves the system in a **safe state** - **Circular Wait**, which is one way of deadlock prevention,impose a total ordering of all resource types. - OS will break **hold and wait** to prevent deadlock,which means that OS will require each thread to request and be allocated all its resources before it begins execution. - When system has only one instance of each resource type, OS can use **Resource-Allocation Graph Algorithm** to execute deadlock avoidance ![image](https://hackmd.io/_uploads/rkEx9QDIT.png) > allocation: 已獲得的資源數量 > max: 完成工作所需的最大資源量 > available: 系統目前的可用資源 - 將 max - allocation 得出 need - 接著看 available 是否可以負擔 need - 若有某一個 thread 可以負擔,完成後須把 allocation 的數字加回去