2016年8月30日 星期二

[轉] Android睡眠喚醒機制--Kernel態

一、簡介
      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()函數代碼如下:    

[cpp] view plain copy

  1. void request_suspend_state(suspend_state_t new_state)  
  2. {  
  3.     unsigned long irqflags;  
  4.     int old_sleep;  
  5.   
  6. #ifdef CONFIG_PLAT_RK  
  7.     if (system_state != SYSTEM_RUNNING)  
  8.         return;  
  9. #endif  
  10.   
  11.     spin_lock_irqsave(&state_lock, irqflags);  
  12.     old_sleep = state & SUSPEND_REQUESTED;  
  13.     if (debug_mask & DEBUG_USER_STATE) {  
  14.         struct timespec ts;  
  15.         struct rtc_time tm;  
  16.         getnstimeofday(&ts);  
  17.         rtc_time_to_tm(ts.tv_sec, &tm);  
  18.         pr_info("request_suspend_state: %s (%d->%d) at %lld "  
  19.             "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n",  
  20.             new_state != PM_SUSPEND_ON ? "sleep" : "wakeup",  
  21.             requested_suspend_state, new_state,  
  22.             ktime_to_ns(ktime_get()),  
  23.             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,  
  24.             tm.tm_hour, tm.tm_min, tm.tm_sec, ts.tv_nsec);  
  25.     }  
  26.     if (!old_sleep && new_state != PM_SUSPEND_ON) {  
  27.         state |= SUSPEND_REQUESTED;  
  28.                   //進入Early suspend處理,執行函數early_suspend  
  29.         queue_work(suspend_work_queue, &early_suspend_work);  
  30.     } else if (old_sleep && new_state == PM_SUSPEND_ON) {  
  31.         state &= ~SUSPEND_REQUESTED;  
  32.         wake_lock(&main_wake_lock);  
  33.                   //進入Late resume處理,執行函數late_resume  
  34.         queue_work(suspend_work_queue, &late_resume_work);  
  35.     }  
  36.     requested_suspend_state = new_state;  
  37.     spin_unlock_irqrestore(&state_lock, irqflags);  
  38. }  

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函數。
[cpp] view plain copy

  1. static void early_suspend(struct work_struct *work)  
  2. {  
  3.     struct early_suspend *pos;  
  4.     unsigned long irqflags;  
  5.     int abort = 0;  
  6.   
  7. #ifdef CONFIG_PLAT_RK  
  8.     if (system_state != SYSTEM_RUNNING)  
  9.         return;  
  10. #endif  
  11.   
  12.     mutex_lock(&early_suspend_lock);  
  13.     spin_lock_irqsave(&state_lock, irqflags);  
  14.     if (state == SUSPEND_REQUESTED)  
  15.         state |= SUSPENDED;  
  16.     else  
  17.         abort = 1;  
  18.     spin_unlock_irqrestore(&state_lock, irqflags);  
  19.   
  20.     if (abort) {  
  21.         if (debug_mask & DEBUG_SUSPEND)  
  22.             pr_info("early_suspend: abort, state %d\n", state);  
  23.         mutex_unlock(&early_suspend_lock);  
  24.         goto abort;  
  25.     }  
  26.   
  27.     if (debug_mask & DEBUG_SUSPEND)  
  28.         pr_info("early_suspend: call handlers\n");  
  29.     list_for_each_entry(pos, &early_suspend_handlers, link) {  
  30.         if (pos->suspend != NULL) {  
  31.             if (debug_mask & DEBUG_VERBOSE)  
  32.                 pr_info("early_suspend: calling %pf\n", pos->suspend);  
  33.             pos->suspend(pos);  
  34.         }  
  35.     }  
  36.     mutex_unlock(&early_suspend_lock);  
  37.   
  38. #ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE  
  39.     suspend_sys_sync_queue();  
  40. #else  
  41.     if (debug_mask & DEBUG_SUSPEND)  
  42.         pr_info("early_suspend: sync\n");  
  43.   
  44.     sys_sync();  
  45. #endif  
  46. abort:  
  47.     spin_lock_irqsave(&state_lock, irqflags);  
  48.     if (state == SUSPEND_REQUESTED_AND_SUSPENDED)  
  49.         wake_unlock(&main_wake_lock);  
  50.     spin_unlock_irqrestore(&state_lock, irqflags);  
  51. }  
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的設備。
[cpp] view plain copy

  1. static void late_resume(struct work_struct *work)  
  2. {  
  3.     struct early_suspend *pos;  
  4.     unsigned long irqflags;  
  5.     int abort = 0;  
  6.   
  7. #ifdef CONFIG_PLAT_RK  
  8.     if (system_state != SYSTEM_RUNNING)  
  9.         return;  
  10. #endif  
  11.   
  12.     mutex_lock(&early_suspend_lock);  
  13.     spin_lock_irqsave(&state_lock, irqflags);  
  14.     if (state == SUSPENDED)  
  15.         state &= ~SUSPENDED;  
  16.     else  
  17.         abort = 1;  
  18.     spin_unlock_irqrestore(&state_lock, irqflags);  
  19.   
  20.     if (abort) {  
  21.         if (debug_mask & DEBUG_SUSPEND)  
  22.             pr_info("late_resume: abort, state %d\n", state);  
  23.         goto abort;  
  24.     }  
  25.     if (debug_mask & DEBUG_SUSPEND)  
  26.         pr_info("late_resume: call handlers\n");  
  27.     list_for_each_entry_reverse(pos, &early_suspend_handlers, link) {  
  28.         if (pos->resume != NULL) {  
  29.             if (debug_mask & DEBUG_VERBOSE)  
  30.                 pr_info("late_resume: calling %pf\n", pos->resume);  
  31.   
  32.             pos->resume(pos);  
  33.         }  
  34.     }  
  35.     if (debug_mask & DEBUG_SUSPEND)  
  36.         pr_info("late_resume: done\n");  
  37. abort:  
  38.     mutex_unlock(&early_suspend_lock);  
  39. }  
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的休眠流程...
[cpp] view plain copy

  1. static void suspend(struct work_struct *work)  
  2. {  
  3.     int ret;  
  4.     int entry_event_num;  
  5.     struct timespec ts_entry, ts_exit;  
  6.   
  7.     if (has_wake_lock(WAKE_LOCK_SUSPEND)) {  
  8.         if (debug_mask & DEBUG_SUSPEND)  
  9.             pr_info("suspend: abort suspend\n");  
  10.         return;  
  11.     }  
  12.   
  13.     entry_event_num = current_event_num;  
  14. #ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE  
  15.     suspend_sys_sync_queue();  
  16. #else  
  17.     sys_sync();  
  18. #endif  
  19.     if (debug_mask & DEBUG_SUSPEND)  
  20.         pr_info("suspend: enter suspend\n");  
  21.     getnstimeofday(&ts_entry);  
  22.     ret = pm_suspend(requested_suspend_state);  
  23.     getnstimeofday(&ts_exit);  
  24.   
  25.     if (debug_mask & DEBUG_EXIT_SUSPEND) {  
  26.         struct rtc_time tm;  
  27.         rtc_time_to_tm(ts_exit.tv_sec, &tm);  
  28.         pr_info("suspend: exit suspend, ret = %d "  
  29.             "(%d-%02d-%02d %02d:%02d:%02d.%09lu UTC)\n", ret,  
  30.             tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,  
  31.             tm.tm_hour, tm.tm_min, tm.tm_sec, ts_exit.tv_nsec);  
  32.     }  
  33.   
  34.     if (ts_exit.tv_sec - ts_entry.tv_sec <= 1) {  
  35.         ++suspend_short_count;  
  36.   
  37.         if (suspend_short_count == SUSPEND_BACKOFF_THRESHOLD) {  
  38.             suspend_backoff();  
  39.             suspend_short_count = 0;  
  40.         }  
  41.     } else {  
  42.         suspend_short_count = 0;  
  43.     }  
  44.   
  45.     if (current_event_num == entry_event_num) {  
  46.         if (debug_mask & DEBUG_SUSPEND)  
  47.             pr_info("suspend: pm_suspend returned with no event\n");  
  48.         wake_lock_timeout(&unknown_wakeup, HZ / 2);  
  49.     }  
  50. }  
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輸出。

沒有留言:

張貼留言