文章目錄
- Windows驅動之電源管理
-
- 1. NtShutdownSystem
- 2. 電源狀态
-
- 2.1 電源狀态轉換
- 2.2 處理IRP_MJ_POWER
Windows驅動之電源管理
随着移動網際網路的發展,對于電量要求越來越高了,例如手機作業系統需要盡可能的減少電量的消耗,到達移動裝置用電時間比較久。在Windows設計之初就考慮到了這個問題,每個裝置都有自己的電源狀态。
例如當系統不使用的時候,整個作業系統就會挂起,将記憶體資料儲存在檔案中,這個時候整個系統就處于耗電極低的狀态。本文來讨論一下Windows的電源管理。
1. NtShutdownSystem
我們在使用開始菜單關閉作業系統其實就是調用
NtShutdownSystem
,然後這個函數調用
NtSetSystemPowerState
設定系統的電源狀态;接着這個函數繼續調用
PopGracefulShutdown
函數來關閉作業系統。然後
PopGracefulShutdown
調用
PopShutdownSystem
來關閉系統。最後,這個函數調用如下代碼:
-
發送:PopQuerySystemPowerStateTraverse
請求IRPIRP_MN_QUERY_POWER
-
發送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. 電源狀态
電源狀态包括裝置電源狀态和系統電源狀态,其中裝置有四種電源狀态

- 在D0狀态中,裝置處于全供電狀态。
- 在D3狀态中,裝置處于無供電(或最小限度的電流)狀态。
- 中間的D1和D2狀态指出裝置的兩個不同睡眠狀态。
随着裝置從D0狀态變化到D3狀态,裝置将消耗越來越少的電力,同時需要保留的目前狀态上下文資訊也越來越少。而裝置再轉變回D0狀态的延遲期則相應增加。
這四種裝置電源狀态被定義成了:
typedef enum _DEVICE_POWER_STATE {
PowerDeviceUnspecified = 0,
PowerDeviceD0,
PowerDeviceD1,
PowerDeviceD2,
PowerDeviceD3,
PowerDeviceMaximum
} DEVICE_POWER_STATE, *PDEVICE_POWER_STATE;
不同于裝置電源狀态,作業系統有自己的電源狀态,如下:
- Shutdown狀态就是電源關閉狀态。
- Hibernate狀态是另一種Shutdown狀态,它把計算機的整個狀态都記錄到硬碟上,是以在電源恢複供電時可以使計算機快速恢複到記錄前的狀态。
- 在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
副功能碼 | 描述 |
---|---|
| 确定預期的電源狀态改變是否安全 |
| 指令驅動程式改變電源狀态 |
| 指令總線驅動程式使用喚醒特征;使功能驅動程式能了解喚醒信号何時發生 |
| 為上下文儲存和恢複提供優化 |
這些類型的參數被定義如下:
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的各個成員定義如下:
域名 | 描述 |
---|---|
| 電源管理器内部使用的上下文值 |
| 或 (POWER_STATE_TYPE類型的枚舉值) |
| 電源狀态,可為 或 |
| 指出轉換到 狀态的原因代碼 |
各個類型定義如下:
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
可以有如下三種方式處理電源的相關請求
PoStartNextPowerIrp
通知電源管理器可以出隊并發送下一個電源管理IRP。在釋放一個電源管理請求的控制之前,你必須調用
PoStartNextPowerIrp
,即使你以錯誤狀态完成該IRP,也要這樣做。做這個調用的原因是,電源管理器自己需要維持一個電源管理請求隊列,是以必須通知它确實可以出隊一個請求,以及向裝置發送下一個請求。
電源管理IRP到來時,回調函數處于一個系統線程的上下文中,不要阻塞這個線程。如果你的裝置有INRUSH特征,或者你清除了裝置對象中的
DO_POWER_PAGABLE
标志,那麼電源管理器将在
DISPATCH_LEVEL
級上向你發送IRP。而當你執行在
DISPATCH_LEVEL
級上時,不可能阻塞一個線程。如果你設定了
DO_POWER_PAGABLE
标志,你會在
PASSIVE_LEVEL
級上收到電源管理IRP,此時,如果你在服務一個系統IRP時請求了裝置電源管理IRP然後阻塞,将導緻死鎖:電源管理器不會向你發送裝置IRP,除非你的系統IRP派遣例程傳回,是以你将永遠等待下去。