Try   HackMD

Linux 核心設計: Power Management(1): System Sleep model

BKK19-TR02 - Linux Kernel Power Management - 101 對 Linux Kernel 的 PM 框架做了 overview,推薦在開始本系列文章前預習一番。投影片可以參考此連結

引言

在 Linux 中,大部分程式碼都是屬驅動程式(Device Driver),而多數 Linux 電源管理(Power Management, PM) 的程式碼也是由驅動程式引導。因此,作為認識電源管理的起點,認識驅動程式使用何種模型和介面連接與系統交互,以完成電源管理的目標,是首要了解的課題。

在 Linux 中使用兩種模型來進行電源管理: System Sleep model 和 Runtime Power Management model。本篇將以第一種模型為主進行探討,而 Runtime Power Management model 會在下一章節進行分析。

Overview

睡眠(Sleep)是指系統整體性的低功耗狀態。在這種狀態下,userspace 將暫停執行,而核心的活動也將顯著減少。 Device、bus 和 class 這三大構成完整裝置驅動程式的要素,將協作以各自扮演對應角色,做好正確恢復的準備,完成降低耗能的目標。在 Linux 中支援數種的睡眠狀態,並且這些狀態也能對應到 ACPI 規範的 Global System States

Kernel Interface

裝置驅動程式層級在實作上是透過填入 struct dev_pm_ops 結構中的 callback 來實現 system sleep。更精準的說是除了 runtime_* 名稱以外的 callback 皆與 system sleep 有關。

struct dev_pm_ops 在一個裝置的 struct dev_pm_domainstruct bus_typestruct device_type 或者 struct class 中皆存在。Linux 系統的 PM 框架將利用子系統或者內部裝置的這些 dev_pm_ops,偕同彼此以進行完整的電源管理流程。留意到並非每種裝置驅動程式的實作介面皆需提供完整的 dev_pm_ops,部分裝置的驅動程式框架會基於 dev_pm_ops 提供更直接的應用介面。

由於裝置與裝置之間的連接或主從等關係,會讓系統在宏觀上有層次結構的概念,比如 PCI 裝置依賴於 PCI controller 的存在。System sleep 需按層次結構自下而上的順序走訪以 suspend 裝置,並反之自上而下 resume 他們,以確保次序的有效性。PM 框架也是依循此理解運作。

而 domain, bus, class, 和 device 間的上下關係也影響 PM 框架運行 struct dev_pm_ops 的次序。對應到 dev->pm_domain->ops, dev->bus->pm, dev->type->pm, dev->class->pmdev->driver->pm,實際的執行順序是:

  1. 如果 dev->pm_domain 存在,選擇執行 dev->pm_domain->ops 提供的 callback
  2. 否則,如果 dev->typedev->type->pm 都存在,選擇 dev->type->pm
  3. 否則,如果 dev->classdev->class->pm 都存在,dev->class->pm
  4. 否則,如果 dev->busdev->bus->pm 都存在,選擇 dev->bus->pm
  5. 如果子系統在以上的 pm 不存在,最後將嘗試裝置驅動程式提供的 dev->driver->pm(若存在)

留意到 PM domain、type、class 和 bus 的這些 callback 可能會再間接呼叫 dev->driver->pm 中的特定於裝置的 PM 方法。或者也可能使用裝置更高級的介面,例如 USB driver 僅需提供 suspend 和 resume 兩個 callbacks,子系統就可藉此實現 suspend、runtime suspend、hibernation 等電源管理行為。

System Sleep states

S0

嚴格來說此狀態非 sleep 的一種。在 S0 下處一般工作狀態,設備電源全開

S0ix: Suspend to Idle(S2I, S2Idle)

此狀態有許多別名,Intel 將其稱為 S0ix,而微軟則稱此為 Modern Stanby。其需要特定的硬體才能支援,是作為替代 S3 的睡眠狀態被設計出。在 S0ix 下可以達與 S3 相近的節能狀態,但減少喚醒所需的時間。

如果進入 S0ix 狀態,系統將會凍結 userspace、暫停計時並將所有 I/O 設備設定為低功耗狀態。這種方法可以帶來相對於 Runtime idle 更多能量耗損的減少,以便處理器可以花費系統暫停時處於最深度空閒狀態的時間。

延伸閱讀: 何謂新式待命模式 – Modern Standby?

在 S0ix 下,系統是透過 in-band 中斷進行喚醒的。所以理論上任何在正常工作狀態下能造成中斷的裝置都可以設定為 S2Idle 下的 wakeup source。

此狀態可以在不支援 standby 或 S2RAM 的平台上使用,也可以與任何更深層睡眠的狀態一起使用,以減少恢復延遲。此外,這機制只在 CONFIG_SUSPEND 被配置時啟用。

S1: Standby

也稱為 Power on Suspend。這狀態可以提供適度的節能,此時 CPU 的所有暫存器將被重置,並且停止執行指令。但由於 CPU 和記憶體的電源仍被維持,系統不會丟失 context,因此可以迅速的返回工作狀態。

具體實現上,和 S2I 類似,Standby 也會凍結用戶空間、暫停計時並將所有 I/O 裝置置於低功耗狀態。但除此之外還會將非啟動(nonboot) CPU 關閉。並且在進入此狀態期間,所有 low-level system function 也都會暫停進入 suspend(?)。因此,相對於 S0ix,這應該可以節省更多的能量,但恢復延遲也理所當然會比其更長。

相對於 S2I,在 S1 可以將系統從此狀態喚醒的裝置集合通常會較少,並且可能需要依賴平台來適當設定 wakeup 功能。

Standby 的支持需要設定 CONFIG_SUSPEND 並且平台向 suspend 子系統註冊之,才表示支援此狀態。

S2(?)

CPU powered off. Dirty cache is flushed to RAM.

S3: Suspend to RAM(STR, S2RAM)

在此狀態下,除記憶體外的單元都會進入低功耗狀態。記憶體會被設置成 self refresh mode 模式以保持其內容。也因此此狀態可顯著節省能源。

實現上,進入 standby 時執行的所有步驟也會在 S2RAM 時執行,且根據平台差異可能還會發生其他操作。尤其是在基於 ACPI 的系統上,kernel 會將控制權轉交給 platform firmware(BIOS)。且 S2RAM 的最後一步通常會關閉一些不直接由 kernel 控制的低階元件。

裝置和 CPU 雖然關閉,但其狀態應被保存在未斷電的記憶體中。在許多情況下,所有周邊匯流排在進入 S2RAM 時都會斷電,因此裝置必須能夠處理從 S2RAM 返回到開啟狀態的轉換。

在基於 ACPI 的系統上,S2RAM 需要 platform firmware 中的一些 bootstrap 程式碼才能恢復。在其他平台上也可能發生這種情況。此外,恢復方式相對於 S2I 和 standby,可以將系統從 S2RAM 喚醒的裝置集合通常會更少,甚至可能需要依賴平台支援來適當設定的喚醒功能。

如果設定了 CONFIG_SUSPEND 並且平台向子系統註冊了對 S2RAM 才支援。在基於 ACPI 的系統上,它會對應到 ACPI 定義的 S3 系統狀態。

S4: Hibernation

這種狀態也別稱為 Suspend-to-Disk(STD),其提供了最大的節能效果,且即使在沒有支援 suspend 功能的低階平台下也可以使用。但是,它需要一些低階的程式碼來恢復。

休眠與其他 sleep 有顯著不同。需要三次系統狀態變更才能進入休眠狀態,並需要兩次系統狀態變更才能從中恢復。

首先,當休眠被觸發時,核心停止所有系統活動,並建立 system image 在記憶體上。接下來,系統會暫時復原 I/O 以將 image 保存到永久性儲存的裝置,之後系統再進入目標的低功耗狀態,其中包括記憶體的幾乎所有硬體組件的電源都將被切斷,除了一組有限的 wakeup source。一旦 system image 被儲存,系統可能會進入特殊的低功耗狀態(如 ACPI S4),或者只是將自己斷電。

在試圖喚醒時,控制權會先在 platform firmwire 的 bootloader 上,其會引導一個稱為 restore kernel 的特殊 kernel 之啟動。後者將在永久性存儲中查找之前保存的 image,如果找到,則將其加載到記憶體中。接下來,系統中的所有活動會如休眠的第一步般停止,此時 restore kernel 使用 image 內容覆蓋自身,並轉移控制權到 image kernel 中的一個特殊的 trampoline。最終,image kernel 會將系統恢復到休眠前的狀態,以允許 userspace 再次運行。

如果設定了 CONFIG_HIBERNATION 則支援休眠。注意到只有在特定 CPU 架構支援相關系統復原的低階程式碼下,才能設定之。

S5: Shutdown

No previous content is retained. Some components may remain powered so the computer can "wake" on input from the keyboard, clock, modem, LAN, or USB device.

PM sysfs 介面

Linux 中的電源管理機制為 userspace 提供 sysfs 介面,以允許控制系統的睡眠狀態。此介面位於 /sys/power/ 目錄中,並由數個屬性(檔案)組成,以下我們將逐個探討。

state

在這個檔案中包含所支援的睡眠狀態相關的字串清單。我們可以對這個檔案寫入清單中的任一字串,以導致 kernel 將系統轉換到對應的睡眠狀態。

舉例來說,"disk"、"freeze" 和 "standby" 分別表示 Hibernation、S2I 和 Standby 睡眠狀態。寫入 "mem" 字串則會根據稍後會提到的 mem_sleep 檔案的內容進行睡眠。

$ sudo cat /sys/power/state
freeze mem disk

一些細節可見 decode_state 之實作方式

如果系統不支援任何的睡眠狀態,此檔案則不會在系統中存在。

mem_sleep

這個檔案中包含支援的不同類型 suspend 機制的字串清單。我們可以對此檔案寫入清單中的任一字串,以允許 userspace 選擇對上述的 state 檔案寫入 "mem" 時將對應的睡眠方式。

舉例來說,"s2idle"、"shallow" 和 "deep" 分別對應 suspend to idle、standby 和 suspend to RAM。

$ sudo cat /sys/power/mem_sleep
s2idle [deep]

[] 框起來的字串表示當前的選擇

如果系統不支援任何的 suspend 機制,此檔案則不會在系統中存在。

disk

這個檔案控制 Hibernation/Suspend-to-Disk 的運作方式。具體來說,它告訴核心在建立完 hibernation image 後要做什麼。也就是對 state 寫入 "disk" 字串之後的行為。

$ sudo cat /sys/power/disk
[platform] shutdown reboot suspend test_resume
  • platform: 這選項會將系統置於特殊的低功耗狀態(例如 ACPI S4),以允許特定喚醒方式可用,並可能允許 platform firmware 在喚醒後採用簡化的初始化方式。這只有在對應平台支援的情況下可選
  • shutdown: 將系統直接 poweroff
  • reboot: 重新開機(主要用在診斷系統)
  • suspend: 結合 suspend,將系統置於上述 mem_sleep 檔案所選擇的狀態。如果系統成功從該狀態喚醒,則捨棄 hibernation image 並繼續。否則,使用該 image 來恢復系統狀態
  • test_resume: 此方式用以診斷。行為上會好像系統剛從休眠狀態中醒來一樣的載入 image,運行 restore kernel 然後進行完整的 restore。

如果系統不支援任何的 hibernation 機制,此檔案則不會在系統中存在。

image_size

這個檔案會控制 hibernation image 的大小。可以對其寫入一個非負整數的數字字串,則進行 hibernation 時,系統會盡最大可能確保 image 大小不會超過之(以 bytes 為單位)。

但注意如果無法實現指定大小,仍然會建立 hybernation image,這個檔案只是要求盡可能達到指定數字。換言之,向該檔案寫入 0 會導致 hiberanation image 的大小達到最小。

讀取該檔案會得到目前的設定值,其預設為可用 RAM 大小的 2/5 左右。

pm_trace

此檔案控制名為「PM trace」的機制。簡單來說是會將最後一次 suspend 或 resume event point 保存在 RTC memory 中,且該資訊可以在 reboot 後取得。這機制有助於更有效地 debug 由於 suspend 或 resume 期間發生的 device driver 故障而導致的系統鎖定或重新啟動。

該檔案內容預設為 0,可以向其中寫入非零整數的字串將其變更為1。則如果檔案內容為 1,每個 suspend/resume 事件點的足跡將覆蓋實際的 RTC 資訊,依次儲存在 RTC memory 中,因此如果儲存後立即發生系統崩潰,資訊可以保留下來並用於除錯。

wakeup

額外補充: 不同於上述可見的 sysfs 節點是對於整個 PM 子系統的控制方式。每個由 PM 子系統管理的裝置可以有各異的 PM 功能支援,可由 /sys/devices/.../power/ 路徑下的檔案取得相關資訊或更改配置。

有些裝置可以被用來觸發喚醒系統的事件,使系統退出睡眠狀態。例如透過對鍵盤輸入或者按下開機按鍵,讓系統從睡眠中恢復。與此功能相關的檔案是 /sys/devices/.../power/wakeup

struct dev_pm_info {
	unsigned int		can_wakeup:1;
	...
	struct wakeup_source	*wakeup;

要在 Linux 核心中使裝置能被用來喚醒系統的睡眠狀態,驅動程式需先使用 device_set_wakeup_capable() 來設置裝置對應的 struct device 下的 power.can_wakeup 欄位。這表示裝置首先在物理上可支援此功能。

power.wakeup 則是一個 struct wakeup_source 的指標,定義了當將裝置作為喚醒系統的觸發來源時,如何把喚醒事件通知到 PM core 的程式碼實作,使後者能夠將整個系統都恢復到運作狀態。

當裝置在硬體上能夠支援系統喚醒,且在核心中也提供 power.wakeup 的實作並由 device_set_wakeup_capable 啟用,在 userspace 的/sys/devices/.../power/ 路徑下就可以看到 wakeup 檔案。在 userspace 可以向其中寫入 "enabled" 或者 "disabled" 來決定是否要將該裝置作為喚醒訊號的提供者。

留意到系統的喚醒與 runtime PM 時談論的 remote wakeup 不同。雖然有些情況兩者在物理上是透過一樣的機制,但 Remote wakeup 主要是指在低功耗狀態的裝置可以觸發特定中斷,以通知系統應將其置於全速狀態的功能。但這些中斷在系統進入睡眠時不一定能夠觸發。

小結

  • 根據上述,我們知道有兩種方法可以使系統進入 S2I。第一個是將 "freeze" 直接寫入 /sys/power/state。第二種是將 "s2idle" 寫入 /sys/power/mem_sleep,然後再將 "mem" 寫入 /sys/power/state
  • 同樣的,有兩種方法可以使系統進入 standby。一種是對 state 寫入 "standby",另一種是將 "shallow" 寫入 mem_sleep,然後再對 state 寫 "mem"
  • 只有一種方法可以讓系統進入 S2RAM: 將 "deep" 寫入 /sys/power/mem_sleep,然後將 "mem" 寫入 /sys/power/state

Asynchronous Suspend/Resume

System Suspend 流程

關於在 Linux 中如何運用 dev_pm_ops 中的 callback 來達到 PM 的管理,以及每個 callback 實作上需注意的細節可參考以下幾個章節。

進入 System Suspend

Entering System Suspend

1. prepare

首先使用的 callback 是 prepare。該 callback 的作用是防止 suspend 開始前,有裝置正好要進行註冊造成的競爭問題。與其他 suspend callback 的方向不同,prepare 是從裝置樹的頂端開始向底進行。

prepare() 完成並返回,對應裝置下不能就不能再被註冊新的子設備。prepare() 中也可以為即將到來的系統電源轉換做相應準備,但此時還不能將裝置置於低功耗狀態。

此外,如果裝置支援 runtime PM,則 prepare() 中不得對相應狀態做調整。但 prepare 的返回值(返回正值)可用於向 PM core 提示它可以安全地將裝置留在 runtime suspend,而不需要先將其喚醒然後再重新 suspend 一遍。前提是該裝置的所有子裝置也都處於 runtime suspend。

更準確的說,如果裝置和其子代的 prepare() 都返回正值且都在 runtime suspend 的狀態, PM core 就會跳過 suspend, suspend_latesuspend_noirq 階段,以及後續對應的 resume 中的步驟。這個過程在術語上被命名為 direct-complete

我們可以設置 DPM_FLAG_NO_DIRECT_COMPLETEDPM_FLAG_SMART_PREPARE 來要求不同的 prepare 方式。這些內容正好在 PCI/PCIe(2): 電源管理 - Flags 章節整理過,這裡就不多花心思說明。

2. suspend

suspend() 中的主要可能的行為是使裝置停止所執行的 I/O、保存必要的裝置暫存器值、然後將其置於適當的低功耗狀態。並且這裡也可以啟用裝置的喚醒事件。

3. suspend_late

對於許多裝置來說,suspend 要細分為「靜止裝置」和「保存裝置狀態」兩個階段,然而保存裝置狀態卻又不得不在開啟裝置中斷的狀態下達成。因此核心提供的 callback suspendsuspend_late 可用來區分兩者,同時避免依賴中斷的問題。

此外,在 suspend_late 可以確保相關裝置停用 runtime PM,因此有需在此前提下進行的操作,可於此 callback 下實作。

4. suspend_noirq

suspend_noirq 發生在裝置的 IRQ handler 被停用之後。因此在 suspend_noirq 應該保存先前未儲存的裝置暫存器,最終將裝置置於適當的低功耗狀態(如果在 suspend()/suspend_late() 時還未做)。

多數的裝置是不需要實作此 callback 的。其存在的理由主要來自不同裝置共用 interrupt vector 的狀況。例如 PCI,如果沒有區別 suspend_noirq,在 PCI 裝置 A 被置於低功耗時可能出現 PCI 裝置 B 的中斷,此時由於共享 interrupt handler,導致競爭而產生錯誤。

驅動程式在執行完上述的一系列 PM callback 後,它就應該停止所有 I/O 工作,包含 DMA 與 IRQ 等。並保存所有必要的硬體狀態,以在未來可以正確恢復到先前的設置。然後就可以將對應裝置置於低功耗模式。

在 suspend 的設計上還要注意 device_may_wakeup() 的回傳結果。若回傳 true,這表示裝置在進入低功號模式以前還要設定好硬體,以便後者可以產生喚醒的訊號。例如 GPIO 訊號或者 PCI 的 PME 等。

如果執行 callback 的過程中,其中的任何一個的回傳值為錯誤代碼,系統將不會進入低功耗狀態。而是回朔之前的所有 suspend 操作,以確保系統重新恢復至 active。

退出 System Suspend

Leaving System Suspend

1. resume_noirq

從 suspend 狀態中恢復首先執行 resume_noirq,顧名思義是處理需要在中斷被開啟前進行的操作,通常就是撤銷 suspend_noirq 階段的行為。如 PCI 一類允許 bus 上的裝置共用 interrupt vector 的狀況下,則在此 callback 需使驅動程式處於能夠識別中斷來源是哪個裝置的狀態,並讓其得以正確處理之。

2. resume_early

resume_early 涉及 suspend_late 階段操作的撤銷。

3. resume

resume 會使裝置恢復到 active 狀態,使其可以恢復正常的 I/O 執行。通常就是撤銷 suspend 階段的操作。

4. complete

complete 撤銷 prepare 階段的操作。留意到與其他退出 System suspend 的階段不同,在 complete 時,裝置是從樹的底下開始往根進行的。

留意如果前面的 ->prepare callback 是返回正數值,這代表當初裝置在 suspend 過程中都是保持在 runtime suspend 狀態的,沒有先將其喚醒然後再重新 suspend 過。換句話說,其 ->suspend->suspend_late->suspend_noirq->resume_noirq、 -> resume_early->resume 都被跳過。在這種情況下,->complete 全權負責在退出 System suspend 後將設備調整到預期狀態的任務。

在以上階段結束後,驅動程式的功能理應恢復到 suspend 之前。可以使用 DMA 和 IRQ 執行 I/O。不過細節上因平台的不同會存在差異。例如某些系統支援多個階段的運行狀態,而 resume 結束時的狀態可能與 suspend 開始時的不一樣。

System Hibernation 流程

進入 Hibernation

Entering Hibernation

相較於 system suspend,使系統進入 Hibernation 更複雜,因為它涉及 system image 的建立和保存。因此,休眠有更多階段,而這些階段始終在任務被 freeze 並釋放足夠的記憶體後運行。

1. prepare

Hibernation 始於 prepare,這部分與 System Suspend 相同。

2. freeze

->freeze 方法應該會暫停裝置,以便它不會產生 IRQ 或 DMA,並且如果需要,保存相關暫存器的值。但關鍵不同是裝置在這階段不必處於低功耗狀態。此外,也不準備好產生任何喚醒事件。

3. freeze_late

freeze_late 近似於 suspend_late。但同理裝置在這階段不必處於低功耗狀態,也不準備好產生任何喚醒事件。

4. freeze_noirq

freeze_noirq 近似於 suspend_noirq。但在這階段不必處於低功耗狀態,也不準備好產生任何喚醒事件。

在經過上述四個階段後,PM core 應已將 system image 建立完成。所有裝置這時都應處於非活動狀態,且記憶體內容應保持不受影響,以便 system image 形同系統此時狀態的 snapshot。

5. thaw_noirq

thaw_noirq 類似於前面的 resume_noirq。主要差異在於其假設裝置處於與 freeze_noirq 階段結束時相同的狀態。

6. thaw_early

thaw_early 類似於上述的 resume_early 階段。如有必要,應撤銷前面的 freeze_late 操作。

7. thaw

thaw 類似 resume。其應使裝置返回到 active 狀態,以便在可以進行 system image 的保存。

8. complete

complete 與此前在退出 System suspend 所述相同。

此時 system image 已被儲存,然後裝置即可以為即將到來的系統休眠做好準備。

9. prepare

再次執行 prepare,與之前描述相同

10. poweroff

poweroff 類似 suspend

11. poweroff_late

poweroff_late 階段類似 suspend_late 階段。

12. poweroff_noirq

poweroff_noirq 階段與 suspend_noirq 階段類似。

總結來說,->poweroff->poweroff_late->poweroff_noirq 應該分別與 ->suspend->suspend_late->suspend_noirq 執行大致相同的操作。主要的差異是它們不需要儲存裝置暫存器值,因為這些都應該已經在 freezefreeze_latefreeze_noirq 保存好。此外,在許多機器上,韌體會關閉整個系統的電源,因此callback 此時無需特別將裝置置於低功耗狀態。

退出 Hibernation

Leaving Hibernation

從 Hibernation 狀態恢復(通常以 Restore 稱之)同樣比起 system resume 更複雜,因為它需要將 system image 載入到記憶體中以恢復 Hibernation 前的記憶體內容。

理想上,似乎可以由 bootloader 將 image 載入到記憶體中,恢復休眠前的記憶體內容。但實際上這是不可能完成的,因為 不具能對 bootloader 傳遞這些必要的資訊的方式。

因此,這裡的作法是先讓 bootloader 將一個特殊的 kernel,通稱為 "Restore Kernel" 載入到記憶體中,並以通常的方式將控制權轉移給它。然後 Restore Kernel 負責讀取 system image 來恢復休眠前的記憶體內容,然後再將控制權交給這個 image kernel。

因此,這裡可以看到 restore 涉及兩個不同的 kernel。而 restore kerel 可能與 image kernel 完全不同: 不同的 config,甚至是不同的版本。

為了能夠將 image kernel 載入到記憶體中,restore kernel 需要至少使其能夠存取 image kernel 所在的儲存裝置的驅動程式。載入 system image 後,restore kernel 就可以準備將控制權轉移 image kernel。要進行的步驟與建立 system image 時相似:

  1. prepare
  2. freeze
  3. freeze_noirq

但注意到只有 restore kernel 涵蓋的驅動程式執行這些步驟。 其他裝置仍將處於 bootloader 所設置狀態。

如果記憶體內容的恢復失敗,restore 將使用:

  1. thaw_noirq
  2. thaw_early
  3. thaw
  4. complete

然後繼續運作,但這是相對少發生的情況。

大多數情況下,記憶體內容成功恢復,控制權轉移到 image kernel,然後 image kernel 接手後續的恢復任務,讓所有裝置復原到休眠前的功能。步驟是:

  1. restore_noirq: 類似 resume_noirq
  2. restore_early: 類似 resume_early
  3. restore: 類似 resume
  4. complete

對比 resume_early/resume_noirqrestore_early/resume_noirq 的差異是,後者假設裝置已被 bootload 或 restoer kenel 存取或重新設定。因此,裝置的狀態可能與從 freeze 系列步驟記住的狀態不同。例如可能需要重新初始化。不過也有許多狀況下兩者的流程是沒有不同的。

PM Notifier

PM Notifier 的用途

雖然 Linux 中的電源管理定義多種類型的 callback,以統一化的方式,提供讓系統中的各個模組實作進入省電狀態的方法。然而這些 callback 對於某些裝置或子系統可能發生得太晚或太早。比方說,若希望在 suspend 或 hibernation 之前或恢復之後執行一些操作,但條件是要求 userspace 是非凍結的狀態,此時 ->prepare()->complete() 皆無法滿足此前提。

以實際的例子來說,裝置驅動程式可能希望在 resume 或 restore 時將韌體上傳到其裝置中,但即使是最後階段的 ->complete() 都不適於呼叫 request_firmware() 來完成此操作,因為這時 userspace 的 process 仍是被凍結住的。解決方案可能是在凍結之前將韌體載入到記憶體中,並在 ->resume() 中再從記憶體中上傳韌體。

為此情況,裝置驅動程式可以註冊 power management notifier(PM Notifier),這些讓一段特定程序可以在任務凍結之前和解凍之後呼叫。

註冊 PM Notifier

註冊和取消註冊 PM notifier 分別需使用 register_pm_notifier()unregister_pm_notifier()。如果取消註冊,也可以直接使用 pm_notifier() 這個 macro 來進行註冊。

Notifier 的 prototype 如下,其中 mode 表示著此 notifer 的類型,可以是以下幾種:

int func(struct notifier_block *notifier,
     unsigned long mode, void *arg)
  • PM_HIBERNATION_PREPARE: 接收到此 event 表示系統將進入休眠,因此任務將凍結
  • PM_POST_HIBERNATION: 系統記憶體狀態已從 hibernate image 恢復或進入休眠期間發生錯誤,因此執行了 restore callback,任務已解凍
  • PM_RESTORE_PREPARE: 系統將恢復 hibernate image。如果順利,image kernel 將發出 PM_POST_HIBERNATION 通知
  • PM_POST_RESTORE: 從休眠狀態 restore 發生錯誤,之後任務被解凍。
  • PM_SUSPEND_PREPARE: 接收到此 event 表示系統將進入 suspend,因此任務將凍結
  • PM_POST_SUSPEND: 系統剛剛 resume 或 suspend 期間發生錯誤。已經執行 resume callback,任務已解凍

Reference