1. 程式人生 > >android /linux休眠與喚醒(二)

android /linux休眠與喚醒(二)

1. 當所有wake_lock被釋放,自動進入休眠;

2. echo mem > /sys/power/state;(也需要等待wake_lock全部釋放才能進入suspend);

2.2. 休眠主要步驟

1. 凍結使用者態程序、核心執行緒;

2. 呼叫註冊的裝置的suspend回撥,其順序就是按照註冊順序;

3. 休眠核心裝置、使cpu進入休眠狀態(或者關閉--supper standby)。

注:凍結程序 --- 就是核心把程序列表中的所有的程序狀態設定為停止,並且儲存其執行上下文,當這些程序別解凍後,他們是不知道自己被凍結過,程序繼續執行。

2.3. 相關的實現檔案

n linux/kernel/power/

核心實現的與平臺無關的休眠框架,休眠的入口處,負責凍結程序、裝置休眠、

在適當的地方回撥具體平臺實現的休眠函式。

n linux/arch/arm/mach-sunxi/pm/

平臺相關的休眠處理:負責現場保護,保證resume後,能夠恢復到休眠前的狀態。

n linux/driver/base/power/

裝置休眠實現,通過連結串列管理設備註冊的suspend/resume回撥函式。

2.4. 休眠喚醒流程概述

2.4.1. 休眠準備/凍結程序

當進入到suspend_prpare()函式後,核心為suspend分配一個虛擬終端來輸出資訊,然後廣播一個核心要進入suspend的notify,接著呼叫suspend_freeze_processes()函式凍結所有的程序。

2.4.2. 裝置休眠

經過上一步的處理,所有的程序(包括workqueuq/kthread)都已經停止了,此處呼叫suspend_devices_and_enter()將外設休眠,如果平臺定義了suspend_ops(我們的平臺是在arch/arm/mach-sunxi/pm下定義的),這裡就會呼叫suspend_ops->begin()。

裝置休眠是在driver/base/power/main.c中實現的,device_suspend()->dpm_suspend()呼叫,dpm_suspend()會依次呼叫驅動註冊的suspend()回撥來休眠外設。

接下來,會將非啟動的cpu關掉,此後只會有一個cpu在執行。

2.4.3. 系統進入休眠

呼叫suspend_enter(),這個函式會關掉arch irq,呼叫device_power_down(),該函式會呼叫suspend_late()函式,這個函式就是讓系統真正進入休眠;

呼叫平臺實現的suspend_ops->enter()來執行平臺自定義的休眠操作,我們平臺的

super_standby就是在此處實現的,程式碼也停止在suspend_ops->enter()。

2.4.4. 喚醒

如果有效的喚醒源發生,系統將退出休眠狀態,程式碼會重新開始執行,喚醒的順序和休眠的順序相反,所有系統裝置和匯流排會首先喚醒,使能中斷,使能休眠時停止的非啟動cpu,以及呼叫平臺相關的suspend_ops->finish()。

接著suspend_device_and_enter()也會繼續喚醒每個裝置,使能虛擬終端,最後呼叫

suspend_ops->end()。

從suspend_device_and_enter()返回到enter_state()中,此時外設已經喚醒,但是程序和任務都還在凍結狀態,這裡會呼叫suspend_finish()來解凍這些程序和任務,再發出notify來表示已經從suspend狀態退出,喚醒終端。

3. 程序凍結

3.1. 與凍結相關的標誌位

與程序凍結相關的3個標誌位:

n PF_NOFREEZE

-- 不可休眠的,對於user space的程序,這個bit都是0,即都是可休眠的;

n PF_FROZEN

-- 標誌著該程序已經進入凍結狀態;

n PF_FREEZE_SKIP

-- 如果設定了該bit,會跳過凍結處理。

這3個標誌位都是保留在task_struct->flags中,即每個程序都有自己獨有的凍結標誌。

如果程序的PF_NOFREEZE沒有設定,則在休眠的時候,這個程序就會被凍結掉,對於所有使用者態的程序,這個bit都是沒有設定的,即是說,休眠的時候,所有使用者程序都是會被凍結的;但是,有一些核心執行緒,這個bit也是沒有設定的,即需要凍結。

3.2. 程序凍結的實現

3.2.1. system_freezing_cnt變數

全域性變數,標誌著是否正在執行休眠流程;

凍結程序的第一步就是:atomic_inc(&system_freezing_cnt);

退出休眠的最後一步就是解凍程序:thaw_processes(),需要將system_freezing_cnt減1,

atomic_dec(&system_freezing_cnt);

3.2.2. try_to_freeze_tasks

參考Documentation/power/freezing-of-tasks.txt,try_to_freeze_tasks其實是:sends a fake signal to all user space processes,即通過向所有使用者程序發出一個欺騙訊號,通過訊號處理來實現程序凍結的;在try_to_freeze_tasks中其實並沒有將程序掛起或者說使其不再執行,僅僅是對程序發出了一個訊號,同時在訊號處理的開始首先檢查是否需要進入凍結狀態;把凍結程序交給訊號處理來做,try_to_freeze_tasks要做的就是設定一些標誌位來指示訊號處理要凍結它了,這樣該程序在返回使用者空間的時候就乖乖進入到凍結狀態了(使用者態的程序都是從核心態返回使用者態的時候凍結的);

程序凍結髮生時刻

所謂的凍結,就是程序進入一個迴圈,判斷是否在凍結狀態;如果需要處於凍結狀態,則將current->flags的PF_FROZEN置位。try_to_freeze()最終就是呼叫__refrigerator(),使當前程序進入這樣一個迴圈:

    // kernel/freezer.c  __refrigerator

    // 凍結後的程序,其實就是在這樣一個迴圈中,等到退出凍結狀態

    for (;;) {

        set_current_state(TASK_UNINTERRUPTIBLE);

        spin_lock_irq(&freezer_lock);

        current->flags |= PF_FROZEN;

        if (!freezing(current) ||

            (check_kthr_stop && kthread_should_stop()))   // 直到該條件不滿足

            current->flags &= ~PF_FROZEN;               // 清除PF_FROZEN標誌

        spin_unlock_irq(&freezer_lock);

        if (!(current->flags & PF_FROZEN))

            break;

        was_frozen = true;

        schedule();

}

所有的freezeable的程序,try_to_freeze()會執行__refrigerator()設定程序的PF_FROZEN標誌,程序狀態設定為TASK_UNINTERRUPTIBLE,迴圈直到需要退出凍結狀態(system_freezing_cnt為0),退出凍結狀態前先清除PF_FROZEN標誌;

3.2.3. 核心執行緒凍結

對於user space的程序,try_to_freeze()是在signal-handing core中被呼叫的;但是對於核心態的執行緒,需要在合適的地方,主動呼叫try_to_freeze(),或者,使用wait_event_freezable()或wait_event_freezable_timeout(),這兩個wait_event_freezable_*巨集都會檢查是否需要呼叫try_to_freeze(),舉例如下:

    // 核心執行緒預設是不休眠的,如果需要休眠,通過set_freezable修改PF_NOFREEZE

    set_freezable();  

    do {

        hub_events();

        wait_event_freezable(khubd_wait,   // 在合適的地方,檢查是否需要進休眠

                !list_empty(&hub_event_list) ||

                kthread_should_stop());

    } while (!kthread_should_stop() || !list_empty(&hub_event_list));

(from drivers/usb/core/hub.c::hub_thread()).

4. 裝置電源管理

4.1. device pm callback

從實現上看,裝置電源管理就是在系統狀態遷移的時候呼叫設備註冊的特定回撥函式,這些回撥函式統一封裝在一個數據結構中,struct dev_pm_ops:

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);

};

該資料結構會包含在struct device / struct device_driver裡面,由具體的device和driver進行初始化,休眠喚醒的時候,由pm core進行回撥。實際使用最多的就是suspend和resume介面。

對於裝置驅動,只需要實現相應的回撥函式即可支援電源管理;剩下的都由pm core負責完成。在suspend/resume過程中,pm core會依次呼叫:

-> prepare -> suspend -> suspend_late -> suspend_noirq 

-> wakeup

 -> resume_noirq -> resume_early -> resume -> complete

4.2. dev_pm_ops與驅動模型

struct class {

.....

const struct dev_pm_ops *pm;

.....

};

struct bus_type {

....

const struct dev_pm_ops *pm;

....

};

struct device_driver {

....

const struct dev_pm_ops *pm;

....

};

struct device_type {

....

const struct dev_pm_ops *pm;

....

};

裝置模型中的class/bus_type/device_driver/device_type結構體都包含了dev_pm_ops資料結構,在裝置的不同層次上,實現電源管理控制。

以i2c bus為例,在註冊i2c_bus_type的時候,也指定了電源管理的回撥dev_pm_ops為i2c_device_pm_ops:

   struct bus_type i2c_bus_type = {

    .name       = "i2c",

    .match      = i2c_device_match,

    .probe      = i2c_device_probe,

    .remove     = i2c_device_remove,

    .shutdown   = i2c_device_shutdown,

    .pm     = &i2c_device_pm_ops,

};

其中,i2c_device_pm_ops.suspend = i2c_device_pm_suspend,即i2c休眠的時候,最終呼叫到的就是i2c_device_pm_suspend這個回撥,i2c_device_pm_suspend的具體實現如下:

static int i2c_device_pm_suspend(struct device *dev)

{

    const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;

    if (pm)

        return pm_generic_suspend(dev);

    else

        return i2c_legacy_suspend(dev, PMSG_SUSPEND);

}

可以看到,i2c_device_pm_suspend函式只是判斷dev->driver->pm是否實現了,如果驅動的dev_pm_ops是有效的,則呼叫pm_gengric_suspend(dev)進行真正的裝置休眠。

從i2c_bus這個例程中也可以看到,驅動實現的時候,只需要正確填寫driver->pm上的回撥函式即可,具體在什麼時候回撥,pm core已經實現好了。

4.3. drivers/power/base

4.2簡單說介紹了一下,dev_pm_ops是如何嵌在device結構體上的;此處,將介紹核心是怎麼通過連結串列,對所有的裝置電源管理,程式碼實現在:drivers/power/base下。核心為裝置電源管理定義了5個連結串列,分別對應裝置休眠的不同階段。

相關的連結串列:

LIST_HEAD(dpm_list);

LIST_HEAD(dpm_prepared_list);

LIST_HEAD(dpm_suspended_list);

LIST_HEAD(dpm_late_early_list);

LIST_HEAD(dpm_noirq_list);

n dpm_list

在註冊裝置的時候,device->add() --> device_pm_add(),即對於每一個註冊到系統中的裝置,都會掛到dpm_list上;裝置解除安裝的時候,也會從dpm_list上移除掉;正常執行狀態下,裝置都是掛在dpm_list上的;

n dpm_prepared_list

休眠過程中,pm core呼叫dpm_prepare()後,各裝置從dpm_list遷移到dpm_prepared_list上;其實dpm_prepare()最終就是執行dev_pm_ops->prepare回撥;根據優先順序,獲得用於prepare的callback函式;由於裝置模型有bus、driver、device等多個層級,而prepare介面可能由任意一個層級實現;這裡的優先順序是:只要優先順序高的層級註冊了prepare,就會優先使用它,而不會使用優先順序低的prepare。優先順序為:dev->pm_domain->ops、dev->type->pm、dev->class->pm、dev->bus->pm、dev->driver->pm(這個優先順序同樣適用於其它callbacks);具體實現優先順序判斷的程式碼如下:

    device_lock(dev);

    dev->power.wakeup_path = device_may_wakeup(dev);

    // 按照優先順序順序,查詢有效的prepare()回撥函式

    if (dev->pm_domain) {

        info = "preparing power domain ";

        callback = dev->pm_domain->ops.prepare;

    } else if (dev->type && dev->type->pm) {

        info = "preparing type ";

        callback = dev->type->pm->prepare;

    } else if (dev->class && dev->class->pm) {

        info = "preparing class ";

        callback = dev->class->pm->prepare;

    } else if (dev->bus && dev->bus->pm) {

        info = "preparing bus ";

        callback = dev->bus->pm->prepare;

    }

    // 如果上面沒有得到prepare,則使用driver->pm->prepare回撥

    if (!callback && dev->driver && dev->driver->pm) {

        info = "preparing driver ";

        callback = dev->driver->pm->prepare;

    }

    // 執行回撥函式

    if (callback) {

        error = callback(dev);

        suspend_report_result(callback, error);

    }

    device_unlock(dev);

    return error;

n dpm_suspended_list

n dpm_late_early_list

n dpm_noirq_list

休眠喚醒連結串列遷移