Windows驱动之电源管理

文章目录

  • Windows驱动之电源管理
    • 1. NtShutdownSystem
    • 2. 电源状态
      • 2.1 电源状态转换
      • 2.2 处理IRP_MJ_POWER

Windows驱动之电源管理

随着移动互联网的发展,对于电量要求越来越高了,例如手机操作系统需要尽可能的减少电量的消耗,到达移动设备用电时间比较久。在Windows设计之初就考虑到了这个问题,每个设备都有自己的电源状态。

例如当系统不使用的时候,整个操作系统就会挂起,将内存数据保存在文件中,这个时候整个系统就处于耗电极低的状态。本文来讨论一下Windows的电源管理。

1. NtShutdownSystem

我们在使用开始菜单关闭操作系统其实就是调用NtShutdownSystem,然后这个函数调用NtSetSystemPowerState设置系统的电源状态;接着这个函数继续调用PopGracefulShutdown函数来关闭操作系统。然后PopGracefulShutdown调用PopShutdownSystem来关闭系统。最后,这个函数调用如下代码:

  1. PopQuerySystemPowerStateTraverse发送:IRP_MN_QUERY_POWER请求IRP
  2. PopSetSystemPowerStateTraverse发送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;
}

2. 电源状态

电源状态包括设备电源状态和系统电源状态,其中设备有四种电源状态

Windows驱动之电源管理_第1张图片

  1. 在D0状态中,设备处于全供电状态。
  2. 在D3状态中,设备处于无供电(或最小限度的电流)状态。
  3. 中间的D1和D2状态指出设备的两个不同睡眠状态。

随着设备从D0状态变化到D3状态,设备将消耗越来越少的电力,同时需要保留的当前状态上下文信息也越来越少。而设备再转变回D0状态的延迟期则相应增加。

这四种设备电源状态被定义成了:

typedef enum _DEVICE_POWER_STATE {
  PowerDeviceUnspecified = 0,
  PowerDeviceD0,
  PowerDeviceD1,
  PowerDeviceD2,
  PowerDeviceD3,
  PowerDeviceMaximum
} DEVICE_POWER_STATE, *PDEVICE_POWER_STATE;

不同于设备电源状态,操作系统有自己的电源状态,如下:
Windows驱动之电源管理_第2张图片

  1. Shutdown状态就是电源关闭状态。
  2. Hibernate状态是另一种Shutdown状态,它把计算机的整个状态都记录到硬盘上,因此在电源恢复供电时可以使计算机快速恢复到记录前的状态。
  3. 在Hibernate和Working状态之间是三个有不同电力消耗级别的中间状态。

系统电源状态如下:

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;

2.1 电源状态转换

系统初始化后即进入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 DevicePowerStateSystemPowerState (POWER_STATE_TYPE类型的枚举值)
State 电源状态,可为DEVICE_POWER_STATESYSTEM_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;

2.2 处理IRP_MJ_POWER

可以有如下三种方式处理电源的相关请求

Windows驱动之电源管理_第3张图片

PoStartNextPowerIrp通知电源管理器可以出队并发送下一个电源管理IRP。在释放一个电源管理请求的控制之前,你必须调用PoStartNextPowerIrp,即使你以错误状态完成该IRP,也要这样做。做这个调用的原因是,电源管理器自己需要维持一个电源管理请求队列,所以必须通知它确实可以出队一个请求,以及向设备发送下一个请求。

电源管理IRP到来时,回调函数处于一个系统线程的上下文中,不要阻塞这个线程。如果你的设备有INRUSH特征,或者你清除了设备对象中的DO_POWER_PAGABLE标志,那么电源管理器将在DISPATCH_LEVEL级上向你发送IRP。而当你执行在DISPATCH_LEVEL级上时,不可能阻塞一个线程。如果你设置了DO_POWER_PAGABLE标志,你会在PASSIVE_LEVEL级上收到电源管理IRP,此时,如果你在服务一个系统IRP时请求了设备电源管理IRP然后阻塞,将导致死锁:电源管理器不会向你发送设备IRP,除非你的系统IRP派遣例程返回,因此你将永远等待下去。

你可能感兴趣的:(Windows驱动开发)