淺述內核中“掛起到空閑”的實現
簡介
Linux 內核提供了多種睡眠狀態,各個狀態通過設置係統中的不同部件進入低耗電模式來節約能源。目前總共有四種睡眠狀態,分別是:掛起到空閑suspend to idle、加電待機power-on standby(standby)、掛起到內存suspend to ram和掛起到磁盤suspend to disk。這些狀態分別對應 ACPI 的 4 種狀態:S0,S1,S3 和 S4。掛起到空閑suspend to idle是純軟件實現的,用於將 CPU 維持在盡可能深的 idle 狀態。加電待機power-on standby(standby)則使設備處於低功耗狀態,並且關閉所有非引導 CPU。掛起到內存suspend to ram就更進一步,關閉所有 CPU 並且設置 RAM 進入自刷新模式。掛起到磁盤suspend to disk則是最省功耗的模式,關閉盡可能多的係統,包括關閉內存。然後內存中的內容會被寫到硬盤,待喚醒計算機的時候將硬盤中的內容重新恢複到內存中。
這篇博文主要介紹掛起到空閑suspend to idle的實現。如上所說,它主要通過軟件實現。一般平台的掛起過程包括凍結用戶空間並將外圍設備調至低耗電模式。但是,係統並不是直接關閉和熱插拔掉 CPU,而是靜靜地強製將 CPU 進入空閑idle狀態。隨著外圍設備進入了低耗電模式,除了喚醒相關的中斷外不應有其他中斷產生。喚醒中斷包括那些設置用於喚醒係統的計時器(比如 RTC,普通計時器等)、或者電源開關、USB 和其它外圍設備等。
在凍結過程中,當係統進入空閑狀態時會調用一個特殊的 cpu 空閑函數。這個 enter_freeze()
函數可以和調用使 cpu 空閑的 enter()
函數一樣簡單,也可以複雜得多。該函數複雜的程度由將 SoC 置為低耗電模式的條件和方法決定。
先決條件
platform_suspend_ops
一般情況,為了支持 S2I,係統必須實現 platform_suspend_ops
並提供最低限度的掛起支持。這意味著至少要完成 platform_suspend_ops
中的 valid()
函數。如果掛起到空閑suspend to idle和掛起到內存suspend to ram都要支持,valid 函數中應使用 suspend_valid_only_mem
。
不過,最近內核增加了對 S2I 的自動支持。Sudeep Holla 提出了一個變更,可以讓係統不需要滿足platform_suspend_ops
條件也能提供 S2I 支持。這個補丁已經被接收並將合並在 4.9 版本中,該補丁可從這裏獲取: https://lkml.org/lkml/2016/8/19/474。
如果定義了 suspend_ops
,那麼可以通過查看 /sys/power/state
文件得知係統具體支持哪些掛起狀態。如下操作:
# cat /sys/power/state
freeze mem
這個示例的結果顯示該平台支持 S0(掛起到空閑suspend to idle)和 S3(掛起到內存suspend to ram)。按 Sudeep 的變更,那些沒有實現 platform_suspend_ops
的平台將隻顯示 freeze 狀態。
喚醒中斷
一旦係統處於某種睡眠狀態,係統必須要接收某個喚醒事件才能恢複係統。這些喚醒事件一般由係統的設備產生。因此一定要確保這些設備驅動使用喚醒中斷,並且將自身配置為接收喚醒中斷後產生喚醒事件。如果沒有正確識別喚醒設備,係統收到中斷後會繼續保持睡眠狀態而不會恢複。
一旦設備正確實現了喚醒接口的調用,就可用來生成喚醒事件。請確保 DT 文件正確配置了喚醒源。下麵是一個配置喚醒源示例,該文件來自(arch/arm/boot/dst/am335x-evm.dts
):
gpio_keys: volume_keys@0 {
compatible = “gpio-keys”;
#address-cells = <1>;
#size-cells = <0>;
autorepeat;
switch@9 {
label = “volume-up”;
linux,code = <115>;
gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
wakeup-source;
};
switch@10 {
label = “volume-down”;
linux,code = <114>;
gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
wakeup-source;
};
};
如上所示,有兩個 gpio 鍵被配置為喚醒源,在係統掛起期間,其中任何一個鍵被按下都會產生一個喚醒事件。
可替代 DT 文件配置的另一個喚醒源配置就是設備驅動,如果設備驅動自身在代碼裏麵配置了喚醒支持,那麼就會使用該默認喚醒配置。
實施
凍結功能
如果係統希望能夠充分使用掛起到空閑suspend to idle,那麼應該在 CPU 空閑驅動代碼中定義 enter_freeze()
函數。enter_freeze()
與 enter()
的函數原型略有不同。因此,不能將 enter()
同時指定給 enter
和enter_freeze
。至少,係統會直接調用 enter()
。如果沒有定義 enter_freeze()
,係統會掛起,但是不會觸發那些隻有當 enter_freeze()
定義了才會觸發的函數,比如 tick_freeze()
和stop_critical_timing()
都不會發生。這會導致計時器中斷喚醒係統,但不會導致係統恢複,因為係統處理完中斷後會繼續掛起。
在掛起過程中,中斷越少越好(最好一個也沒有)。
下圖顯示了能耗和時間的對比。圖中的兩個尖刺分別是掛起和恢複。掛起前後的能耗尖刺是係統退出空閑態進行記錄操作,進程調度,計時器處理等。因延遲的緣故,係統進入更深層次空閑狀態需要花費一段時間。
能耗使用時序圖
下圖為 ftrace 抓取的 4 核 CPU 在係統掛起和恢複操作之前、之中和之後的活動。可以看到,在掛起期間,沒有請求或者中斷被處理。
Ftrace 抓取的掛起/恢複活動圖
空閑狀態
你必須確定哪個空閑狀態支持凍結。在凍結期間,電源相關代碼會決定用哪個空閑狀態來實現凍結。這個過程是通過在每個空閑狀態中查找誰定義了 enter_freeze()
來決定的。CPU 空閑驅動代碼或者 SoC 掛起相關代碼必須確定哪種空閑狀態實現凍結操作,並通過給每個 CPU 的可應用空閑狀態指定凍結功能來進行配置。
例如, Qualcomm 會在平台掛起代碼的掛起初始化函數處定義 enter_freeze
函數。這個工作是在 CPU 空閑驅動已經初始化後進行,以便所有結構已經定義就位。
掛起/恢複相關驅動支持
你可能會在第一次成功掛起操作後碰到驅動相關的 bug。很多驅動開發者沒有精力完全測試掛起和恢複相關的代碼。你甚至可能會發現掛起操作並沒有多少工作可做,因為 pm_runtime
已經做了你要做的掛起相關的一切工作。由於用戶空間已經被凍結,設備此時已經處於休眠狀態並且 pm_runtime
已經被禁止。
測試相關
測試掛起到空閑suspend to idle可以手動進行,也可以使用腳本/進程等實現自動掛起、自動睡眠,或者使用像 Android 中的wakelock
來讓係統掛起。如果手動測試,下麵的操作會將係統凍結。
/ # echo freeze > /sys/power/state
[ 142.580832] PM: Syncing filesystems … done.
[ 142.583977] Freezing user space processes … (elapsed 0.001 seconds) done.
[ 142.591164] Double checking all user space processes after OOM killer disable… (elapsed 0.000 seconds)
[ 142.600444] Freezing remaining freezable tasks … (elapsed 0.001 seconds) done.
[ 142.608073] Suspending console(s) (use no_console_suspend to debug)
[ 142.708787] mmc1: Reset 0x1 never completed.
[ 142.710608] msm_otg 78d9000.phy: USB in low power mode
[ 142.711379] PM: suspend of devices complete after 102.883 msecs
[ 142.712162] PM: late suspend of devices complete after 0.773 msecs
[ 142.712607] PM: noirq suspend of devices complete after 0.438 msecs
< system suspended >
….
< wake irq triggered >
[ 147.700522] PM: noirq resume of devices complete after 0.216 msecs
[ 147.701004] PM: early resume of devices complete after 0.353 msecs
[ 147.701636] msm_otg 78d9000.phy: USB exited from low power mode
[ 147.704492] PM: resume of devices complete after 3.479 msecs
[ 147.835599] Restarting tasks … done.
/ #
在上麵的例子中,需要注意 MMC 驅動的操作占了 102.883ms 中的 100ms。有些設備驅動在掛起的時候有很多工作要做,比如將數據刷出到硬盤,或者其他耗時的操作等。
如果係統定義了凍結freeze,那麼係統將嚐試掛起操作,如果沒有凍結功能,那麼你會看到下麵的提示:
/ # echo freeze > /sys/power/state
sh: write error: Invalid argument
/ #
未來的發展
目前在 ARM 平台上的掛起到空閑suspend to idle有兩方麵的工作需要做。第一方麵工作在前麵 platform_suspend_ops
小節中提到過,是總允許接受凍結狀態以及合並到 4.9 版本內核中的工作。另一方麵工作是凍結功能的支持。
如果你希望設備有更好的響應及表現,那麼應該繼續完善凍結功能的實現。然而,由於很多 SoC 會使用 ARM 的 CPU 空閑驅動,這使得 ARM 的 CPU 空閑驅動完善它自己的通用凍結功能的工作更有意義了。而事實上,ARM 正在嚐試添加此通用支持。如果 SoC 供應商希望實現他們自己的 CPU 空閑驅動或者需要在進入更深層次的凍結休眠狀態時提供額外的支持,那麼隻有實現自己的凍結功能。
原文發布時間為:2017-03-06
本文來自雲棲社區合作夥伴“Linux中國”
最後更新:2017-05-24 16:32:18