虚拟机生成标识符

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"。 标头文件包含以下定义:

    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. 打开 "" 的句柄 \ \ 。 \读取 vmgenerationcounter "设备。 或者,可以使用 PnP 管理器来使用设备接口 GUID _ DEVINTERFACE _ VM _ GENCOUNTER ( {3ff2c92b-6598-4e60-8e1c-0ccf4927e319} ) 。 如果应用未在虚拟机中运行,则不会显示这些对象。

  3. ioctl _ Vmgencounter.sys _ 读取 ioctl 发送到驱动程序以检索代标识符。

    Ioctl _ Vmgencounter.sys _ READ ioctl 在两种模式、轮询事件驱动 模式下运行。

    若要在轮询模式下发出 IOCTL,请提交长度为零的输入缓冲区的 IOCTL。 为响应这一点,驱动程序将检索当前的代标识符,将其写入到输出缓冲区,并完成 IOCTL。

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

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

    在上面列出的所有来宾操作系统上,都支持轮询模式。 仅 Windows Vista sp2、Windows Server 2008 sp2 和更高版本的操作系统上支持事件驱动模式。 在早期的操作系统上,IOCTL 将失败,并在事件驱动模式下发出时 _ 不 _ 支持 错误代码错误。

    在驱动程序检索到的时间和 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;
};

确定是否发生了时间移位事件

获取虚拟机生成标识符后,应将其存储起来供将来使用。 在您的应用程序执行与时间敏感的操作(如提交到数据库)之前,您应该重新获取该代标识符,并将其与存储的值进行比较。 如果标识符已更改,这意味着发生了时间移位事件,并且你的应用程序必须正确操作。