虚拟机生成标识符

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

先决条件

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

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

    • Windows 8
    • Windows Server 2012
    • Microsoft server 2012 Hyper-V
  • 虚拟机必须运行支持虚拟机生成标识符的来宾作系统。 目前,以下是以下各项。

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

    • 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 Service Pack 2 (SP2)
    • Windows Server 2003 R2
    • Windows Server 2003 Service Pack 2 (SP2)
    • Windows Vista Service Pack 2 (SP2)
    • 具有 Service Pack 3 的 Windows XP (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;
};

确定时间班次事件是否已发生

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