登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

wonview的博客

 
 
 

日志

 
 

Wakelock机制分析---从JNI层到内核层  

2011-06-01 10:24:56|  分类: embedded linux |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

wakelock机制分析---从JNI层到内核层

JNI层通过操作设备
JNI层通过象/sys/power/state写"mem",来实现系统挂起的操作。下面详细分析其流程。
#
# cd /sys/power
# ls
state
pm_test
wake_lock
wake_unlock
wait_for_fb_sleep
wait_for_fb_wake
# echo mem >state
[DVK8000] ---------File: kernel/power/main.c, Line: 166, Function: state_store
[DVK8000] ---------File: kernel/power/earlysuspend.c, Line: 156, Function: request_suspend_state
[DVK8000] ---------File: kernel/power/earlysuspend.c, Line: 83, Function: early_suspend
[DVK8000] ---------File: kernel/power/wakelock.c, Line: 499, Function: wake_unlock //在wake_unlock里面,如果没有其他锁了,则调用suspend_work:static DECLARE_WORK(suspend_work, suspend); 一个工作队列.

[DVK8000] ---------File: kernel/power/wakelock.c, Line: 273, Function: suspend
[DVK8000] ---------File: kernel/power/suspend.c, Line: 314, Function: pm_suspend
[DVK8000] ---------File: kernel/power/suspend.c, Line: 274, Function: enter_state
[DVK8000] ---------File: kernel/power/suspend.c, Line: 92, Function: suspend_prepare
[DVK8000] ---------File: kernel/power/suspend.c, Line: 205, Function: suspend_devices_and_enter
Suspending console(s) (use no_console_suspend to debug)

自此,console挂起,没有调试信息可以看到了。下面的流程是:
总的调用流程是:
suspend_devices_and_enter
suspend_console
suspend_test_start
dpm_prepare
device_prepare
dpm_suspend
device_suspend
suspend_enter
在suspend_devices_and_enter------>dpm_suspend_start中完成了设备的suspend之后,再进入suspend_enter,在这里真正让CPU休眠,也就是真正进入休眠的动作。


下面具体分析:

//file: kernel/power/suspend.c
int suspend_devices_and_enter(suspend_state_t state)
{
int error;
kkdbg();

if (!suspend_ops)
return -ENOSYS;

if (suspend_ops->begin) {
error = suspend_ops->begin(state);
if (error)
goto Close;
}
suspend_console();
suspend_test_start(); //一个测试系统休眠唤醒过程是否有异常的方法。
error = dpm_suspend_start(PMSG_SUSPEND); // 开始挂起,下面会分析这个函数。
if (error) {
printk(KERN_ERR "PM: Some devices failed to suspend\n");
goto Recover_platform;
}
suspend_test_finish("suspend devices"); //如果只是测试设备的挂起,则不真正挂起,而是将系统恢复。
if (suspend_test(TEST_DEVICES))
goto Recover_platform;

suspend_enter(state); // 真正进入挂起。下面分析这个函数

Resume_devices:
suspend_test_start();
dpm_resume_end(PMSG_RESUME);
suspend_test_finish("resume devices");
resume_console();
Close:
if (suspend_ops->end)
suspend_ops->end();
return error;

Recover_platform:
if (suspend_ops->recover)
suspend_ops->recover();
goto Resume_devices;
}

suspend_test_start和suspend_test_finish。
代码kernel/power/suspend_test.c
系统通过设置一个RTC的唤醒闹钟来测试系统的挂起部分代码的运行情况。
当挂起发生,正在挂起的设备不会占用很长时间,有些系统只需要几ms。
尽管所占用的时间与相应平台有关,不过当我们在系统bootup时,我们允许更长的时间。所以这个时间是粗略设置的。这里我们预设为10s。
在suspend_test_start里,保存当前jiffies,在suspend_test_finish里计算suspend所用时间。
如果超过预设的时间,则会有一个warning。在休眠或者唤醒的过程 ,这种warning常意味着系统有性能问题。


下面分析dpm_suspend_start,代码路径转到/drivers/base/power/main.c
Prepare all non-sysdev devices for system PM transition and execute "suspend" callbacks for them.
{
int error;

might_sleep(); //如果定义了kernel_hacking里面的CONFIG_DEBUG_SPINLOCK_SLEEP,则这个宏用于调试sleep相关bug
error = dpm_prepare(state);
if (!error)
error = dpm_suspend(state);
return error;
}

dpm_prepare
dpm_prepare - Prepare all non-sysdev devices for a system PM transition.

static int dpm_prepare(pm_message_t state)
{
struct list_head list;
int error = 0;
kkdbg();

INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
transition_started = true;
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.next); //从dpm_list里面得到设备节点
get_device(dev);

dev->power.status = DPM_PREPARING;
mutex_unlock(&dpm_list_mtx);

pm_runtime_get_noresume(dev); //dev->power.usage_count 引用计数加1,避免系统自动释放这个设备的资源
if (pm_runtime_barrier(dev) && device_may_wakeup(dev)) {
/* Wake-up requested during system sleep transition. */
pm_runtime_put_noidle(dev); // 设备无法休眠
error = -EBUSY;
} else {
error = device_prepare(dev, state); // 设备做休眠前的准备
}

mutex_lock(&dpm_list_mtx);
if (error) {
dev->power.status = DPM_ON;
if (error == -EAGAIN) {
put_device(dev);
error = 0;
continue; // retry
}
printk(KERN_ERR "PM: Failed to prepare device %s "
"for power transition: error %d\n",
kobject_name(&dev->kobj), error);
put_device(dev);
break;
}
dev->power.status = DPM_SUSPENDING;
if (!list_empty(&dev->power.entry))
list_move_tail(&dev->power.entry, &list);
put_device(dev);
}
list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
return error;
}

pm_runtime_barrier(dev)
判断这个设备是否有未完成的PM相关操作(在dev->power.wait_queue)或者是有resume的请求,

device_may_wakeup(dev)
判断这个设备是否可以被唤醒
can_wakeup为1表明一个设备可以被唤醒,设备驱动为了支持Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup.
should_wakeup则是在设备的电源状态发生变化的时候被device_may_wakeup()用来测试,测试它该不该变化.
因此can_wakeup表明的是一种能力
should_wakeup表明的是有了这种能力以后去不去做某件事。

device_prepare - Prepare a device for system power transition.Execute the ->prepare() callback(s) for given device
这个函数执行设备的prepare的回调函数,处理在挂起之前的prepare的工作。


dpm_suspend:Execute "suspend" callbacks for all non-sysdev devices.

static int dpm_suspend(pm_message_t state)
{
struct list_head list;
int error = 0;

kkdbg();

INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list)) {
struct device *dev = to_device(dpm_list.prev); //遍历链表,获取dpm_list上的设备

get_device(dev);
mutex_unlock(&dpm_list_mtx);

dpm_drv_wdset(dev); //Sets up driver suspend/resume watchdog timer.为了保证设备挂起的异常出现,使用watchdog确保之。
error = device_suspend(dev, state);
dpm_drv_wdclr(dev);

mutex_lock(&dpm_list_mtx);
if (error) {
pm_dev_err(dev, state, "", error);
put_device(dev);
break;
}
dev->power.status = DPM_OFF;
if (!list_empty(&dev->power.entry))
list_move(&dev->power.entry, &list);
put_device(dev);
}
list_splice(&list, dpm_list.prev);
mutex_unlock(&dpm_list_mtx);
return error;
}

这里device_suspend 的作用是Execute "suspend" callbacks for given device.就是设备驱动编写过程中需要实现的部分。


dpm_suspend_start之后,系统进入suspend_enter,开始处理CPU的休眠:
static int suspend_enter(suspend_state_t state)
{
int error;
kkdbg();

if (suspend_ops->prepare) {
error = suspend_ops->prepare();
if (error)
return error;
}

error = dpm_suspend_noirq(PMSG_SUSPEND);
if (error) {
printk(KERN_ERR "PM: Some devices failed to power down\n");
goto Platfrom_finish;
}

if (suspend_ops->prepare_late) {
error = suspend_ops->prepare_late();
if (error)
goto Power_up_devices;
}

if (suspend_test(TEST_PLATFORM))
goto Platform_wake;

error = disable_nonboot_cpus();
if (error || suspend_test(TEST_CPUS))
goto Enable_cpus;

arch_suspend_disable_irqs();
BUG_ON(!irqs_disabled());

error = sysdev_suspend(PMSG_SUSPEND);
if (!error) {
if (!suspend_test(TEST_CORE))
error = suspend_ops->enter(state);
sysdev_resume();
}

arch_suspend_enable_irqs();
BUG_ON(irqs_disabled());

Enable_cpus:
enable_nonboot_cpus();

Platform_wake:
if (suspend_ops->wake)
suspend_ops->wake();

Power_up_devices:
dpm_resume_noirq(PMSG_RESUME);

Platfrom_finish:
if (suspend_ops->finish)
suspend_ops->finish();

return error;
}

至此,系统的挂起处理完成。


驱动需要实现的电源管理工作:

从上面分析可知,设备的挂起动作最终会调用设备驱动的pm->suspend.所以在设备驱动程序编写时,需要添加其电源管理的接口:
struct dev_pm_ops xxx_pm = {
//................
} ;
device_driver->pm = &xxx_pm;


驱动编程里wakelock的使用:
wakelock是为了锁住系统,除了极少部分用户相关和应用程序相关的 wakelock需要在用户空间实现其策略之外,大部分设备在使用时都可以自己acquire wakelock,保证自己在on状态;设备不需要时,在release之。当然,这个需要驱动工程师的实现。

内核里,使用三个list来管理所有的wakelock:
一个是inactive_locks,用来管理所有inactive的wakelock;
另外两个是active的wakelock的list,分别是WAKE_LOCK_SUSPEND和WAKE_LOCK_IDLE type的wakelock,放在 active_wake_locks 数组。

驱动使用wakelock的流程一般如下:


1、一般会在定义设备结构体(通常是struct device_drvier的 driver_private),添加一个struct wake_lock的成员;
2、在设备init的时候,调用函数 wake_lock_init 初始化wakelock,此时这个wakelock挂在inactive_locks里;
3、在需要设备保持on的时候,使用wake_lock锁住CPU的on状态(禁止sleep),这个wakelock会被转到active_wake_locks里。
在不需要使用这个设备的时候,记得将其wake_unlock,再次放到inactive_locks .比如在打开audio设备,做AUDIO_START时,用wake_lock禁止休眠,在AUDIO_STOP时,用wake_unlock解锁。
4、设备exit的时候,使用wake_lock_destroy将其从wakelock的链表里面移除。


对android的电源管理的总结

传统的linux下的设备电源管理实现是:用户空间由一个守护进程来负责,使用timeout来关屏和休眠,内核需要做的就是提供设备关断、背光调整、关机、休眠的调用接口(ioctl)和CPU的动态调整频率的接口。
应用程序需要对电源管理参与较多:背光控制、显示设备开关、输入设备开关,设置应用的性能等级(performance level, 用于设置CPU的频率)。

android的电源管理实现如下:
应用编程只需要关注与自己相关的事情,包括是否需要显示设备、输入设备、背光、CPU和无线模块等保持on状态,并用wakelock来保持这种状态。应用程序只需要申请和释放锁,内核的wakelock机制会check有没有锁,来决定应该休眠,并执行设备休眠的动作。
其他的关机、重启、休眠、电压通道、调频、调压、性能与频率动态自适应等省电处理与java应用层无关了,这都交给内核层和电源管理守护进程来负责。

将电源管理相关的事情分为两部分,TI平台无关的,侧重应用开发的,包括:
应用编程使用wakelock的电源管理;
应用编程的关机、重启、休眠的功能;

TI平台相关的,包括:
TPS65950的DC/DC、LDO的控制输出;
OMAP3530的时钟电源管理;
开机、重启、休眠、休眠唤醒的上电时序和状态锁存;
电源域的管理:电源域休眠、状态锁存、唤醒恢复、电源域间依赖的处理;
根据性能需要的动态频率调整和自适应电压调整。

目前,通过验证JNI层提供的功能,已经清楚了应用编程中所需的电源管理的处理。
android的应用编程与电源管理相关的部分只有以上分析的几个地方:wakelock和休眠、关机、重启,且被JNI层隔开,流程也已经分析完毕,相对比较孤立。
其他的大部分电源管理的工作都是在内核空间实现的。为了便于测试,我打算使用android内核,跑一个和android的应用和服务无关的根文件系统,在字符界面测试以上工作。

下一步工作安排:

在字符界面测试内核的电源管理功能。

wifi的wakelock和背光调整。

  评论这张
 
阅读(2441)| 评论(1)

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018