随着移动互联网的发展,对于电量要求越来越高了,例如手机操作系统需要尽可能的减少电量的消耗,到达移动设备用电时间比较久。在Windows设计之初就考虑到了这个问题,每个设备都有自己的电源状态。
例如当系统不使用的时候,整个操作系统就会挂起,将内存数据保存在文件中,这个时候整个系统就处于耗电极低的状态。本文来讨论一下Windows的电源管理。
我们在使用开始菜单关闭操作系统其实就是调用NtShutdownSystem
,然后这个函数调用NtSetSystemPowerState
设置系统的电源状态;接着这个函数继续调用PopGracefulShutdown
函数来关闭操作系统。然后PopGracefulShutdown
调用PopShutdownSystem
来关闭系统。最后,这个函数调用如下代码:
PopQuerySystemPowerStateTraverse
发送:IRP_MN_QUERY_POWER
请求IRPPopSetSystemPowerStateTraverse
发送IRP_MN_SET_POWER
如果是操作系统Standby将调用如下:
Status = NtSetSystemPowerState(PowerActionSleep,
PowerSystemSleeping1,
0);
整个设置设备电源状态的代码如下:
NTSTATUS
NTAPI
PopSetSystemPowerState(SYSTEM_POWER_STATE PowerState, POWER_ACTION PowerAction)
{
//...
IopInitDeviceTreeTraverseContext(&Context,
IopRootDeviceNode,
PopQuerySystemPowerStateTraverse,
&PowerContext);
Status = IopTraverseDeviceTree(&Context);
IopInitDeviceTreeTraverseContext(&Context,
IopRootDeviceNode,
PopSetSystemPowerStateTraverse,
&PowerContext);
IopTraverseDeviceTree(&Context);
//...
}
NTSTATUS
PopSetSystemPowerStateTraverse(PDEVICE_NODE DeviceNode,
PVOID Context)
{
//...
Status = PopSendSetSystemPowerState(TopDeviceObject,
PowerStateContext->SystemPowerState,
PowerStateContext->PowerAction);
//...
}
NTSTATUS
PopSendSetSystemPowerState(PDEVICE_OBJECT DeviceObject, SYSTEM_POWER_STATE SystemState, POWER_ACTION PowerAction)
{
KEVENT Event;
IO_STATUS_BLOCK IoStatusBlock;
PIO_STACK_LOCATION IrpSp;
PIRP Irp;
NTSTATUS Status;
KeInitializeEvent(&Event,
NotificationEvent,
FALSE);
Irp = IoBuildSynchronousFsdRequest(IRP_MJ_POWER,
DeviceObject,
NULL,
0,
NULL,
&Event,
&IoStatusBlock);
if (!Irp) return STATUS_INSUFFICIENT_RESOURCES;
IrpSp = IoGetNextIrpStackLocation(Irp);
IrpSp->MinorFunction = IRP_MN_SET_POWER;
IrpSp->Parameters.Power.Type = SystemPowerState;
IrpSp->Parameters.Power.State.SystemState = SystemState;
IrpSp->Parameters.Power.ShutdownType = PowerAction;
Status = PoCallDriver(DeviceObject, Irp);
if (Status == STATUS_PENDING)
{
KeWaitForSingleObject(&Event,
Executive,
KernelMode,
FALSE,
NULL);
Status = IoStatusBlock.Status;
}
return Status;
}
电源状态包括设备电源状态和系统电源状态,其中设备有四种电源状态
随着设备从D0状态变化到D3状态,设备将消耗越来越少的电力,同时需要保留的当前状态上下文信息也越来越少。而设备再转变回D0状态的延迟期则相应增加。
这四种设备电源状态被定义成了:
typedef enum _DEVICE_POWER_STATE {
PowerDeviceUnspecified = 0,
PowerDeviceD0,
PowerDeviceD1,
PowerDeviceD2,
PowerDeviceD3,
PowerDeviceMaximum
} DEVICE_POWER_STATE, *PDEVICE_POWER_STATE;
系统电源状态如下:
typedef enum _SYSTEM_POWER_STATE {
PowerSystemUnspecified = 0,
PowerSystemWorking = 1,
PowerSystemSleeping1 = 2,
PowerSystemSleeping2 = 3,
PowerSystemSleeping3 = 4,
PowerSystemHibernate = 5,
PowerSystemShutdown = 6,
PowerSystemMaximum = 7
} SYSTEM_POWER_STATE, *PSYSTEM_POWER_STATE;
系统初始化后即进入Working状态。大部分设备也以D0状态启动,但某些设备的驱动程序会在设备启动时使设备进入低电源消耗状态。在系统启动并正常运行后,这些设备的驱动程序才使设备进入一个稳定的状态,在这个状态中,系统电源处于Working状态,而设备处于的状态取决于具体活动和设备自身的能力。
用户的活动或外部事件会导致电源状态的改变。一个常见的电源状态转换情景是用户在开始菜单上选择“关闭系统”中的“standby”选项,使计算机进入等待状态。在响应这个命令过程中,电源管理器首先向每个驱动程序发送带有IRP_MN_QUERY_POWER
副功能码的IRP_MJ_POWER
请求以询问设备能否接受即将到来的电源关闭请求。如果所有驱动程序都同意,电源管理器将发送第二个带有IRP_MN_SET_POWER
副功能码的电源管理IRP,然后驱动程序把其设备置入低电源状态以响应这个IRP。如果有任何一个驱动程序否决了这个查询,电源管理器仍旧发出这个IRP_MN_SET_POWER
请求,但它用原来的电源级别换成了请求的电源级别。
系统并不总是发送IRP_MN_QUERY_POWER
请求。某些事件(如电池电力将要耗尽)必须被无条件接受,并且操作系统也不再发出查询请求。如果查询发出后,并且驱动程序也接受了请求的电源状态,那么驱动程序将不再启动任何会妨碍未来电源状态设置请求的操作。例如,磁带机驱动程序在使一个进入低电源状态的查询请求成功返回前先确保当前没有执行备份操作。另外,该驱动程序还拒绝任何后来的备份命令,除非是另一个电源状态设置请求。
电源管理器的请求为IRP_MJ_POWER
类型,其中有如下类型的副IRP
副功能码 | 描述 |
---|---|
IRP_MN_QUERY_POWER |
确定预期的电源状态改变是否安全 |
IRP_MN_SET_POWER |
命令驱动程序改变电源状态 |
IRP_MN_WAIT_WAKE |
命令总线驱动程序使用唤醒特征;使功能驱动程序能了解唤醒信号何时发生 |
IRP_MN_POWER_SEQUENCE |
为上下文保存和恢复提供优化 |
这些类型的参数被定义如下:
typedef struct _IO_STACK_LOCATION {
UCHAR MajorFunction;
UCHAR MinorFunction;
UCHAR Flags;
UCHAR Control;
union {
//...
//
// Parameters for IRP_MN_WAIT_WAKE
//
struct {
SYSTEM_POWER_STATE PowerState;
} WaitWake;
//
// Parameter for IRP_MN_POWER_SEQUENCE
//
struct {
PPOWER_SEQUENCE PowerSequence;
} PowerSequence;
//
// Parameters for IRP_MN_SET_POWER and IRP_MN_QUERY_POWER
//
struct {
ULONG SystemContext;
POWER_STATE_TYPE POINTER_ALIGNMENT Type;
POWER_STATE POINTER_ALIGNMENT State;
POWER_ACTION POINTER_ALIGNMENT ShutdownType;
} Power;
//...
} Parameters;
};
其中Power的各个成员定义如下:
域名 | 描述 |
---|---|
SystemContext |
电源管理器内部使用的上下文值 |
Type |
DevicePowerState 或SystemPowerState (POWER_STATE_TYPE类型的枚举值) |
State |
电源状态,可为DEVICE_POWER_STATE 或SYSTEM_POWER_STATE |
ShutdownType |
指出转换到PowerSystemShutdown 状态的原因代码 |
各个类型定义如下:
typedef enum _SYSTEM_POWER_STATE {
PowerSystemUnspecified,
PowerSystemWorking,
PowerSystemSleeping1,
PowerSystemSleeping2,
PowerSystemSleeping3,
PowerSystemHibernate,
PowerSystemShutdown,
PowerSystemMaximum
} SYSTEM_POWER_STATE, *PSYSTEM_POWER_STATE;
#define POWER_SYSTEM_MAXIMUM PowerSystemMaximum
typedef enum _DEVICE_POWER_STATE {
PowerDeviceUnspecified,
PowerDeviceD0,
PowerDeviceD1,
PowerDeviceD2,
PowerDeviceD3,
PowerDeviceMaximum
} DEVICE_POWER_STATE, *PDEVICE_POWER_STATE;
typedef union _POWER_STATE {
SYSTEM_POWER_STATE SystemState;
DEVICE_POWER_STATE DeviceState;
} POWER_STATE, *PPOWER_STATE;
typedef enum _POWER_STATE_TYPE {
SystemPowerState = 0,
DevicePowerState
} POWER_STATE_TYPE, *PPOWER_STATE_TYPE;
typedef enum {
PowerActionNone,
PowerActionReserved,
PowerActionSleep,
PowerActionHibernate,
PowerActionShutdown,
PowerActionShutdownReset,
PowerActionShutdownOff,
PowerActionWarmEject
} POWER_ACTION, *PPOWER_ACTION;
可以有如下三种方式处理电源的相关请求
PoStartNextPowerIrp
通知电源管理器可以出队并发送下一个电源管理IRP。在释放一个电源管理请求的控制之前,你必须调用PoStartNextPowerIrp
,即使你以错误状态完成该IRP,也要这样做。做这个调用的原因是,电源管理器自己需要维持一个电源管理请求队列,所以必须通知它确实可以出队一个请求,以及向设备发送下一个请求。
电源管理IRP到来时,回调函数处于一个系统线程的上下文中,不要阻塞这个线程。如果你的设备有INRUSH特征,或者你清除了设备对象中的DO_POWER_PAGABLE
标志,那么电源管理器将在DISPATCH_LEVEL
级上向你发送IRP。而当你执行在DISPATCH_LEVEL
级上时,不可能阻塞一个线程。如果你设置了DO_POWER_PAGABLE
标志,你会在PASSIVE_LEVEL
级上收到电源管理IRP,此时,如果你在服务一个系统IRP时请求了设备电源管理IRP然后阻塞,将导致死锁:电源管理器不会向你发送设备IRP,除非你的系统IRP派遣例程返回,因此你将永远等待下去。