极力推荐Android 开发大总结文章:欢迎收藏
程序员Android 力荐 ,Android 开发者需要的必备技能
本篇文章主要介绍 Android
开发中的电源管理部分知识点,本篇文章转载网络,通过阅读本篇文章,您将收获以下内容:
- Sleep/Suspend
- SPM
- wakeup 唤醒源
原文地址:http://www.robinheztto.com/2017/04/20/android-power-basic/
1. Sleep/Suspend
系统休眠Sleep,Linux Kernel中称作Suspend。系统进入Suspend状态确切来说时CPU进入了Suspend模式,因为对整个系统来说CPU休眠是整个系统休眠的先决条件。CPU Suspend即CPU进入Wait for interrupt状态(WFI),SW完全不跑了,停在suspend workqueue里面。
Android系统从灭屏到系统进入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时钟有没有关,只需要看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图(来自蜗窝科技)
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); }
至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!