Android睡眠喚醒機制–Kernel態

來源:互聯網
上載者:User

一、簡介

      Android系統中定義了幾種低功耗狀態:earlysuspend、suspend、hibernation.
      1) earlysuspend: 是一種低功耗的狀態,某些裝置可以選擇進入某種功耗較低的狀態,比如 LCD可以降低亮度或滅掉;
      2) suspend: 是指除電源管理以外的其他外圍模組以及cpu均不工作,只有記憶體保持自重新整理的狀態; 
      3) hibernation是指所有記憶體鏡像都被寫入磁碟中,然後系統關機,恢複後系統將能恢複到“關機”之前的狀態。是最徹底的低功耗模式,它把所有記憶體鏡像都寫入磁碟中,然後系統關機。該檔案還在sysfs檔案系統中建立了多個entry,分別是/sys/power/disk,/sys/power/resume和/sys/power/image_size,這樣使用者可以直接通過 sysfs 來控制系統進出hibernation狀態。這塊代碼跟標準Linux核心沒有什麼區別。
     

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

二、使用者介面

      電源管理核心層給應用程式層提供的介面就是sysfs 檔案系統,所有的相關介面都通過sysfs實現。Android上層frameworks也是基於sysfs做了封裝,最終提供給Android java應用程式的是java類的形式。

Android系統會在sysfs裡面建立以entry:
     /sys/power/state
     /sys/power/wake_lock
     /sys/power/wake_unlock

     echo mem > /sys/power/state或echo standby > /sys/power/state: 命令系統進入earlysuspend狀態,那些註冊了early suspend handler的驅動將依次進入各自的earlysuspend 狀態。

     echo on > /sys/power/state: 將退出early suspend狀態

     echo disk > /sys/power/state: 命令系統進入hibernation狀態

    echo lockname > /sys/power/wake_lock: 加鎖“lockname”
    echo lockname > /sys/power/wake_unlock: 解鎖“lockname”
    上述是分別加鎖和解鎖的命令,一旦系統中所有wakelock被解鎖,系統就會進入suspend狀態,可見Linux中原本使系統suspend 的操作(echo mem > /sys/power/state 等)在Android被替換成使系統進入early suspend;而wake lock 機製成為使用者命令系統進入suspend狀態的唯一途徑。

 

三、Android 休眠(suspend)

1. 相關檔案
     • kernel/kernel/power/main.c
     • kernel/kernel/power/earlysuspend.c
     • kernel/kernel/power/wakelock.c

 

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

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

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

3. Android Suspend
      main.c檔案是整個架構的入口。使用者可以通過讀寫sys檔案/sys/power/state實現控制系統進入低功耗狀態。使用者對於/sys/power/state的讀寫會調用到main.c中的state_store(),使用者可以寫入const char * const pm_states[] 中定義的字串, 比如“on”,“mem”,“standby”,“disk”。 

      state_store()首先判斷使用者寫入的是否是“disk”字串,如果是則調用hibernate()函數命令系統進入hibernation狀態。如果是其他字串則調用request_suspend_state()(如果定義 CONFIG_EARLYSUSPEND)或者調用enter_state()(如果未定義CONFIG_EARLYSUSPEND)。  request_suspend_state()函數是android相對標準linux改動的地方,它實現在earlysuspend.c中。在標準linux核心中,使用者通過
sysfs 寫入“mem”和“standby”時,會直接調用enter_state()進入suspend模式,但在android中則會調用request_suspend_state()函數進入early suspend狀態。request_suspend_state()函數代碼如下:    

 

void request_suspend_state(suspend_state_t new_state){unsigned long irqflags;int old_sleep;#ifdef CONFIG_PLAT_RKif (system_state != SYSTEM_RUNNING)return;#endifspin_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;                  //進入Early suspend處理,執行函數early_suspendqueue_work(suspend_work_queue, &early_suspend_work);} else if (old_sleep && new_state == PM_SUSPEND_ON) {state &= ~SUSPEND_REQUESTED;wake_lock(&main_wake_lock);                  //進入Late resume處理,執行函數late_resumequeue_work(suspend_work_queue, &late_resume_work);}requested_suspend_state = new_state;spin_unlock_irqrestore(&state_lock, irqflags);}

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

   註:fbearlysuspend.c和consoleearlysuspend.c這兩個檔案實現了針對lcd framebuffer的earlysuspend支援和console的earlysuspend支援。實際上這兩個檔案就是利用上面earlysuspend.c提供的介面註冊了針對framebuffer和console的early suspend handler,並提供相應的handler函數。

static void early_suspend(struct work_struct *work){struct early_suspend *pos;unsigned long irqflags;int abort = 0;#ifdef CONFIG_PLAT_RKif (system_state != SYSTEM_RUNNING)return;#endifmutex_lock(&early_suspend_lock);spin_lock_irqsave(&state_lock, irqflags);if (state == SUSPEND_REQUESTED)state |= SUSPENDED;elseabort = 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) {if (debug_mask & DEBUG_VERBOSE)pr_info("early_suspend: calling %pf\n", pos->suspend);pos->suspend(pos);}}mutex_unlock(&early_suspend_lock);#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUEsuspend_sys_sync_queue();#elseif (debug_mask & DEBUG_SUSPEND)pr_info("early_suspend: sync\n");sys_sync();#endifabort:spin_lock_irqsave(&state_lock, irqflags);if (state == SUSPEND_REQUESTED_AND_SUSPENDED)wake_unlock(&main_wake_lock);spin_unlock_irqrestore(&state_lock, irqflags);}

5. 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;#ifdef CONFIG_PLAT_RKif (system_state != SYSTEM_RUNNING)return;#endifmutex_lock(&early_suspend_lock);spin_lock_irqsave(&state_lock, irqflags);if (state == SUSPENDED)state &= ~SUSPENDED;elseabort = 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) {if (debug_mask & DEBUG_VERBOSE)pr_info("late_resume: calling %pf\n", pos->resume);pos->resume(pos);}}if (debug_mask & DEBUG_SUSPEND)pr_info("late_resume: done\n");abort:mutex_unlock(&early_suspend_lock);}

6. Wake Lock
    wake_lock防止正在啟動並執行系統進入suspend或其它低功耗狀態。

     Android改動較大的另一處是增加了wakelock機制。實現在wakelock.c和userwakelock.c中。wakelock可以阻止處於正常運行(active)或者空閑(idle)狀態的系統進入睡眠等低功耗狀態。直到所持有的wakelock全部被釋放,系統才能進入睡眠等低功耗的狀態。

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

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

    2) 鎖有兩種類型:
    • WAKE_LOCK_SUSPEND:這種鎖會防止系統進入睡眠(suspend)。
    • WAKE_LOCK_IDLE:這種鎖不會影響系統的休眠,用於阻止系統在持有鎖的過程中進入低功耗狀態。即直到wake_lock被釋放,系統才會從idle狀態進入低功耗狀態,此低功耗狀態將使中斷延遲或禁用一組中斷。

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

7. 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;struct timespec ts_entry, ts_exit;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;#ifdef CONFIG_SUSPEND_SYNC_WORKQUEUEsuspend_sys_sync_queue();#elsesys_sync();#endifif (debug_mask & DEBUG_SUSPEND)pr_info("suspend: enter suspend\n");getnstimeofday(&ts_entry);ret = pm_suspend(requested_suspend_state);getnstimeofday(&ts_exit);if (debug_mask & DEBUG_EXIT_SUSPEND) {struct rtc_time tm;rtc_time_to_tm(ts_exit.tv_sec, &tm);pr_info("suspend: exit suspend, ret = %d ""(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret,tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec);}if (ts_exit.tv_sec - ts_entry.tv_sec <= 1) {++suspend_short_count;if (suspend_short_count == SUSPEND_BACKOFF_THRESHOLD) {suspend_backoff();suspend_short_count = 0;}} else {suspend_short_count = 0;}if (current_event_num == entry_event_num) {if (debug_mask & DEBUG_SUSPEND)pr_info("suspend: pm_suspend returned with no event\n");wake_lock_timeout(&unknown_wakeup, HZ / 2);}}

8. 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輸出。

 

轉自:http://bbs.ednchina.com/BLOG_ARTICLE_1784575.HTM

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.