赞
踩
android在2.6.**的内核代码中加入了earlysuspend和wakelock机制,而wakelock主要是为了避免睡眠流程和唤醒事件的冲突,如果睡眠流程和唤醒事件的冲突,会导致唤醒事件的丢失。比如:如果再向/sys/power/state中写入mem和唤醒事件同时发生,因为睡眠流程第一步就是冻结用户空间进程,如果用户空间进程已经冻结,那么这个唤醒事件就不可能传到用户空间,只有另外一个唤醒事件将系统唤醒,这个事件才能去处理。在手机里,如果这个事件是一个电话的话,这是不能接受的。
为了解决上面的问题,在内核收到事件后,会启动一个wakelock锁来阻止睡眠,这样睡眠失败。内核将事件传到用户空间后将wakelock解锁。
这样以来还是有问题,因为事件传到用户空间后,内核的wakelock释放,这样还会走睡眠流程,用户空间根本来不及处理这个事件。这样就要求有一种机制允许进程从内核读取事件的值,并且阻止/sys/power/state写值。这时候,这个进程出现了,这就是powermanager.处理事件的进程通过IPC与powermanager同步。但是这样还是有问题:比如,如果按下按键,键盘驱动检测到,并激活一个wakelock;键值已经注册,并且用户空间进程等待处理。用户空间进程激活IPC阻止powermanager向/sys/power/state写值,并且从内核读取键值,之后键盘驱动去除wakelock并等待下一个事件产生。同时,读取键值处理完之后,IPC使powermanager知道可以睡眠了,这样powermanger向/sys/power/state里写值,这时kernel中已经没有锁。如果这个时候另一个按键按下,在睡眠开始之前,键盘驱动激活wakelock,键值被用户空间读取,键盘驱动去除wakelock在键值被读取之后。而这时,睡眠流程正在走,注意到没有wakelock所,对他来说什么都没有发生。这样用户空间进程动作还没有做,就被冻结掉了。这样这个键值的处理只能另外的事件唤醒系统后。
这样替换IPC,android的开发人员想到了wakelock,这样用户空间也可以创建,释放wakelock,这样有wakelock后,就会避免上面的冲突了。
流程图见附件android.png
linux社区没有接受android相关wakelock的patch,原因:1,用户空间和内核空间都可以操纵wakelock,这样相当于用户空间直接操作内核空间的对象,破坏了用户空间和内核空间的独立性。2,拥有wakelock就不能进入睡眠,第三方的应用如果使用不好wakelock,将会对电池的续航造成很大影响。
linux的内核在处理省电方式上有两个模型,一个是system suspend,一个是pm runtime syspend。system suspend关注到整个系统层面的省电,即S状态;pm runtime syspend关注到设备的省电,即D状态。linux论坛曾有人认为将cpuidle和pm runtime合并起来可以替换system suspend,也有人认为应该将pm runtime和system suspend两个在内核中都使用。
2009年android的第一版代码基于的内核在2.6.27之前,这时候runtime PM还没有开发的很好,而且支持的硬件也比较少,目前有很多硬件设备支持了,比如USB子系统,PCI子系统,SCSI子系统,I2C子系统,MMC子系统,串口设备,MISC设备等等。
linux内核为了撇弃wakelock,在内核中加入了唤醒事件框架。这个框架主要定义个两个计数器:event_count处理完的唤醒事件的计数,event_in_process数据正在被处理的唤醒事件的计数。提供的相关接口如下:
pm_stay_awake:增加event_in_process计数,带有一个设备指针参数用来更新power.wakeup_count,在sysfs中,位置在/sys/devices/.../power/wakeup_count
大致对应于wakelock的锁定
pm_relax:减少event_in_process计数,同时增加event_count计数。去除pm_wakeup_event设置的定时器
大致对应于wakelock的解锁
pm_wakeup_event:增加event_in_process计数,同时建立一个定时器来减少event_in_process计数和增加event_count计数。第一个设备指针参数用来更新设备power.wakeup_count,第二个参数是减少event_in_process计数前等待的毫秒数。为设备对象上报唤醒事件。 大致对应pm_stay_awake加一个计时器在不久后做pm_relax动作
event_count计数的当前值,可以从/sys/power/wakeup_count来读取。写入event_count计数的值,将会保存在saved_count这个变量里。但是,写入成功的条件是写入的值必须和event_count的值相等。写入过程中,events_check_enabled被设置,它将告诉PM core在睡眠流程中使用event_in_process计数和event_count计数。PM core在睡眠流程中会调用pm_wakeup_pending来检查目前的计数。
在后来的改动中,引入了一个结构wakeup_source来表示能产生唤醒事件的实体。
device_set_wakeup_enable():第二个参数设置为true,唤醒源设备对象被创建并加入系统唤醒源的列表上。wakeup_source保存在设备的power.wakeup_source中。第二个参数设置为false,销毁唤醒源设备。
wakeup_source_create():创建唤醒源设备
wakeup_source_add():将唤醒源设备加入全局的唤醒源设备列表
wakeup_source_register():实现wakeup_source_create()和wakeup_source_add()
如果/sys/power/wakeup_count使用正确,内核就就可以根据唤醒源的值决定是放弃失眠还是进行睡眠。用户空间不能使用唤醒源,这样用户空间允许使用wakelock。
目前内核中文件wakelock.h的函数:
32 struct wake_lock {
33 struct wakeup_source ws;
34 };
35
36 static inline void wake_lock_init(struct wake_lock *lock, int type,
37 const char *name)
38 {
39 wakeup_source_init(&lock->ws, name);
40 }
41
42 static inline void wake_lock_destroy(struct wake_lock *lock)
43 {
44 wakeup_source_trash(&lock->ws);
45 }
46
47 static inline void wake_lock(struct wake_lock *lock)
48 {
49 __pm_stay_awake(&lock->ws);
50 }
51
52 static inline void wake_lock_timeout(struct wake_lock *lock, long timeout)
53 {
54 __pm_wakeup_event(&lock->ws, jiffies_to_msecs(timeout));
55 }
56
57 static inline void wake_unlock(struct wake_lock *lock)
58 {
59 __pm_relax(&lock->ws);
60 }
61
62 static inline int wake_lock_active(struct wake_lock *lock)
63 {
64 return lock->ws.active;
65 }
可以看到,之前的wake_lock实现已经变为__pm_stay_awake。
在marvell的代码中,可以关注system/core/libsuspend下的代码,里面有怎么去操纵wakeup_count。
还可以关注一下vendor/marvell/generic/power-hal/代码,里面有怎样去操纵runtime/control这个节点的。
查看marvell logcat log:
5133 I/PowerHAL( 488): power_set_interactive called with on 0
5134 I/PowerHAL( 488): power_set_for_device /sys/marvell/touch/power with on 0
5135 I/PowerHAL( 488): /sys/marvell/*touch*/power/control opened
5136 D/AlarmManagerService( 488): Set alarms of type 2
5137 E/Sensors ( 488): AccelerometerSensor: enable=0
5138 I/PowerHAL( 488): write auto done
5139 I/PowerHAL( 488): wait_for_set_complete done, status is suspended
5140 I/PowerHAL( 488): power_set_for_device /sys/bus/*i2c*/devices/2-005d/power with on 0
5141 W/PowerHAL( 488): /sys/bus/i2c/devices/2-005d/power/control doesn't exist
5142 I/PowerHAL( 488): power_set_for_device /sys/devices/platform/*mrvl8xxx_fm.0*/power with on 0
5143 I/PowerHAL( 488): /sys/devices/platform/mrvl8xxx_fm.0/power/control opened
5144 I/PowerHAL( 488): write auto done
5145 I/PowerHAL( 488): wait_for_set_complete done, status is suspended
5146 I/PowerHAL( 488): power_set_for_device /sys/devices/platform/*mmp-fb*/power with on 0
5147 W/PowerHAL( 488): /sys/devices/platform/mmp-fb/power/control doesn't exist
5148 I/PowerHAL( 488): power_set_for_device /sys/devices/platform/*pxa168-fb.0*/power with on 0
5149 I/PowerHAL( 488): /sys/devices/platform/pxa168-fb.0/power/control opened
5150 I/PowerHAL( 488): write auto done
5151 I/PowerHAL( 488): wait_for_set_complete done, status is suspended
5152 I/PowerHAL( 488): power_set_for_device /sys/devices/platform/*pxa168-fb.1*/power with on 0
5153 I/Sensors ( 488): AccelerometerSensor interval: 66667000 ns, 66 ms
5154 W/PowerHAL( 488): /sys/devices/platform/pxa168-fb.1/power/control doesn't exist
5155 I/PowerHAL( 488): power_set_for_device /sys/devices/platform/*galcore*/power with on 0
5156 I/PowerHAL( 488): /sys/devices/platform/galcore/power/control opened
5157 I/PowerHAL( 488): write auto done
5158 I/PowerHAL( 488): wait_for_set_complete done, status is suspended
睡眠前将control设置为auto,设备可以进入睡眠状态;唤醒前将control设置为on,始终保持在active状态。
MTK代码目前没有支持runtime PM.
在ICS上,当暗屏/亮屏时,会调用powerManagerService的setPowerState,最终会调到 hardware/libhardware_legacy/power/power.c下的set_screen_state,在这个函数中,通过向/sys/power/state中写入 “mem”或"on",让系统进入early suspend。进入early suspend后,若没人持有wakelock,则系统即进入suspend状态了。
再看下Jellybean的代码,之前的power.c这个hal,已经不存在了。搜索源码,发现相关功能的实现,已经挪到 system/core/libsuspend下,而且实现方式也有所改变(PowerManagerService的调用方式还是一样):将功能模块命名的更合理了:以前叫screen_state, 现在叫auto suspend.方法调用过程也更易读易懂了:以前是通过 set_screen_state(1)和set_screen_state(0)来进入和退出suspend状态,现在改成了autosuspend_enable()和autosuspend_disable()
suspend策略更多了,发现共有三种策略:autosuspend_earlysuspend; autosuspend_autosleep和 autosuspend_wakeup_count。
现在每次调用autosuspend功能时,都尝试着选择三种策略的一种来使用:
30 static int autosuspend_init(void)
31 {
32 if (autosuspend_inited) {
33 return 0;
34 }
35
36 autosuspend_inited = true;
37
38 ALOGV;
39 autosuspend_ops = autosuspend_earlysuspend_init();
40 if (autosuspend_ops) {
41 goto out;
42 }
43 ALOGV;
44
45 autosuspend_ops = autosuspend_autosleep_init();
46 if (autosuspend_ops) {
47 goto out;
48 }
49 ALOGV;
50
51 autosuspend_ops = autosuspend_wakeup_count_init();
52 if (autosuspend_ops) {
53 goto out;
54 }
55
56 if (!autosuspend_ops) {
57 ALOGE;
58 return -1;
59 }
60
61 out:
62 ALOGV;
63 return 0;
64 }
这里的autosuspend_earlysuspend,和ICS上的suspend,也就是power HAL上的实现是一致的,即early suspend。
从这个初始化代码,也可以看到,三种策略的选择是 earlysuspend > autosleep > wakeup_count,
目前marvell支持autosleep:
546 V/libsuspend( 490): early suspend
547 W/libsuspend( 490): Error writing 'on' to /sys/power/state: Invalid argument
548 V/libsuspend( 490): auto sleep
549 I/libsuspend( 490): Selected autosleep
550 V/libsuspend( 490): autosuspend_autosleep_disable
551 V/libsuspend( 490): autosuspend_autosleep_disable done
552 V/libsuspend( 490): autosuspend initialized
553 V/libsuspend( 490): autosuspend_disable
。。。
2985 V/libsuspend( 495): autosuspend_enable
2986 V/libsuspend( 495): autosuspend_autosleep_enable
具体log看111
从marvell的kernel log来看:
996 <4>[ 52.794219] c0 1275 (AsyncTask #2) [<c0113e10>] (unwind_backtrace+0x0/0x11c) from [<c039644c>] (goodix_ts_runtime_suspend+0xc/0x140)
997 <4>[ 52.800109] c0 1275 (AsyncTask #2) [<c039644c>] (goodix_ts_runtime_suspend+0xc/0x140) from [<c033a6b4>] (pm_generic_runtime_suspend+0x2c/0x38)
998 <4>[ 52.800140] c0 1275 (AsyncTask #2) [<c033a6b4>] (pm_generic_runtime_suspend+0x2c/0x38) from [<c05cb04c>] (_rpm_callback+0x30/0x58)
999 <4>[ 52.800170] c0 1275 (AsyncTask #2) [<c05cb04c>] (_rpm_callback+0x30/0x58) from [<c033e934>] (rpm_suspend+0x304/0x554)
1000 <4>[ 52.800201] c0 1275 (AsyncTask #2) [<c033e934>] (rpm_suspend+0x304/0x554) from [<c033f900>] (_pm_runtime_suspend+0x30/0x78)
1001 <4>[ 52.800231] c0 1275 (AsyncTask #2) [<c033f900>] (_pm_runtime_suspend+0x30/0x78) from [<c033ab08>] (pm_generic_runtime_idle+0x20/0x50)
1002 <4>[ 52.800231] c0 1275 (AsyncTask #2) [<c033ab08>] (pm_generic_runtime_idle+0x20/0x50) from [<c05cb04c>] (_rpm_callback+0x30/0x58)
1003 <4>[ 52.800262] c0 1275 (AsyncTask #2) [<c05cb04c>] (_rpm_callback+0x30/0x58) from [<c033edb8>] (rpm_idle+0x1bc/0x2c0)
1004 <4>[ 52.800292] c0 1275 (AsyncTask #2) [<c033edb8>] (rpm_idle+0x1bc/0x2c0) from [<c033ef18>] (pm_runtime_allow+0x5c/0x68)
1005 <4>[ 52.800292] c0 1275 (AsyncTask #2) [<c033ef18>] (pm_runtime_allow+0x5c/0x68) from [<c033a4c8>] (control_store+0x60/0xac)
1006 <4>[ 52.800323] c0 1275 (AsyncTask #2) [<c033a4c8>] (control_store+0x60/0xac) from [<c0332e48>] (dev_attr_store+0x18/0x24)
1007 <4>[ 52.800354] c0 1275 (AsyncTask #2) [<c0332e48>] (dev_attr_store+0x18/0x24) from [<c021fc94>] (sysfs_write_file+0x100/0x148)
1008 <4>[ 52.800384] c0 1275 (AsyncTask #2) [<c021fc94>] (sysfs_write_file+0x100/0x148) from [<c01d1740>] (vfs_write+0xa8/0x130)
1009 <4>[ 52.800415] c0 1275 (AsyncTask #2) [<c01d1740>] (vfs_write+0xa8/0x130) from [<c01d1990>] (sys_write+0x34/0x68)
1010 <4>[ 52.800445] c0 1275 (AsyncTask #2) [<c01d1990>] (sys_write+0x34/0x68) from [<c010e440>] (ret_fast_syscall+0x0/0x30)
这里和上面的logcat正好相符,/sys/marvell/*touch*/power/control opened对应内核中的control_store,从而最终调用到driver的runtime suspend。
再看一下展讯的logcat:Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。