linux-3.4 電源管理框架(1)
1. linux 中支援的電源管理
省電模式
'standby' (Power-On Suspend) 顯示屏斷電,主機通電 ================= 待機 'mem' (Suspend-to-RAM) 掛起到記憶體,需要外部中斷喚醒 ========= 待機 'disk' (Suspend-to-Disk) 掛起到硬碟,比掛起到記憶體恢復更加耗時 = 休眠
關機:計算機所有程式關閉,系統退出,關閉電源
休眠:計算機儲存現有軟體狀態,系統退出,關閉電源
待機:執行狀態儲存在記憶體當中。關閉 CPU、硬碟等,只對記憶體供電,不會關閉電源
linux 使用者空間操作示例:
# cat /sys/power/state (讀操作)
freeze standby disk === 表明該系統支援三種電源管理
# echo "freeze" > /sys/power/state (寫操作)
會導致電腦發生 suspend-to-ram
在 linux 核心裡面 /kernel/power/main.c
檔案中有 power_attr(state);
展開可得
static struct kobj_attribute state_attr = {
.attr = {
.name = __stringify(state), /* 轉換為字串屬性,即 #state */
.mode = 0644, /* 許可權設定為 0644, */
},
.show = state_show, /* 發生讀取操作時候呼叫到的函式 */
.store = state_store, /* 發生寫入操作時候呼叫到的函式 */
}
核心空間與使用者空間的對映關係
核心空間(internel) ——->使用者空間(externel)
核心物件(kernel objects) ——->目錄(directories)
物件屬性(object attributes) ——->普通檔案(regular files)
物件關係(object relationshiops) ——->符號連結(symbolic links)
static int __init pm_init(void)
{
int error = pm_start_workqueue();
if (error)
return error;
hibernate_image_size_init();
hibernate_reserved_size_init();
power_kobj = kobject_create_and_add("power", NULL); /* 建立一個叫 power 的 kobject(也就是目錄),隸屬於 sysfs 檔案系統 */
if (!power_kobj)
return -ENOMEM;
error = sysfs_create_group(power_kobj, &attr_group); /* 建立 power 目錄下面的屬性檔案組,也就是普通的檔案,這個在 attr_group 中定義 */
if (error)
return error;
return pm_autosleep_init();
}
# echo freeze > /sys/power/state (寫操作)
此條命令會導致的後果是 state_store 被呼叫,freeze 被作為引數傳入
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t n)
{
suspend_state_t state;
int error;
error = pm_autosleep_lock(); /* 獲取鎖 */
if (error)
return error;
if (pm_autosleep_state() > PM_SUSPEND_ON) { /* 判斷狀態,如果不是 ON 就說明裝置已進入某種省電模式 */
error = -EBUSY;
goto out;
}
state = decode_state(buf, n); /* 解析傳入的字串,轉換為狀態值 */
if (state < PM_SUSPEND_MAX)
error = pm_suspend(state); /* 進入 suspend 模式 */
else if (state == PM_SUSPEND_MAX)
error = hibernate();
else
error = -EINVAL;
out:
pm_autosleep_unlock();
return error ? error : n;
}
2. suspend 框架
裝置通知函式 pm_notifier_call_chain
在電源管理開始之後立馬被呼叫,該函式用於給指定物件傳送指定的通知,原型 int pm_notifier_call_chain(unsigned long val),它的引數就是通知的型別碼:
/* Hibernation 與 suspend 事件 */
#define PM_HIBERNATION_PREPARE 0x0001 /* 開始執行關機動作 */
#define PM_POST_HIBERNATION 0x0002 /* 關機完畢 */
#define PM_SUSPEND_PREPARE 0x0003 /* 開始對系統執行 suspend 操作 */
#define PM_POST_SUSPEND 0x0004 /* suspend 結束 */
#define PM_RESTORE_PREPARE 0x0005 /* 開始恢復儲存的映象 */
#define PM_POST_RESTORE 0x0006 /* 恢復完畢 */
pm_notifier_call_chain 函式的目的是:對 pm_chain_head 連結串列中的所有物件的 notifier_call 成員進行回撥
1、 連結串列的生成:
/* 向核心註冊一個 notifiter,該連結串列具有同步機制 */
int register_pm_notifier(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&pm_chain_head, nb);
}
2、 notifiter 結構體:
該結構體被註冊之後裡面的 notifier_call 函式指標會在 pm_notifier_call_chain 被呼叫的時候進行回撥,裡面可以根據不同的操作碼選擇相應的操作
struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block __rcu *next;
int priority;
};
3. 程序凍結函式 suspend_freeze_processes
目的:凍結使用者空間程序、核心空間執行緒
任務:每一個任務都有三個標誌位,PF_NOFREEZE
, PF_FROZEN
和 PF_FREEZER_SKIP
(輔助用)。應該在每一個程序的 task_pcb
模組裡面被設定,所有的使用者程序與部分核心執行緒都是 PF_NOFREEZE unset 狀態,此時可以正常的進入 suspend 或者 hibernate 模式。
實現過程
- freeze_processes() 函式最先被執行
- system_freezing_cnt 變數標識系統是否正在執行休眠過程,該變數在 freeze_processes() 函式中被設定
- try_to_freeze_tasks() 函式被執行,用來向所有的使用者程序傳送一個 fake signal,並且喚醒所有的核心執行緒
- 所有的可休眠任務都呼叫 try_to_freeze() 來進行迴應,最終導致 __refrigerator() 函式被呼叫,該函式設定任務的 PF_FROZEN 標誌,改變任務狀態為 TASK_UNINTERRUPTIBLE,使之迴圈直到 PF_FROZEN 標誌被清除
- freeze_kernel_threads() 在使用者程序被凍結之後被呼叫,用來凍結核心執行緒。使用者程序的 try_to_freeze() 函式在訊號處理碼被髮送之後自動的被呼叫,而核心執行緒則需要在適當的位置直接呼叫 try_to_freeze() 或者 wait_event_freezable()|wait_event_freezable_timeout() 巨集定義來實現 try_to_freeze 的功能。
4. 兩個重要的結構體
platform_suspend_ops 結構體,PM 核心層
1. 結構體原型
/**
* @valid: 判斷傳入的系統睡眠模式是否被該平臺所支援
* @begin: 為過渡到給定的系統睡眠狀態進行初始化,在掛起裝置之前被執行
* @prepare: 準備進入指定的模式,在裝置被掛起之後執行,也就是 .suspend() 方法被回撥之後,裝置驅動的 .suspend_late() 回撥方法之前
* @prepare_late: 結束準備,在 disabling nonboot CPUs 之前和裝置驅動的 .suspend_late() 回撥方法之後被執行.
* @enter: 進入指定的狀態,該回調函式強制要求被呼叫
* @wake: 系統離開睡眠模式時被執行(可選)。如果 @prepare_late() 生效,那麼該方法也必須生效
* @finish: 結束喚醒過程(可選)。如果 @prepare() 生效,該方法也就必須生效
* @suspend_again: 檢測系統是否需要再次掛起。如果該平臺在 suspend 過程中想輪詢感測器或者執行一些不需要藉助使用者空間和所有裝置的程式碼,可以在該方法裡面實現
* @end: 在恢復裝置時被 PM 核心呼叫,用來指明系統已經返回到活動狀態或者在進入睡眠過程中被取消(可選)。如果 @begin() 方法生效,該方法也應該被生效
* @recover: 在 suspend 失敗時恢復系統
*/
struct platform_suspend_ops {
int (*valid)(suspend_state_t state);
int (*begin)(suspend_state_t state);
int (*prepare)(void);
int (*prepare_late)(void);
int (*enter)(suspend_state_t state);
void (*wake)(void);
void (*finish)(void);
bool (*suspend_again)(void);
void (*end)(void);
void (*recover)(void);
};
2. 回撥過程
suspend 過程:@begin() -> @prepare() -> @prepare_late() -> @enter()
喚醒過程:@wake() -> @finish() -> @end()
dev_pm_ops 結構體,裝置驅動層
1. 結構體原型
/**
* 外部可見的狀態轉換依靠包含該結構體的回撥來處理,這個過程涉及了兩個層級的回撥(PM core 的子系統級和裝置驅動級)。首先,PM core 執行 PM domains, device types, classes 和 bus types 提供的回撥。它們都是子系統級的回撥(如果提供了的話)
*********
它們最後都應該執行由裝置驅動提供的回撥。比如 platform_bus_type,它的 pm 回撥函式最終還是會執行 device driver 提供的 pm 回撥函式,子系統級的回撥只是起一個跳轉作用
*********
* @prepare: 該回調函式的原則上是為了防止在裝置返回之後它的子裝置被註冊。 如果 @prepare() 檢測到無法處理的狀態 (如:子裝置已經在註冊過程中), 它可能會返回 -EAGAIN, 以便 PM core 可以再次操作它 (例如一個新的子裝置被註冊完畢) 從競態條件恢復.
* @complete: 撤銷 @prepare() 所做的改變,也就是與 @prepare 相反.
* @suspend: 在系統進入 sleep 狀態之前執行,也就是主記憶體上下文被保護之前
* @suspend_late: 接著 @suspend() 的操作.
* @resume: 在系統從 sleep 被喚醒時執行,也就是主記憶體上下文被保護之後。
===============================================================================
上面是 suspend 對應的回撥,下面是 hibernation 對應的回撥
===============================================================================
* @freeze: Hibernation 專屬, 在建立 hibernation 映象之前執行,類似於 suspend
* @thaw: Hibernation 專屬, 在建立 hibernation 映象之後或者建立失敗的情況下執行。
* @poweroff: Hibernation 專屬, 在儲存 hibernation 映象之後執行.
* @restore: Hibernation 專屬, 在從 hibernation 映象恢復主記憶體上下文之後執行, 類似 @resume().
* @suspend_noirq: 完成 suspend 動作。執行所有額外的掛起裝置所需要的操作,這些操作可能與它的驅動的中斷處理有競爭。
* @freeze_noirq: 完成 freeze 操作。執行所有額外的休眠裝置所需要的操作,這些操作可能與它的驅動的中斷處理有競爭。
* @thaw_noirq: 為 thaw 做準備
* @poweroff_noirq: 完成 poweroff 操作,類似於 @suspend_noirq()。
* @restore_noirq: 為 restore 操作做準備,類似於 @resume_noirq()。
*
* 檢視 Documentation/power/devices.txt 來獲得更多與以上回調相關的資訊。
*
===============================================================================
下面是與 runtime power management 模型有關的回撥
===============================================================================
* @runtime_suspend: 為 device 準備環境,在此環境下裝置不能與 CPU(s) 和 RAM 通訊。
* @runtime_resume: 使裝置進入全面啟用狀態以響應硬體或軟體產生的喚醒事件。
* wakeup event generated by hardware or at the request of software. If
* @runtime_idle: 裝置似乎處於非活動狀態,如果滿足必要條件,會被放入低功耗狀態。
*
* 檢視 Documentation/power/runtime_pm.txt 來了解 runtime 模型。
*
*/
struct dev_pm_ops {
int (*prepare)(struct device *dev);
void (*complete)(struct device *dev);
int (*suspend)(struct device *dev);
int (*resume)(struct device *dev);
int (*freeze)(struct device *dev);
int (*thaw)(struct device *dev);
int (*poweroff)(struct device *dev);
int (*restore)(struct device *dev);
int (*suspend_late)(struct device *dev);
int (*resume_early)(struct device *dev);
int (*freeze_late)(struct device *dev);
int (*thaw_early)(struct device *dev);
int (*poweroff_late)(struct device *dev);
int (*restore_early)(struct device *dev);
int (*suspend_noirq)(struct device *dev);
int (*resume_noirq)(struct device *dev);
int (*freeze_noirq)(struct device *dev);
int (*thaw_noirq)(struct device *dev);
int (*poweroff_noirq)(struct device *dev);
int (*restore_noirq)(struct device *dev);
int (*runtime_suspend)(struct device *dev);
int (*runtime_resume)(struct device *dev);
int (*runtime_idle)(struct device *dev);
};
2. 回撥過程
suspend 過程:@prepare() -> @suspend() -> @suspend_late() -> @suspend_noirq()
resume 過程:@resume_noirq() -> @resume_early() -> @resume() -> @complete()
回撥過程的思維導圖: