嵌套虚拟化

嵌套虚拟化是指模拟硬件虚拟化扩展的 Hyper-V 虚拟机监控程序。 其他虚拟化软件可以使用这些模拟扩展 (例如嵌套虚拟机监控程序) 在 Hyper-V 平台上运行。

此功能仅适用于来宾分区。 必须为每个虚拟机启用它。 Windows 根分区不支持嵌套虚拟化。

以下术语用于定义各种级别的嵌套虚拟化:

术语 定义
L0 虚拟机监控程序 在物理硬件上运行的 Hyper-V 虚拟机监控程序。
L1 根 Windows 根操作系统。
L1 来宾 没有嵌套虚拟机监控程序的 Hyper-V 虚拟机。
L1 虚拟机监控程序 在 Hyper-V 虚拟机中运行的嵌套虚拟机监控程序。
L2 根 在 Hyper-V 虚拟机的上下文中运行的根 Windows 操作系统。
L2 来宾 在 Hyper-V 虚拟机的上下文中运行的嵌套虚拟机。

与裸机相比,虚拟机监控程序在 VM 中运行时可能会导致明显的性能回归。 可以使用 L0 虚拟机监控程序提供的启发式接口,优化 L1 虚拟机监控程序以在 Hyper-V VM 中运行。

启发式 VMCS (Intel)

在 Intel 平台上,虚拟化软件使用虚拟机控制数据结构 (VMCS) 来配置与虚拟化相关的处理器行为。 必须使用 VMPTRLD 指令激活 VMCS,并使用 VMREAD 和 VMWRITE 指令进行修改。 这些指令通常是嵌套虚拟化的重要瓶颈,因为它们必须模拟。

虚拟机监控程序公开了“启发式 VMCS”功能,该功能可用于使用来宾物理内存中的数据结构控制与虚拟化相关的处理器行为。 可以使用正常的内存访问指令修改此数据结构,因此 L1 虚拟机监控程序无需执行 VMREAD、VMWRITE 或 VMPTRLD 指令。

L1 虚拟机监控程序可以选择使用启发式 VMCS,方法是将 1 写入 虚拟处理器助手页中的相应字段。 VP 助手页中的另一个字段控制当前处于活动状态的启发式 VMCS。 每个启发式 VMCS 都是一个页面, (大小) 4 KB,并且必须最初为零。 无需执行 VMPTRLD 指令即可使启发式 VMCS 处于活动状态或当前。

L1 虚拟机监控程序使用启发式 VMCS 执行 VM 条目后,VMCS 将被视为在处理器上处于活动状态。 启发式 VMCS 只能在单个处理器上同时处于活动状态。 L1 虚拟机监控程序可以执行 VMCLEAR 指令,将启发式 VMCS 从活动状态转换为非活动状态。 在启发式 VMCS 处于活动状态时的任何 VMREAD 或 VMWRITE 指令不受支持,并可能导致意外行为。

HV_VMX_ENLIGHTENED_VMCS结构定义启发式 VMCS 的布局。 所有非合成字段都映射到 Intel 物理 VMCS 编码。

清理字段

L0 虚拟机监控程序可以选择缓存启发式 VMCS 的一部分。 启发式 VMCS 清理字段控制从嵌套 VM 条目上的来宾内存重新加载启发式 VMCS 的哪些部分。 L1 虚拟机监控程序每次修改启发式 VMCS 时都必须清除相应的 VMCS 清理字段,否则 L0 虚拟机监控程序可能使用过时的版本。

清理字段启发通过启发式 VMCS 的合成“CleanFields”字段进行控制。 默认情况下,设置所有位,以便 L0 虚拟机监控程序必须为每个嵌套的 VM 条目重新加载相应的 VMCS 字段。

功能发现

使用 CPUID 叶0x40000004报告对启发式 VMCS 接口的支持。

启发式 VMCS 结构进行了版本控制,以考虑将来的更改。 每个启发式 VMCS 结构都包含一个版本字段,该字段由 L0 虚拟机监控程序报告。

当前支持的唯一 VMCS 版本是 1。

虚拟机监控程序实现注意事项

对于大多数 VMCS 字段,如果 VM 支持 VMCS 字段,则 VM 支持相应的启发式 VMCS 字段,这通过体系结构功能发现机制确定。 CPUID 叶0x4000000A中报告异常。

如果体系结构功能发现机制指示支持未定义启发式 VMCS 字段的 VMCS 字段,L1 虚拟机监控程序选择使用启发式 VMCS,则不应启用该功能。

Hyper-V L0 虚拟机监控程序不会指示对未定义启发式 VMCS 字段或异常的 VMCS 字段的支持。 如果另一个 L0 虚拟机监控程序需要定义新的启发式 VMCS 字段或异常,请联系 Microsoft。

将 VMCB 字段 (AMD)

AMD 在 VMCB 中预留了空间供虚拟机监控程序使用,以及关联的清理位。 保留字节位于 VMCB 的控制部分,偏移量0x3E0-3FF。 清理位为 31 位 (每当虚拟机监控程序修改 VMCB) 的“启蒙”区域时,清理位都必须失效。

Hyper-V 利用保留的 VMCB 区域来配置启发。 HV_SVM_ENLIGHTENED_VMCB_FIELDS 结构记录格式。

启发式 MSR 位图

L0 虚拟机监控程序在 Intel 和 AMD 平台上模拟“MSR-Bitmap”控件,使虚拟化软件能够控制哪些 MSR 访问会生成拦截。

L1 虚拟机监控程序可以与 L0 虚拟机监控程序协作,提高 MSR 访问效率。 它可以通过将启发式 VMCS/VMCB 字段中的相应字段设置为 1 来启用启发式 MSR 位图。 启用后,L0 虚拟机监控程序不会监视 MSR 位图的更改。 相反,L1 虚拟机监控程序必须在更改其中一个 MSR 位图后使相应的清理字段失效。

CPUID 叶0x4000000A中报告了对启发式 MSR 位图的支持。

与实时迁移的兼容性

Hyper-V 能够实时将子分区从一个主机迁移到另一个主机。 实时迁移通常对子分区透明。 但是,对于嵌套虚拟化,L1 虚拟机监控程序可能需要了解迁移。

实时迁移通知

L1 虚拟机监控程序可以请求在其分区迁移时收到通知。 此功能在 CPUID 中枚举为“AccessReenlightenmentControls”特权。 L0 虚拟机监控程序公开综合 MSR (HV_X64_MSR_REENLIGHTENMENT_CONTROL) ,L1 虚拟机监控程序可以使用该 MSR 来配置中断矢量和目标处理器。 每次迁移后,L0 虚拟机监控程序将使用指定的向量注入中断。

#define HV_X64_MSR_REENLIGHTENMENT_CONTROL (0x40000106)

typedef union
{
    UINT64 AsUINT64;
    struct
    {
        UINT64 Vector :8;
        UINT64 RsvdZ1 :8;
        UINT64 Enabled :1;
        UINT64 RsvdZ2 :15;
        UINT64 TargetVp :32;
    };
} HV_REENLIGHTENMENT_CONTROL;

指定的向量必须与固定的 APIC 中断相对应。 TargetVp 指定虚拟处理器索引。

TSC 仿真

来宾分区可以在具有不同 TSC 频率的两台计算机之间实时迁移。 在这些情况下,可能需要重新计算 引用 TSC 页中的 TscScale 值。

L0 虚拟机监控程序可以选择在迁移后模拟所有 TSC 访问,直到 L1 虚拟机监控程序有机会重新计算 TscScale 值。 L1 虚拟机监控程序可以通过写入HV_X64_MSR_TSC_EMULATION_CONTROL MSR 来选择使用 TSC 仿真。 如果选择加入,则 L0 虚拟机监控程序在迁移后模拟 TSC 访问。

L1 虚拟机监控程序可以使用 HV_X64_MSR_TSC_EMULATION_STATUS MSR 查询当前是否正在模拟 TSC 访问。 例如,L1 虚拟机监控程序可以订阅 实时迁移通知 ,并在收到迁移中断后查询 TSC 状态。 它还可以在使用此 MSR 更新 TscScale 值) 后关闭 TSC 仿真 (。

#define HV_X64_MSR_TSC_EMULATION_CONTROL (0x40000107)
#define HV_X64_MSR_TSC_EMULATION_STATUS (0x40000108)

typedef union
{
    UINT64 AsUINT64;
    struct
    {
        UINT64 Enabled :1;
        UINT64 RsvdZ :63;
    };
 } HV_TSC_EMULATION_CONTROL;

typedef union
{
    UINT64 AsUINT64;
    struct
    {
        UINT64 InProgress : 1;
        UINT64 RsvdP1 : 63;
    };
} HV_TSC_EMULATION_STATUS;

虚拟 TLB

虚拟机监控程序公开的虚拟 TLB 可以扩展到缓存从 L2 GPA 到 GPA 的转换。 与逻辑处理器上的 TLB 一样,虚拟 TLB 是一个不一致的缓存,这种非一致性对来宾可见。 虚拟机监控程序公开用于管理 TLB 的操作。

直接虚拟刷新

虚拟机监控程序 (HvCallFlushVirtualAddressSpaceHvCallFlushVirtualAddressSpaceExHvCallFlushVirtualAddressListHvCallFlushVirtualAddressListEx) 公开 hypercalls,使操作系统能够更有效地管理虚拟 TLB。 L1 虚拟机监控程序可以选择允许其来宾使用这些超调用,并将处理这些虚拟机监控程序的责任委托给 L0 虚拟机监控程序。 这需要在 Intel 平台上使用分区辅助页 (和虚拟 VMCS) 。

使用时,虚拟 TLB 使用创建它们的 VMCS 或 VMCB () 嵌套上下文的标识符标记所有缓存映射。 为了响应来自 L2 来宾的直接虚拟刷新 hypercall,L0 虚拟机监控程序会使嵌套上下文创建的所有缓存映射失效,其中

  • VmId 与调用方 VmId 相同
  • VpId 包含在指定的 ProcessorMask 中,或者指定了 HV_FLUSH_ALL_PROCESSORS

CPUID 叶0x4000000A中报告了对直接虚拟刷新的支持。

配置

通过以下方法启用虚拟刷新超调用的直接处理:

  1. 虚拟处理器助手页 的 NestedEnlightenmentsControl.Features.DirectHypercall 字段设置为 1。
  2. 将启发式 VMCS 或 VMCB 的 EnlightenmentsControl.NestedFlushVirtualHypercall 字段设置为 1。

在启用它之前,L1 虚拟机监控程序必须配置启发式 VMCS/VMCB 的以下附加字段:

  • VpId:受启发的 VMCS/VMCB 控制的虚拟处理器的 ID。
  • VmId:启发式 VMCS/VMCB 所属的虚拟机的 ID。
  • PartitionAssistPage:分区协助页的来宾物理地址。

L1 虚拟机监控程序还必须通过 CPUID 向来宾公开以下功能。

  • UseHypercallForLocalFlush
  • UseHypercallForRemoteFlush

分区助手页

分区辅助页是 L1 虚拟机监控程序必须分配的页大小对齐的内存区域,在可以使用直接刷新超调用之前为零。 其 GPA 必须写入启发式 VMCS/VMCB 中的相应字段。

struct
{
    UINT32 TlbLockCount;
} VM_PARTITION_ASSIST_PAGE;

合成VM-Exit

如果调用方分区助手页的 TlbLockCount 为非零,则 L0 虚拟机监控程序在处理直接虚拟刷新 hypercall 后,会向 L1 虚拟机监控程序提供具有综合退出原因的VM-Exit。

在 Intel 平台上,会交付具有退出原因 HV_VMX_SYNTHETIC_EXIT_REASON_TRAP_AFTER_FLUSH 的VM-Exit。 在 AMD 平台上,会传送包含退出代码 HV_SVM_EXITCODE_ENL 的VM-Exit,并将 ExitInfo1 设置为 HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH

#define HV_VMX_SYNTHETIC_EXIT_REASON_TRAP_AFTER_FLUSH 0x10000031

#define HV_SVM_EXITCODE_ENL 0xF0000000
#define HV_SVM_ENL_EXITCODE_TRAP_AFTER_FLUSH   (1)

二级地址转换

为来宾分区启用嵌套虚拟化后,分区公开的内存管理单元 (MMU) 包括对二级地址转换的支持。 二级地址转换是 L1 虚拟机监控程序可用于虚拟化物理内存的功能。 在使用时,将作为来宾物理地址 (GPA) 的某些地址被视为 L2 来宾物理地址 (L2 GPA) ,并通过遍历一组分页结构转换为 GPA。

L1 虚拟机监控程序可以决定使用二级地址空间的方式和位置。 每个第二级地址空间由来宾定义的 64 位 ID 值标识。 在 Intel 平台上,此值与 EPT 指针相同。 在 AMD 平台上,该值等于 nCR3 VMCB 字段。

兼容性

虚拟机监控程序公开的二级地址转换功能通常与 VMX 或 SVM 对地址转换的支持兼容。 但是,存在以下来宾可观察到的差异:

  • 在内部,虚拟机监控程序可以使用将 L2 GPA 转换为 SPA 的影子页表。 在此类实现中,这些影子页表在软件中显示为大型 TRB。 但是,可能会观察到一些差异。 首先,影子页表可以在两个虚拟处理器之间共享,而传统的 TRB 是每个处理器的结构,并且是独立的。 此共享可能是可见的,因为一个虚拟处理器的页面访问可以填充另一个虚拟处理器随后使用的影子页表条目。
  • 某些虚拟机监控程序实现可能会对来宾页表使用内部写入保护,从内部数据结构 (延迟刷新 MMU 映射,例如,影子页表) 。 这在体系结构上对来宾不可见,因为对这些表的写入将由虚拟机监控程序以透明方式处理。 但是,其他分区或设备对基础 GPA 页执行的写入可能不会触发相应的 TLB 刷新。
  • 在某些虚拟机监控程序实现中,第二级页面错误可能不会使缓存的映射失效。

启发式第二级 TLB 刷新

虚拟机监控程序还支持一组增强功能,使来宾能够更高效地管理第二级 TLB。 这些增强的操作可以与旧 TLB 管理操作互换使用。

虚拟机监控程序支持以下超调用来使 TRB 失效:

Hypercall 说明
HvCallFlushGuestPhysicalAddressSpace 使缓存的 L2 GPA 到二级地址空间中的 GPA 映射失效。
HvCallFlushGuestPhysicalAddressList 在二级地址空间的一部分内使缓存的GPA/L2 GPA 映射失效。

在 AMD 平台上,所有 TLB 条目在体系结构上都使用 ASID (地址空间标识符) 进行标记。 ASID 失效会导致与 ASID 关联的所有 TLB 整体失效。 嵌套虚拟机监控程序可以选择使用“启发式 TLB”,方法是在 HV_SVM_ENLIGHTENED_VMCB_FIELDS 中将 EnlightenedNptTlb 设置为“1”。 如果嵌套虚拟机监控程序选择加入启发,ASID 无效只会刷新从一级地址转换 (即虚拟地址空间) 派生的 TLB 整体。 若要刷新从嵌套页表派生的 TLB 条目 (NPT) 并强制 L0 虚拟机监控程序重新生成影子页表,必须使用 HvCallFlushGuestPhysicalAddressSpace 或 HvCallFlushGuestPhysicalAddressList 超calls。

嵌套虚拟化异常

在 Intel 平台上。 L1 虚拟机监控程序可以选择将虚拟化异常合并到页面错误异常类中。 L0 虚拟机监控程序在虚拟机监控程序嵌套虚拟化功能 CPUID 叶中播发对此点燃的支持。

可以通过在虚拟处理器助手 页HV_NESTED_ENLIGHTENMENTS_CONTROL数据 结构中将 VirtualizationException 设置为“1”来启用此启发。

嵌套虚拟化 MSR

嵌套 VP 索引寄存器

L1 虚拟机监控程序公开一个 MSR,该 MSR 报告当前处理器的基础 VP 索引。

MSR 地址 注册名称 说明
0x40001002 HV_X64_MSR_NESTED_VP_INDEX 在嵌套根分区中,报告当前处理器的基础 VP 索引。

嵌套 SynIC MSR

在嵌套根分区中,以下 MSR 允许访问基础虚拟机监控程序的相应 SynIC MSR

若要查找基础处理器的索引,调用方应首先使用 HV_X64_MSR_NESTED_VP_INDEX。

MSR 地址 注册名称 基础 MSR
0x40001080 HV_X64_MSR_NESTED_SCONTROL HV_X64_MSR_SCONTROL
0x40001081 HV_X64_MSR_NESTED_SVERSION HV_X64_MSR_SVERSION
0x40001082 HV_X64_MSR_NESTED_SIEFP HV_X64_MSR_SIEFP
0x40001083 HV_X64_MSR_NESTED_SIMP HV_X64_MSR_SIMP
0x40001084 HV_X64_MSR_NESTED_EOM HV_X64_MSR_EOM
0x40001090 HV_X64_MSR_NESTED_SINT0 HV_X64_MSR_SINT0
0x40001091 HV_X64_MSR_NESTED_SINT1 HV_X64_MSR_SINT1
0x40001092 HV_X64_MSR_NESTED_SINT2 HV_X64_MSR_SINT2
0x40001093 HV_X64_MSR_NESTED_SINT3 HV_X64_MSR_SINT3
0x40001094 HV_X64_MSR_NESTED_SINT4 HV_X64_MSR_SINT4
0x40001095 HV_X64_MSR_NESTED_SINT5 HV_X64_MSR_SINT5
0x40001096 HV_X64_MSR_NESTED_SINT6 HV_X64_MSR_SINT6
0x40001097 HV_X64_MSR_NESTED_SINT7 HV_X64_MSR_SINT7
0x40001098 HV_X64_MSR_NESTED_SINT8 HV_X64_MSR_SINT8
0x40001099 HV_X64_MSR_NESTED_SINT9 HV_X64_MSR_SINT9
0x4000109A HV_X64_MSR_NESTED_SINT10 HV_X64_MSR_SINT10
0x4000109B HV_X64_MSR_NESTED_SINT11 HV_X64_MSR_SINT11
0x4000109C HV_X64_MSR_NESTED_SINT12 HV_X64_MSR_SINT12
0x4000109D HV_X64_MSR_NESTED_SINT13 HV_X64_MSR_SINT13
0x4000109E HV_X64_MSR_NESTED_SINT14 HV_X64_MSR_SINT14
0x4000109F HV_X64_MSR_NESTED_SINT15 HV_X64_MSR_SINT15