Android or Linux 的休眠與喚醒

Linux休眠/喚醒簡介

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

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(),用戶可以寫入constchar * const pm_state[] 中定義的字符串,比如"mem","standby".

然後state_store()會調用enter_state(),它首先會檢查一些狀態參數,然後同步文件系統.

準備, 凍結進程

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

讓外設進入休眠

現在,所有的進程(也包括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是板級的電源管理操作,通常註冊在文件arch/xxx/mach-xxx/pm.c中。

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

核心設備的停止

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

Linux Resume流程

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

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

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

Android系統Suspendresume的函數流程

Android 休眠(suspend)介紹

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

涉及到的文件:

linux_source/kernel/power/main.c

linux_source/kernel/power/earlysuspend.c

linux_source/kernel/power/wakelock.c

特性介紹

1EarlySuspend

Early suspendandroid引進的一種機制,這個機制作用在關閉顯示的時候,一些和顯示有關的設備,比如LCD背光,重力感應器,觸摸屏,這些設備都會關掉,但是系統可能還是在運行狀態(這時候還有wakelock)進行任務的處理,例如在掃描SD卡上的文件等.在嵌入式設備中,背光是一個很大的電源消耗,所以android會加入這樣一種機制。

2LateResume

Late Resume是和suspend配套的一種機制,是在內核喚醒完畢開始執行的,主要就是喚醒在EarlySuspend的時候休眠的設備.

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

來電

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

用戶按鍵用戶按鍵事件會送到WindowManager,WindowManager會處理這些按鍵事件,按鍵分爲幾種情況,如果案件不是喚醒鍵(能夠喚醒系統的按鍵)那麼WindowManager會主動放棄wakeLock來使系統進入再次休眠,如果按鍵是喚醒鍵,那麼WindowManger就會調用PowerManagerService中的接口來執行Late Resume.

Late Resume會依次喚醒前面調用了EarlySuspend的設備.

3WakeLock

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

3AndroidSuspend

當用戶寫入mem或者standby/sys/power/state中的時候,state_store()會被調用,然後Android會在這裏調用request_suspend_state()而標準的Linux會在這裏進入enter_state()這個函數.如果請求的是休眠,那麼early_suspend這個workqueue就會被調用,並且進入early_suspend狀態。調用request_suspend_state()後在suspend_work_queue工作線程上面註冊一個early_suspend_work工作者,

然後又通過staticDECLARE_WORK(early_suspend_work, early_suspend);註冊一個工作任務early_suspend。所以系統最終會調用early_suspend函數。

註冊加入suspendresume流程

platform_device_register()-->platform_device_add()-->device_add()-->device_pm_add()-->,最終加入到了dpm_list的鏈表中,在其中的dpm_suspenddpm_suspend中通過遍歷這個鏈表來進行查看哪個device中包含suspendresume項。

系統喚醒和休眠

Kernel[針對AndroidLinux2.6.28內核]:

其主要代碼在下列位置:

Drivers/base /main.c

kernel/power /main.c

kernel/power/wakelock.c

kernel/power/earlysuspend.c

其對Kernel提供的接口函數有

EXPORT_SYMBOL(wake_lock_init);//初始化Suspendlock,在使用前必須做初始化

EXPORT_SYMBOL(wake_lock);//申請lock,必須調用相應的unlock來釋放它

static DEFINE_TIMER(expire_timer,expire_wake_locks, 0, 0);//定時時間到,加入到suspend隊列中;

EXPORT_SYMBOL(wake_unlock);//釋放lock

EXPORT_SYMBOL_GPL(device_power_up);//打開特殊的設備

EXPORT_SYMBOL_GPL(device_power_down);//關閉特殊設備

EXPORT_SYMBOL_GPL(device_resume);//重新存儲設備的狀態;

EXPORT_SYMBOL_GPL(device_suspend);:保存系統狀態,並結束掉系統中的設備;

EXPORT_SYMBOL(register_early_suspend);//註冊earlysuspend的驅動

EXPORT_SYMBOL(unregister_early_suspend);//取消已經註冊的earlysuspend的驅動

Androidsuspent執行流程

函數的流程如下所示:

應用程序通過對/sys/power/state的寫入操作可以使系統進行休眠的狀態,會調用/kernel/power/main.c中的state_store函數。pm_states包括:

PM_SUSPEND_ONPM_SUSPEND_STANDBYPM_SUSPEND_MEM滿足的狀態。

1)當狀態位PM_SUSPEND_ON的狀態的時候,request_suspend_state();當滿足休眠的狀態的時候,調用request_suspend_statesuspend_work_queue工作線程上創建early_suspend_work隊列,queue_work(suspend_work_queue,&early_suspend_work)

2)然後通過DECLARE_WORK(early_suspend_work,early_suspend);early_suspend_work工作隊列中添加工作任務調用early_suspend,所以early_suspend函數會被調用。

3early_suspend函數中通過

list_for_each_entry(pos,&early_suspend_handlers, link) {

if (pos->suspend != NULL)

pos->suspend(pos)

在鏈表中找註冊的suspend函數,這個suspendearly的。early_suspend後面調用wake_unlock函數。語句:wake_unlock(&main_wake_lock);

4wake_unlock()中調用mod_timer啓動expire_timer定時器,當定時時間到了,則執行expire_wake_locks函數,將suspend_work加入到suspend_work_queue隊列中,分析到這裏就可以知道了early_suspend_worksuspend_work這兩個隊列的先後順序了(先執行early,定義一段時間後才執行suspend_work),然後會在suspend_work隊列中加入suspend的工作任務,所以wakelock.c中的suspend函數會被調用。

5suspend調用了pm_suspend,通過判斷當前的狀態,選擇enter_state(),在enter_state中,經過了suspend_preparesuspend_testsuspend_device_and_enter(),在suspend_device_and_enter中調用dpm_suspend_start(),然後調用dpm_suspend()

6dpm_suspend中利用while循環在dpm_list鏈表查找所有devic,然後調用device_suspend來保存狀態和結束系統的設備。到了這裏,我們就又可以看見在初始化的時候所看到的隊列dpm_list

dpm_list鏈表的添加是在device_pm_add中完成,請看上一節中。

Wake Lock

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

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

鎖有兩種類型:

WAKE_LOCK_SUSPEND這種鎖會防止系統進入睡眠

WAKE_LOCK_IDLE這種鎖不會影響系統的休眠,作用我不是很清楚.

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

1)在wake_unlock(),如果發現解鎖以後沒有任何其他的wakelock,就開始休眠

2)在定時器都到時間以後,定時器的回調函數會查看是否有其他的wakelock,如果沒有,就在這裏讓系統進入睡眠.

3)在wake_lock(),對一個wakelock加鎖以後,會再次檢查一下有沒有鎖,我想這裏的檢查是沒有必要的,更好的方法是使加鎖的這個操作原子化,而 不是繁冗的檢查.而且這樣的檢查也有可能漏掉.

Android於標準Linux休眠的區別

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

當進入凍結進程的時候,android首先會檢查有沒有wakelock,如果沒有,纔會停止這些進程,因爲在開始suspend和凍結進程期間有可能有人申請了wake lock,如果是這樣,凍結進程會被中斷.

suspend_late(),會最後檢查一次有沒有wakelock,這有可能是某種快速申請wakelock,並且快速釋放這個鎖的進程導致的,如果有這種情況,這裏會返回錯誤,整個suspend就會全部放棄.如果pm_suspend()成功了,LOG的輸出可以通過在kernelcmd裏面增加"no_console_suspend"來看到suspendresume過程中的log輸出。

Android的電源管理主要是通過Wakelock來實現的,在最底層主要是通過如下隊列來實現其管理:

LIST_HEAD(dpm_list);

系統正常開機後進入到AWAKE狀態,Backlight會從最亮慢慢調節到用戶設定的亮度,系統screenoff timer(settings->sound & display-> Display settings ->Screen timeout)開始計時,在計時時間到之前,如果有任何的activity事件發生,Touchclick, keyboard pressed等事件,則將Resetscreen off timer, 系統保持在AWAKE狀態.如果有應用程序在這段時間內申請了Fullwake lock,那麼系統也將保持在AWAKE狀態,除非用戶按下powerkey.AWAKE狀態下如果電池電量低或者是用AC供電screenoff timer時間到並且選中Keepscreen on while pluged in選項,backlight會被強制調節到DIM的狀態。

如果Screenoff timer時間到並且沒有Fullwake lock或者用戶按了powerkey,那麼系統狀態將被切換到NOTIFICATION,並且調用所有已經註冊的early_suspend_handlers函數,通常會把LCDBacklight驅動註冊成earlysuspend類型,如有需要也可以把別的驅動註冊成earlysuspend,這樣就會在第一階段被關閉.接下來系統會判斷是否有partialwake lock acquired, 如果有則等待其釋放,在等待的過程中如果有useractivity事件發生,系統則馬上回到AWAKE狀態;如果沒有partialwake lock acquired, 則系統會馬上調用函數pm_suspend關閉其它相關的驅動,CPU進入休眠狀態。

系統在Sleep狀態時如果檢測到任何一個Wakeupsource,CPU會從Sleep狀態被喚醒,並且調用相關的驅動的resume函數,接下來馬上調用前期註冊的earlysuspend驅動的resume函數,最後系統狀態回到AWAKE狀態.這裏有個問題就是所有註冊過earlysuspend的函數在進Suspend的第一階段被調用可以理解,但是在resume的時候,Linux會先調用所有驅動的resume函數,而此時再調用前期註冊的earlysuspend驅動的resume函數有什麼意義呢?個人覺得android的這個earlysuspendlateresume函數應該結合Linux下面的suspendresume一起使用,而不是單獨的使用一個隊列來進行管理。

Android Framework層面

其主要代碼文件如下

frameworks/base/core/java/android/os/PowerManager.java

frameworks/base/services/java/com/android/server/PowerManagerService.java

frameworks/base/core/java/android/os/Power.java

frameworks/base/core/jni/android_os_power.cpp

hardware/libhardware/power/power.c

其中PowerManagerService.java是核心,Power.java提供底層的函數接口,JNI層進行交互,JNI層的代碼主要在文件android_os_Power.cpp,Linuxkernel交互是通過Power.c來實現的,AndriodKernel的交互主要是通過sys文件的方式來實現的,具體請參考Kernel層的介紹。

這一層的功能相對比較複雜,比如系統狀態的切換,背光的調節及開關,WakeLock的申請和釋放等等,但這一層跟硬件平臺無關,而且由Google負責維護,問題相對會少一些,有興趣的朋友可以自己查看相關的代碼。

Android powermanagement應用層分析

Android提供了android.os.PowerManager類,該類用於控制設備的電源狀態的切換。

該類對外有三個接口函數:

1voidgoToSleep(long time);

強制設備進入Sleep狀態

要注意權限問題。

2newWakeLock(intflags, String tag);

取得相應層次的鎖

flags參數說明:

PARTIAL_WAKE_LOCK: Screen off,keyboard light off

SCREEN_DIM_WAKE_LOCK: screen dim,keyboard light off

SCREEN_BRIGHT_WAKE_LOCK: screenbright, keyboard light off

FULL_WAKE_LOCK: screen bright,keyboard bright

ACQUIRE_CAUSES_WAKEUP:一旦有請求鎖時強制打開Screenkeyboardlight

ON_AFTER_RELEASE:在釋放鎖時resetactivity timer

如果申請了partialwakelock,那麼即使按Power,系統也不會進Sleep,Music播放時

如果申請了其它的wakelocks,Power,系統還是會進Sleep

3voiduserActivity(long when, boolean noChangeLights);

User activity事件發生,設備會被切換到Fullon的狀態,同時ResetScreen off timer

1)在使用以上函數的應用程序中,必須在其Manifest.xml文件中加入下面的權限:

<uses-permissionandroid:name="android.permission.WAKE_LOCK" />

<uses-permissionandroid:name="android.permission.DEVICE_POWER" />

2)所有的鎖必須成對的使用,如果申請了而沒有及時釋放會造成系統故障.如申請了partial

wakelock,而沒有及時釋放,那系統就永遠進不了Sleep模式。

Android powermanagement Java層分析


其主要代碼文件如下:
frameworks/base/core/java/android/os/PowerManager.java
frameworks/base/services/java/com/android/server/PowerManagerService.java
frameworks/base/core/java/android/os/Power.java
其中PowerManagerService.java是核心,PowerManager.java是提供給應用層調用的,
Power.java
提供底層的函數接口,JNI層進行交互。PowerManagerService.java類的作用
就是提供PowerManager的功能,以及整個電源管理狀態機的運行。裏面函數和類比較多,就從對外和對內分兩塊來說:

Android power management JNI層分析

其主要代碼文件如下:
frameworks/base/core/jni/android_os_power.cpp
hardware/libhardware/power/power.c
JNI
層的代碼主要在文件android_os_Power.cpp,Linuxkernel交互是通過Power.c來實現
,AndriodKernel的交互主要是通過sys文件的方式來實現的

Android Kernel power management分析


1
、其主要代碼在下列位置:
drivers/android/power.c
其對Kernel提供的接口函數有
EXPORT_SYMBOL(android_init_suspend_lock);
//
初始化Suspendlock,在使用前必須做初始化
EXPORT_SYMBOL(android_uninit_suspend_lock);
//
釋放suspendlock相關的資源
EXPORT_SYMBOL(android_lock_suspend);
//
申請lock,必須調用相應的unlock來釋放它
EXPORT_SYMBOL(android_lock_suspend_auto_expire);
//
申請partialwakelock,定時時間到後會自動釋放
EXPORT_SYMBOL(android_unlock_suspend);//
釋放lock
EXPORT_SYMBOL(android_power_wakeup);//
喚醒系統到on
EXPORT_SYMBOL(android_register_early_suspend);//
註冊earlysuspend的驅動
EXPORT_SYMBOL(android_unregister_early_suspend);
//
取消已經註冊的earlysuspend的驅動提供給AndroidFramework層的proc文件如下:
"/sys/android_power/acquire_partial_wake_lock"//
申請partialwake lock
"/sys/android_power/acquire_full_wake_lock"//
申請fullwake lock
"/sys/android_power/release_wake_lock"//
釋放相應的wakelock
"/sys/android_power/request_state"//
請求改變系統狀態,standby和回到wakeup兩種狀態
"/sys/android_power/state"//
指示當前系統的狀態
2
Android的電源管理主要是通過Wakelock來實現的,在最底層主要是通過如下三個隊列來實現其管理:
staticLIST_HEAD(g_inactive_locks);
staticLIST_HEAD(g_active_partial_wake_locks);
staticLIST_HEAD(g_active_full_wake_locks);
所有初始化後的lock都會被插入到g_inactive_locks的隊列中,而當前活動的partialwakelock都會被插入到g_active_partial_wake_locks隊列中,活動的fullwake lock被插入到

g_active_full_wake_locks隊列中,所有的partialwake lock fullwake lock在過期後或unlock後都會被移到inactive的隊列,等待下次的調用。
3
、在Kernel層使用wakelock步驟如下:
1)
調用函數android_init_suspend_lock初始化一個wakelock
2)
調用相關申請lock的函數android_lock_suspendAndroid_lock_suspend_auto_expire
 
請求lock,這裏只能申請partialwake lock, 如果要申請Fullwakelock,則需要調用函數android_lock_partial_suspend_auto_expire(該函數沒有EXPORT出來),這個命名有點奇怪,不要跟前面的android_lock_suspend_auto_expire搞混了。
3)
如果是autoexpirewakelock則可以忽略,不然則必須及時的把相關的wakelock釋放掉,否則會造成系統長期運行在高功耗的狀態。
4)
在驅動卸載或不再使用Wakelock時請記住及時的調用android_uninit_suspend_lock釋放資源。
4
、系統的狀態:
USER_AWAKE,//Full on status
USER_NOTIFICATION, //Early suspended driver butCPU keep on
USER_SLEEP // CPU enter sleep mode
五、Android電源管理流程
系統正常開機後進入到AWAKE狀態,Backlight會從最亮慢慢調節到用戶設定的亮度,系統screenoff timer(settings->sound & display-> Display settings ->Screen timeout)開始計時,在計時時間到之前,如果有任何的activity事件發生,Touchclick,keyboard pressed等事件,則將Resetscreen off timer, 系統保持在AWAKE狀態.如果有應用程序在這段時間內申請了Fullwake lock,那麼系統也將保持在AWAKE狀態,除非用戶按下powerkey.。在AWAKE狀態下如果電池電量低或者是用AC供電screenoff timer時間到並且選中Keepscreen on while pluged in選項,backlight會被強制調節到DIM的狀態。如果Screenoff timer時間到並且沒有Fullwake lock或者用戶按了powerkey,那麼系統狀態將被切換到NOTIFICATION,並且調用所有已經註冊的g_early_suspend_handlers函數,通常會把LCDBacklight驅動註冊成earlysuspend類型,如有需要也可以把別的驅動註冊成earlysuspend,這樣就會在第一階段被關閉.接下來系統會判斷是否有partialwake lock acquired, 如果有則等待其釋放,在等待的過程中如果有useractivity事件發生,系統則馬上回到AWAKE狀態;如果沒有partialwake lock acquired, 則系統會馬上調用函數pm_suspend關閉其它相關的驅動,CPU進入休眠狀態.系統在Sleep狀態時如果檢測到任何一個Wakeupsource,CPU會從Sleep狀態被喚醒,並且調用相關的驅動的resume函數,接下來馬上調用前期註冊的earlysuspend驅動的resume函數,最後系統狀態回到AWAKE狀態.這裏有個問題就是所有註冊過earlysuspend的函數在進Suspend的第一階段被調用可以理解,但是在resume的時候,Linux會先調用所有驅動的resume函數,而此時再調用前期註冊的earlysuspend驅動的resume函數有什麼意義呢?個人覺得android的這個earlysuspendlateresume函數應該結合Linux下面的suspendresume一起使用,而不是單獨的使用一個隊列來進行管理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章