虚拟机生成标识符

Windows 8和Windows Server 2012引入了虚拟机上运行的软件检测可能发生时间转移事件的功能。 由于虚拟机快照的应用程序或虚拟机备份的还原,可能会发生时间转移事件。 有关此功能的详细信息,请参阅 虚拟机生成 ID 白皮书

先决条件

若要从虚拟机内部使用虚拟机生成标识符,虚拟机必须符合以下条件。

  • 虚拟机必须在实现对虚拟机生成标识符的支持的虚拟机监控程序上运行。 目前,以下是以下内容:

    • Windows 8
    • Windows Server 2012
    • Microsoft Hyper-V Server 2012
  • 虚拟机必须运行一个来宾操作系统,该操作系统支持虚拟机生成标识符。 目前,以下是以下内容。

    以下操作系统对虚拟机生成标识符具有本机支持。

    • Windows 8
    • Windows Server 2012

    如果安装了来自Windows 8或Windows Server 2012的 Hyper-V 集成服务,则可以使用以下操作作为来宾操作系统。

    • Windows Server 2008 R2 Service Pack 1 (SP1)
    • Windows 7 Service Pack 1 (SP1)
    • Windows Server 2008 with Service Pack 2 (SP2)
    • Windows Server 2003 R2
    • Windows Server 2003 Service Pack 2 (SP2)
    • Windows Vista Service Pack 2 (SP2)
    • Windows XP Service Pack 3 (SP3)

获取虚拟机生成标识符

若要以编程方式获取虚拟机生成标识符,请执行以下步骤。

注意

必须以管理员或系统权限运行以下内容才能正常运行。

 

  1. 在应用中包括头文件“vmgenerationcounter.h”。 头文件包含以下定义:

    DEFINE_GUID(
        GUID_DEVINTERFACE_VM_GENCOUNTER,
        0x3ff2c92b, 
        0x6598, 
        0x4e60, 
        0x8e, 
        0x1c, 
        0x0c, 
        0xcf, 
        0x49, 
        0x27, 
        0xe3, 
        0x19);
    
    #define VM_GENCOUNTER_SYMBOLIC_LINK_NAME L"VmGenerationCounter"
    
    #define IOCTL_VMGENCOUNTER_READ CTL_CODE( \
        FILE_DEVICE_ACPI, \
        0x1, METHOD_BUFFERED, \
        FILE_READ_ACCESS | FILE_WRITE_ACCESS)
    
    typedef struct _VM_GENCOUNTER
    {
        ULONGLONG GenerationCount;
        ULONGLONG GenerationCountHigh;
    } VM_GENCOUNTER, *PVM_GENCOUNTER;
    
  2. 使用 CreateFile 函数打开“\.\VmGenerationCounter”设备的句柄。 或者,可以使用 PnP 管理器使用设备接口 GUID_DEVINTERFACE_VM_GENCOUNTER ({3ff2c92b-6598-4e60-8e1c-0ccf4927e319}) 。 如果应用未在虚拟机中运行,这些对象将不存在。

  3. IOCTL_VMGENCOUNTER_READ IOCTL 发送到驱动程序以检索生成标识符。

    IOCTL_VMGENCOUNTER_READ IOCTL 以两种模式之一运行,即轮询事件驱动

    若要在轮询模式下发出 IOCTL,请使用零长度的输入缓冲区提交 IOCTL。 为此,驱动程序检索当前生成标识符,将其写入输出缓冲区,并完成 IOCTL。

    若要在事件驱动模式下发出 IOCTL,请使用包含现有生成标识符的输入缓冲区提交 IOCTL。 为此,驱动程序会等待,直到当前生成标识符与传入的生成标识符不同。 生成标识符发生更改时,驱动程序会将当前生成标识符写入输出缓冲区并完成 IOCTL。

    在这两种模式下,输出缓冲区的格式和长度由 VM_GENCOUNTER 结构决定。

    上述所有来宾操作系统都支持轮询模式。 事件驱动模式仅在具有 SP2、Windows Server 2008 和 SP2 及更高版本的操作系统Windows Vista 上受支持。 在早期操作系统上,IOCTL 将在事件驱动模式下发出时出现错误代码 ERROR_NOT_SUPPORTED 失败。

    生成标识符可以在驱动程序检索到的时间与 IOCTL 完成的时间之间更改。 这可能会导致客户端应用接收过时的数据。 为了避免这种情况,客户端应用可以使用事件驱动模式来确保最终将了解生成标识符的任何更新。 通过将客户端应用的当前标识符作为输入,事件驱动模式可避免可能导致调用方错过通知的潜在争用条件。

下面的代码示例演示如何执行上述操作以获取虚拟机生成标识符。

注意

以下代码必须使用管理员或系统权限运行才能正常运行。

 

HRESULT GetVmCounter(bool fWaitForChange)
{
    BOOL success = FALSE;
    DWORD error = ERROR_SUCCESS;
    VM_GENCOUNTER vmCounterOutput = {0};
    DWORD size = 0;
    HANDLE handle = INVALID_HANDLE_VALUE;
    HRESULT hr = S_OK;

    handle = CreateFile(
        L"\\\\.\\" VM_GENCOUNTER_SYMBOLIC_LINK_NAME,
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    if (handle == INVALID_HANDLE_VALUE)
    {
        error = GetLastError();

        wprintf(
            L"Unable to open device %s. Error code = %d.", 
            VM_GENCOUNTER_SYMBOLIC_LINK_NAME, 
            error);

        hr = HRESULT_FROM_WIN32(error);

        goto Cleanup;
    }

    /*
    Call into the driver. 

    Because the 4th parameter to DeviceIoControl (nInBufferSize) is zero, this 
    is a polling request rather than an event-driven request.
    */
    success = DeviceIoControl(
        handle,
        IOCTL_VMGENCOUNTER_READ,
        NULL,
        0,
        &vmCounterOutput,
        sizeof(vmCounterOutput),
        &size,
        NULL);

    if (!success)
    {
        error = GetLastError();

        wprintf(L"Call IOCTL_VMGENCOUNTER_READ failed with %d.", error);

        hr = HRESULT_FROM_WIN32(error);

        goto Cleanup;
    }

    wprintf(
        L"VmCounterValue: %I64x:%I64x",
        vmCounterOutput.GenerationCount,
        vmCounterOutput.GenerationCountHigh);

    if (fWaitForChange)
    {
        /*
        Call into the driver again in event-driven mode. DeviceIoControl won't 
        return until the generation identifier has changed.
        */
        success = DeviceIoControl(
            handle,
            IOCTL_VMGENCOUNTER_READ,
            &vmCounterOutput,
            sizeof(vmCounterOutput),
            &vmCounterOutput,
            sizeof(vmCounterOutput),
            &size,
            NULL);

        if (!success)
        {
            error = GetLastError();

            wprintf(L"Call IOCTL_VMGENCOUNTER_READ failed with %d.", error);

            hr = HRESULT_FROM_WIN32(error);

            goto Cleanup;
        }

        wprintf(
            L"VmCounterValue changed to: %I64x:%I64x",
            vmCounterOutput.GenerationCount,
            vmCounterOutput.GenerationCountHigh);
    }

Cleanup:

    if (handle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(handle);
    }

    return hr;
};

确定时间转移事件是否已发生

获取虚拟机生成标识符后,应将其存储以供将来使用。 在应用执行时间敏感操作(例如提交到数据库)之前,应重新获取生成标识符并将其与存储的值进行比较。 如果标识符已更改,则表示发生时间转移事件,并且你的应用必须适当行事。