Try   HackMD

Linux 核心設計: Power Management(3): 測試工具

引言

在過去章節中,我們知道 Linux 可以透過各式的機制提供電源管理之功能。然而如果功能實作中有錯誤,加上睡眠狀態可能部分功能將暫停之狀態下,如果發生問題,核心是否還可以產生關鍵除錯訊息?或者可否搭配特定工具來釐清問題? 對於相關功能的核心的開發者,這是至關重要的問題。

除此之外,系統的睡眠和喚醒時間也是調校的重點。對於使用者而言,平台進入低功耗狀態與恢復運作的時間是愈短愈好。則如何分析各個裝置驅動程式的耗時,或者哪個睡眠/恢復階段是時間的瓶頸,同樣是不能忽視的問題。

因此,在本章節中我們將討論如何對 Linux 中的電源管理進行測試、除錯及分析。期待可以彙整不同的工具,以便在問題出現時有效率地找出原因所在!

PM on QEMU

由於 Power management 並非是系統的必要功能,讀者手上的硬體可能不支援相關功能(比如某些 Raspberry Pi)。所幸 QEMU 對此有一定程度的支援,可以讓我們很容易的測試。因此在正文開始之前,先簡單說明如何透過 QEMU 來觀察系統的睡眠與喚醒。

測試方法

首先,在啟動 QEMU 時,建議額外加上 -monitor telnet:localhost:7001,server,nowait,nodelay,這樣我們就可以在另外一個 session 運行 telnet localhost 7001 開啟 QEMU monitor,後者可以用來將系統喚醒。

如果不開啟另一個 session,也可以用 ctrl-a c 切換模式

則假設要測試 S2R,運行以下命令。

# echo mem > /sys/power/state

等待系統進入睡眠後,在 monitor 模式由以下命令可喚醒。

(qemu) system_wakeup

查看 QEMU console 的訊息,如果可以看到 PM: suspend entry/PM: suspend exit 訊息,就是成功讓系統進入睡眠然後喚醒了!

[   20.617496] PM: suspend entry (deep)
[   20.618524] Filesystems sync: 0.000 seconds
[   20.622658] Freezing user space processes
[   20.625029] Freezing user space processes completed (elapsed 0.002 seconds)
[   20.625204] OOM killer disabled.
[   20.625547] Freezing remaining freezable tasks
[   20.628371] Freezing remaining freezable tasks completed (elapsed 0.002 seconds)
[   20.629149] printk: Suspending console(s) (use no_console_suspend to debug)
[   20.635591] sd 0:0:0:0: [sda] Synchronizing SCSI cache
[   20.636752] ata2.00: Check power mode failed (err_mask=0x1)
[   20.637965] ata1.00: Entering standby power mode
[   20.641079] ACPI: PM: Preparing to enter system sleep state S3
[   20.641624] ACPI: PM: Saving platform NVS memory
[   20.641724] Disabling non-boot CPUs ...
[   20.643126] ACPI: PM: Low-level resume complete
[   20.643411] ACPI: PM: Restoring platform NVS memory
[   20.647040] ACPI: PM: Waking up from system sleep state S3
[   20.652441] pci 0000:00:01.0: PIIX3: Enabling Passive Release
[   20.652599] sd 0:0:0:0: [sda] Starting disk
[   20.682151] OOM killer enabled.
[   20.682229] Restarting tasks ... done.
[   20.683722] PM: suspend exit
[   20.807839] ata1: found unknown device (class 0)
[   20.809233] ata2: found unknown device (class 0)
[  270.650816] hrtimer: interrupt took 34062334 ns
[  317.229329] clocksource: timekeeping watchdog on CPU0: hpet retried 1 times befores
[  318.171384] clocksource: timekeeping watchdog on CPU0: hpet wd-wd read-back delay s
[  318.202563] clocksource: wd-tsc-wd read-back delay of 252130ns, clock-skew test sk!
[  332.879666] kworker/u4:0 (11) used greatest stack depth: 12880 bytes left

Kernel 機制

pm_trace

Reference: DebuggingKernelSuspend

Linux 的一種電源管理機制是可以透過 suspend 和 resume 流程來為裝置提供省電功能。而在此流程可能發生的錯誤中,又以在 resume 部分出現問題為主,且多數原因來自無法從斷電狀態完成 resume 的裝置驅動程式。

針對 resume 問題的除錯並不容易。核心中提供一種稱為 pm_trace 的方法,這可以在 resume 期間記錄進行的進度,並在手動重新啟動後取得此資料。

但首先要克服的問題是,考慮到資料要在重新啟動後保留,理當將 trace 資料儲存於永久性儲存裝置中。然而在 resume 階段,電腦此時還沒有啟動可用的永久性儲存裝置,這就產生了矛盾。幸好在 PC 主機板上的 real time clock(RTC) 可在重啟後仍然保留資訊。因此,pm_trace 就是藉此硬體來完成 resume 的追蹤。

要啟動 pm_trace 並且強制觸發 suspend,我們可以通過以下命令:

$ sudo sh -c "sync && echo 1 > /sys/power/pm_trace && pm-suspend"

如果 pm-suspend 指令不存在的話,可透過以下命令安裝:

$ sudo apt install pm-utils

或是通過控制 /sys/power/state 來進入 suspend。

$ sudo sh -c "sync && echo 1 > /sys/power/pm_trace && echo mem > /sys/power/state"

此時電腦應會進入 suspend 狀態,通常伴隨著電源 LED 緩慢閃爍。發生這種情況後,按電源按鈕則開始 resume。觀察到磁碟指示燈短暫亮起後,這應該就表示 resume 已經開始進行了。如果 resume 無法完成,會有大約 3 分鐘的時間來重啟並得以保留 RTC 中的資訊,可以按住電源按鈕直到電腦關閉再重新開啟。

確保載入出現 resume 問題的相同核心的話,我們就可以開始觀察 pm_trace 所追蹤到的內容了。通過 dmesg 應該可以看到類似下面的信息:

$ dmesg
...
[   11.323206]   Magic number: 0:798:264
[   11.323257]   hash matches drivers/base/power/resume.c:46

除此之外,很可能還有另一行 hash match 字樣的內容。如果是這樣,這表示你足夠幸運,因為最後這顯示的很可能就是造成問題的罪魁禍首。例如:

hash matches device i2c-9191

證明這一點的方法是在啟動 suspend 之前手動將該 module 移除,然後重複測試。此外,如果獲得的是一段 device number 而不是特定名稱,那麼 lspci/sys/devices/pci* 可能可以幫忙找到對應資訊。

pm_test

Hibernation(STD)

Linux 核心設計: Power Management(1): System Sleep model 一節中我們介紹了 sysfs 的介面,可以藉此設定 hiberation 後的行為(/sys/power/disk)並觸發之(/sys/power/state)。在 Linux 文件 Debugging hibernation and suspend 中也以此建議了幾種常見的 hibernation 情境,建議作為基本測試的項目(詳見文件)。

/* 1 */ 
# echo reboot > /sys/power/disk
# echo disk > /sys/power/state

/* 2-1. */
# echo platform > /sys/power/disk
# echo disk > /sys/power/state

/* 2-2. (if 'platform' is not supported) */
# echo shutdown > /sys/power/disk
# echo disk > /sys/power/state

/* 3.  Check if failures to resume from hibernation are related
 * to bad platform firmware */
# echo test_resume > /sys/power/disk
# echo disk > /sys/power/state

不過,若直接以上述方式測試,若發生問題時有時並不容易直接釐清。要找出 hibernation 失敗的原因,可以搭配使用 kernel 提供的特殊工具 /sys/power/pm_test(前提是核心使用 CONFIG_PM_DEBUG 進行編譯)。藉此工具可透過測試模式運作 hibernation。目前有 5 種測試模式可選擇:

  • freezer: 測試 process 被 freeze 的情況
  • devices: 測試 process freezed + device suspending
  • platform: 測試 process freezed + device suspending + platform global control methods(詳見 ACPI Device Power Management)
  • processors: 測試 process freezed + device suspending + platform global control methods + disable nonboot CPUs
  • core: 測試 process freezed + device suspending + platform global control methods + disable nonboot CPUs + platform/system devices suspending

注意到最後 platform global control methods 依賴支援 ACPI 的平台,並只在 /sys/power/disk 為 platform 情況下可運作。

使用 pm_test 測試 hibernation 的方法是,先將相應的字串寫入 /sys/power/pm_test,然後發出標準的休眠命令。例如,要使用 "devices" 測試模式並選擇 "platfrom" 的休眠模式,則依序執行下列操作:

# echo devices > /sys/power/pm_test
# echo platform > /sys/power/disk
# echo disk > /sys/power/state

以上述案例來說,則核心將嘗試:

  1. process freezed
  2. device suspending
  3. 等待幾秒鐘。預設為 5 秒,但可透過 suspend.pm_test_delay 模組參數進行設定
  4. device resuming
  5. process thawed

其他測試模式可以此類推。

如果想解除測試模式,則將 none 寫入 /sys/power/pm_test。讀取 /sys/power/pm_test 則可看到所有可用測試選項,其中目前所選由方括號 [] 指示。

在測試模式中我們可以看到每個測試級別都比前一個更深入,而core 等級盡可能深入地測試硬體和驅動程序但無需創建 hibernation image。因此,根據經驗,測試上建議應依照 freezer -> devices -> platform -> processors ->core 的順序測試,且最好在每個級別上重複測試幾次,以避免任何隨機因素。

則按照上述順序嘗試,可以有系統的推論分析:

  • 如果「freezer」測試失敗: 存在無法凍結的任務。這種情況下,通常在 dmesg 輸出可找到有問題的任務
  • 如果「devices」測試失敗,很可能存在無法 suspend 或 resume 的裝置驅動程式(留意到後者可能會讓系統測試後 hang 或者不穩定)。要找到這個驅動程序,一種效率不高但可行方式是二分搜尋:
    • 如果測試失敗,卸載目前載入的一半驅動程式並重複測試
    • 如果測試成功,則載入最近卸載的一半驅動程式並重複
    • 如卸載所有 modules 測試仍失敗,可能需在 kernel config 中尋找編譯為 builtin module 的驅動程式,並改為使用對應的獨立 module 再次測試
  • 如果「platform」測試失敗,則表示 platform firmware(例如 ACPI)的處理有異常
  • 如果「processors」測試失敗,則表示停用/啟用 non-boot CPU 有異常
    • 可以嘗試使用 /sys/devices/system/cpu/cpu*/online 來 hotplug CPU 比對之間問題
  • 如果「core」測試失敗,這表示 platform/system devices 的 suspend 失敗(這些裝置只在一個 CPU 上 suspend,且 CPU 關閉中斷),則問題很可能與硬體相關且嚴重

Suspend(STR)

我們可以將 "freezer"、"devices"、"platform"、"processors" 或 "core" 寫入 /sys/power/pm_test(如果核心編譯時開啟 CONFIG_PM_DEBUG 選項),則會運行與給定字串對應的 suspend 測試程式碼。由於 STR test mode 的定義方式與上一小節的 hibernation 相同,這裡就不再重複說明。

no_console_suspend

一般情況下,Linux 系統在進入低功耗狀態時,console 也會被暫停,則此時我們可能會錯過重要的除錯資訊。為此,可以通過在開啟參數(bootargs) 加入 no_console_suspend,或者由下面的命令讓 console 在電源管理流程中不進行暫停:

echo N | sudo tee /sys/module/printk/parameters/console_suspend

留意到此功能可能因平台限制而無法進行完整的電源管理流程,因此不能預設使用,只能在需要釐清錯誤時開啟。

pm_pr_dbg

在編譯配置加入 CONFIG_PM_SLEEP_DEBUGCONFIG_DYNAMIC_DEBUG 下,電源管理流程可以涵蓋更豐富的資訊,具體是藉由 pm_pr_dbg 所輸出的訊息,完整的訊息控制規則請參閱註解說明:

/**
 * pm_pr_dbg - print pm sleep debug messages
 *
 * If pm_debug_messages_on is enabled and the system is entering/leaving
 *      suspend, print message.
 * If pm_debug_messages_on is disabled and CONFIG_DYNAMIC_DEBUG is enabled,
 *	print message only from instances explicitly enabled on dynamic debug's
 *	control.
 * If pm_debug_messages_on is disabled and CONFIG_DYNAMIC_DEBUG is disabled,
 *	don't print message.
 */

要開啟此功能,可通過下述命令。

echo 1 | sudo tee /sys/power/pm_debug_messages

initcall_debug

initcall_debug 可以允許顯示裝置驅動程式初始化階段(init/probe)的有關訊息,並計算其耗時。在電源管理流程中,由於也會涉及裝置的初始化,因此可以藉此功能獲得更多除錯資訊。

可以藉由在開機參加入 initcall_debug 或是以下方式開啟此功能。

echo 1 | sudo tee /sys/module/kernel/parameters/initcall_debug

pm-graph

呈開頭所述,電源管理的重要議題之一是分析系統的睡眠與喚醒時間。在此需求下,我們需要方法以剖析低電源模式的進入與退出過程的各階段耗時。pm-graph 可以彙整 ftrace 的輸出結果,並將其輸出成易於觀察的時間圖,因此在分析電源管理流程中的時間瓶頸時,能夠給予十分直接的線索。

Reference