在過去章節中,我們知道 Linux 可以透過各式的機制提供電源管理之功能。然而如果功能實作中有錯誤,加上睡眠狀態可能部分功能將暫停之狀態下,如果發生問題,核心是否還可以產生關鍵除錯訊息?或者可否搭配特定工具來釐清問題? 對於相關功能的核心的開發者,這是至關重要的問題。
除此之外,系統的睡眠和喚醒時間也是調校的重點。對於使用者而言,平台進入低功耗狀態與恢復運作的時間是愈短愈好。則如何分析各個裝置驅動程式的耗時,或者哪個睡眠/恢復階段是時間的瓶頸,同樣是不能忽視的問題。
因此,在本章節中我們將討論如何對 Linux 中的電源管理進行測試、除錯及分析。期待可以彙整不同的工具,以便在問題出現時有效率地找出原因所在!
由於 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
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*
可能可以幫忙找到對應資訊。
在 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 suspendingplatform
: 測試 process freezed + device suspending + platform global control methods(詳見 ACPI Device Power Management)processors
: 測試 process freezed + device suspending + platform global control methods + disable nonboot CPUscore
: 測試 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
以上述案例來說,則核心將嘗試:
其他測試模式可以此類推。
如果想解除測試模式,則將 none
寫入 /sys/power/pm_test
。讀取 /sys/power/pm_test
則可看到所有可用測試選項,其中目前所選由方括號 []
指示。
在測試模式中我們可以看到每個測試級別都比前一個更深入,而core 等級盡可能深入地測試硬體和驅動程序但無需創建 hibernation image。因此,根據經驗,測試上建議應依照 freezer -> devices -> platform -> processors ->core 的順序測試,且最好在每個級別上重複測試幾次,以避免任何隨機因素。
則按照上述順序嘗試,可以有系統的推論分析:
我們可以將 "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_DEBUG
或 CONFIG_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 可以彙整 ftrace 的輸出結果,並將其輸出成易於觀察的時間圖,因此在分析電源管理流程中的時間瓶頸時,能夠給予十分直接的線索。