原文:Linux電源管理(5)_Hibernate和Sleep功能介紹
1. 前言
Hibernate和Sleep兩個功能是Linux PM的核心功能,它們的目的是類似的:暫停使用——>保存上下文——>關閉系統以節電········>恢復系統——>恢復上下文——>繼續使用。
本文以內核向用戶空間提供的接口為突破口,從整體上對這兩個功能進行介紹,并會在后續的文章中,分析它們的實現邏輯和執行動作。
2. Hibernate和Sleep相關的術語梳理
▆ Hibernate(冬眠)和Sleep(睡眠)
是Linux電源管理在用戶角度的抽象,是用戶可以看到的實實在在的東西。它們的共同點,是保存系統運行的上下文后掛起(suspend)系統,并在系統恢復后接著運行,就像什么事情都沒有發生一樣。它們的不同點,是上下文保存的位置、系統恢復的觸發方式以及具體的實現機制。
▆ Suspend
有兩個層次的含義。一是Hibernate和Sleep功能在底層實現上的統稱,都是指掛起(Suspend)系統,根據上下文的保存位置,可以分為Suspend to Disk(STD,即Hibernate,上下文保存在硬盤/磁盤中)和Suspend to RAM(STR,為Sleep的一種,上下文保存在RAM中);二是Sleep功能在代碼級的實現,表現為“kernel/power/suspend.c”文件。
▆ Standby,是Sleep功能的一個特例,可以翻譯為“打盹”。
正常的Sleep(STR),會在處理完上下文后,由arch-dependent代碼將CPU置為低功耗狀態(通常為Sleep)。而現實中,根據對功耗和睡眠喚醒時間的不同需求,CPU可能會提供多種低功耗狀態,如除Sleep之外,會提供Standby狀態,該狀態下,CPU處于淺睡眠模式,有任何的風吹草動,就會立即醒來。
▆ Wakeup
這是我們第一次正式的提出Wakeup的概念。我們多次提到恢復系統,其實在內核中稱為Wakeup。表面上,wakeup很簡單,無論是冬眠、睡眠還是打盹,總得有一個刺激讓我們回到正常狀態。但復雜的就是,什么樣的刺激才能讓我們醒來?
動物界,溫度回升可能是唯一可以讓動物從冬眠狀態醒來的刺激。而踢一腳、鬧鐘響等刺激,則可以讓我們從睡眠狀態喚醒。對于打盹來說,則任何的風吹草動,都可以喚醒。
而在計算機界,冬眠(Hibernate)時,會關閉整個系統的供電,因此想醒來,唯有Power按鈕可用。而睡眠時,為了縮短Wakeup時間,并不會關閉所有的供電,另外,為了較好的用戶體驗,通常會保留某些重要設備的供電(如鍵盤),那樣這些設備就可以喚醒系統。
這些刻意保留下來的、可以喚醒系統的設備,統稱為喚醒源(Wakeup source)。而Wakeup source的選擇,則是PM設計工作(特別是Sleep、Standby等功能)的重點。
3. 軟件架構及模塊匯整
3.1 軟件架構
內核中該部分的軟件架構大概可以分為三個層次,如下圖:
1)API Layer,描述用戶空間API的一個抽象層。
這里的API有兩類,一類涉及Hibernate和Sleep兩個功能(global APIs),包括實際功能、測試用功能、Debug用功能等,通過sysfs和debugfs兩種形式提供;另一類是Hibernate特有的(STD APIs),通過sysfs和字符設備兩種形式提供。
2)PM Core,電源管理的核心邏輯層,位于kernel/power/目錄下,包括主功能(main)、STD、STR&Standby以及輔助功能(assistant)等多個子模塊。
主功能,主要負責實現global APIs相關的邏輯,為用戶空間提供相應的API;
STD,包括hibernate、snapshot、swap、block_io等子模塊,負責實現STD功能和硬件無關的邏輯;
STR&Stanby,包括suspend和suspend_test兩個子模塊,負責實現STR、Standby等功能和硬件無關的邏輯。
3)PM Driver,電源管理驅動層,涉及體系結構無關驅動、體系結構有關驅動、設備模型以及各個設備驅動等多個軟件模塊。
3.2 用戶空間接口
3.2.1 /sys/power/state
state是sysfs中一個文件,為PM的核心接口,在“kernel/power/main.c”中實現,用于將系統置于指定的Power State(供電模式,如Hibernate、Sleep、Standby等)。不同的電源管理功能,在底層的實現,就是在不同Power State之間切換。
讀取該文件,返回當前系統支持的Power State,形式為字符串。在內核中,有兩種類型的Power State,一種是Hibernate相關的,名稱為“disk”,除“disk”之外,內核在"kernel/power/suspend.c"中通過數組的形式定義了另外3個state,如下:
1: const char *const pm_states[PM_SUSPEND_MAX] = { ? 2: ? ? ? ? [PM_SUSPEND_FREEZE] ? ? = "freeze", ? 3: ? ? ? ? [PM_SUSPEND_STANDBY] ? ?= "standby", ? 4: ? ? ? ? [PM_SUSPEND_MEM] ? ? ? ?= "mem", ? 5: };
這些Power State的解釋如下:
▆ freeze
這種Power State,并不涉及具體的Hardware或Driver,只是凍結所有的進程,包括用戶空間進程及內核線程。和我們熟知的“冬眠”和“睡眠”相比,就稱為“閉目養神”吧(可想而知,能節省的能量是有限的)。
【注:我們在之前的描述中,并沒有特別描述該State,因為它在較早的內核中,只是Sleep、Hibernate等功能的一部分,只是在近期才獨立出來。另外一個原因是,該state的省電效果不是很理想,所以其引用場景也是有限的。】
▆ standby,即第2章所描述的Standby狀態。
▆ mem,即通常所講的Sleep功能,也是第2章所描述的STR,Suspend to RAM。
▆ disk,即Hibernate功能,也是第2章所描述的STD,Suspend to Disk。
寫入特定的Power State字符串,將會把系統置為該模式。
3.2.2 /sys/power/pm_trace
PM Trace用于提供電源管理過程中的Trace記錄,由“CONFIG_PM_TRACE”宏定義(kernel/power/Kconfig)控制是否編譯進內核,并由“/sys/power/pm_trace”文件在運行時控制是否使能該功能。
3.2.3 /sys/power/pm_test
PM test用于對電源管理功能的測試,由“CONFIG_PM_DEBUG”宏定義(kernel/power/Kconfig)控制是否編譯進內核。其核心思想是:
▆ 將電源管理過程按照先后順序,劃分為多個步驟,如core、platform、devices等。這些步驟稱作PM Test Level。
▆ 系統通過一個全局變量(pm_test_level),保存系統當前的PM Test Level。該變量的值可以通過”/sys/power/pm_test“文件獲取及修改。
▆ 在每一個電源管理步驟結束后,插入PM test代碼,該代碼以當前執行步驟為參數,會判斷當前的PM Test Level和執行步驟是否一致,如果一致,則說明該步驟執行成功。出于Test考量,執行成功后,系統會打印Test信息,并在等待一段時間后,退出PM過程。
▆ 開發人員可以通過修改全局的Test Level,有目的測試所關心的步驟是否執行成功。
上面已經講了,該文件用于獲取及修改PM Test Level,具體的Level信息在“kernel/power/main.c”中定義,格式如下(具體的意義,比較簡單,對著相關的代碼看,非常清晰,這里就不啰嗦了):
1: static const char * const pm_tests[__TEST_AFTER_LAST] = { ? 2: ? ? ? ? [TEST_NONE] = "none", ? 3: ? ? ? ? [TEST_CORE] = "core", ? 4: ? ? ? ? [TEST_CPUS] = "processors", ? 5: ? ? ? ? [TEST_PLATFORM] = "platform", ? 6: ? ? ? ? [TEST_DEVICES] = "devices", ? 7: ? ? ? ? [TEST_FREEZER] = "freezer", ? 8: };
3.2.4 /sys/power/wakeup_count
該接口只和Sleep功能有關,因此由“CONFIG_PM_SLEEP”宏定義(kernel/power/Kconfig)控制。它的存在,是為了解決Sleep和Wakeup之間的同步問題。
我們知道,系統睡眠后,可以通過保留的Wakeup source喚醒系統。而在當今的CPU體系中,喚醒系統就是喚醒CPU,而喚醒CPU的唯一途徑,就是Wakeup source產生中斷(內核稱作Wakeup event)。而內核要保證在多種狀態下,Sleep/Wakeup的行為都能正常,如下:
▆ 系統處于sleep狀態時,產生了Wakeup event。此時應該直接喚醒系統。這一點沒有問題。
▆ 系統在進入sleep的過程中,產生了Wakeup event。此時應該放棄進入sleep。
這一點就不那么容易做到了。例如,當Wakeup event發生在“/sys/power/state”被寫之后、內核執行freeze操作之前。此時用戶空間程序依舊可以處理Wakeup event,或者只是部分處理。而內核卻以為該Event已經被處理,因此并不會放棄此次sleep動作。
這就會造成,Wakeup event發生后,用戶空間程序已經后悔了,不想睡了,但最終還是睡下去了。直到下一個Wakeup event到來。
為了解決上面的問題,內核提供wkaeup_count機制,配合“/sys/power/state”,以實現Sleep過程中的同步。該機制的操作行為如下:
▆ wakeup_count是內核用來保存當前wakeup event發生的計數。
▆ ?用戶空間程序在寫入state切換狀態之前,應先讀取wakeup_count并把獲得的count寫回給wakeup_count。
▆ 內核會比對寫回的count和當前的count是否一致,如果不一致,說明在讀取/寫回操作之間,產生了新的的wakeup event,內核就會返回錯誤。
▆ 用戶空間程序檢測到寫入錯誤之后,不能繼續后的動作,需要處理響應的event并伺機再次讀取/寫回wakeup_count。
▆ 如果內核比對一致,會記錄write wakeup_count成功時的event快照,后面繼續suspend動作時,會檢查是否和快照相符,如果不符,會終止suspend。
▆ 用戶空間程序檢測到寫入正確后,可以繼續對state的寫入,以便發起一次狀態切換。而此時是安全的。
3.2.6 /sys/power/image_size
該接口也是STD特有的。我們知道,STD的原理是將當前的運行上下文保存在系統的disk(如NAND Flash,如硬盤),然后選擇合適的方式關閉或重啟系統。保存上下文是需要存儲空間的,不光是disk中的存儲空間,也包括位于內存的用于交換或緩沖的空間。
而該接口,就是設置或者獲取當前內存中需要分配多少空間,用于緩沖需要寫入到disk的數據。單位為byte。
3.2.7 /sys/power/reserverd_size
reserverd_size用于指示預留多少內存空間,用于在->freeze() 和 ->freeze_noirq()過程中保存設備驅動分配的空間。以免在STD的過程中丟失。
3.2.8 /sys/power/resume
該接口也是STD特有的。正常情況下,在重新開機后,內核會在后期的初始化過程中,讀取保存在disk中的image,并恢復系統。而該接口,提供了一種在用戶空間手動的讀取image并恢復系統的方法。
通常情況下,該操作出現在系統正常運行的過程中,需要加載并執行另外的image。
3.2.9 debugfs/suspend_status
該接口是以debugfs的形式,向用戶空間提供suspend過程的統計信息,包括:成功的次數、失敗的次數、freeze失敗的次數等等。
3.2.10 /dev/snapshot
該接口也是STD特有的。它通過字符設備的形式,向用戶空間提供software的STD操作。我們會在后續的文章中詳細描述。