Android電源管理系列之基礎知識
本篇文章主要介紹 Android
開發中的電源管理部分知識點,本篇文章轉載網路,通過閱讀本篇文章,您將收穫以下內容:
- Sleep/Suspend
- SPM
- wakeup 喚醒源
原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/
公眾號ID:ProgramAndroid
獲取更多資訊

微信公眾號:ProgramAndroid
我們不是牛逼的程式設計師,我們只是程式開發中的墊腳石。
我們不傳送紅包,我們只是紅包的搬運工。
1. Sleep/Suspend
系統休眠Sleep,Linux Kernel中稱作Suspend。系統進入Suspend狀態確切來說時CPU進入了Suspend模式,因為對整個系統來說CPU休眠是整個系統休眠的先決條件。CPU Suspend即CPU進入Wait for interrupt狀態(WFI),SW完全不跑了,停在suspend workqueue裡面。
Android系統從滅屏到系統進入Suspend的大體流程框架如下:

滅屏到系統進入Suspend的大體流程
/frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java /frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp /system/core/libsuspend/ /kernel-x.x/kernel/power/
2.SPM
SPM即System Power Manager,管理著包括AP,Modem,Connectivity等子系統。在CPU進入WFI狀態後,整個系統就依靠SPM監控各個子系統的狀態來控制睡眠/喚醒的流程。
SPM控制Cpu Suspend之後系統是否能掉到最小電流,當系統的關鍵資源(memory、clock)沒有任何使用的時候,它就會讓系統進入一個真正的深睡狀態(最小電流),但只要檢測到有任何資源請求還沒釋放,系統就無法降到底電流。在底電流的debug流程中,不僅僅要看CPU有沒有Suspend成功,還要看SPM的狀態是否正確。
MTK平臺:
CPU在進入WFI狀態前會把SPM的firmware寫入到SPM裡的可程式設計控制器PCM中(Programmable Command Master),然後PCM就依據firmware的邏輯來控制SPM的工作。系統中存在32k,26M二個時鐘,系統工作在最小電流的時候,SPM只依靠32K時鐘工作,因此要判斷系統是不是已經到深休狀態,就要看26M有沒有關閉。

26M 時鐘邏輯
如上圖所示,26M時鐘有沒有關,只需要看SCLKENA訊號有沒有關閉,而SPM對這個訊號的輸出以及子系統的訊號輸入,都記錄在SPM的暫存器裡面,這個就是我們通過log排查的依據。
相關程式碼如下:
/kernel-x.x/drivers/misc/mediatek/base/power/spm_vx/
3. wakeup 喚醒源
主要涉及以下程式碼
kernel/msm-4.4/drivers/base/power/wakeup.c kernel/msm-4.4/include/linux/pm_wakeup.h kernel/msm-4.4/include/linux/pm.h kernel/msm-4.4/include/linux/device.h
Android以Linux kernel為系統核心,其電源管理也是基於Linux電源管理子系統的,但是由於傳統的Linux系統主要針對PC而非移動裝置,對於PC系統,Linux預設是在使用者不再使用時才再觸發系統進入休眠(STR、Standby、Hibernate),但是對於移動裝置的使用特點而言,並沒有明確的不再使用裝置的時候,使用者隨時隨地都可能需要使用裝置,於是在Android上就有提出了“Opportunistic suspend”的概念,即逮到機會就睡直到下次被喚醒,以適應移動裝置的使用特點。
早期,Android為解決該問題,在標準Linux的基礎上增加了Early Suspend和Late Resume機制,Early suspend是在熄屏後,提前將一些不會用到的裝置(比如背光、重力感應器和觸控式螢幕等裝置)關閉,但此時系統仍能持有wake lock後臺執行。該方案將Linux原來suspend的流程改變,並增加了Android自己的處理函式,與kernel的流程與機制相沖突,另外一個問題就是存在suspend和wakeup events之間的同步問題,比如當系統進入suspend流程中,會進行freeze process,device prepared,device suspend,disabled irq等操作,如果在suspend流程中有wakeup events產生,而此時系統無法從suspend過程中喚醒。
後來Linux加入wakeup events framework,包括wake lock、wakeup count、autosleep等機制。用來解決system suspend和system wakeup events之間的同步問題。同時在Android4.4中,Android中也去掉了之前的”wakelocks”機制,利用wakeup events framework重新設計了wakelocks,並且維持上層API不變。
system suspend和system wakeup events之間的同步問題:
- 核心空間的同步:
wakeup events產生後,通常是以中斷的形式通知device driver。driver會處理events,處理的過程中,系統不能suspend。 - 使用者空間的同步:
一般情況下,driver對wakeup events處理後,會交給使用者空間程式繼續處理,處理的過程,也不允許suspend。這又可以分為兩種情況:
進行後續處理的使用者程序,根本沒有機會被排程,即該wakeup events無法上報到使用者空間。
進行後續處理的使用者程序被排程,處理的過程中(以及處理結束後,決定終止suspend操作),系統不能suspend。(wake lock功能)
wakeup events framework主要解決上述三個同步問題,核心空間的同步(framework的核心功能),使用者空間的同步情形1(wakeup count功能),使用者空間的同步情形2(wake lock功能)。
程式碼分析
下面是wakeup events framework的architecture圖(來自 ofollow,noindex">蝸窩科技 )

wakeup events framework sysfs將裝置的wakeup資訊,以sysfs的形式提供到使用者空間,供使用者空間訪問(在drivers/base/power/sysfs.c中實現)。
wakeup source
在kernel中,只有裝置才能喚醒系統,但並不是所有裝置都具備喚醒系統的能力,具備喚醒能力的裝置即“wakeup source”,會在裝置結構體struce device中標誌該裝置具有喚醒能力。
kernel/msm-4.4/include/linux/device.h
struct device { ... struct dev_pm_infopower; struct dev_pm_domain*pm_domain; ... }
kernel/msm-4.4/include/linux/pm_wakeup.h
static inline bool device_can_wakeup(struct device *dev) { return dev->power.can_wakeup; } static inline bool device_may_wakeup(struct device *dev) { return dev->power.can_wakeup && !!dev->power.wakeup; }
由device_can_wakeup()函式可知,通過dev->power.can_wakeup來判斷該裝置是否能喚醒系統,struct dev_pm_info的定義如下:
kernel/msm-4.4/include/linux/pm.h
..... unsigned intcan_wakeup:1; ...... #ifdef CONFIG_PM_SLEEP ...... struct wakeup_source*wakeup; ...... #else unsigned intshould_wakeup:1; #endif ...... };
struct dev_pm_info中的can_wakeup標識該裝置是否具有喚醒能力。具備喚醒能力的裝置在sys/devices/xxx/下存在power相關目錄,用於提供所有的wakeup資訊,這些資訊是以struct wakeup_source的結構組織,即struct dev_pm_info中的wakeup指標。
kernel/msm-4.4/include/linux/pm_wakeup.h
/** * struct wakeup_source - Representation of wakeup sources * * @name: 喚醒源的名字 * @entry: 用來將喚醒源掛到連結串列上,用於管理 * @lock: 同步機制,用於訪問連結串列時使用 * @wakeirq:Optional device specific wakeirq * @timer: 定時器,用於設定該喚醒源的超時時間 * @timer_expires:定時器的超時時間 * @total_time:wakeup source處於active狀態的總時間,可指示該wakeup source對應的裝置的繁忙程度、耗電等級 * @max_time: wakeup source處於active狀態的最長時間(越長越不合理) * @last_time: wakeup source處於active狀態的上次時間 * @prevent_sleep_time: wakeup source阻止系統自動休眠的總時間 * @event_count:wakeup source上報wakeup event的個數 * @active_count: wakeup source處於active狀態的次數 * @relax_count: wakeup source處於deactive狀態的次數 * @expire_count: wakeup source timeout次數 * @wakeup_count: wakeup source abort睡眠的次數 * @active: wakeup source的狀態 * @has_timeout: The wakeup source has been activated with a timeout. */ struct wakeup_source { const char*name; struct list_headentry; spinlock_tlock; struct wake_irq*wakeirq; struct timer_listtimer; unsigned longtimer_expires; ktime_t total_time; ktime_t max_time; ktime_t last_time; ktime_t start_prevent_time; ktime_t prevent_sleep_time; unsigned longevent_count; unsigned longactive_count; unsigned longrelax_count; unsigned longexpire_count; unsigned longwakeup_count; boolactive:1; boolautosleep_enabled:1; };
wakeup source代表一個具有喚醒能力的裝置,該裝置產生的可以喚醒系統的事件,就稱作wakeup event。當wakeup source產生wakeup event時,需要將wakeup source切換為activate狀態;當wakeup event處理完畢後,要切換為deactivate狀態。當wakeup source產生wakeup event時,需要切換到activate狀態,但並不是每次都需要切換,因此有可能已經處於activate狀態了。因此active_count可能小於event_count,換句話說,很有可能在前一個wakeup event沒被處理完時,又產生了一個,這從一定程度上反映了wakeup source所代表的裝置的繁忙程度。wakeup source在suspend過程中產生wakeup event的話,就會終止suspend過程,wakeup_count記錄了wakeup source終止suspend過程的次數(如果發現系統總是suspend失敗,檢查一下各個wakeup source的該變數,就可以知道問題出在誰身上了)。
為了方便檢視系統的wakeup sources的資訊,linux系統在/sys/kernel/debug下建立了一個”wakeup_sources”檔案,此檔案記錄了系統的喚醒源的詳細資訊。
kernel/msm-4.4/drivers/base/power/wakeup.c
static int wakeup_sources_stats_show(struct seq_file *m, void *unused) { struct wakeup_source *ws; seq_puts(m, "name\t\tactive_count\tevent_count\twakeup_count\t" "expire_count\tactive_since\ttotal_time\tmax_time\t" "last_change\tprevent_suspend_time\n"); rcu_read_lock(); list_for_each_entry_rcu(ws, &wakeup_sources, entry) print_wakeup_source_stats(m, ws); rcu_read_unlock(); print_wakeup_source_stats(m, &deleted_ws); return 0; } static int wakeup_sources_stats_open(struct inode *inode, struct file *file) { return single_open(file, wakeup_sources_stats_show, NULL); } static const struct file_operations wakeup_sources_stats_fops = { .owner = THIS_MODULE, .open = wakeup_sources_stats_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static int __init wakeup_sources_debugfs_init(void) { wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources", S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops); return 0; } postcore_initcall(wakeup_sources_debugfs_init);
如下圖示,通過 cat /sys/kernel/debug/wakeup_sources
獲取 wakeup_sources
資訊:

機制
wakeup events framework中抽象了wakeup source和wakeup event的概念,向各個device driver提供wakeup source的註冊、使能等及wakeup event的上報、停止等介面,同時也向上層提供wakeup event的查詢介面,以判斷是否可以suspend或者是否需要終止正在進行的suspend。
pm_stay_awake()
當裝置有wakeup event正在處理時,需要呼叫該介面通知PM core,該介面的實現如下:
kernel/msm-4.4/drivers/base/power/wakeup.c
void pm_stay_awake(struct device *dev) { unsigned long flags; if (!dev) return; spin_lock_irqsave(&dev->power.lock, flags); __pm_stay_awake(dev->power.wakeup); spin_unlock_irqrestore(&dev->power.lock, flags); } void __pm_stay_awake(struct wakeup_source *ws) { unsigned long flags; if (!ws) return; spin_lock_irqsave(&ws->lock, flags); wakeup_source_report_event(ws); del_timer(&ws->timer); ws->timer_expires = 0; spin_unlock_irqrestore(&ws->lock, flags); } static void wakeup_source_report_event(struct wakeup_source *ws) { ws->event_count++; /* This is racy, but the counter is approximate anyway. */ if (events_check_enabled) ws->wakeup_count++; if (!ws->active) wakeup_source_activate(ws); }
pm_stay_awake中直接呼叫pm_stay_awake,\pm_stay_awake直接呼叫wakeup_source_report_event。
kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_report_event(struct wakeup_source *ws) { ws->event_count++; /* This is racy, but the counter is approximate anyway. */ if (events_check_enabled) ws->wakeup_count++; if (!ws->active) wakeup_source_activate(ws); }
wakeup_source_report_event中增加wakeup source的event_count次數,即表示該source又產生了一個event。然後根據events_check_enabled變數的狀態,增加wakeup_count。如果wakeup source沒有active,則呼叫wakeup_source_activate進行activate操作。
kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_activate(struct wakeup_source *ws) { unsigned int cec; /* * active wakeup source should bring the system * out of PM_SUSPEND_FREEZE state */ freeze_wake(); ws->active = true; ws->active_count++; ws->last_time = ktime_get(); if (ws->autosleep_enabled) ws->start_prevent_time = ws->last_time; /* Increment the counter of events in progress. */ cec = atomic_inc_return(&combined_event_count); trace_wakeup_source_activate(ws->name, cec); }
wakeup_source_activate中首先呼叫freeze_wake,將系統從suspend to freeze狀態下喚醒,然後設定active標誌,增加active_count,更新last_time。如果使能了autosleep,更新start_prevent_time,此刻該wakeup source會開始阻止系統auto sleep。增加“wakeup events in progress”計數,增加該計數意味著系統正在處理的wakeup event數目不為零,即系統不能suspend。
pm_relax()
pm_relax
和 pm_stay_awake
成對出現,用於在wakeup event處理結束後通知PM core,其實現如下
kernel/msm-4.4/drivers/base/power/wakeup.c
void pm_relax(struct device *dev) { unsigned long flags; if (!dev) return; spin_lock_irqsave(&dev->power.lock, flags); __pm_relax(dev->power.wakeup); spin_unlock_irqrestore(&dev->power.lock, flags); } void __pm_relax(struct wakeup_source *ws) { unsigned long flags; if (!ws) return; spin_lock_irqsave(&ws->lock, flags); if (ws->active) wakeup_source_deactivate(ws); spin_unlock_irqrestore(&ws->lock, flags); }
pm_relax中直接呼叫pm_relax,\pm_relax判斷wakeup source如果處於active狀態,則呼叫wakeup_source_deactivate介面,deactivate該wakeup source
kernel/msm-4.4/drivers/base/power/wakeup.c
static void wakeup_source_deactivate(struct wakeup_source *ws) { unsigned int cnt, inpr, cec; ktime_t duration; ktime_t now; ws->relax_count++; if (ws->relax_count != ws->active_count) { ws->relax_count--; return; } ws->active = false; now = ktime_get(); duration = ktime_sub(now, ws->last_time); ws->total_time = ktime_add(ws->total_time, duration); if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time)) ws->max_time = duration; ws->last_time = now; del_timer(&ws->timer); ws->timer_expires = 0; if (ws->autosleep_enabled) update_prevent_sleep_time(ws, now); /* * Increment the counter of registered wakeup events and decrement the * couter of wakeup events in progress simultaneously. */ cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count); trace_wakeup_source_deactivate(ws->name, cec); split_counters(&cnt, &inpr); if (!inpr && waitqueue_active(&wakeup_count_wait_queue)) wake_up(&wakeup_count_wait_queue); }
至此,本篇已結束,如有不對的地方,歡迎您的建議與指正。期待您的關注,
感謝您的閱讀,謝謝!
如有侵權,請聯絡小編,小編對此深感抱歉,同時小編會立即停止侵權行為。
歡迎關注微信公眾號:程式設計師Android
公眾號ID:ProgramAndroid
獲取更多資訊

微信公眾號:ProgramAndroid
我們不是牛逼的程式設計師,我們只是程式開發中的墊腳石。
我們不傳送紅包,我們只是紅包的搬運工。

點選閱讀原文,獲取更多福利
