---
# System prepended metadata

title: Linux Suspend/Resume 實驗（二）

---

# Linux Suspend/Resume 實驗（二）
## 追蹤 suspend 階段
要優化系統的 suspend 和 resume 流程，首先要弄清楚：當我下命令讓電腦休眠時，Linux 內部到底呼叫了哪些函式？了解這整條流程，才能知道從哪裡開始優化。

我首先觀察最簡單的 suspend 模式 — freeze (Suspend-to-Idle)。當我執行下列指令後：
```shell
echo "freeze" | sudo tee /sys/power/state
```
系統會開始進行 freeze 流程，首先系統會呼叫 [linux/kernel/power/main.c](https://github.com/torvalds/linux/blob/master/kernel/power/main.c) 當中的 `state_store()` 這個函式，這個函式主要的效果是 根據使用者輸入的字串（如 echo mem > /sys/power/state），決定要讓系統進入哪種 suspend 模式，或是執行休眠（hibernate），並呼叫相對應的核心 API。
```c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
			   const char *buf, size_t n)
```
若使用者輸入的狀態為 freeze 或 mem，系統將會呼叫 `pm_suspend()` 函式以進入對應的 suspend 模式；若輸入為 disk，則會轉而呼叫 `hibernate()`，進行休眠操作。

在 `pm_suspend()` 的執行過程中，系統會進一步呼叫 `enter_state()`，該函式負責整個 suspend 流程，包括凍結使用者行程、通知驅動進入低功耗模式，以及執行平台相關的 suspend 操作，在輸入 freeze 後總體流程會是下列這樣：
```
state_store
    pm_suspend
        enter_state
            s2idle_begin
            ksys_sync_helper
            suspend_prepare
            suspend_devices_and_enter
            suspend_finish        
```
經觀察分析發現，ksys_sync_helper 是導致 suspend_enter 階段耗時較長的主要原因。
![Screenshot from 2025-05-25 15-11-08](https://hackmd.io/_uploads/rJ0cfrgGlg.png)
而這個函式的功能為執行完整的系統同步，確保所有修改過的資料都安全寫入磁碟
>Perform a full system sync to ensure all modified data is safely written to disk.

他的步驟為下列：
 1. 喚醒所有 flusher 執行緒，以平行的方式把各個裝置的資料寫回去。
 2. 同步所有 inode，將檔案資料刷新到磁碟，並確保 flusher 執行緒已完成。
 3. 對所有已掛載的檔案系統呼叫 ->sync_fs() 方法：首先以非同步方式執行，接著再以同步方式執行，以確保確保 metadata (描述資料的資料)確實寫入儲存裝置。
 4. 額外把所有 block device 的頁面快取從記憶體寫入磁碟。因為像 ext2 這類檔案系統可能只更新 metadata（例如 inode 或 bitmap）到 block device 的頁面快取，而不會在 ->sync_fs() 中主動把資料從記憶體寫入磁碟。
 5. 如果啟用了 laptop_mode（筆電省電模式），則觸發額外的同步處理。


**flusher 執行緒**是核心中負責將 dirty pages 從 page cache 寫回到儲存裝置的背景執行緒，可以輸入下列命令來觀察：
```shell
ps -eo pid,comm | grep flush
```
可以觀察到這種工作是由 kworker 這個執行緒來執行的
```
77646 kworker/u32:3-flush-259:5
87512 kworker/u32:0-flush-259:5
```
經過測試不管是哪種程度的 suspend 都會在 `suspend_enter` 階段做 `sync filesystem`，嘗試將 `dirty page` 寫回磁碟中，但是根據 [System Sleep States](https://www.kernel.org/doc/html/next/admin-guide/pm/sleep-states.html) 所述 memory 只有在 Hibernation 的情況下才會斷電，因此 memory 只要不是 Hibernation 狀況下都不會消失，那是不是只要不是使用 Hibernation 就能不用 `sync filesystem` 不去將 `dirty page` 寫回磁碟中？
>This state (also referred to as STR or S2RAM), if supported, offers significant energy savings as everything in the system is put into a low-power state, except for memory。

接下來要說明剩下的三個 suspend 主要函式，分別是：
* suspend_prepare
* suspend_devices_and_enter
* suspend_finish

其中，`suspend_prepare` 的主要工作是凍結使用者空間的行程與核心空間中可凍結的執行緒，這一步是為了避免在 suspend 過程中有其他程式繼續運作而造成干擾。它會呼叫 [linux/kernel/power/process.c](https://github.com/torvalds/linux/blob/master/kernel/power/process.c#L28) 中的 `freeze_processes()` 和 `freeze_kernel_threads()` 來完成這個步驟。

而 `suspend_finish` 則是在系統喚醒後執行，負責將先前被凍結的所有行程與執行緒回復執行，使系統能夠正常回到運作狀態。這裡會呼叫 `thaw_processes()` 。

再來是最關鍵的 `suspend_devices_and_enter` ，它是整個 suspend 流程的核心部分，會處理：

* 裝置的 suspend 流程（包含 late suspend、noirq suspend）
* 呼叫實際執行低功耗狀態的函式
* 判斷是否有喚醒事件，並進行必要的 resume 操作

下列為輸入 freeze 後 `suspend_devices_and_enter()` 函式的運作流程：
```
suspend_devices_and_enter()
 ├── 檢查所輸入 state 是否支援
 ├── 記錄所輸入 state
 ├── pm_set_suspend_no_platform()
 ├── platform_suspend_begin()
 ├── console_suspend_all()
 ├── dpm_suspend_start()
 ├── suspend_enter()
 ├── dpm_resume_end()
 ├── console_resume_all()
 └── platform_resume_end()

```
而其中的 [`dpm_suspend_start()`](https://github.com/torvalds/linux/blob/master/drivers/base/power/main.c#L2009) 會呼叫 `dpm_prepare` 和 `dpm_suspend`， `dpm_prepare()` 負責對所有非 sysdev 裝置（指的是所有用 struct device 管理的裝置）呼叫其註冊的 ->prepare() callback，進行 suspend 前的預備作業，例如鎖定狀態、檢查裝置是否可以被暫停等。這個階段不會實際改變裝置的運作狀態，而是為後續 suspend 動作建立條件。

接下來的 `dpm_suspend()` 則會對這些裝置呼叫 ->suspend() callback，這是驅動中負責將裝置真正進入低功耗的核心步驟。
```
suspend_devices_and_enter()
└─> dpm_suspend_start()
    ├─> dpm_prepare()  // 預備階段
    │   └─> device_prepare()  // 各裝置的 prepare callback
    └─> dpm_suspend()   // 實際暫停
        └─> device_suspend()  // 各裝置的 suspend callback
```

![Screenshot from 2025-05-25 21-04-02](https://hackmd.io/_uploads/SkmISqxGex.png)

而再來下列為 `suspend_enter` 的執行流程：
```
[suspend_enter()]
     ↓
[platform_suspend_prepare]
     ↓
[dpm_suspend_late]
     ↓
[platform_suspend_prepare_late]
     ↓
[dpm_suspend_noirq]
     ↓
[platform_suspend_prepare_noirq]
     ↓
┌─────────────────────────────────────────┐
│  if (state == S2IDLE)                   │
│      → [s2idle_loop()]                  │
│  else                                   │
│      → [pm_sleep_disable_secondary_cpus]│
│           ↓                             │
│        [arch_suspend_disable_irqs]      │
│           ↓                             │
│        [syscore_suspend]                │
│           ↓                             │
│        [suspend_ops->enter()]           │
│           ↓                             │
│        [syscore_resume]                 │
│           ↓                             │
│        [arch_suspend_enable_irqs]       │
│           ↓                             │
│        [pm_sleep_enable_secondary_cpus] │
└─────────────────────────────────────────┘
           ↓
[platform_resume_noirq] → [dpm_resume_noirq]
           ↓
[platform_resume_early] → [dpm_resume_early]
           ↓
     [platform_resume_finish]


```

![Screenshot from 2025-05-25 21-21-02](https://hackmd.io/_uploads/BklwF5lMgg.png)
隨後執行 `s2idle_loop()`，系統進入 suspend-to-idle 模式的主要控制迴圈。

在這個階段中，所有使用者行程已經凍結、裝置已暫停，僅剩處理器持續 idle 等待喚醒事件。根據原始註解：
>Suspend-to-idle equals:
frozen processes + suspended devices + idle processors.

`s2idle_loop()` 會重複檢查是否有任何有效的喚醒事件發生。若平台提供 `s2idle_ops->wake()`，則優先使用該函式進行判斷；否則回退至 Linux 通用的 `pm_wakeup_pending()` 進行確認。若無喚醒條件，則透過 `s2idle_enter()` 讓包括目前執行的處理器在內的所有 CPU 同步進入 idle 狀態，並一同等待喚醒事件的發生。只要其中任一來源觸發有效喚醒事件，便會中斷等待，喚醒整個系統並結束 suspend-to-idle 狀態。
![Screenshot from 2025-05-26 13-49-47](https://hackmd.io/_uploads/rJJGWtbflg.png)

## 追蹤 resume 階段

由 `suspend_enter()` 的流程可知，目前系統正停留在 `s2idle_loop()` 中，進入 idle 狀態並等待喚醒事件發生：
```
[suspend_enter()]
     ↓
[platform_suspend_prepare]
     ↓
[dpm_suspend_late]
     ↓
[platform_suspend_prepare_late]
     ↓
[dpm_suspend_noirq]
     ↓
[platform_suspend_prepare_noirq]
     ↓
┌─────────────────────────────────────────┐
│  if (state == S2IDLE)                   │
│      → [s2idle_loop()]                  │
│  else                                   │
│      → [pm_sleep_disable_secondary_cpus]│
│           ↓                             │
│        [arch_suspend_disable_irqs]      │
│           ↓                             │
│        [syscore_suspend]                │
│           ↓                             │
│        [suspend_ops->enter()]           │
│           ↓                             │
│        [syscore_resume]                 │
│           ↓                             │
│        [arch_suspend_enable_irqs]       │
│           ↓                             │
│        [pm_sleep_enable_secondary_cpus] │
└─────────────────────────────────────────┘
           ↓
[platform_resume_noirq] → [dpm_resume_noirq]
           ↓
[platform_resume_early] → [dpm_resume_early]
           ↓
     [platform_resume_finish]
```
根據原始程式碼可以得知如果睡眠狀態是 Suspend-to-Idle 系統喚醒之後將從 `platform_resume_noirq()` 開始回復流程，接著依序執行 `dpm_resume_noirq()`、`platform_resume_early()`、`dpm_resume_early()` 等函式，完成裝置與平台的 resume 操作，最後回到 `suspend_devices_and_enter()` 去執行 `dpm_resume_end()` ，這個函式的作用是去執行另外兩個函式 `dpm_resume()` 和 `dpm_complete()`：
```c
void dpm_resume_end(pm_message_t state)
{
	dpm_resume(state);
	dpm_complete(state);
}
EXPORT_SYMBOL_GPL(dpm_resume_end);
```
![Screenshot from 2025-05-26 14-13-34](https://hackmd.io/_uploads/Sypc8KWGxg.png)
![Screenshot from 2025-05-26 14-17-45](https://hackmd.io/_uploads/SkT9PY-Mlx.png)

## 分析最久階段
根據下列兩張圖可以發現耗時最久的在於 suspend_enter 階段的 `sync_filesystems` 和回復階段的 dpm_resume 階段，因此可以嘗試去改進這兩個階段的效率：
![Screenshot from 2025-05-26 14-28-14](https://hackmd.io/_uploads/rkxEqKZGxg.png)
![Screenshot from 2025-05-26 14-29-27](https://hackmd.io/_uploads/r1dL9tWfle.png)








