Linux Kernel and Android 休眠與喚醒

來源:互聯網
上載者:User
簡介

休眠/喚醒在嵌入式Linux中是非常重要的部分,嵌入式裝置儘可能的進入休眠狀 態來延長電池的續航時間.這篇文章就詳細介紹一下Linux中休眠/喚醒是如何工作 的, 還有Android中如何把這部分和Linux的機制聯絡起來的.

國際化
  • English Version: link
  • 中文版: link

作者: zhangjiejing <kzjeef#gmail.com>  Date: 2010-04-07, http://www.thinksrc.com

版本資訊
  • Linux Kernel: v2.6.28
  • Android: v2.0
對於休眠(suspend)的簡單介紹

在Linux中,休眠主要分三個主要的步驟:

  1. 凍結使用者態進程和核心態任務
  2. 調用註冊的裝置的suspend的回呼函數
    • 順序是按照註冊順序
  3. 休眠核心裝置和使CPU進入休眠態凍結進程是核心把進程列表中所有的進程的狀態都設定為停止,並且儲存下所有進程的上下文. 當這些進程被解凍的時候,他們是不知道自己被凍結過的,只是簡單的繼續執行.如何讓Linux進入休眠呢?使用者可以通過讀寫sys檔案/sys /power/state 是實現控制系統進入休眠. 比如
    # echo standby > /sys/power/state

    命令系統進入休眠. 也可以使用

    # cat /sys/power/state

    來得到核心支援哪幾種休眠方式.

Linux Suspend 的流程相關的檔案:

你可以通過訪問 Linux核心網站 來得到原始碼,下面是檔案的路徑:

  • linux_soruce/kernel/power/main.c
  • linux_source/kernel/arch/xxx/mach-xxx/pm.c
  • linux_source/driver/base/power/main.c

接下來讓我們詳細的看一下Linux是怎麼休眠/喚醒的. Let 's going to see how these happens.

使用者對於/sys/power/state 的讀寫會調用到 main.c中的state_store(), 使用者可以寫入 const char * const pm_state[] 中定義的字串, 比如"mem", "standby".

然後state_store()會調用enter_state(), 它首先會檢查一些狀態參數,然後同步檔案系統. 下面是代碼:

/** *      enter_state - Do common work of entering low-power state. *      @state:         pm_state structure for state we're entering. * *      Make sure we're the only ones trying to enter a sleep state. Fail *      if someone has beat us to it, since we don't want anything weird to *      happen when we wake up. *      Then, do the setup for suspend, enter the state, and cleaup (after *      we've woken up). */static int enter_state(suspend_state_t state){        int error;        if (!valid_state(state))                return -ENODEV;        if (!mutex_trylock(&pm_mutex))                return -EBUSY;        printk(KERN_INFO "PM: Syncing filesystems ... ");        sys_sync();        printk("done./n");        pr_debug("PM: Preparing system for %s sleep/n", pm_states[state]);        error = suspend_prepare();        if (error)                goto Unlock;        if (suspend_test(TEST_FREEZER))                goto Finish;        pr_debug("PM: Entering %s sleep/n", pm_states[state]);        error = suspend_devices_and_enter(state); Finish:        pr_debug("PM: Finishing wakeup./n");        suspend_finish(); Unlock:        mutex_unlock(&pm_mutex);        return error;}
準備, 凍結進程

當進入到suspend_prepare()中以後, 它會給suspend分配一個虛擬終端來輸出信 息, 然後廣播一個系統要進入suspend的Notify, 關閉掉使用者態的helper進程, 然 後一次調用suspend_freeze_processes()凍結所有的進程, 這裡會儲存所有進程 當前的狀態, 也許有一些進程會拒絕進入凍結狀態, 當有這樣的進程存在的時候, 會導致凍結失敗,此函數就會放棄凍結進程,並且解凍剛才凍結的所有進程.

/** *      suspend_prepare - Do prep work before entering low-power state. * *      This is common code that is called for each state that we're entering. *      Run suspend notifiers, allocate a console and stop all processes. */static int suspend_prepare(void){        int error;        unsigned int free_pages;        if (!suspend_ops || !suspend_ops->enter)                return -EPERM;        pm_prepare_console();        error = pm_notifier_call_chain(PM_SUSPEND_PREPARE);        if (error)                goto Finish;        error = usermodehelper_disable();        if (error)                goto Finish;        if (suspend_freeze_processes()) {                error = -EAGAIN;                goto Thaw;        }        free_pages = global_page_state(NR_FREE_PAGES);        if (free_pages < FREE_PAGE_NUMBER) {                pr_debug("PM: free some memory/n");                shrink_all_memory(FREE_PAGE_NUMBER - free_pages);                if (nr_free_pages() < FREE_PAGE_NUMBER) {                        error = -ENOMEM;                        printk(KERN_ERR "PM: No enough memory/n");                }        }        if (!error)                return 0; Thaw:        suspend_thaw_processes();        usermodehelper_enable(); Finish:        pm_notifier_call_chain(PM_POST_SUSPEND);        pm_restore_console();        return error;}
讓外設進入休眠

現在, 所有的進程(也包括workqueue/kthread) 都已經停止了, 核心態人物有 可能在停止的時候握有一些訊號量, 所以如果這時候在外設裡面去解鎖這個訊號 量有可能會發生死結, 所以在外設的suspend()函數裡面作lock/unlock鎖要非常 小心,這裡建議設計的時候就不要在suspend()裡面等待鎖. 而且因為suspend的時候,有一些Log是無法輸出的,所以一旦出現問題,非常難調試.

然後kernel在這裡會嘗試釋放一些記憶體.

最後會調用suspend_devices_and_enter()來把所有的外設休眠, 在這個函數中, 如果平台註冊了suspend_pos(通常是在板級定義中定義和註冊), 這裡就會調用 suspend_ops->begin(), 然後driver/base/power/main.c 中的 device_suspend()->dpm_suspend() 會被調用,他們會依次調用驅動的suspend() 回調來休眠掉所有的裝置.

當所有的裝置休眠以後, suspend_ops->prepare()會被調用, 這個函數通常會作 一些準備工作來讓板機進入休眠. 接下來Linux,在多核的CPU中的非啟動CPU會被關掉, 通過注釋看到是避免這些其他的CPU造成race condion,接下來的以後只有一個CPU在運行了.

suspend_ops 是板級的電源管理操作, 通常註冊在檔案 arch/xxx/mach-xxx/pm.c 中.

接下來, suspend_enter()會被調用, 這個函數會關閉arch irq, 調用 device_power_down(), 它會調用suspend_late()函數, 這個函數是系統真正進入 休眠最後調用的函數, 通常會在這個函數中作最後的檢查. 如果檢查沒問題, 接 下來休眠所有的系統裝置和匯流排, 並且調用 suspend_pos->enter() 來使CPU進入 省電狀態. 這時候,就已經休眠了.代碼的執行也就停在這裡了.

/** *      suspend_devices_and_enter - suspend devices and enter the desired system *                                  sleep state. *      @state:           state to enter */int suspend_devices_and_enter(suspend_state_t state){        int error, ftrace_save;        if (!suspend_ops)                return -ENOSYS;        if (suspend_ops->begin) {                error = suspend_ops->begin(state);                if (error)                        goto Close;        }        suspend_console();        ftrace_save = __ftrace_enabled_save();        suspend_test_start();        error = device_suspend(PMSG_SUSPEND);        if (error) {                printk(KERN_ERR "PM: Some devices failed to suspend/n");                goto Recover_platform;        }        suspend_test_finish("suspend devices");        if (suspend_test(TEST_DEVICES))                goto Recover_platform;        if (suspend_ops->prepare) {                error = suspend_ops->prepare();                if (error)                        goto Resume_devices;        }        if (suspend_test(TEST_PLATFORM))                goto Finish;        error = disable_nonboot_cpus();        if (!error && !suspend_test(TEST_CPUS))                suspend_enter(state);        enable_nonboot_cpus(); Finish:        if (suspend_ops->finish)                suspend_ops->finish(); Resume_devices:        suspend_test_start();        device_resume(PMSG_RESUME);        suspend_test_finish("resume devices");        __ftrace_enabled_restore(ftrace_save);        resume_console(); Close:        if (suspend_ops->end)                suspend_ops->end();        return error; Recover_platform:        if (suspend_ops->recover)                suspend_ops->recover();        goto Resume_devices;}
Resume

如果在休眠中系統被中斷或者其他事件喚醒, 接下來的代碼就會開始執行, 這個 喚醒的順序是和休眠的循序相反的,所以系統裝置和匯流排會首先喚醒,使能系統中 斷, 使能休眠時候停止掉的非啟動CPU, 以及調用suspend_ops->finish(), 而且 在suspend_devices_and_enter()函數中也會繼續喚醒每個裝置,使能虛擬終端, 最後調用 suspend_ops->end().

在返回到enter_state()函數中的, 當 suspend_devices_and_enter() 返回以後, 外設已經喚醒了, 但是進程和任務都還是凍結狀態, 這裡會調用suspend_finish()來解凍這些進程和任務, 而且發出Notify來表示系統已經從suspend狀態退出, 喚醒終端.

到這裡, 所有的休眠和喚醒就已經完畢了, 系統繼續運行了.

Android 休眠(suspend)

在一個打過android補丁的核心中, state_store()函數會走另外一條路,會進 入到request_suspend_state()中, 這個檔案在earlysuspend.c中. 這些功能都 是android系統加的, 後面會對earlysuspend和late resume 進行介紹.

涉及到的檔案:
  • linux_source/kernel/power/main.c
  • linux_source/kernel/power/earlysuspend.c
  • linux_source/kernel/power/wakelock.c
特性介紹Early Suspend

Early suspend 是android 引進的一種機制, 這種機制在上遊備受爭議,這裡 不做評論. 這個機製作用在關閉顯示的時候, 在這個時候, 一些和顯示有關的 裝置, 比如LCD背光, 比如重力感應器, 觸控螢幕, 這些裝置都會關掉, 但是系 統可能還是在運行狀態(這時候還有wake lock)進行任務的處理, 例如在掃描 SD卡上的檔案等. 在嵌入式裝置中, 背光是一個很大的電源消耗,所以 android會加入這樣一種機制.

Late Resume

Late Resume 是和suspend 配套的一種機制, 是在核心喚醒完畢開始執行的. 主要就是喚醒在Early Suspend的時候休眠的裝置.

Wake Lock

Wake Lock 在Android的電源管理系統中扮演一個核心的角色. Wake Lock是一種鎖的機制, 只要有人拿著這個鎖, 系統就無法進入休眠, 可以被使用者態程式和核心獲得. 這個鎖可以是有逾時的或者是沒有逾時的, 逾時的鎖會在時間過去以後自動解鎖. 如果沒有鎖了或者逾時了, 核心就會啟動休眠的那套機制來進入休眠.

Android Suspend

當使用者寫入mem 或者 standby到 /sys/power/state中的時候, state_store()會被調用, 然後Android會在這裡調用 request_suspend_state() 而標準的Linux會在這裡進入enter_state()這個函數. 如果請求的是休眠, 那麼early_suspend這個workqueue就會被調用,並且進入early_suspend狀態.

void request_suspend_state(suspend_state_t new_state){        unsigned long irqflags;        int old_sleep;        spin_lock_irqsave(&state_lock, irqflags);        old_sleep = state & SUSPEND_REQUESTED;        if (debug_mask & DEBUG_USER_STATE) {                struct timespec ts;                struct rtc_time tm;                getnstimeofday(&ts);                rtc_time_to_tm(ts.tv_sec, &tm);                pr_info("request_suspend_state: %s (%d->%d) at %lld "                        "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)/n",                        new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",                        requested_suspend_state, new_state,                        ktime_to_ns(ktime_get()),                        tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,                        tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);        }        if (!old_sleep && new_state != PM_SUSPEND_ON) {                state |= SUSPEND_REQUESTED;                queue_work(suspend_work_queue, &early_suspend_work);        } else if (old_sleep && new_state == PM_SUSPEND_ON) {                state &= ~SUSPEND_REQUESTED;                wake_lock(&main_wake_lock);                queue_work(suspend_work_queue, &late_resume_work);        }        requested_suspend_state = new_state;        spin_unlock_irqrestore(&state_lock, irqflags);}
Early Suspend

在early_suspend()函數中, 首先會檢查現在請求的狀態還是否是suspend, 來 防止suspend的請求會在這個時候取消掉(因為這個時候使用者進程還在運行),如 果需要退出, 就簡單的退出了. 如果沒有, 這個函數就會把early suspend中 註冊的一系列的回調都調用一次, 然後同步檔案系統, 然後放棄掉 main_wake_lock, 這個wake lock是一個沒有逾時的鎖,如果這個鎖不釋放, 那 麼系統就無法進入休眠.

   static void early_suspend(struct work_struct *work){        struct early_suspend *pos;        unsigned long irqflags;        int abort = 0;        mutex_lock(&early_suspend_lock);        spin_lock_irqsave(&state_lock, irqflags);        if (state == SUSPEND_REQUESTED)                state |= SUSPENDED;        else                abort = 1;        spin_unlock_irqrestore(&state_lock, irqflags);        if (abort) {                if (debug_mask & DEBUG_SUSPEND)                        pr_info("early_suspend: abort, state %d/n", state);                mutex_unlock(&early_suspend_lock);                goto abort;        }        if (debug_mask & DEBUG_SUSPEND)                pr_info("early_suspend: call handlers/n");        list_for_each_entry(pos, &early_suspend_handlers, link) {                if (pos->suspend != NULL)                        pos->suspend(pos);        }        mutex_unlock(&early_suspend_lock);        if (debug_mask & DEBUG_SUSPEND)                pr_info("early_suspend: sync/n");        sys_sync();abort:        spin_lock_irqsave(&state_lock, irqflags);        if (state == SUSPEND_REQUESTED_AND_SUSPENDED)                wake_unlock(&main_wake_lock);        spin_unlock_irqrestore(&state_lock, irqflags);}
Late Resume

當所有的喚醒已經結束以後, 使用者進程都已經開始運行了, 喚醒通常會是以下的幾種原因:

  • 來電

如果是來電, 那麼Modem會通過發送命令給rild來讓rild通知WindowManager有 來電響應,這樣就會遠程調用PowerManagerService來寫"on" 到 /sys/power/state 來執行late resume的裝置, 比如點亮螢幕等.

  • 使用者按鍵使用者按鍵事件會送到WindowManager中, WindowManager會處理這些 按鍵事件,按鍵分為幾種情況, 如果案例不是喚醒鍵(能夠喚醒系統的按鍵) 那麼WindowManager會主動放棄wakeLock來使系統進入再次休眠, 如果按鍵 是喚醒鍵,那麼WindowManger就會調用PowerManagerService中的介面來執行 Late Resume.
  • Late Resume 會依次喚醒前面調用了Early Suspend的裝置.
static void late_resume(struct work_struct *work){        struct early_suspend *pos;        unsigned long irqflags;        int abort = 0;        mutex_lock(&early_suspend_lock);        spin_lock_irqsave(&state_lock, irqflags);        if (state == SUSPENDED)                state &= ~SUSPENDED;        else                abort = 1;        spin_unlock_irqrestore(&state_lock, irqflags);        if (abort) {                if (debug_mask & DEBUG_SUSPEND)                        pr_info("late_resume: abort, state %d/n", state);                goto abort;        }        if (debug_mask & DEBUG_SUSPEND)                pr_info("late_resume: call handlers/n");        list_for_each_entry_reverse(pos, &early_suspend_handlers, link)                if (pos->resume != NULL)                        pos->resume(pos);        if (debug_mask & DEBUG_SUSPEND)                pr_info("late_resume: done/n");abort:        mutex_unlock(&early_suspend_lock);}
Wake Lock

我們接下來看一看wake lock的機制是怎麼運行和起作用的, 主要關注 wakelock.c檔案就可以了.

wake lock 有加鎖和解鎖兩種狀態, 加鎖的方式有兩種, 一種是永久的鎖住, 這樣的鎖除非顯示的放開, 是不會解鎖的, 所以這種鎖的使用是非常小心的. 第二種是逾時鎖, 這種鎖會鎖定系統喚醒一段時間, 如果這個時間過去了, 這個鎖會自動解除.

鎖有兩種類型:

  1. WAKE_LOCK_SUSPEND 這種鎖會防止系統進入睡眠
  2. WAKE_LOCK_IDLE 這種鎖不會影響系統的休眠, 作用我不是很清楚.

在wake lock中, 會有3個地方讓系統直接開始suspend(), 分別是:

  1. 在wake_unlock()中, 如果發現解鎖以後沒有任何其他的wake lock了, 就開始休眠
  2. 在定時器都到時間以後, 定時器的回呼函數會查看是否有其他的wake lock, 如果沒有, 就在這裡讓系統進入睡眠.
  3. 在wake_lock() 中, 對一個wake lock加鎖以後, 會再次檢查一下有沒有鎖, 我想這裡的檢查是沒有必要的, 更好的方法是使加鎖的這個操作原子化, 而 不是繁冗的檢查. 而且這樣的檢查也有可能漏掉.
Suspend

當wake_lock 運行 suspend()以後, 在wakelock.c的suspend()函數會被調用,這 個函數首先sync檔案系統,然後調用pm_suspend(request_suspend_state),接 下來pm_suspend()就會調用enter_state()來進入Linux的休眠流程..

      static void suspend(struct work_struct *work){        int ret;        int entry_event_num;        if (has_wake_lock(WAKE_LOCK_SUSPEND)) {                if (debug_mask & DEBUG_SUSPEND)                        pr_info("suspend: abort suspend/n");                return;        }        entry_event_num = current_event_num;        sys_sync();        if (debug_mask & DEBUG_SUSPEND)                pr_info("suspend: enter suspend/n");        ret = pm_suspend(requested_suspend_state);        if (current_event_num == entry_event_num) {                wake_lock_timeout(&unknown_wakeup, HZ / 2);        }}
Android於標準Linux休眠的區別

pm_suspend() 雖然會調用enter_state()來進入標準的Linux休眠流程,但是還 是有一些區別:

  • 當進入凍結進程的時候, android首先會檢查有沒有wake lock,如果沒有, 才會停止這些進程, 因為在開始suspend和凍結進程期間有可能有人申請了 wake lock,如果是這樣, 凍結進程會被中斷.
  • 在suspend_late()中, 會最後檢查一次有沒有wake lock, 這有可能是某種快速申請wake lock,並且快速釋放這個鎖的進程導致的,如果有這種情況, 這裡會返回錯誤, 整個suspend就會全部放棄.如果pm_suspend()成功了,LOG的輸出可以通過在kernel cmd裡面增加 "no_console_suspend" 來看到suspend和resume過程中的log輸出。
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.