# Linux Suspend/Resume 實驗(四) ## [Improvements of async suspend and resume of devices](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#m4e494f3fb77e6c1237c0097c5d3fe7b9762a86af) 這個 patch set 改進 Linux 核心中非同步(asynchronous) suspend/resume 的處理機制。 在一般情況下,系統在執行裝置 suspend 或 resume 時,會採用 同步(synchronous)方式,也就是: * 同步(sync):每次僅處理一個裝置,必須等前一個裝置 suspend/resume 完成後,才會繼續下一個。 * 非同步(async):允許多個彼此沒有相依關係的裝置同時進行 suspend/resume,以提升整體處理效率。 這個 patch set 整體設計的核心想法是: 有些裝置即使還沒完全符合所有依賴條件,但只要部分條件已經滿足,就可以提早開始執行非同步 suspend/resume。 這樣可以避免一次把太多還不能立刻執行的 async work items 塞進佇列裡,因為那些裝置可能還要等其他裝置處理完才能動作,如果都塞進去等著,只會浪費資源、增加系統負擔,讓整體變慢。 >The overall idea is still to start async processing for devices that have at least some dependencies met, but not necessarily all of them, to avoid overhead related to queuing too many async work items that will have to wait for the processing of other devices before they can make progress. 本次提出的 patch set 一共包含 5 個 patch ,為優化 Linux 核心中非同步 suspend/resume 的處理流程。以下是某一台測試系統的初步效能結果,用以觀察這些變更對實際執行時間的影響: * "Baseline":指的是未套用這些 patch 的 linux-pm.git/testing 分支 * "Parent/child":表示套用了 patch [1–3/5] 的版本 * "Device links":則是套用了完整的 patch [1–5/5] 的版本 測試結果中各階段的定義如下: * s/r 指的是正常的 suspend/resume 流程,也就是裝置的標準睡眠與喚醒操作。 * noRPM 是在進入更深層 suspend 前的 late suspend 階段,以及剛喚醒時的 early resume。 * noIRQ 則是進入 suspend/resume 過程中關閉中斷(IRQ)之後所執行的操作階段。 ``` Baseline Parent/child Device links Suspend Resume Suspend Resume Suspend Resume s/r 427 449 298 450 294 442 noRPM 13 1 13 1 13 1 noIRQ 31 25 28 24 28 26 s/r 408 442 298 443 301 447 noRPM 13 1 13 1 13 1 noIRQ 32 25 30 25 28 25 s/r 408 444 310 450 298 439 noRPM 13 1 13 1 13 1 noIRQ 31 24 31 26 31 24 ``` 從結果可以明顯看出,在套用 patch [1–3/5] 之後,suspend 的效能有了明顯提升,而這部分的改善很大程度上可歸因於 patch [2/5]。 而這三個 patch 會在 Linux 6.16 的開發週期中合併。 [PM: sleep: Resume children after resuming the parent](https://github.com/torvalds/linux/commit/0cbef962ce1ff344ecfe32d1c874978f1f7d410a) [PM: sleep: Suspend async parents after suspending children](https://github.com/torvalds/linux/commit/aa7a9275ab814705b60ba8274277d91da6ab6122) [PM: sleep: Make suspend of devices more asynchronous](https://github.com/torvalds/linux/commit/443046d1ad66607f324c604b9fbdf11266fa8aad) ## [Resume children after resuming the parent](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#mece5c40fd3453ffb8e929057388f833ccfcec05c) 之前的 async resume 邏輯: * 對於每個 resume 階段(除了 complete 階段,這個階段是都是是串行執行的),resume 邏輯首將所有在 dpm_list 裡的異步裝置排入處理隊列。然後它會再次遍歷這個 dpm_list,逐個恢復同步裝置。 * 異步裝置在開始自身的恢復操作之前,需要等待所有它的上游裝置(superior devices)完成恢復。 * 這樣的過程會導致異步裝置在實際恢復之前經歷多次的休眠和喚醒週期,進而造成 kworker 執行緒的停頓。為了解決這個問題,workqueue 會啟動更多的 kworker 執行緒來處理其他異步裝置。 * 結果是,會創建過多的執行緒,並且經歷過多的喚醒、休眠和上下文切換,這會使得全異步恢復變得比同步恢復更慢。 ```c static bool is_async(struct device *dev) { return dev->power.async_suspend && pm_async_enabled && !pm_trace_is_enabled(); } ``` ```c void dpm_resume(pm_message_t state) { ... list_for_each_entry(dev, &dpm_suspended_list, power.entry) { dpm_clear_async_state(dev);// 清除這個裝置先前的 async 執行狀態 dpm_async_fn(dev, async_resume);// 試圖對這個裝置排程 async resume } while (!list_empty(&dpm_suspended_list)) { dev = to_device(dpm_suspended_list.next); list_move_tail(&dev->power.entry, &dpm_prepared_list); if (!dev->power.work_in_progress) { get_device(dev); mutex_unlock(&dpm_list_mtx); device_resume(dev, state, false); put_device(dev); mutex_lock(&dpm_list_mtx); } } ... } ``` ``` dpm_resume() └── dpm_async_fn(dev, async_resume) └── async_schedule_dev_nocall(async_resume, dev) ``` 這個 patch 修改: * 讓程式碼在裝置的 parent resume 處理完成後,才啟動其子裝置的 async resume * 只有對於沒有父裝置的裝置,才會一開始就啟動 async resume。 另外: * 在對某裝置執行同步 resume 之前,會先檢查它是否可以安全地改用 async resume,避免對正在以 async 模式 resume 的裝置造成等待或衝突 本次改動涉及多個與裝置電源恢復(resume)流程相關的核心函式,包括: * dpm_resume * device_resume * dpm_resume_early * device_resume_early * dpm_noirq_resume_devices * device_resume_noirq 下列為其中一個例子: ```diff static void device_resume(struct device *dev, pm_message_t state, bool async) { ... + dpm_async_resume_children(dev, async_resume); } ``` 現在是在該裝置 resume 完成後,主動觸發它所有子裝置的 async resume。 ```diff void dpm_resume(pm_message_t state) { ... list_for_each_entry(dev, &dpm_suspended_list, power.entry) { dpm_clear_async_state(dev); - dpm_async_fn(dev, async_resume); + if (dpm_root_device(dev)) + dpm_async_with_cleanup(dev, async_resume); } while (!list_empty(&dpm_suspended_list)) { dev = to_device(dpm_suspended_list.next); list_move_tail(&dev->power.entry, &dpm_prepared_list); - if (!dev->power.work_in_progress) { + if (!dpm_async_fn(dev, async_resume)) { get_device(dev); mutex_unlock(&dpm_list_mtx); device_resume(dev, state, false); put_device(dev); mutex_lock(&dpm_list_mtx); } } ... } ``` 其中的 `dpm_root_device` 函式的作用是確認是否為 root_device。 這個 patch 根據目前的測試結果,本身並不會對系統整體的 resume 時間造成可測量的影響,但這項修改除了讓 async resume 對於資源較少的系統更加友善。 ## [Suspend async parents after suspending children](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#mdd5a8d91c02383f8508dd456c2eb3b7163dfdafb) 之前的 async suspend 邏輯: * async suspend 邏輯遍歷 dpm_list,當它遇到異步裝置時,它會 queue 該裝置的工作並繼續處理下一個裝置。然後,若它遇到同步裝置,它會等待該同步裝置及其所有 subordinate devices 完成 suspend 操作後再繼續。 * 這表示異步裝置可能會被無關的同步裝置阻塞,這樣在 suspend 前它甚至無法排隊。 * 一旦異步裝置被 queue,它將經歷與恢復邏輯相似的效率問題(例如執行緒創建、喚醒、休眠和上下文切換的開銷)。 參照之前針對 resume 流程所做的修改,這次的 patch 讓: * `device_suspend()` 在裝置本身處理完成後,才開始其父裝置的非同步 suspend; * `dpm_suspend()` 則會優先處理沒有子裝置的裝置,這樣它們就不需要因等待與其無關的「同步裝置」而被延遲 suspend。 在這個 patch 中可以發現他先定義了兩個函式: ```c static bool dpm_leaf_device(struct device *dev) ``` 這個函式為判斷一個裝置是否為 leaf device,也就是沒有子裝置的裝置。 leaf 裝置通常可以在最早的階段進行 async suspend,因為它們沒有下游依賴者,不需要等待子裝置完成。 ```c static void dpm_async_suspend_parent(struct device *dev, async_func_t func) ``` 這個函式為當某個裝置的 suspend 處理完成後,這個函式會啟動其父裝置的 async suspend,前提是父裝置也允許 async suspend。 ```diff static int device_suspend(struct device *dev, pm_message_t state, bool async) { ... complete_all(&dev->power.completion); TRACE_SUSPEND(error); - return error; + + if (error || async_error) + return error; + + dpm_async_suspend_parent(dev, async_suspend); + + return 0; } ``` ```diff int dpm_suspend(pm_message_t state) { ktime_t starttime = ktime_get(); + struct device *dev; int error = 0; ... + list_for_each_entry_reverse(dev, &dpm_prepared_list, power.entry) { + dpm_clear_async_state(dev); + if (dpm_leaf_device(dev)) + dpm_async_with_cleanup(dev, async_suspend); + } while (!list_empty(&dpm_prepared_list)) { - struct device *dev = to_device(dpm_prepared_list.prev); + dev = to_device(dpm_prepared_list.prev); list_move(&dev->power.entry, &dpm_suspended_list); - dpm_clear_async_state(dev); ... mutex_lock(&dpm_list_mtx); - if (error || async_error) + if (error || async_error) { + list_splice(&dpm_prepared_list, &dpm_suspended_list); break; + } } ... } ``` 這個 patch 是讓 suspend 時間縮短的關鍵。 ## [Make suspend of devices more asynchronous](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#mecd027cb96d152ffe8161df9af0e8947a4c3a2f2) 這個 patch 使 `device_suspend_late()` 和 `device_suspend_noirq()` 在處理完裝置本身之後,再啟動其父裝置的 async suspend;同時,`dpm_suspend_late()` 和 `dpm_noirq_suspend_devices()` 會優先處理沒有子裝置的裝置,讓它們不需要等待與其無關的其他裝置完成 suspend。 和上述第二個 patch 的改動類似。 ### ubuntu 編譯 #### 安裝必要套件核心 ```shell sudo apt update sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev ``` 這些是編譯 kernel 所需的基本工具與開發函式庫。 #### 取得原始核心程式碼 使用 Git 從官方來源取得最新的 kernel 程式碼: ```shell git clone https://github.com/torvalds/linux.git cd linux ``` #### 使用目前系統設定作為起點 使用你目前系統正在運行的 .config 作為編譯設定的起點,這能避免遺漏重要的驅動或選項: ```shell cp /boot/config-$(uname -r) .config ``` * config-$(uname -r) 是目前使用中的 kernel 設定檔。 #### 進行圖形化選單設定 ```shell make menuconfig ``` 這個指令會開啟一個文字介面的設定選單,讓你可以以互動方式瀏覽並修改所有 kernel 的設定選項。 如果你需要調整像是「關閉模組簽章」、「啟用特定驅動程式支援」或「開啟除錯功能」等,都可以透過這個選單來完成。 #### 關閉模組簽章與安全限制 在 Ubuntu 中,預設會啟用模組簽章(CONFIG_MODULE_SIG)與 Lockdown 安全限制(CONFIG_SECURITY_LOCKDOWN_LSM),特別是當系統啟用了 UEFI Secure Boot 的情況下。這會導致自行編譯的 kernel 或模組在載入時被拒絕,甚至在編譯階段出現錯誤(例如找不到簽章金鑰 certs/signing_key.pem 或 debian/canonical-certs.pem) 只是想進行開發、除錯或測試,並不需要這些安全機制,可以手動關閉相關設定,方法如下: ```shell scripts/config --disable CONFIG_SECURITY_LOCKDOWN_LSM scripts/config --disable CONFIG_SECURITY_LOCKDOWN_LSM_EARLY scripts/config --disable CONFIG_MODULE_SIG scripts/config --set-str CONFIG_SYSTEM_TRUSTED_KEYS "" scripts/config --disable SYSTEM_REVOCATION_KEYS scripts/config --disable SYSTEM_TRUSTED_KEYS make olddefconfig ``` #### 啟動編譯 ```shell make -j$(nproc) ``` #### 掛載模組 ```shell sudo make modules_install sudo make install sudo update-grub ``` 這三行是在 成功編譯 Linux kernel 後的安裝步驟,用來: 1. 安裝驅動模組 1. 安裝核心映像與相關檔案 1. 更新 GRUB 開機選單 編譯完後要將 initramfs 縮小否則會出現下列情況: ``` Loading Linux 6.15.0 ... Loading initial ramdisk ... error: out of memory. Press any key to continue... ``` 輸入下列指令: ``` sudo dracut --force --hostonly /boot/initrd.img-6.15.0 6.15.0 ``` ### 修改前測試 Suspend-to-Idle 的結果 以下為搭載 Intel Core i7-10700 處理器,核心版本為 6.15 的個人電腦執行 Suspend-to-Idle 測試之結果。 ``` wu@wu-Pro-E500-G6-WS720T:~$ uname -r 6.15.0-wu-kernel+ ``` ``` Baseline Parent/child Suspend Resume Suspend Resume s/r 536 166 noRPM 14 1.8 noIRQ 37 38 s/r 532 140 noRPM 15 1.8 noIRQ 38 38 s/r 526 141 noRPM 14 1.8 noIRQ 39 38 ``` ### 修改後測試 Suspend-to-Idle 的結果 ``` Baseline Parent/child Suspend Resume Suspend Resume s/r 536 166 529 164 noRPM 14 1.8 15 2 noIRQ 37 38 39 37 s/r 532 140 545 141 noRPM 15 1.8 15 2 noIRQ 38 38 39 38 s/r 526 141 532 140 noRPM 14 1.8 14 37 noIRQ 39 38 39 2 ``` 可以觀察到效能上並未有明顯改善,可能是因為該系統在暫停與恢復方面原本就已具備良好表現。事實上,根據 patch 中的測試結果,桌機部分亦顯示其在這方面本就沒有太多優化空間;又或者是系統實際進入的 suspend 狀態不夠深(例如未達到 Suspend-to-RAM 或 Hibernation 等較省電的模式),因此無法充分發揮 patch 所帶來的效能提升。 >While both Dell XPS13 systems show a consistent improvement after applying the first three patches, everything else is essentially a wash (particularly on the desktop machine that seems to suspend and resume as fast as it gets already). ``` Baseline Parents/children Device links Suspend Resume Suspend Resume Suspend Resume Dell XPS13 9360 s/r 427 449 298 450 294 442 noRPM 13 1 13 1 13 1 noIRQ 31 25 28 24 28 26 s/r 408 442 298 443 301 447 noRPM 13 1 13 1 13 1 noIRQ 32 25 30 25 28 25 s/r 408 444 310 450 298 439 noRPM 13 1 13 1 13 1 noIRQ 31 24 31 26 31 24 Dell XPS13 9380 s/r 439 283 318 290 319 290 noRPM 15 2 15 1 15 2 noIRQ 198 1766 202 1743 204 1766 s/r 439 281 318 280 320 280 noRPM 15 2 15 1 15 1 noIRQ 199 1781 203 1783 205 1770 s/r 440 279 319 281 320 283 noRPM 14 2 15 1 15 1 noIRQ 197 1777 202 1765 203 1724 Coffee Lake Desktop s/r 138 347 130 345 132 344 noRPM 15 2 20 2 15 2 noIRQ 15 25 23 25 16 26 s/r 133 345 124 343 131 346 noRPM 14 1 13 1 13 1 noIRQ 15 25 14 25 14 25 s/r 124 343 126 345 128 345 noRPM 13 1 13 1 13 1 noIRQ 14 25 14 25 14 26 ``` 根據作者的實驗結果,其 suspend 的速度明顯優於本系統的表現。為了進一步釐清差異的原因,我使用 pm-graph 工具分析各個裝置在 suspend 階段的耗時情形,並發現了一個關鍵因素:目前本系統在 suspend 階段的主要瓶頸來自機械式硬碟的存取延遲。因此,即使進一步優化程式邏輯,整體效能仍可能受到硬體限制的影響。這也可能是我無法觀察到明顯提昇的主因之一。 ![image](https://hackmd.io/_uploads/BkwV5sCGee.png) 當我將機械式硬碟拔除後,並觀察執行數據時,發現結果與上述 Coffee Lake Desktop 的表現相符,Suspended time 降至約一百二十多毫秒。然而,這樣一來,桌機的 suspend 時間已經足夠快速,因此無法明顯觀察到進一步的優化效果。 於是我另外找了一台筆電來觀察結果,下列為測試結果: ``` Baseline Parents/children Suspend Resume Suspend Resume acer aspire a515-57g s/r 412 371 402 370 noRPM 28 7 22 6 noIRQ 59 162 56 152 s/r 409 375 404 374 noRPM 29 7 28 5 noIRQ 55 152 56 153 s/r 402 375 411 373 noRPM 30 5 29 5 noIRQ 65 157 52 164 ``` 目前看起來差異不大,我猜這個 patch 可能會在效能比較差的機器上會發揮更明顯的效果。 ## [Make async suspend handle suppliers like parents](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#mc1d3aee3440cf22dd91098c01bc6a0330ec8687f) 這個 patch 在之前的基礎上,對裝置的 async suspend 流程做了一些調整: * 如果某個裝置還有其他裝置依賴它,就不要太早讓它進入 async suspend; 相反地,該裝置所依賴的供應裝置,應該在它本身完成掛起後,再開始執行這些供應裝置的 async suspend。 ## [Make async resume handle consumers like children](https://lore.kernel.org/lkml/10629535.nUPlyArG6x@rjwysocki.net/T/#m1e5c52e9bb815f499133f47133c04593dad2af7b) 這個 patch 延續之前的改動,這次調整了裝置 resume 階段的 async 處理流程: * 如果一個裝置還依賴其他供應裝置,就不要太早開始喚醒它; 相反地,那些依賴這個裝置的其他裝置,應該等這個裝置先喚醒完成,再開始執行自己的 async resume。 ### 改動實做 #### 將 async suspend 專注於 leaf 節點 在原本的 async suspend 流程中,第一層迴圈會先針對 leaf 節點進行呼叫,而當 leaf 節點的 suspend 動作完成後,流程會進一步觸發其父節點的 suspend。此次修改的目的是調整該行為,使第一層迴圈中所呼叫的 leaf 節點不再主動觸發其父節點 suspend。 我認為,第一層已呼叫的 leaf 節點數量已經足夠覆蓋 suspend 流程的主要進度,因此不需要額外再針對父節點進行多餘的呼叫,避免因此增加額外的 context switch 負擔。 實測結果顯示,修改後流程的速度約為原本設計的**三倍**,因此似乎是不可行的。 #### 將 async resume 的對象從 root 節點變更為 root 節點和其小孩 實測結果顯示修改後與原本效果與效能無明顯差異。 #### 優化 suspend 流程:僅針對非 leaf 或有 consumers 裝置呼叫 dpm_wait_for_subordinate() 原本 suspend 流程中,針對每個裝置在 suspend 階段都會無條件呼叫 dpm_wait_for_subordinate(dev, async),以確保 subordinate 裝置(通常是 child devices 或透過 device_link 關聯的 consumers)能夠在 parent 裝置 suspend 前正確完成 suspend 動作。 然而 **leaf device** 以及沒有 consumers 關聯的裝置,本身不存在 subordinate suspend 順序依賴,這類裝置呼叫 dpm_wait_for_subordinate() 並無作用,反而可能會增加 suspend 流程中的執行時間。 因此此次修改調整為僅針對以下條件成立的裝置呼叫 dpm_wait_for_subordinate(): ``` if (!dpm_leaf_device(dev) || !list_empty(&dev->links.consumers)) dpm_wait_for_subordinate(dev, async); ``` 但實際針對這段修改後的執行時間進行測量,並透過 T-test 進行統計檢定後發現,與原本直接對所有裝置執行 dpm_wait_for_subordinate 的結果相比,整體 suspend 效能並無顯著差異,兩者效果相當。 #### 將 dpm_root_device 判斷整合進 dpm_wait_for_superior 由於這個 patch 將 dpm_root_device 這個函式改為下列: ```diff static bool dpm_root_device(struct device *dev) { - return !dev->parent; + lockdep_assert_held(&dpm_list_mtx); + + /* + * Since this function is required to run under dpm_list_mtx, the + * list_empty() below will only return true if the device's list of + * consumers is actually empty before calling it. + */ + return !dev->parent && list_empty(&dev->links.suppliers); } ``` 原本設計上,裝置樹中的根節點會先啟動 async resume。現在修改後,變更為根節點且要沒有供應者關係的裝置才會啟動 async resume。 定義好這個邏輯函式後,我發現它也可以直接整合進 dpm_wait_for_superior() 函式中使用。dpm_wait_for_superior() 的作用是 在 resume 過程中等待父節點和所有供應者完成相關操作,以確保 resume 順序的正確性。 而將根節點或沒有供應者的裝置判斷整合進 dpm_wait_for_superior() 的意圖是 → 讓這類裝置可以提前返回,不需額外執行多餘的函式,進而提早解鎖 dpm_list_mtx,提升整體流程效率。 在確認上述修改方向後,我首先針對 if (dpm_root_device(dev)) 這個條件進行統計,試圖了解該條件在流程中實際成立的次數。初步統計結果顯示,這行條件判斷成立的次數多達 584 次。 基於這個數據,我推測我新增的判斷函式應該也會被觸發大約數百次。然而,進一步實際觀察執行結果時發現,該函式實際僅被執行了 15 次,與預期相差甚大。另一方面,dpm_wait_for_superior 整體被呼叫的次數則達到 777 次。 這結果顯示,儘管在 device_resume_noirq 中 dpm_root_device(dev) 條件成立的次數很多,但實際進入我新增函式的次數明顯更少,說明流程中仍有其他條件或邏輯影響實際執行路徑。 ```c void dpm_noirq_resume_devices(pm_message_t state) { ... list_for_each_entry(dev, &dpm_suspended_list, power.entry) { dpm_clear_async_state(dev); if (dpm_root_device(dev)) dpm_async_with_cleanup(dev, async_resume); } ``` ``` PM: count value: 15 PM: count value: 777 ``` 於是我接著觀察 device_resume_noirq 的執行流程,並進行測試後發現了可能的原因。在 device_resume_noirq 中,觀察到以下幾段關鍵程式碼: ```c static void device_resume_noirq(struct device *dev, pm_message_t state, bool async) { ... if (dev->power.syscore || dev->power.direct_complete) goto Out; if (!dev->power.is_noirq_suspended) goto Out; if (!dpm_wait_for_superior(dev, async)) goto Out; skip_resume = dev_pm_skip_resume(dev); ``` 我推測當 dpm_root_device(dev) 條件成立時,或許在執行到 dpm_wait_for_superior 函式之前,流程已經提前跳出,因此導致 dpm_wait_for_superior 的實際執行次數遠低於預期。為了驗證這個推測,我針對流程中的兩個判斷式分別進行了統計,觀察各自的判斷成立次數,以進一步確認是否存在提前跳出的情況。 測試結果顯示,第一個判斷式成立的次數高達一千多次。基於這個結果,我進一步推測,當 dpm_root_device(dev) 條件成立時,該裝置的 dev->power.syscore 或 dev->power.direct_complete 也很可能為真,從而導致流程在進入 dpm_wait_for_superior 之前就已經提前跳出。 ## suspend resume 改進方向 目前的實作流程裡還是有不少多餘的 context switch。當一個裝置要被 resume 時,必須先等父裝置處理完成,接著還要再等待它的 supplier 完成相關操作。而這個等待的過程是透過 wait_for_completion() 函式來實現的。 wait_for_completion() 是一個阻塞式的同步機制。當呼叫該函式時,當前執行緒會進入睡眠狀態,並且會將自己加入至對應的等待隊列(實作上是一個鏈結串列 swait_queue),直到目標工作透過 complete() 或 complete_all() 函式明確發出完成通知,將 done 欄位設為大於 0 的數值,此時等待隊列中的執行緒才會被喚醒,並繼續執行後續流程。 然而,在實際的裝置 resume 流程中,單一裝置通常並不只有一個 supplier。當裝置進行 resume 時,若某一個 supplier 率先完成並觸發 complete(),當前裝置就會被喚醒;但其他尚未完成的 supplier 仍可能處於處理中,導致該裝置在 resume 流程中反覆進入 wait_for_completion() 等待尚未完成的 supplier。這樣的重複等待與喚醒會產生額外的 context switch,進一步增加 resume 流程中的同步等待開銷與整體延遲。 特別是在裝置層級較深、供應鏈鏈結複雜的系統中,這種情況會更加明顯,導致整體 resume 流程效率下降。因此,如何降低不必要的同步等待與優化裝置間的依賴處理,成為提升 resume 效能的一個重要課題。上述的情形同樣也能套用在 suspend 上。