Windows CE的电源管理之一
Windows CE的基本电源管理功能
在所有版本的Windows CE操作系统中,图形、视窗和事件子系统(GWES)在电源管理方面都发挥了关键作用。这是因为早期版本的电源管理功能是由用户的活动所驱动的,而GWES负责处理所有用户的输入,如键盘、鼠标和触摸屏。GWES设置定时器监控用户的活动,当一段时间内用户没有任何输入时,便使系统进入休眠状态。通过注册表可以设置这几个定时器的超时值,它们可以分别被用于电池供电或外部电源供电时。当然,通过注册表也可以禁用GWES的电源管理功能,它在Windows CE.NET以后的版本中是默认被禁用的,这有利于电源管理器的集中管理。
图1 Windows CE基本的电源转换流程
No Power | 既没有电池也没有外部电源供电. |
On | 所有设备上电的常规运行状态. |
Suspend | 休眠状态,这时大部分设备关闭,仅RAM(自刷新)和外部时钟运行. |
Idle | 空闲状态,这时可停止CPU的运行. |
Critical off | 电池电压过低的状态. |
Power-on reset | 系统清空RAM并初始化文件系统. |
Cold boot | First application of power, for example, when a backup battery is installed. |
Warm boot | 软启(热启动),清空RAM并返回运行(On)状态. |
On-to-Idle | 从全速运行状态到空闲状态的转换. |
Idle-to-On | 处理器从低功耗状态回到全速运行状态. |
On-to-Suspend | 由于某些事件的触发,处理器转换到停止运行状态。并调用设备驱动函数 XXX_PowerDown. |
Suspend-to-On | 由特定的唤醒事件触发,处理器从停止状态返回到全速运行态。并调用设备驱动函数XXX_PowerUp. |
On-to-Critical off | 当电池电压过低时转换到Critical off状态. |
上图是Windows CE系统基本的电源状态转换策略,对应有5种系统电源状态(等级):No Power, On, Suspend, Idle, Critical off。相关描述和转换方式参见上表。
基本的电源管理功能所采用的节能方法是使系统适时的进入休眠状态,当下面的一种事件发生时,系统将进入休眠状态(SUSPEND):
l 用户按下On/Off按钮;
l 监控用户活动的定时器超时;
l 应用程序调用API,如GwesPowerOffSystem或SetSystemPowerState。
当下面的一种事件发生时,系统将退出休眠状态:
l 用户再次按下On/Off按钮;
l 发生某个警告事件,如某个日期或时间定时器的到时提醒;
l 发生某个唤醒事件,由外设如串口设备或者网卡触发中断来唤醒系统。
虽然通过用户操作、应用程序或者外设都可以使系统进入或者退出休眠状态,但基本的电源管理功能所能控制的粒度过大,对应于CPU只有三种状态:On,Idle和Suspend,对应于所有外设只有两种状态:On和Suspend。而且,当系统进出休眠状态时,应用程序都得不到任何通知。
Windows CE的高级电源管理功能
加入了电源管理组件的Windows CE具有高级的电源管理功能,它允许每个外设具有自己的电源状态,有别于一般的系统电源状态(System Power State),被称作设备电源状态(Device Power State)。现在应用程序有能力设置个别外设的电源状态,比如一个文件传输程序,在保持串口或者蓝牙端口正常通讯时,可以关闭显示屏幕和背光。这就为实现更高级别的动态电源管理提供了可能。
我们可以通过注册表任意设定一组系统电源状态,使其对应于我们设计的状态模型。对于设备电源状态则没有这么大的灵活度,它具有5个设备状态:
D0:Full on;D1:Low on;D2:Standby;D3:Sleep;D4:Off
当定义好系统电源状态,并为每个外设分配了设备电源状态后,通过注册表,我们可以将两者进行映射。在某个系统电源状态下,比如一个电池供电的系统,当电池电量已经少于50%时,显示屏幕和背光可能处于D1状态,而网络设备可以设置为D3状态。也就是说,在同一时刻,不同的外设可能处于不同的设备电源状态中。这样的灵活性意味着每个设备可以最小程度的消耗电池资源。
如图2所示,电源管理器实现为一个名为Pm.dll的动态链接库,电源管理接口分为应用程序和驱动程序两部分。驱动程序通过DeviceIoControl服务例程来处理电源管理器发来的设备电源状态改变请求,另外电源管理器通过消息队列通知应用程序电源相关的事件。
为了获得高级电源控制的功能,必须通过Platform Builder将电源管理组件编译到内核镜像中。实现的源代码参见{WINCEROOT}/Private/Winceos/Coreos/Device/PMIF/pmif.c,这段代码只是一个封装,它会调用{WINCEROOT}/Public/Common/Oak/Drivers/PM中的组件。参考这个实现OEM可以根据需要设计自己的电源管理策略。
下一篇将分三个部分来解读WinCE的电源管理,首先从系统平台开发的角度,这部分一般由OEM厂商负责实现,然后介绍电源管理的接口,分别从设备驱动和应用程序的角度介绍如何应用电源管理。
OEM Adaptation Layer(OAL)是一层与硬件平台相关的代码,它在电源状态转换中扮演着重要的角色。首先必须实现的是以下几个与硬件相关的函数:
OEMInit:初次上电时(或在冷启后)被调用,一般在这个函数中处理一些重要的初始化工作,如初始化系统内存,建立调试环境,设置系统中断等;
OEMIdle:没有线程可调度运行时被内核调用,这时可将CPU置于低功耗状态,并保证其可以快速返回运行状态;
OEMPowerOff:系统进入休眠(Suspend)状态前被调用,它即是系统进入休眠状态前被调用的最后一个函数,也是在系统被唤醒后所执行的第一个函数(中断处理程序以外);
Interrupt Handler:一些用来唤醒系统的中断处理程序。当中断发生时,内核首先调用中断处理程序,其中一些中断是可以将系统从睡眠状态中唤醒的,但中断时间内能处理的事情非常少,在中断处理程序里大部分API也是不能被调用的。
高级的电源管理功能允许任何设备驱动将其中断作为系统的唤醒源,在版本Windows CE.NET 4.1以前,OAL掌管着所有的中断处理,这就意味着在生成内核镜像以后,我们就无法在系统中添加新的中断处理程序了。现在即使这个设备是在运行时才安装到系统中来的,比如PCMCIA或者CF接口的无线网卡,它可以通过调用API(LoadIntChainHandler和FreeIntChainHandler)实现中断处理程序的安装和卸载。在系统中添加了新的中断处理程序后,它就可以通过内核IOCTL代码(IOCTL_HAL_ENABLE_WAKE)实现其作为系统的唤醒源。
位于操作系统内核层的电源管理策略,也要为设备驱动及上层应用程序提供接口,通过PowerPolicyNotify向电源管理组件发送事件请求,设备及应用程序就可以参与到系统的电源管理策略中来。
2. 设备驱动的电源管理
我们可以为设备驱动添加电源管理功能,首先就是要在驱动程序中添加电源管理的IOCTL代码,然后通知电源管理器该驱动是支持电源管理的,最后在驱动中编码实现该设备支持的几种电源状态。
2.1 建立支持电源管理的设备驱动
为了建立一个能够对设备进行电源管理的驱动程序,我们必须首先建立一个支持non-COM-related设备接口的驱动程序。non-COM-related设备接口标明这个设备是支持电源管理的。可以用以下方式建立这种接口:
l 可以在注册表中,用激活设备所用的IClass值定义接口;
l 可以在驱动程序的Init函数中,设置注册表中的IClass值;
l 可以使用ActivateDeviceEx的参数REGINI设置IClass值;
l 可以在驱动程序中显示地调用AdvertiseInterface函数。
电源管理器通过IOCTL代码来和驱动通信。通常情况下,当一个驱动程序声明为支持电源管理时,驱动只需要在DeviceIoControl中实现电源的管理即可。下面是电源管理器用来与驱动通信的IOCTL代码:
l IOCTL_POWER_CAPABILITIES:代表电源管理器请求设备驱动返回设备支持的电源状态及相关特征;
l IOCTL_POWER_SET:请求驱动更新设备的电源状态;
l IOCTL_POWER_QUERY:电源管理器询问设备是否准备好进行状态切换;
l IOCTL_POWER_GET:请求驱动返回当前设备的电源状态;
l IOCTL_REGISTER_POWER_RELATIONSHIP:通知父设备注册所有它所控制的设备。
其中IOCTL_POWER_CAPABILITIES和IOCTL_POWER_SET是支持电源管理的设备驱动必须实现的。
2.2 IOCTL代码的实现
在设备自举的时候,设备驱动必须使设备处于D0状态,当电源管理器通过IOCTL_POWER_CAPABILITIES向设备发出查询时,设备驱动应该尽可能详细的报告该设备的电源管理能力,以便将自己纳入到系统的电源管理策略中去。在自举完成后,电源管理器可以根据管理策略,调用IOCTL_POWER_SET调整设备的电源状态。在设备自我管理电源的情况下,设备应该通过DevicePowerNotify函数请求系统改变它们的电源状态。在实现对IOCTL_POWER_SET支持时,设备驱动开发应该注意以下几点:
l 设备并不一定具备所有五种设备电源状态,但至少可以工作在D0状态;
l 电源管理器可能会要求设备进入任何设备电源状态,并不仅仅是设备声明支持的几个。
l 如果一个设备被要求进入一个它并不支持的电源状态,它就会进入另一个它支持的更高功耗的状态。例如,一个设备并不支持D2,它会被要求进入D1。
l 电源管理器可能会通过发出IOCTL_POWER_SET,使设备再次进入它已经处于的当前状态。在这种情况下,设备驱动程序简单的返回成功即可。
l 设备的电源状态不一定与系统的电源状态同步,因为它可能受到应用程序需求的限制。
2.3 休眠和唤醒的处理
支持电源管理的流设备驱动通过XXX_PowerDown和XXX_PowerUp接收系统休眠和唤醒的通知,这些通知在内核调用OEMPowerOff之前发出,并处于中断上下文中。
设备驱动的开发者必须清楚,在系统休眠期间,设备应该处于何种状态。并不是所有的设备在这个时候都应该强制被关闭,比如在音频设备可以不依赖处理器来播放音乐时,在系统休眠期间它的状态就应该交给电源管理器和应用程序来处理。而如果这个音频设备的播放需要处理器频繁的工作,在系统进入休眠状态时,驱动程序应该果断的关闭设备的电源,即使该设备由于应用程序的请求不处于D4状态。
另外,设备的D3状态值得特殊考虑,因为这个状态不仅仅与设备的功耗级别相关,处于D3状态的设备还被允许将系统从休眠状态中唤醒。所以当我们开发支持电源管理的设备驱动时需要注意以下几点:
l 支持唤醒系统的设备不应该主动通过DevicePowerNotify请求进入D3状态,因为一般情况下,只有当系统打算进入休眠状态时,电源管理器才将设备作为唤醒源启用。从代码的角度也应该避免这一点,因为驱动程序无法区分来自IOCTL_POWER_SET中对D3状态的请求是由它自己还是电源管理器发起的;
l 如果有必要,能唤醒系统的设备可以定义一样的D2和D3状态,而只有D3具有启动唤醒的功能;
l 支持D3的设备不一定具有将系统从休眠状态唤醒的功能,不能将系统唤醒的设备,但它又具有低功耗模式,是可以自己主动请求进入D3状态的;
l 如果不能唤醒系统的设备处于D3状态,在系统进入休眠状态时,它应该在XXX_PowerDown中将状态转换为D4,并且在XXX_PowerUp中恢复到D3状态;如果无法这么做,它就不应该支持D3状态,而是在请求进入D3时直接进入D4状态;
做到了以上几点,在系统进入休眠状态时,OEM和应用程序开发人员就可以请求将设备进入D3状态,而不必关心这个设备是否支持唤醒系统。
3. 应用程序的电源管理
电源管理组件提供了一组接口供应用程序参与到电源管理的活动中,应用程序可以通过RequestPowerNotifications函数请求电源管理器向其发送电源相关的通知,也可以通过SetPowerRequirement通知电源管理器将设备设置在特殊的电源状态下。这样,指定设备的电源状态就不会随系统电源状态的改变而改变。
3.1 电源通知机制
电源相关的通知通过消息队列传递给应用程序,通常应用程序新建一个消息队列,并通过RequestPowerNotifications将这个消息队列的句柄传递给电源管理器,同时创建一个线程侦听来自这个队列的消息。电源管理器定义了如下几种通知:
l PBT_RESUME:当系统从休眠状态被唤醒是产生;
l PBT_POWERSTATUSCHANGE:当系统接入或者断开外部电源时产生;
l PBT_TRANSITION:当电源管理器执行系统电源状态转换时发生;
l PBT_POWERINFOCHANGE:当电池信息更新时发生。
3.2 电源请求机制
电源请求机制为应用程序提供了强大的能力控制电源管理器调整设备的电源等级,与其他所有的电源设置相比,它具有很高的优先级。举例来说,假设有一个条形码阅读器连接在COM1端口,并且COM1只有在最高电源等级(D0)时才能驱动这个条形码阅读器。为了使其正常工作,应用程序将调用SetPowerRequirement把COM1指定D0状态。假设之后串口驱动自身决定降低一个电源等级,驱动调用DevicePowerNotify通知电源管理器它期望的设备电源状态,驱动程序的这个请求将不起作用,直到应用程序调用ReleasePowerRequirement为止。继续这个例子,假设这时的系统电源状态转换为低能耗等级,虽然与之相关的COM1电源等级为D3,由于应用程序的电源请求,COM1将继续维持在D0状态。
在调用SetPowerRequirement函数时,指定POWER_FORCE标志将强制设备不进入休眠状态,即使这时系统已处于休眠状态。
3.3 设置系统电源状态
在某些应用的场合下,应用程序可能需要改变系统的电源状态。OEM通过注册表定义了系统支持的电源状态, 应用程序可以通过GetSystemPowerState返回当前系统电源状态的名称,也可以通过SetSystemPowerState改变系统的电源状态。
本篇将以Windows Mobile为例介绍Windows CE电源管理的实现,大体上,Windows Mobile分为Pocket PC和Smartphone两种版本。这两者之间的主要区别在于触摸屏和电源模型,Smartphone采用的是“Always On”模型。为了说清楚它们的区别,我们就先从系统电源状态说起吧(这里有些系统电源状态是从WM5开始才有的)。
1. Windows Mobile的系统电源状态
可以通过注册表查看系统电源状态对应的具体设备的电源状态,[HLM/System/CurrentControlSet/Control/Power/State]。
现在我们知道,Smartphone没有真正的睡眠模式,即使它会在一段时间后关闭背光和屏幕,但它并没有睡着,只是休息一下眼睛罢了,它的大脑和四肢仍在正常工作。PocketPC所采用的模型比Smartphone要复杂的多,你可以按下电源键让系统睡眠,在必要时,也可以唤醒系统做一些工作然后再继续睡眠。如果你在Smartphone上运行一个桌面精灵之类的程序,她为了引起你的注意,长时间的蹦啊跳啊,不管白天还是黑夜,可想而知,你的待机时间将......
你可能会觉得PocketPC的“Sleep”模型比Smartphone的“Always On”模型要省电,其实恰恰相反。因为在系统睡眠的过程中,它需要通知所有的设备驱动,为了让它们保存一些重要的信息并关闭相应的硬件设备,在系统被唤醒时也需要通知它们恢复先前的工作。这个过程不仅耗时还可能会耗更多的电,因为一些设备在频繁的状态转换过程中会消耗比较多的能量。这也就是为什么当你收到一条短信时,睡眠状态的PocketPC要花3到6秒的时间来处理,而Smartphone只需要几个微秒:)
2. Windows Mobile的电源管理策略
我们可以用系统电源状态机来简单的描述Windows Mobile的电源管理策略,以PocketPC为例,系统电源状态机如下图所示:
系统内部的电源管理器负责协调电源状态的转换,电源状态的转换主要由一下几种方式触发:
3. Windows Mobile电源管理相关API的应用
最后,通过几个应用场景简单介绍一下常用的电源管理相关的API的使用: