在 WDM 驱动程序中使用浮点数
上次更新时间
- 2016 年 7 月
使用浮点运算时,Windows 的内核模式 WDM 驱动程序必须遵循某些准则。 这些在 x86 和 x64 系统之间有所不同。 默认情况下,Windows 会关闭这两个系统的算术异常。
x86 系统
x86 系统的内核模式 WDM 驱动程序必须在调用 KeSaveExtendedProcessorState 和 KeRestoreExtendedProcessorState 之间包装浮点计算的使用。 浮点运算必须置于非内联子例程中,以确保在检查 KeSaveExtendedProcessorState 的返回值之前,不会执行浮点计算,因为编译器重新排序。
编译器使用 MMX/x87 也称为浮点单元, (FPU) 寄存器进行此类计算,用户模式应用程序可以同时使用这些计算。 在使用这些寄存器之前未能保存这些寄存器,或者在完成后无法还原它们,可能会导致应用程序中出现计算错误。
x86 系统的驱动程序可以调用 KeSaveExtendedProcessorState ,并在 IRQL <= DISPATCH_LEVEL 执行浮点计算。 x86 系统上 (的 ISR) 中断服务例程不支持浮点操作。
x64 系统
64 位编译器不对浮点运算使用 MMX/x87 寄存器。 而是使用 SSE 寄存器。 不允许 x64 内核模式代码访问 MMX/x87 寄存器。 编译器还负责正确保存和还原 SSE 状态,因此,不需要调用 KeSaveExtendedProcessorState 和 KeRestoreExtendedProcessorState ,并且可以在 ISR 中使用浮点操作。 使用其他扩展处理器功能(如 AVX)需要保存和还原扩展状态。 有关详细信息,请参阅 在 Windows 驱动程序中使用扩展处理器功能。
注意:通常,Arm64 与 AMD64 相似,无需先调用保存浮点状态。 但是,需要移植到内核上的 x86 的代码可能仍需要跨平台执行此操作。
示例
以下示例演示 WDM 驱动程序应如何包装其 FPU 访问:
__declspec(noinline)
VOID
DoFloatingPointCalculation(
VOID
)
{
double Duration;
LARGE_INTEGER Frequency;
Duration = 1000000.0;
DbgPrint("%I64x\n", *(LONGLONG*)&Duration);
KeQueryPerformanceCounter(&Frequency);
Duration /= (double)Frequency.QuadPart;
DbgPrint("%I64x\n", *(LONGLONG*)&Duration);
}
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
XSTATE_SAVE SaveState;
NTSTATUS Status;
Status = KeSaveExtendedProcessorState(XSTATE_MASK_LEGACY, &SaveState);
if (!NT_SUCCESS(Status)) {
goto exit;
}
__try {
DoFloatingPointCalculation();
}
__finally {
KeRestoreExtendedProcessorState(&SaveState);
}
exit:
return Status;
}
在此示例中,对浮点变量的赋值发生在对 KeSaveExtendedProcessorState 和 KeRestoreExtendedProcessorState 的调用之间。 由于对浮点变量的任何赋值都使用 FPU,因此在初始化此类变量之前,驱动程序必须确保 KeSaveExtendedProcessorState 返回且没有错误。
上述调用在 x64 系统上是不必要的,在指定XSTATE_MASK_LEGACY标志时是无害的。 因此,在为 x64 系统编译驱动程序时,无需更改代码。
在基于 x86 的系统上,从 KeSaveExtendedProcessorState 返回时,通过调用 FNINIT 将 FPU 重置为其默认状态。