在 WDM 驱动程序中使用浮点数

上次更新时间

  • 2016 年 7 月

Windows的内核模式 WDM 驱动程序在使用浮点操作时必须遵循某些准则。 x86 和 x64 系统之间的差异。 默认情况下,Windows关闭两个系统的算术异常。

x86 系统

x86 系统的内核模式 WDM 驱动程序必须在调用 KeSaveExtendedProcessorStateKeRestoreExtendedProcessorState 之间包装浮点计算的使用。 浮点操作必须放置在非内联子例程中,以确保在检查 KeSaveExtendedProcessorState 的返回值之前,不会执行浮点计算,因为编译器重新排序。

编译器利用 MMX/x87 也称为浮点单元 (FPU) 注册此类计算,该计算可由用户模式应用程序并发使用。 在使用这些寄存器之前未能保存这些寄存器,或者在完成后还原这些寄存器可能会导致应用程序中出现计算错误。

x86 系统的驱动程序可以调用 KeSaveExtendedProcessorState ,并在 IRQL <= DISPATCH_LEVEL执行浮点计算。 x86 系统上 (ISR) 中断服务例程不支持浮点操作。

x64 系统

64 位编译器不使用 MMX/x87 寄存器执行浮点操作。 而是使用 SSE 寄存器。 不允许 x64 内核模式代码访问 MMX/x87 寄存器。 编译器还负责正确保存和还原 SSE 状态,因此,对 KeSaveExtendedProcessorStateKeRestoreExtendedProcessorState 的调用是不必要的,可以在 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;
}

在此示例中,对浮点变量的赋值发生在对 KeSaveExtendedProcessorStateKeRestoreExtendedProcessorState 的调用之间。 由于对浮点变量的任何赋值都使用 FPU,因此驱动程序必须在初始化此类变量之前确保 KeSaveExtendedProcessorState 返回且没有错误。

在 x64 系统上不需要上述调用,并在指定XSTATE_MASK_LEGACY标志时无害。 因此,在编译 x64 系统的驱动程序时,无需更改代码。

在基于 x86 的系统上,在 KeSaveExtendedProcessorState 返回时,FPU 通过调用 FNINIT 重置为其默认状态。