前言 1. 待機、睡眠與休眠的區別? 2. Android開發者官網當中提到“idle states”,該如何理解,這個狀態會對設備及我們的程式造成何種影響? 3. 進入Doze模式中的idle狀態,我們的程式還能運行嗎? 4. 手機睡眠之後,為何我們寫Alarm程式、來電顯示程式依舊會生效? 如果 ...
前言
- 待機、睡眠與休眠的區別?
- Android開發者官網當中提到“idle states”,該如何理解,這個狀態會對設備及我們的程式造成何種影響?
- 進入Doze模式中的idle狀態,我們的程式還能運行嗎?
- 手機睡眠之後,為何我們寫Alarm程式、來電顯示程式依舊會生效?
如果你也有以上疑問,那麼本文會對你解開疑惑有一定的幫助
ACPI簡介
要理解第一個問題,得先從ACPI(高級配置與電源介面)說起,ACPI是一種規範(包含軟體與硬體),用來供操作系統應用程式管理所有電源介面。
ACPI將電腦系統的狀態劃分為四個全局狀態(G0-G3),共7個狀態,其中G0對應S0;G1將低功耗狀態細分為四個狀態,對應S1-S4;G2、G3代表關機狀態分別對應S5、S6。
ACPI State | Description |
---|---|
S0 | 正常工作狀態 |
S1 | CPU與RAM供電正常,但CPU不執行指令 |
S2 | 比S1更深的一個睡眠層次,這種模式通常不採用 |
S3 | 掛起到記憶體 |
S4 | 掛起到硬碟 |
S5 | Soft Off,CPU、外設等斷電,但電源依舊會為部分極低耗設備供電 |
S6 | Mechanical Off,全部斷電 |
這裡只需要對ACPI的七個狀態有個大致瞭解即可,下一節會有具體的例子來說明各個狀態。
Linux系統電源狀態
在Linux操作系統中,將電源劃分為如下幾個狀態:
ACPI | State | Linux State Description |
---|---|---|
S0 | On(on) | Working |
S1 | Standby(standby) | CPU and RAM are powered but not executed |
S2 | ------ | ------ |
S3 | Suspend to RAM(mem) | CPU is Off,RAM is powered and the running content is saved to RAM |
S4 | Suspend to Disk(disk) | All content is saved to Disk and power down |
S5 | Shutdown | Shutdown the system |
On:正常工作狀態
STR(Suspend to RAM):
掛起到記憶體,俗稱待機、睡眠(Sleep),進入該狀態,系統的主要工作如下:
1、將系統當前的運行狀態等數據保存在記憶體中,此時仍需要向RAM供電,以保證後續快速恢復至工作狀態
2、凍結用戶態的進程和內核態的任務(進入內核態的進程或內核自己的task)
3、關閉外圍設備,如顯示屏、滑鼠等,中斷喚醒外設不會關閉,如電源鍵
4、CPU停止工作
Standby也屬於睡眠的一種方式,屬於淺睡眠。該模式下CPU並未斷電,依舊可以接收處理某些特定事件,視具體設備而定,恢復至正常工作狀態的速度也比STR更快,但也更為耗電。舉個例子來說,以該方式進入睡眠時,後續通過點擊鍵盤也能將系統喚醒。而以mem進入的睡眠為深度睡眠,只能通過中斷喚醒設備喚醒系統,如電源鍵(此時按電源鍵,不會經過正常的開機流程的BIOS、BOOTLOAD等),此時按鍵盤是無法喚醒系統的。
STD(Suspend to Disk):
掛起到硬碟,俗稱休眠(Hibernation)將系統當前的運行狀態等數據保存到硬碟上,並自動關機。下次開機時便從硬碟上讀取之前保存的數據,恢復到休眠關機之前的狀態。
譬如在休眠關機時,桌面打開了一個應用,那麼下一次開機啟動時,該應用也處於打開狀態。而正常的關機-開機流程,該應用是不會打開的。
Linux內核代碼聲明如下,位於kernel/power/suspend.c
在新版內核中,進程freeze的功能被單獨抽離出來作為一個電源狀態,該狀態僅僅是凍結進程,並不會使系統進入低功耗狀態(如切斷CPU時鐘源、關閉外設供電等)。
相關巨集定義位於:linux/include/linux/suspend.h
其中狀態4就是STD,所謂的休眠狀態(Hibernation)
小結:
至此,我們可以知道,睡眠與休眠是2個不同的概念,睡眠屬於STR,而休眠屬於STD,切勿混為一談。
網上也有很多關於“Android休眠”的文章,事實上,Android手機壓根兒就不支持休眠模式。
查看Linux支持的電源模式
#查看系統支持的電源模式
$ cat /sys/power/state
#休眠系統命令
$ sudo pm-hibernate
看來Ubuntu-17.0.4版本是不支持休眠功能了,state當中並沒有disk,執行休眠命令也提示找不到。
在公司測試Ubuntu-16.0.4是支持休眠的,休眠時會將當前RAM中的數據保持至swap分區,以供後續恢復。
查看Android支持的電源模式
這裡我使用的是模擬器查看的,真機也一樣,Android手機是不支持休眠模式的,休眠模式需要一塊與RAM大小一致存儲空間,這在移動設備上可是個不小的開銷。
Idle State
Android上的Idle狀態分為二類:Cpu Idle和Device Idle
Cpu Idle
Linux系統運行的基礎是基於進程調度,實際上內核調度的線程(task),內核並不會區分線程與進程,都將他們當做一個線程(task)來處理;當所有的進程都沒事兒乾的時候,系統就會啟用idle進程,使系統進入低功耗狀態(如關閉一些服務、模塊功能,降低CPU工作頻率等),即idle狀態,以達到省電的目的。
idle狀態又可以劃分為不同的層級,以MTK的晶元為例,通常劃分為以下幾個狀態:
狀態 | 描述 |
---|---|
soidle(screen on idle) | 亮屏 Idle 模式,該模式下與正常工作狀態差別不大,唯一的區別就cpu處於空閑狀態 |
rgidle | 淺度 Idle 模式,cpu處於 WFI(wait for interrupt),屏幕熄滅,同時關閉一些不需要的服務及模塊,註意此狀態cpu的時鐘源與RTC模塊是工作正常的,此時是可以通過TimerTask的定時觸發激活系統的,TimerTask依賴於CPU的RTC模塊,而Alarm則依賴於PMIC的RTC模塊 |
dpidle(deep idle) | 深度idle模式,該模式下cpu的時鐘源和hrtimer(高精度定時器模塊(RTC))被關閉,所有進程(包括系統進程)被凍結,即進入上文所述的睡眠狀態 |
idle進程是由原始進程(pid=0)在初始化init進程(pid=1)之後演變而來,可以說是init進程的祖先,關於其詳細介紹可參考如下鏈接:
Device Idle
Device Idle屬於Doze模式中概念,即指當手機屏幕熄屏、不充電、靜置不動,有網友分析了源碼,指出6.0手機需要靜置1時4分30秒才能進入Doze模式。
Doze模式的限制 | |
---|---|
網路接入被暫停 | |
系統忽略wake locks | |
標準的AlarmManager | alarms(包括setExact()和setWindow())被延緩到下一個maintenance window |
如果你需要在Doze狀態下啟動設置的alarms,使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()。當有setAlarmClock()的alarms啟動時,系統會短暫退出Doze模式 | |
系統不會掃描Wi-Fi | |
系統不允許sync adapters運行 | |
系統不允許JobScheduler運行 |
結合上文分析的cpu idle不難發現Doze模式中的idle狀態在概念屬於淺idle狀態,只是關閉了一些特定服務和模塊,並非立即進入睡眠,當然這個過程當中依舊有可能滿足睡眠條件而進入睡眠狀態,至於如何進入請參考下文【睡眠觸發入口】一節。
Android電源管理框架
Android採用linux內核,所以電源狀態整體上是與linux操作系統相同,下圖是Android的電源管理框架:
WakeLock
喚醒鎖,一種鎖機制,用於阻止系統進入睡眠狀態,只要有應用獲取到改鎖,那麼系統就無法進入睡眠狀態。
該機制起初是早期Android為Linux內核打得一個補丁,並想合入到linux內核,但被Linux社區拒絕,後續Linux內核引入自己的Wakelock機制,Android系統也使用的是linux的Wakelock機制,所以該機制並非Android特有的機制。
Android系統提供了兩種類型的鎖,每一個類型又可分為超時鎖與普通鎖,超時鎖,超時會自動釋放,而普通鎖則必需要手動釋放:
類型 | 描述 |
---|---|
WAKE_LOCK_SUSPEND | 阻止系統進入睡眠狀態(STR) |
WAKE_LOCK_IDLE | 阻止系統從idle進程進入那些具有較大中斷時延、禁用了較多中斷源的低功耗狀態(睡眠除外),持有該類型的鎖,不影響系統進入睡眠狀態。自Android API-17(對應android linux內核版本3.4)移除了該類型的喚醒鎖。 |
中斷時延:電腦接收到中斷信號到操作系統作出響應,並完成轉入中斷服務程式(ISR)的時間。
內核當中關於WakeLock的主要源碼位於:
kernel_common/include/linux/wakelock.h
kernel_common/kernel/power/wakelock.c
Android Linux內核3.0版本
Android Linux內核3.4版本
應用層提供的鎖類型如下,這些鎖都需要手動釋放:
FLAG | CPU | 屏幕 | 鍵盤 |
---|---|---|---|
PARTIAL_WAKE_LOCK | 開啟 | 關閉 | 關閉 |
SCREEN_DIM_WAKE_LOCK | 開啟 | 變暗 | 關閉 |
SCREEN_BRIGHT_WAKE_LOCK | 開啟 | 變亮 | 關閉 |
FULL_WAKE_LOCK | 開啟 | 變亮 | 變亮 |
鎖的釋放
Linux3.4內核中摒棄了之前的wakelock機制,引入wakeup source機制來進行睡眠管理,為了保證上層介面不變,Android的Linux內核便將wakeup source包裝成wakelock,WakeLock的數據結構如下:
wakelock數據結構
當我們應用層釋放鎖之後,它並不會馬上消失。wakelock分為激活和非激活狀態,非激活狀態300S之內,無人在申請wakelock,那麼它將從紅黑二叉樹,LRU鏈表當中刪除,如此便可復用鎖,節省系統開銷。
睡眠觸發入口
在wakelock中,有3個地方可以讓系統從early_suspend進入suspend狀態。
wake_unlock,系統每釋放一個鎖,就會檢查是否還存其他激活的wakelock,若不存在則執行Linux的標準suspend流程進入睡眠狀態
在超時鎖的超時回調函數,判斷是否存在其他激活的wakelock,若不存在,則進入睡眠狀態
autosleep機制,android 4.1引入該機制,亮屏時會向autosleep節點寫入off,熄屏則會寫入mem。Android一滅屏,就會嘗試進入睡眠,失敗之後系統處於idle進程超過一定時間,則又嘗試進入睡眠,判斷標準同上,若存在wakelock則進入失敗
關於autosleep機制的內核源碼分析,可以參考如下文章:
Early Suspend
預掛起機制是Android特有的掛起機制, 這個機製作用是關閉一些與顯示相關的外設,比如LCD背光、重力感應器、 觸摸屏,但是其他外設如WIFI、藍牙等模塊等並未關閉。
此時,系統依舊可以處理事件,如音樂播放軟體,息屏後依舊能播放音樂。
需要註意的是Early Suspend機制與WakeLock機制相互獨立,就算有應用持有wakelock鎖,系統依舊可以通過Early Suspend機制關閉與顯示相關的外設。
註意:
Android 4.4起,也就是引入ART的版本,摒棄了early suspend機制,改用了fb event通知機制,即後續版本只有suspend、resume以及runtime suspend、runtime resume。
Late Resume
遲喚醒機制,用於喚醒預掛起的設備
睡眠狀態轉換
一般情況下,當我們息屏後,系統將先通過Early Suspend機制進入Idle狀態,如果滿足進入睡眠的條件(沒有進程持有喚醒鎖)則會通過Linux的Suspend機制進入Sleep(睡眠)狀態。
內核源碼流程分析可參考如下文章:
源碼位於kernel_common/kernel/power/main.c:
Android中休眠與喚醒之wake_lock, early_suspend, late_resume
看到這兒,不知你是否疑問,既然系統睡眠了,CPU斷電不執行指令了,為何我們定的Alarm會生效以及能接收到來電?
手機來電與Alarm為何能喚醒系統
原來Android在硬體架構上將處理器分為二類:Application Processor(AP)和Baseband Processor(BP),AP是ARM架構的處理器,用於運行Linux+Android系統,耗電量高;BP用於運行實時操作系統(RTOS),用於處理手機通信,耗電量低。
當AP進入睡眠,有來電時,Modem(數據機)將喚醒AP;而我們平時所用的Alarm在硬體上則是依賴PMIC(電源管理晶元)中的RTC模塊,所以即使AP斷電進入睡眠,我們定的鬧鐘依舊會生效。
若想更深入的瞭解,則可參考Android RIL機制相關的文章。
總結
- 待機、睡眠與休眠的區別
實際上待機(standby)與睡眠(mem)屬於不同模式,但現在大多操作系統都不支持待機模式了,我們也習慣將待機等同於睡眠,睡眠屬於STR,休眠屬於STD,Android手機不支持休眠!!!
- Android開發者官網當中提到“idle state”,該如何理解,這個狀態會對設備及我們的程式造成何種影響
所謂的idle狀態,就是指系統進入某個低功耗狀態,以MTK為例,常見的狀態有soidle、rgidle以及dpidle。rgidle只是限制我們程式使用某些模塊,如Doze模式中不能訪問網路;而dpidle則會凍結所有進程,系統進入睡眠。
- 進入Doze模式中的idle狀態,我們的程式還能運行嗎?
Doze模式中的idle概念上屬於rgidle狀態,此時我們的程式是能運行的,只是不能訪問網路等,但是在這個過程中,系統可能會滿足進入睡眠條件,凍結所有進程,這樣我們的程式就不會得到執行。
可以自己寫個死迴圈的線程(普通線程,非looper線程),強制手機進入Doze的idle模式,你會發現你的程式依舊在執行,但是靜置在哪兒一段時間後,你會發現你的線程被凍結,不會執行,當你點亮屏幕,你的線程又會繼續工作。
- 手機睡眠之後,為何我們寫Alarm程式、來電顯示程式依舊會生效?
Android在硬體架構上將處理器分為AP與BP,應用程式運行與AP之中,睡眠只是將AP斷電,BP(Modem)不會斷電,當有來電時,BP將會喚醒AP。
Alarm在硬體上依賴的是Modem中的PMIC的RTC模塊,而不是AP中的RTC模塊,當定時器觸發時,可以喚醒AP,使我們的Alarm程式依舊會得到執行