Linux Power Managment
謹以此文紀念過往的歲月
一.前言
在這個對節能要求越來越嚴格的年代,對裝置的電源管理就顯的很重要的了,尤其對于可移動裝置,在電源有限的情況下,續航能力就顯的很重要的。在本文中将介紹linux是如何對裝置電源進行管理的。
二.睡眠
Linux的電源管理的主要幾個檔案集中在/kernel/power/main.c和/driver/base/power/main.c中。主要以platform裝置來看linux的睡眠和喚醒。
不過在看具體的代碼之前,需要了解suspend的幾個狀态,在linux中定義了兩種:
#define PM_SUSPEND_ON ((__force suspend_state_t) 0) suspend是否是打開的
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 1) suspend備用
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3) mem suspend
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
還有幾個跟具體平台相關的結構體:
struct platform_suspend_ops {
int (*valid)(suspend_state_t state); --判定平台是否支援該種睡眠狀态
int (*begin)(suspend_state_t state); --初始化一個給定的系統過渡到睡眠狀态
int (*prepare)(void); --睡眠前的準備
int (*enter)(suspend_state_t state); --真正的進入睡眠
void (*finish)(void); --當系統離開睡眠模式,在nonboot cpus正确後,恢複裝置之前調用。
void (*end)(void); --在正确恢複裝置之後調用,表明系統進入工作狀态或者在過渡到睡眠狀态時出現錯誤。
void (*recover)(void); --從一個挂起失敗中恢複系統
};
以s3c6410為例,在s3c6410_pm_init函數中調用suspend_set_ops來設定上述函數,不過該函數本質是将一個全局變量的suspend_ops設定為某一特定平台的platform_suspend_ops。
static struct platform_suspend_ops s3c6410_pm_ops = {
.enter = s3c6410_pm_enter,
.valid = suspend_valid_only_mem,
在s3c6410中僅僅實作了上面兩個函數并沒有其他函數。可以從suspend_valid_only_mem的函數名中知道,該平台僅僅支援SUSPEND_MEM。而enter将會在下面的源碼中涉及。
我們還是從一個函數開始神秘的linux電源管理。pm_suspend為核心提供了一個可見的睡眠函數,通過調用這個函數可以讓系統進入睡眠。我們以傳入的參數為PM_SUSPEND_MEM為例來開始我們的電源管理之旅,不過suspend涉及的内容太多,我們更加的關注裝置的電源管理。
int pm_suspend(suspend_state_t state)
{
if (state > PM_SUSPEND_ON && state <= PM_SUSPEND_MAX)
return enter_state(state);
return -EINVAL;
}
對enter_state進行一些删減就會發現沒有太多東西。
static int enter_state(suspend_state_t state)
if (!valid_state(state)) --檢測平台是否支援傳入的睡眠狀态。S3c6410僅支援mem狀态。
return -ENODEV;
if (!mutex_trylock(&pm_mutex)) --鎖定互斥鎖
return -EBUSY;
sys_sync(); --同步系統,将緩存中的資料回寫到塊裝置中
下面就将進入真正的suspend。
suspend_prepare();
suspend_devices_and_enter(state);
suspend_finish();
當程式執行到此就證明系統不但睡眠還被喚醒了。不要看從上面的code到這兒僅僅幾句話,但是不知道經過了多少歲月。我們将仔細的來看上面的三個函數。
mutex_unlock(&pm_mutex);
return 0;
對于下面的三個函數我更加的關注第二函數,沒有辦法啊,第一個和第三個函數我看不懂啊,其中主要是存儲和恢複使用者空間和程序。對于這個部分,确是不懂。飯還是慢慢來吃啊,一口吃不成一個胖子啊。
suspend_prepare();
suspend_devices_and_enter(state);
suspend_finish();
int suspend_devices_and_enter(suspend_state_t state)
int error, ftrace_save;
if (!suspend_ops)
return -ENOSYS;
if (suspend_ops->begin) { --如果平台支援begin的話,執行begin
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console(); --将console挂起,從這兒向下系統将不會列印資訊,調用printk将會将資訊儲存在一個緩沖區中。知道console被再次resume,系統才會恢複輸出。
ftrace_save = __ftrace_enabled_save(); --??????
error = device_suspend(PMSG_SUSPEND); --将裝置挂起
if (error) {
goto Recover_platform;
if (suspend_ops->prepare) { --睡前準備。
error = suspend_ops->prepare();
goto Resume_devices;
error = disable_nonboot_cpus(); --禁止所有的非引導CPU,在多核中有用,一個CPU就蛻化為0
if (!error && !suspend_test(TEST_CPUS))
suspend_enter(state); --進入睡眠
enable_nonboot_cpus();
Finish:
if (suspend_ops->finish)
suspend_ops->finish();
Resume_devices:
device_resume(PMSG_RESUME); --resume裝置
__ftrace_enabled_restore(ftrace_save);
resume_console(); --resume 終端輸出
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;
Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
那來看裝置挂起
int device_suspend(pm_message_t state)
int error;
might_sleep(); --????????
error = dpm_prepare(state); --準備挂起
if (!error)
error = dpm_suspend(state); --挂起裝置
其實我們真正關心的裝置挂起就在這裡,在這裡就會看到我們平時用的很多的platform平台中的suspend和resum。
static int dpm_prepare(pm_message_t state)
struct list_head list;
int error = 0;
INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
transition_started = true;
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.next);
get_device(dev);
dev->power.status = DPM_PREPARING;
mutex_unlock(&dpm_list_mtx);
error = prepare_device(dev, state);
mutex_lock(&dpm_list_mtx);
if (error) {
dev->power.status = DPM_ON;
if (error == -EAGAIN) {
put_device(dev);
continue;
}
put_device(dev);
break;
}
dev->power.status = DPM_SUSPENDING;
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &list);
put_device(dev);
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
上面的函數如果對裝置驅動沒有一個大概的了解的話,會看得一頭霧水的。那我就慢慢道來,其實也是一種知識的回顧。
list_empty(&dpm_list)學過linux的都知道這句是判定dpm_list為空,那dpm_list是什麼東西呢,這個連結清單上有什麼東西呢?不急,咱們還是以platform為例,來看這個連結清單上有什麼。從開辟一個platform裝置開始platform_device_alloc-> device_initialize-> device_pm_init
static inline void device_pm_init(struct device *dev)
dev->power.status = DPM_ON;
在開始的時候device的power.status會被設定為DPM_ON。
在platform_device_add中我們還要關注一個指派pdev->dev.bus = &platform_bus_type;裝置的總線類型為platform_bus_type,那我來看看這個總線裡有什麼。
struct bus_type platform_bus_type = {
.pm = PLATFORM_PM_OPS_PTR,
static struct pm_ext_ops platform_pm_ops = {
.base = {
.prepare = platform_pm_prepare,
.complete = platform_pm_complete,
.suspend = platform_pm_suspend,
.resume = platform_pm_resume,
.freeze = platform_pm_freeze,
.thaw = platform_pm_thaw,
.poweroff = platform_pm_poweroff,
.restore = platform_pm_restore,
},
.suspend_noirq = platform_pm_suspend_noirq,
.resume_noirq = platform_pm_resume_noirq,
.freeze_noirq = platform_pm_freeze_noirq,
.thaw_noirq = platform_pm_thaw_noirq,
.poweroff_noirq = platform_pm_poweroff_noirq,
.restore_noirq = platform_pm_restore_noirq,
#define PLATFORM_PM_OPS_PTR &platform_pm_ops
其實對于每一種總線都會有自己的的struct pm_ext_ops *pm;就是電源管理操作集。你看上面的操作集就定義了關于platform總線的pm管理。上面的device_pm_init僅僅将裝置的pm狀态改變,并沒有具體的操作将device加到dpm_list這個連結清單上。
platform_device_add->device_add->device_pm_add這個函數挺熟悉的,不過以前看platform的時候并沒有仔細的看這個函數。今天我們來看這個函數,所謂學習就是今與昔所學互相印證。
void device_pm_add(struct device *dev)
list_add_tail(&dev->power.entry, &dpm_list);
你看這個函數很簡單,對就是很簡單,一個簡簡單單的連結清單添加,就完成了。在linux中,你會發現有很多東西都是采用連結清單将東西串聯起來,利用連結清單将以裝置挂到一個個不同的連結清單中,實作一個裝置的不同的管理。
在device的結構體中有struct dev_pm_info power;這個成員就是用于表明該裝置目前的電源狀态。
enum dpm_state {
DPM_INVALID,
DPM_ON, --正常工作狀态
DPM_PREPARING, --device準備進行pm轉化
DPM_RESUMING, --裝置即将被喚醒
DPM_SUSPENDING, --裝置即将睡眠
DPM_OFF, --裝置此時無效
DPM_OFF_IRQ, --裝置處于深度睡眠
struct dev_pm_info {
pm_message_t power_state;
unsigned can_wakeup:1;
unsigned should_wakeup:1;
enum dpm_state status;
struct list_head entry; --這個成員名稱就暴露了其作用即是作為一個入口。就可以通過這個連結清單的節點逆流而上,找到其所屬的device
到此就應該了解了dpm_list在哪兒被添加了成員。
我們再次回到上面dpm_prepare的函數,繼續來看,為了友善從上面的函數中截取一段主要的來看。
while (!list_empty(&dpm_list)) {
prepare_device(dev, state);
上面的code就是從dpm_list這個連結清單上将一個一個裝置拆下來,進行prepare,添加到另外的一個list中。在所有的裝置都prepare好了後,直接将list和dpm_list合并就行了。還省了很多資源。
static int prepare_device(struct device *dev, pm_message_t state)
down(&dev->sem);
if (dev->bus && dev->bus->pm && dev->bus->pm->base.prepare) {
error = dev->bus->pm->base.prepare(dev);
goto End;
if (dev->type && dev->type->pm && dev->type->pm->prepare) {
error = dev->type->pm->prepare(dev);
if (dev->class && dev->class->pm && dev->class->pm->prepare) {
error = dev->class->pm->prepare(dev);
End:
up(&dev->sem);
上面的函數很清晰明了,就是調用該裝置所屬的bus,type以及class的prepare,當然前提是這些東東都存在的情況下。以一個通用的platform裝置為例,其bus->pm=platform_pm_ops,那很明顯其prepare為platform_pm_prepare,其實作如下:
static int platform_pm_prepare(struct device *dev)
struct device_driver *drv = dev->driver;
int ret = 0;
if (drv && drv->pm && drv->pm->prepare)
ret = drv->pm->prepare(dev);
return ret;
其還是調用了該裝置driver的prepare,當然還是其存在的情況下。
在裝置prepare完成後就應該是dpm_suspend了。其實作的原理與prepare類似,不講述了。
下面就來看suspend_enter這個函數。
static int suspend_enter(suspend_state_t state)
device_pm_lock();
arch_suspend_disable_irqs(); --禁止中斷
if ((error = device_power_down(PMSG_SUSPEND))) { --關閉裝置電源
goto Done;
error = suspend_ops->enter(state); --進入具體平台的suspend,在其中會使系統進入睡眠程式停止運作,直到系統再次運作才會執行下面的code
device_power_up(PMSG_RESUME); --恢複裝置電源
Done:
arch_suspend_enable_irqs(); --使能中斷
BUG_ON(irqs_disabled());
device_pm_unlock();
這個函數比較特别一點,函數說明是關閉特殊裝置,不知道做何意解。
int device_power_down(pm_message_t state)
struct device *dev;
list_for_each_entry_reverse(dev, &dpm_list, power.entry) {
error = suspend_device_noirq(dev, state);
break;
dev->power.status = DPM_OFF_IRQ;
error = sysdev_suspend(state);
if (error)
dpm_power_up(resume_event(state));
關閉某一個裝置,該函數運作于所有中斷被關閉僅有一個CPU在運作。
static int suspend_device_noirq(struct device *dev, pm_message_t state)
if (!dev->bus)
return 0;
if (dev->bus->pm) {
error = pm_noirq_op(dev, dev->bus->pm, state);
} else if (dev->bus->suspend_late) {
error = dev->bus->suspend_late(dev, state);
suspend_report_result(dev->bus->suspend_late, error);
static int pm_noirq_op(struct device *dev, struct pm_ext_ops *ops,pm_message_t state)
switch (state.event) {
case PM_EVENT_SUSPEND:
if (ops->suspend_noirq) {
error = ops->suspend_noirq(dev);
break;
case PM_EVENT_RESUME:
if (ops->resume_noirq) {
error = ops->resume_noirq(dev);
Break
Return err;
還以platform為例:
static int platform_pm_suspend_noirq(struct device *dev)
struct platform_driver *pdrv;
if (!dev->driver)
pdrv = to_platform_driver(dev->driver);
if (pdrv->pm) { --當pdrv->pm存在時
if (pdrv->pm->suspend_noirq)
ret = pdrv->pm->suspend_noirq(dev);
} else { --否則調用suspend_late
ret = platform_legacy_suspend_late(dev, PMSG_SUSPEND);
static int platform_legacy_suspend_late(struct device *dev, pm_message_t mesg)
struct platform_driver *drv = to_platform_driver(dev->driver);
struct platform_device *pdev;
pdev = container_of(dev, struct platform_device, dev);
if (dev->driver && drv->suspend_late)
ret = drv->suspend_late(pdev, mesg);
上面就是裝置的suspend,而裝置的喚醒則相反将前期禁止的中斷打開,使能所有的CPU,再次将所suspend的裝置resume。将suspend所做的一切的一切都恢複原樣。這裡就不描述了。各位看官對linux的電源管理應該有了一個總體的了解。
三.總結
對于Android的睡眠則是在其上添加wakelock,這個可以說是對linux的一點補充吧。以後有時間再看。
【新浪微網誌】 張昺華--sky
【twitter】 @sky2030_
【facebook】 張昺華 zhangbinghua
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利.