極力推薦Android 開發大總結文章:歡迎收藏
程序員Android 力薦 ,Android 開發者須要的必備技能java
本篇文章主要介紹 Android
開發中的電源管理部分知識點,本篇文章轉載網絡,經過閱讀本篇文章,您將收穫如下內容:node
- Sleep/Suspend
- SPM
- wakeup 喚醒源
原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/
linux
系統休眠Sleep,Linux Kernel中稱做Suspend。系統進入Suspend狀態確切來講時CPU進入了Suspend模式,由於對整個系統來講CPU休眠是整個系統休眠的先決條件。CPU Suspend即CPU進入Wait for interrupt狀態(WFI),SW徹底不跑了,停在suspend workqueue裏面。
Android系統從滅屏到系統進入Suspend的大致流程框架以下:
相關代碼以下:android
/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/
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時鐘有沒有關,只須要看SCLKENA信號有沒有關閉,而SPM對這個信號的輸出以及子系統的信號輸入,都記錄在SPM的寄存器裏面,這個就是咱們經過log排查的依據。
相關代碼以下:微信
/kernel-x.x/drivers/misc/mediatek/base/power/spm_vx/
主要涉及如下代碼網絡
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」的概念,即逮到機會就睡直到下次被喚醒,以適應移動設備的使用特色。app
早期,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 framework的architecture圖(來自蝸窩科技)
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_info power; 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 int can_wakeup:1; ...... #ifdef CONFIG_PM_SLEEP ...... struct wakeup_source *wakeup; ...... #else unsigned int should_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_head entry; spinlock_t lock; struct wake_irq *wakeirq; struct timer_list timer; unsigned long timer_expires; ktime_t total_time; ktime_t max_time; ktime_t last_time; ktime_t start_prevent_time; ktime_t prevent_sleep_time; unsigned long event_count; unsigned long active_count; unsigned long relax_count; unsigned long expire_count; unsigned long wakeup_count; bool active:1; bool autosleep_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); }
至此,本篇已結束,若有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!