目录

  1. 概述
  2. CPU虚拟化的硬件支持
    1. 概述
    2. VMCS
      1. 客户机状态域
      2. 宿主机状态域
    3. VMX操作模式
    4. VM-Entry/VM-Exit
      1. VM-Entry
      2. VM-Entry的过程
    5. VM-Exit
      1. 非根模式下的敏感指令
      2. VM-Execution控制域
      3. VM-Exit控制域
      4. VM-Exit信息域
      5. VM-Exit的具体过程
  3. CPU虚拟化的实现
    1. 概述
    2. VCPU的创建
      1. VMCS的创建与初始化
    3. VCPU的运行
      1. 上下文切换
      2. VCPU的硬件优化
        1. 访问CR0
        2. 访问TSC
        3. GDTR/LDTR/IDTR/TR的访问
        4. 读CR2
        5. SYSENTER/SYSEXIT
        6. APIC访问控制
        7. 异常控制
        8. I/O控制
        9. MSR位图
    4. VCPU的退出
    5. VCPU的再运行
    6. 进阶
      1. CPU模式的虚拟化
      2. 多处理器虚拟机
  4. 中断虚拟化
    1. 概述
    2. 虚拟PIC
    3. 虚拟I/O APIC
    4. 虚拟Local APIC
    5. 中断采集
    6. 中断注入
    7. 案例分析
      1. 简单示例
      2. 复杂示例
  5. 内存虚拟化
    1. 概述
    2. EPT
      1. EPT原理
      2. EPT的硬件支持
      3. EPT的软件使用
    3. VPID
  6. I/O虚拟化的硬件支持
    1. 概述
    2. VT-d技术
      1. DMA重映射(DMA Remapping)
      2. I/O页表
      3. VT-d硬件缓存
      4. VT-d硬件的探测
  7. I/O虚拟化的实现
    1. 设备直接分配
    2. 设备I/O地址空间的访问
    3. 设备发现
    4. 配置DMA重映射数据结构
    5. 设备中断虚拟化
  8. 时间虚拟化
    1. 操作系统的时间概念
    2. 客户机的时间概念
    3. 时钟设备仿真
    4. 实现客户机时间概念的一种方法
    5. 实现客户机时间概念的另一种方法
    6. 如何满足客户机时间不等于实际时间的需求

概述

硬件辅助虚拟化技术,就是在CPU、芯片组及I/O设备等硬件中加入专门针对虚拟化的支持,使得系统软件可以更加容易、高效地实现虚拟化功能。

之所以需要在硬件中加入虚拟化的支持,原因是多方面的。首先,由于原有的硬件体系结构在虚拟化方面存在缺陷,例如虚拟化漏洞,导致单纯的软件虚拟化方法存在种种问题,如降优先级(deprivilege)的方法存在特权级压缩(Ring Compression)问题。BT技术存在难以处理自修改代码及自参考代码的问题。PV技术存在需要修改源码的问题。其次,由于硬件架构的限制,某些虚拟化功能尽管可以用软件方法来实现,但是实现起来非常复杂,一个典型的例子就是内存虚拟化的“影子页表”。最后,某些通过软件方法实现的虚拟化功能性能不佳,例如I/O设备的虚拟化。这些问题,都只有通过在CPU体系架构上增加相应的硬件支持,才能彻底解决。

Intel VT是Intel平台上硬件虚拟化技术的总称,包含了对CPU、内存和I/O设备等方面的虚拟化支持。下图列举了Intel VT涵盖的内容。

Intel虚拟技术Intel虚拟技术

上层是通用功能,如资源管理、系统调度等。下层是平台相关的部分,即使用Intel VT实现的处理器虚拟化、内存虚拟化和I/O虚拟化。

在CPU虚拟化方面,Intel VT提供了VT-x(Intel Virtualization technology for x86)技术;在内存虚拟化方面,Intel VT提供了EPT(Extended Page Table)技术;在I/O设备虚拟化方面,Intel VT提供了VT-d(Intel Virtualization Technology for Direct I/O)等技术。

此外,AMD平台也提供了类似的技术,即AMD Virtualization(AMD-V),其原理和使用方式与Intel VT类似。

CPU虚拟化的硬件支持

概述

Intel VT中的VT-x技术扩展了传统的IA32处理器架构,为IA32架构的处理器虚拟化提供了硬件支持。

VT-x的基本思想可以概括为下图所示。

VT-x ArchitectureVT-x Architecture

首先,VT-x引入了两种操作模式,统称为VMX操作模式。

  • 根操作模式(VMX Root Operation):VMM运行所处的模式,以下简称根模式。
  • 非根操作模式(VMX Non-Root Operation):客户机运行所处的模式,以下简称非根模式。

这两种操作模式与IA32特权级0至特权级3是正交的,即每种操作模式下都有相应的特权级03。故在VT-x使用的情况下,描述程序运行在某个特权级,例如特权级0,还必须指出当前是处在根模式还是非根模式。

引入两种操作模式的理由很明显。指令的虚拟化是通过“陷入再模拟”的方式实现的,而IA32架构有19条敏感指令不能通过这种方式处理,导致了虚拟化漏洞。最直观的解决办法,是使得这些敏感指令能够触发异常。可惜这种方法会改变这些指令的语义,导致与原有软件不兼容,这是不可接受的。引入新的模式可以很好地解决问题。非根模式下所有敏感指令(包括19条不能被虚拟化的敏感指令)的行为都被重新定义,使得它们能不经过虚拟化就直接运行或通过“陷入再模拟”的方式来处理;在根模式下,所有指令的行为和传统IA32一样,没有改变,因此原有的软件都能正常运行。

VT-x中,非根模式下敏感指令引起的“陷入”被称为VM-Exit。VM-Exit发生时,CPU自动从非根模式切换为根模式。相应地,VT-x也定义了VM-Entry,该操作由VMM发起,通常是调度某个客户机运行,此时CPU从根模式切换成为非根模式。

其次,为了更好地支持CPU虚拟化,VT-x引入了VMCS(Virtual-Machine Control Structure,虚拟机控制结构)。VMCS保存虚拟CPU需要的相关状态,例如CPU在根模式和非根模式下的特权寄存器的值。VMCS主要供CPU使用,CPU在发生VM-Exit和VM-Entry时都会自动查询和更新VMCS。VMM可以通过指令来配置VMCS,进而影响CPU的行为。

最后,VT-x还引入了一组新的指令,包括VMLAUCH/VMRESUME用于发起VM-Entry,VMREAD/VMWRITE用于配置VMCS等。

VMCS

VMCS的概念和虚拟寄存器的概念类似,可以看作是虚拟寄存器概念上在硬件上的应用。虚拟寄存器的操作和更改完全由软件执行,但VMCS却主要由CPU操作。VMCS是保存在内存中的数据结构,包含了虚拟CPU的相关寄存器的内容和虚拟CPU相关的控制信息,每个VMCS对应一个虚拟CPU。

VMCS在使用时需要与物理CPU绑定。在任意给定时刻,VMCS与物理CPU是一对一的绑定关系,即一个物理CPU只能绑定一个VMCS,一个VMCS也只能与一个物理CPU绑定。VMCS在不同的时刻可以绑定到不同的物理CPU,例如在某个VMCS先和物理CPU1绑定,并在某一个时刻解除绑定关系,并重新绑定到物理CPU2。这种绑定关系的变化被称为VMCS的迁移(Migration)。

VT-x提供了两条指令用于VMCS的绑定与解除绑定。

  • VMPTRLD<VMCS地址>:将指定的VMCS与执行该指令的物理CPU绑定。
  • VMCLEAR:将执行该指令的物理CPU与它的VMCS解除绑定。该指令会将物理CPU缓存中的VMCS结构同步到内存中去,从而保证VMCS和新的物理CPU绑定时,内存中的值是最新的。

VMCS的一次迁移过程如下:

  1. 在CPU1上执行VMCLEAR,解除绑定。
  2. 在CPU2上执行VMPTRLD,进行新的绑定。

VT-x定义了VMCS的具体格式和内容。规定它是一个最大不超过4KB的内存块,并要求是4KB对齐。描述了VMCS的格式,各域描述如下。

VMCS块格式如下表所示。

字节偏移 描述
0 VMCS revision identifier
4 VMX-abort indicator
8 VMCS data(implementation-specific format)
  1. 偏移0处是VMCS版本标识,表示VMCS数据格式的版本号。
  2. 偏移4处是VMX中止指示,VM-Exit执行不成功时产生VMX中止,CPU会在此处存入VMX中止的原因,以方便调试。
  3. 偏移8处是VMCS数据域,该域的格式是CPU相关的,不同型号的CPU可能使用不同格式,具体使用哪种格式由VMCS版本标识确定。

VMCS主要的信息存放在“VMCS数据域”,VT-x提供了两条指令用于访问VMCS。

  • VMREAD<索引>:读VMCS中“索引”指定的域。
  • VMWRITE<索引> <数据>:写VMCS中“索引”指定的域。

VT-x为VMCS数据域的每个字段也定义了相应的“索引”,故通过上述两条指令也可以直接访问VMCS数据域中的各个域。

具体而言,VMCS数据域包括下列6大类信息。

  1. 客户机状态域:保存客户机运行时,即非根模式时的CPU状态。当VM-Exit发生时,CPU把当前状态存入客户机状态域;当VM-Entry发生时,CPU从客户机状态域恢复状态。
  2. 宿主机状态域:保存VMM运行时,即根模式时的CPU状态。当VM-Exit发生时,CPU从该域恢复CPU状态。
  3. VM-Entry控制域:控制VM-Entry的过程。
  4. VM-Execution控制域:控制处理器在VMX非根模式下的行为。
  5. VM-Exit控制域:控制VM-Exit的过程。
  6. VM-Exit信息域:提供VM-Exit原因和其他信息。VM-Exit信息域是只读的。

客户机状态域

客户机状态域用于保存CPU在非根模式下运行时的状态。当发生VM-Entry时,CPU自动将客户机状态域保存的状态加载到CPU中;当发生VM-Exit时,CPU自动将CPU的状态保存回客户机状态域。

客户机状态域中首先包含了一些寄存器额值,这些寄存器是必须由CPU进行切换的,如段寄存器、CR3、IDTR和GDTR。CPU通过这些寄存器的切换来实现客户机地址空间和VMM地址空间的切换。客户机状态域中并不包括通用寄存器和浮点寄存器,它们的保存和恢复由VMM决定,可提高效率和增强灵活性。客户机状态域包含的寄存器如下。

  1. 控制寄存器CR0、CR3和CR4。
  2. 调试寄存器DR7。
  3. RSP、RIP和RFLAGS。
  4. CS、SS、DS、ES、FS、GS、LDTR、TR及影子段描述符寄存器。
  5. GDTR、IDTR及影子段描述符寄存器。

除上述寄存器外,客户机状态域还包含一些MSR的内容。这些MSR既可以由处理器切换,也可以由VMM进行切换。由谁切换,可以通过VMCS的一些控制域设定。这些MSR包括IA32_SYSENTER_CS、IA32_SYSENTER_ESP和IA32_SYSENTER_EIP等。

除此之外,客户机状态域还包含了一些非寄存器内容,主要用于精确模拟虚拟CPU,例如中断状态域等。

宿主机状态域

宿主机状态域用于保存CPU在根模式下运行时的CPU状态。宿主机状态域只在VM-Exit时被恢复,在VM-Entry时不用保存。这是因为宿主机状态域的内容通常几乎不需要改变,例如VM-Exit的入口RIP在VMM整个运行期间都是不变的。当需要改变时,VMM可以直接对该域进行修改,VMCS是保存在内存中的。

宿主机状态域只包含寄存器值,具体内容如下。

  1. 控制寄存器CR0、CR3和CR4。
  2. 调试寄存器DR7。
  3. RSP、RIP和RFLAGS。
  4. CS、SS、DS、ES、FS、GS、TR及影子段描述符寄存器。
  5. GDTR、IDTR及影子段描述符寄存器。
  6. IA32_SYSENTER_CS。
  7. IA32_SYSENTER_ESP。
  8. IA32_SYSENTER_EIP。

与客户机状态域相比,宿主机状态域没有LDTR,正如操作系统内核通常不使用LDT一样,VMM只需要GDT就足够了。

此外,当VM-Exit发生时,宿主机状态域中的CS:RIP指定了VM-Exit的入口地址,SS、RSP指定了VMM的栈地址。

VMX操作模式

作为传统IA32架构的扩展,VMX操作模式这个功能在默认情况下时关闭的,因为传统的操作系统不需要这个功能。当VMM需要使用这个功能时,可以使用VT-x提供新的指令来打开与关闭这个功能,如下图。

VMX操作模式VMX操作模式
  • VMXON:打开VMX操作模式。
  • VMXOFF:关闭VMX操作模式。

描述了开启/关闭VMX的过程,以及VMX开启情况下,VMM和客户软件的交互操作。

  1. VMM执行VMXON指令进入到VMX操作模式,CPU处于VMX根操作模式,VMM软件开始执行。
  2. VMM执行VMLAUNCH或VMRESUME指令产生VM-Entry,客户机软件开始执行,此时CPU进入非根模式。
  3. 当客户机执行特权指令,或者当客户机运行时发生了中断或异常,VM-Exit被触发而陷入到VMM,CPU切换到根模式。VMM根据VMM-Exit的原因做相应处理,然后转到步骤2继续运行客户机。
  4. 如果VMM决定退出,则执行VMXOFF关闭VMX操作模式。

VM-Entry/VM-Exit

VMM在机器加电引导后,会进行类似操作系统一样的初始化工作,并在准备就绪时通过VMXON指令进入根模式。在创建客户机时,VMM会通过VMLAUNCH或VMRESUME指令切换到非根模式运行客户机,客户机引起VM-Exit后又切换回根模式运行VMM。

VM-Entry

VM-Entry是指CPU由根模式切换到非根模式,从软件角度看,是指CPU从VMM切换到客户机执行。这个操作通常由VMM主动发起。在发起之前,VMM会设置好VMCS相关域的内容,例如客户机状态域、宿主机状态域等,然后执行VM-Entry指令。

VT-x为VM-Entry提供了两条指令。

  • VMLAUNCH:用于刚执行过VMCLEAER的VMCS的第一次VM-Entry。
  • VMRESUME:用于执行过VMLAUNCH的VMCS的后续VM-Entry。

VM-Entry的具体行为由VM-Entry控制域规定,该域的具体定义如下图。

VMCS VM-Entry控制域VMCS VM-Entry控制域

VM-Entry控制域中的“事件注入控制”用到了VM-Entry Interruption-Information字段,下图列出该字段的格式。

VM-Entry Interruption-Information字段的格式VM-Entry Interruption-Information字段的格式

每次VM-Entry时,在切换到客户机环境后即将执行客户机指令前,CPU会检查这个32位字段的最高位(即bit31)。如果为1,则根据bit10:8指定的中断类型和bit7:0指定的向量号在当前的客户机中引发一个异常、中断或NMI。此外,如果bit11为1,表示要注入的事件有一个错误码(如Page Fault事件),错误码由另一个VMCS的寄存器VM-Entry exception error code指定。注入的事件最终是用客户机自己的IDT里面指定的处理函数来处理的。这样在客户机虚拟CPU看来,这些事件就和没有虚拟化的环境里面对应的事件没有任何区别。

VM-Entry的过程

当CPU执行VMLAUNCH/VMRESUME进行VM-Entry时,处理器要进行下面的步骤。

  1. 执行基本的检查来确保VM-Entry能开始。
  2. 对VMCS中的宿主机状态域的有效性进行检查,以确保下一次VM-Exit发生时可以正确地从客户机环境切换到VMM环境。
  3. 检查VMCS中客户机状态域的有效性;根据VMCS中客户机状态域来装载处理器的状态。
  4. 根据VMCS中VM-Entry MSR-load区域装载MSR寄存器。
  5. 根据VMCS中VM-Entry事件注入控制的配置,可能需要注入一个事件到客户机中。

第1~4步的检查如果没有通过,CPU会报告VM-Entry失败,这通常意味着VMCS中某些字段的设置有错误。如果所有这些步骤都正常通过了,处理器就会把执行环境从VMM切换到客户机环境,开始执行客户机指令。

VM-Exit

VM-Exit是指CPU从非根模式切换到根模式,从客户机切换到VMM的操作。引发VM-Exit的原因很多,例如在非根模式执行了敏感指令、发生了中断等。处理VM-Exit时间是VMM模拟指令、虚拟特权资源的一大任务。

非根模式下的敏感指令

当成功执行VM-Entry后,CPU就进入了非根模式。敏感指令如果运行在VMX非根操作模式,其行为可能会发生变化。具体来说有如下三种可能。

  1. 行为不变化,但不引起VM-Exit:这意味着虽然是敏感指令,但是它不需要被VMM截获和模拟,例如SYSENTER指令。
  2. 行为变化,产生VM-Exit:这就是典型需要截获并模拟的敏感指令。
  3. 行为变化,产生VM-Exit可控:这类敏感指令是否产生VM-Exit,可以通过VM-Execution域控制。出于优化的目的,VMM可以让某些敏感指令不产生VM-Exit,以减小模式切换带来的上下文开销。

由此可见,使用VT-x技术实现的VMM,并不需要对所有敏感指令进行模拟,这大大减小了VMM实现的复杂性。VM-Execution域的存在又为VMM的实现带来了灵活性。

VM-Execution控制域

VM-Execution控制域用来控制CPU在非根模式运行时的行为,根据虚拟机的实际应用,VMM可以通过配置VM-Execution控制域达到性能优化等目的。VM-Execution控制域主要控制三个方面。

  1. 控制某条敏感指令是否产生VM-Exit,如果产生VM-Exit,则由VMM模拟该指令。
  2. 在某些敏感指令不产生VM-Exit时,控制该指令的行为。
  3. 异常和中断是否产生VM-Exit。

下图列举出一些典型的VM-Execution控制域。

VM-Execution控制域VM-Execution控制域

VM-Exit控制域

VM-Exit控制域规定了VM-Exit发生时CPU的行为,下图描述了该域的内容。

VM-Exit控制域VM-Exit控制域

VM-Exit信息域

VMM除了要通过VM-Exit控制域来控制VM-Exit的行为外,还需要知道VM-Exit的相关信息(如退出原因)。VM-Exit信息域满足了这个要求,其提供的信息可以分为如下4类。

  1. 基本的VM-Exit信息,包括如下内容。

    1. Exit Reason:提供了VM-Exit的基本原因,如下表所示。
    字段 描述
    Basic exit reason VM-Exit的基本原因,如果VM-Entry failure为1,该字段表示VM-Entry失败的原因
    VM-Exit from VMX root operation 该位为1,表示一此VM-Exit发生在CPU处于根模式时
    VM-Entry failure 该位为1,表示一次VM-Entry失败了
    1. Exit qualification:提供VM-Exit的进一步原因。这个字段的值根据VM-Exit基本退出原因的不同而不同。例如,对于因为访问CR寄存器导致的VM-Exit,Exit qualification提供的信息包括:是哪个CR寄存器、访问类型时读还是写、访问的内容等。同样的,VT-x规范也完整地定义了所有VM-Exit退出原因所对应的Exit qualification。对于某些不需要额外信息的退出原因,没有相应的Exit qualification的定义。
  2. 事件触发导致的VM-Exit的信息。事件是指外部中断、异常(包括INT3/INTO/BOUND/UD2导致的异常)和NM1。对于此类VM-Exit,VMM可以通过VM-Exit interruption information字段和VM-Exit interruption error code字段获取额外信息,例如事件类型、实现相关的向量号等。

  3. 事件注入导致的VM-Exit的信息。一个事件在注入客户机时,可能由于某种原因暂时不能成功,而触发VM-Exit。此时,VMM可以从IDT-vectoring information字段和IDT-vectoring error code中获取此类VM-Exit的额外信息,例如事件类型、时间向量号等。

  4. 执行指令导致的VM-Exit的信息。除了第一类中列出的信息外,客户机在执行敏感指令导致VM-Exit时,VMCS中还有三个字段可以提供额外的信息。Guest linear address字段给出了导致VM-Exit指令的客户机线性地址,VM-Exit instruction length字段给出了该指令的长度,VM-Exit instruction information字段给出了该指令为VMX指令时的额外信息。

VM-Exit的具体过程

当一个VM-Exit发生时,依次执行下列步骤。

  1. CPU首先将此次VM-Exit的原因信息记录到VMCS相应的信息域中,VM-Entry interruption-information字段的有效位(bit 31)被清零。
  2. CPU状态被保存到VMCS客户机状态域。根据设置,CPU也可能将客户机的MSR保存到VM-Exit MSR-store区域。
  3. 根据VMCS中宿主机状态域和VM-Exit控制域中的设置,将宿主机状态加载到CPU相应寄存器。CPU也可能根据VM-Exit MSR-store区域来加载VMM的MSR。
  4. CPU由非根模式切换到了根模式,从宿主机状态域中CS:RIP指定的VM-Exit入口函数开始执行。

在VMM处理完VM-Exit后,会通过VMLAUNCH/VMRESUME指令发起VM-Entry进而重新运行客户机。当下一次VM-Exit发生后,又会重复上述处理流程。虚拟化的所有内容就在VMM -> 客户机 -> VMM -> ……的不断切换中完成。

CPU虚拟化的实现

概述

与软件虚拟化技术不同的是,使用Intel VT-x的VMM在处理器虚拟化的实现上更加简单和高效。

和软件虚拟化技术用“CPU执行环境处理器”来描述虚拟CPU类似,硬件虚拟化使用VCPU(Virtual CPU)来描述虚拟CPU。VCPU描述符类似操作系统中进程描述符(或进程 控制块),本质是一个结构体,通常由下列几部分构成。

  1. VCPU标识信息:用于标识VCPU的一些属性,例如VCPU的ID号,VCPU属于哪个客户机。
  2. 虚拟寄存器信息:虚拟的寄存器资源,在使用Intel VT-x的情况下,这些内容包含在VMCS中,例如客户机状态域保存的内容。
  3. VCPU状态信息:类似于进程的状态信息,标识该VCPU当前所处的状态,例如睡眠、运行等,主要供调度器使用。
  4. 额外寄存器/部件信息:主要指未包含在VMCS中的一些寄存器或CPU部件。例如浮点寄存器和虚拟的LAPIC等。
  5. 其他信息:用于VMM进行优化或存储额外信息的字段,例如存放该VCPU私有数据的指针等。

由此可见,Intel VT-x情况下的VCPU可以划分为两个部分,一个是以VMCS为主由硬件使用和更新的部分,这主要是虚拟寄存器;一个是除VMCS之外,由VMM使用和更新的部分,主要指VMCS之外,由VMM使用和更新的部分,主要指VMCS以外的部分。下图展示了VCPU的构成。

当VMM创建客户机时,首先要为客户机创建VCPU,整个客户机的运行实际上可以看作是VMM调度不同的VCPU运行。下面就以VCPU的创建————运行————退出为主线,详解Intel VT-x技术的CPU虚拟化的实现。

VCPU的创建

创建VCPU实际上是创建VCPU描述符,由于本质上VCPU描述符是一个结构体,因此创建VCPU描述符简单来说就是分配相应大小的内存。VCPU描述符包含很多内容,通常会被组织成多级结构,例如第一级结构体可以是各个平台的通用内容,中间包含一个指针指向第二级结构体,包含平台相关的内容,如下图所示。对于这样的多级结构,需要为每一级结构体相应地分配内存。

VCPU结构VCPU结构

VCPU描述符在创建之后,需要进一步初始化才能使用。物理CPU在上电之后,硬件会自动将CPU初始化为特定的状态。VCPU的初始化也是一个类似的过程,将VCPU描述符的各个部分置成可用的状态。通常初始化包含如下内容。

  1. 分配VCPU标识:首先要标识该VCPU属于哪个客户机,再为该VCPU分配一个在客户机范围内唯一的标识。
  2. 初始化虚拟寄存器组:主要指初始化VMCS相关域。这些寄存器的初始化值通常是根据物理CPU上电后各寄存器的值设定的。
  3. 初始化VCPU状态信息:设置VCPU在被调度前需要配置的必要标志。具体情况依据调度器的实现决定。
  4. 初始化额外部件:将未被VMCS包含的虚拟寄存器初始化为物理CPU上电后的值,并配置虚拟LAPIC等部件。
  5. 初始化其他信息:根据VMM的实现初始化VCPU的私有数据。

VMCS的创建与初始化

VMCS的创建与初始化是支持VT-x的VCPU创建的重要部分。

VMCS在分配时,只需要分配一块4KB大小,并对齐到4KB边界的内存即可。初始化则需要根据VT-x的定义,对于前面所列的VMCS相关内容进行初始化,基本思想是根据物理CPU初始化的定义,提供一个和物理CPU初始化后类似的状态。此外,根据VMM的CPU虚拟化策略,设置相应的VMCS控制位。

  1. 客户机状态域:这个状态域描述了VCPU运行时的状态,因此,初始化的取值基本上是参考物理CPU初始化后的状态。例如,物理CPU加电后会通过复位地址跳转到BIOS执行,那么Guest RIP字段可直接设置为虚拟机Guest BIOS的起始指令地址。
  2. 宿主机状态域:这个状态域描述了发生VM-Exit时,CPU切换到VMM时的寄存器的值,因此,初始化的取值是参考了VMM运行时的CPU的状态。例如HOST CS、HOST DS等字段的取值是VMM运行时的段寄存器的值;HOST CR0、HOST CR3等字段的取值是VMM运行时控制寄存器的值,通常是保护模式的、开页的。此外,HOST RIP字段通常被设置为VMM重VMX Exit处理函数(VMX Exit Handler)的入口。
  3. VM-Execution控制域:这个控制域VCPU运行时的一些行为,如执行某些敏感指令是否发生VM-Exit。因此,这个域的取值主要取决于VMM对于相应敏感指令的虚拟化策略。举例来说,对于IN/OUT指令,如果VMM允许客户机软件直接访问某些I/O端口,那么VMM就会将Use I/O bitmaps位置为1,并且在I/O bitmap中将相应的I/O端口所对应的位置为0,这样,客户机软件访问这些I/O端口就不会发生VM-Exit。VM-Execution控制域给VMM带来了很大的灵活度,允许VMM做出很多优化。
  4. VM-Entry控制域:这个状态域主要在每次VM-Entry之前设置,因此在VCPU初始化时不需要特别设置。
  5. VM-Exit控制域:这个状态域的有两个字段VMM通常有兴趣去设置,一个是Acknowledge interrupt on exit,有助于更快地响应外部中断;另一个是Host Address Space,用于支持IA32e模式。
  6. VM-Exit信息域:这个域的值由硬件自动更新,因此不需要初始化。

VCPU的运行

VCPU创建并初始化好之后,就可以通过调度程序被调度运行。调度程序会根据一定的策略算法来选择VCPU运行。

上下文切换

上下文实际上是一个寄存器的集合。这里的寄存器包括通用寄存器、浮点寄存器、段寄存器、控制寄存器以及MSR等。在Intel VT-x的支持下,VCPU的上下文可以分为两部分。故上下文的切换也分为由硬件自动切换(VMCS部分)和VMM软件切换(非VMCS部分)两个部分。其中硬件切换部分可以更好地保证VMM与客户机的隔离,但缺乏灵活性。软件切换部分则可以由VMM自己选择性地切换需要的上下文(例如,浮点寄存器就无须每次都切换),从而有更大的灵活性并节省切换的开销。

下图描述看VT-x支持的CPU上下文切换的过程。

VM-Entry中的上下文切换VM-Entry中的上下文切换

可以归纳为下列几个步骤。

  1. VMM保存自己的上下文,主要是保存VMCS不保存的寄存器,即宿主机状态域以外的部分。
  2. VMM将保存在VCPU中的由软件切换的上下文加载到物理CPU中。
  3. VMM执行VMRESUME/VMLAUNCH指令,触发VM-Entry,此时CPU自动将VCPU上下文中VMCS部分加载到物理CPU,CPU切换到非根模式。

此时,物理CPU已经处于客户机的运行环境了,rip/eip也指向了客户机的指令,这样VCPU就被成功调度并运行了。

上下文切换次数频繁会带来不小的切换开销,因此对上下文切换进行优化是很有必要的。和操作系统一样,VMM也使用“惰性保存/恢复(Lazy Save/Restore)”的方法进行优化,其基本思想是尽量将寄存器的保存/恢复延迟到最后一刻,即其他VCPU或VMM需要用该寄存器的时候再保存/恢复。这种方法能够减少很多不必要的寄存器保存/恢复,提高上下文切换的效率。具体来说,VMM通过考察资源的使用情况来实现“惰性保存/恢复”。

  1. 对于VMM需要使用的寄存器,每次VCPU和VMM切换时都要保存/恢复。
  2. 对于VMM没有使用的寄存器,如果VMM无法知道VCPU是否在最近的执行中曾经修改了这个寄存器(如果扩展通用寄存器DR6)那么在VCPU切换时,不需要对这个寄存器进行保存和恢复。但是,当VMM进行不同的VCPU切换时,例如使一个VCPU睡眠并调度另一个VCPU运行,需要每次都保存和恢复这个寄存器。
  3. 对于VMM没有使用的寄存器,如果VMM可以知道客户机是否在最近的执行中修改了这个寄存器(如浮点寄存器),还可以做进一步的优化。不仅在VCPU和VMM切换时,不需要对这个寄存器进行保存进行保存和恢复,即使切换不同VCPU,也不需要每次都保存/恢复,而是根据需要进行。

举个简单的例子来说明这种情况,如下图。

Lazy Save/Restore示例Lazy Save/Restore示例

VCPU1、VCPU2和VCPU3按照顺序调度到物理CPU上执行,即VCPU1先执行,其次VCPU2,最后VCPU3。其中,VCPU1和VCPU3在执行过程中会使用浮点寄存器,而VCPU2不会。VMM了解到这种情况后,在从VCPU1调度到VCPU2时,只需要保存VCPU1的浮点寄存器而无须加载VCPU2的;从VCPU2调度到VCPU3时,只需要加载VCPU3的浮点寄存器而无须保存VCPU2的。这样就将原本两次保存/加载的工作减少为一次(保存VCPU1半次,加载VCPU3半次)。

VCPU的硬件优化

相对于软件虚拟技术实现的CPU虚拟化,使用Intel VT-x技术的VMM可以采用多种方式对VCPU的实现进行优化。优化的目的,是尽可能少地在客户机和VMM之间切换,从而减少上下文切换的开销。Intel VT-x提供的优化方法可以分为如下两种。

  1. 无条件优化:指以往在软件虚拟技术下必须陷入到VMM中的敏感指令,通过Intel VT-x已可以在客户机中直接执行。如后面将看到的CR2访问、SYSENTER/SYSEXIT指令。
  2. 条件优化:指通过VMCS的VM-Execution控制域,可以配置某些敏感指令是否产生VM-Exit而陷入到VMM中。如CR0、TSC的访问。

下面举几个例子来说明Intel VT-x带来的优化技术。

访问CR0

CR0是一个控制寄存器,控制处理器的状态,如启动保护模式、打开分页机制。操作CR0的指令有MOV TO CR0、MOV FROM CR0、CLTS和LMSW,这些指令必须在特权级0执行,否则产生保护异常。

在基于软件的完全虚拟化技术的虚拟机中,客户机操作系统是特级1、特权级2上执行CR0读写指令,因此所有的指令都产生保护异常,然后VMM模拟操作CR0指令的执行。

在硬件辅助的虚拟机中,虽然CR0的访问同样需要VMM模拟处理,但是VT-x提供了加速方法,能够减少因访问CR0所引起的VM-Exit的次数。首先,VMCS的“VM-Execution控制域”中的CR0 read shadow字段来加速客户机读CR0的指令。每次客户机试图写CR0时,该字段都会自动得到更新,保存客户机要写的值。这样,客户机所有读CR0的指令都不用产生VM-Exit,CPU只要返回CR0 read shadow的值即可。其次,VMCS的“VM-Execution控制域”的CRO guest/host Mask字段提供了客户机写CR0指令的加速。该字段每一位和CR0的每一位对应,表示CR0对应的位是否可以被客户机软件修改。若为0,表示CR0中对应的位可以被客户机软件修改,不产生VM-Exit;若为1,表示CR0中对应的位不能被客户机软件修改,如果客户机软件修改该位,则产生VM-Exit。

同样的机制被用于加速CR4的访问。该优化属于条件优化。

访问TSC

在纯虚拟机软件中,因为读取TSC可以在任何特权级别执行,VMM必须想办法截获TSC读取指令。

在硬件辅助的虚拟机中,当“VM-Execution控制域”中RDTSC exiting字段为1时,客户软件执行RDTSC产生VM-Exit,由VMM模拟该指令。客户机读取TSC在某些操作系统里是一个非常频繁的操作,为了提高效率,VT-x提供了下面的硬件加速。

当VMCS中RDTSC exiting为1且Use TSC offset为1时,硬件加速有效。VMCS中TSC偏移量表示该VMCS所代表的的虚拟CPU TSC相对于物理CPU TSC的偏移,即虚拟TSC = 物理TSC + TSC偏移量。 当客户机软件执行RDTSC时,处理器直接返回虚拟TSC,不产生VM-Exit。

这样,对TSC的虚拟化只需要适时地更新VMCS中TSC偏移量即可,不需要每次TSC访问都产生VM-Exit,大大提高了TSC访问的效率。该优化属于条件优化。

GDTR/LDTR/IDTR/TR的访问

在基于软件的完全虚拟化技术的虚拟机中,客户机操作系统是运行在特权级1、特权级2上,执行LGDT、LIDT、LLDT和LTR指令,会产生保护异常,需要VMM模拟这些指令的执行。在模拟的过程中,对于不同的情况,还有很多复杂的处理。例如,客户机操作系统在GDT(全局描述符表)中,为自身内核段设置的描述符的DPL(描述符特权级)是0(特权级最高)。由于它本身运行在非特权级0上,所以VMM要通过截获LGDT指令,对GDT中的描述符进行修改。同时,像SGDT这样的指令可以在任何特权级下执行,客户机操作系统中的程序只需要读取GDT并判断描述符的DPL就知道自身运行在虚拟机环境下,这也是一个虚拟化的漏洞。

使用Intel VT-x技术,VMCS为客户机和VMM都提供了一套GDTR、IDTR、LDTR和TR,分别保存在客户机状态域和宿主机状态域中(宿主机状态域不包括LDTR,VMM不需要使用它),由硬件切换。而客户机运行在非根模式的特权级0,所以也无须对GDT表等作出任何修改,客户机执行LGDT等指令也无须产生VM-Exit。这样的优化大大降低了VMM的复杂度,使实现一个VMM变得简单。该优化属于无条件优化。

读CR2

在发生缺页异常时,CR2保存产生缺页错误的虚拟地址。缺页错误处理程序通常会读取CR2获得产生该错误的虚拟地址。缺页错误时一个发生频率比较高的异常,这决定了读取CR2是一个高频率的操作。读取CR2必须在特权级0上执行,否则产生保护错误。

在基于软件的完全虚拟化技术的虚拟机中,客户机操作系统是在特权级1、特权级2上执行读取CR2指令,产生保护错误,需要VMM模拟该指令。

使用Intel VT-x技术,VM-Entry/VM-Exit时会切换CR2。并且,客户机操作系统是在非根模式的特权级0执行读取CR2指令,不产生保护错误,故无须VMM模拟该指令,此外,如果客户机在特权级0以外的级别执行读CR2指令,会产生保护错误,该错误是否引发VM-Exit由Exception bitmap控制。该优化属于无条件优化。

SYSENTER/SYSEXIT

早期的系统调用是通过INT指令和IRET指令实现的。在当前主流的IA32 CPU中,Intel推出了经过优化的SYSENTER/SYSEXIT指令以提高效率。现代操作系统都倾向于使用SYSENTER/SYSEXIT实现系统调用。

SYSENTER指令要求跳转的目标代码段运行在特权级0,否则产生保护错误。在软件虚拟化技术中,客户操作系统运行在特权级1、特权级2,当客户应用程序执行SYSENTER会产生保护错误,需要由VMM模拟SYSENTER指令。SYSEXIT指令必须在特权级0执行,否则产生保护错误。和SYSENTER一样,SYSEXIT在软件虚拟化技术中必须由VMM模拟。

使用Intel VT-x技术,客户操作系统运行在非根模式的特权级0,SYSENTER/SYSEXIT都不会引起VM-Exit,即客户机操作系统的系统调用无须VMM干预而直接执行。该优化属于无条件优化。

APIC访问控制

对于现代主流的支持SMP的操作系统来说,LAPIC(高级可编程中断控制器)在中断的递交中扮演着一个非常重要的角色。LAPIC里面有很多寄存器,通常操作系统会以MMIO(内存映射I/O)方式来访问它们。在这些寄存器里,操作系统使用其中的TPR(Task Priority Register)来屏蔽中断优先级小于或者等于TPR的外部中断。

通过虚拟化客户机的MMU,当客户机试图访问LAPIC时,会发生一个缺页异常类型的VM-Exit,从而被VMM拦截到。VMM经过分析,知道客户机正在试图访问LAPIC后,就会模拟客户机对LAPIC的访问。通常,对于客户机的每一个虚拟CPU,VMM都会分配一个虚拟LAPIC结构与之对应,客户机的MMIO操作不会真的影响物理的LAPIC,而只是反映到相应的虚拟LAPIC结构里面。VMM的这种模拟有相当大的开销,如果客户机的每一个LAPIC访问都导致一次缺页异常类型的VM-Exit并由VMM模拟的话,会严重影响到客户机的性能。

针对这种情况,VT-x提供了硬件加速支持。可以设置VMCS中的Use TPR shadow = 1,Virtualize APIC accesses = 1,设置Virtual APIC page为虚拟LAPIC结构的地址,同时修改VCPU页表,使得客户机访问LAPIC时不发生Page Fault(这需要相应地设置VMCS中的Virtual-APIC address寄存器)。同时,对于那些暂时不能注入客户机的中断(如果有的话),还需要挑出优先级最高的那个(就是向量号最大的那个),将其优先级填入VMCS中的TPR threshold寄存器。

这样设置后,对于除了TPR以外的LAPIC寄存器的访问,客户机会直接发生APIC-Access类型的VM-Exit。此时,CPU可告知VMM客户机正试图访问哪个LAPIC寄存器,这可降低VMM对客户机此次访问的模拟开销;而客户机对TPR的读操作则可以直接从虚拟LAPIC结构中的相应偏移出读取而无须发生任何VM-Exit。最后,客户机对TPR的写操作只在必要的时候(客户机把TPR减小到比TPR threshold还要小的时候)才发生TPR-Below-Threshold类型的VM-Exit,这种情况下VMM可检测是否有虚拟中断可以注入客户机。

上面谈到TPR寄存器时,说是用MMIO方式来访问的,其实对于64位的x86平台,专门有一个特别的系统控制寄存器CR8被映射到了TPR(读写CR8就等效于读写TPR),64位的客户机通常CR8寄存器来访问TPR。当客户机试图访问CR8时,会发生一个Control-Register-Accesses类型的VM-Exit。为了更快地模拟客户机对CR8的访问,除了上面提到的设置外,可以设置VMCS中的CR8-load exiting = 0和CR8-store exiting = 0。这样,客户机读CR8时,CPU可以从虚拟LAPIC结构中相应的偏移处直接返回正确的值,而不会发生任何VM-Exit;当客户机写CR8时,只在必要的时候才发生TPR-Below-Threshold类型的VM-Exit。

异常控制

在基于软件的完全虚拟化技术中,客户机产生的异常都会被VMM截获,由VMM决定如何处理,通常是注入给客户机操作系统。

使用Intel VT-x技术,可以用Exception bitmap配置哪些异常需要由VMM截获。对于不需要VMM截获的异常,可以将Exception bitmap中对应的位置为1,则异常发生时直接由客户机操作系统处理。这样的优化可以大大减少由客户机异常引起的VM-Exit。该优化属于条件优化。

I/O控制

在基于软件的完全虚拟化技术中,VMM需要截获I/O指令来实现I/O虚拟化。但由于I/O指令通过设置可以在特权级3执行,截获I/O指令需要额外的处理。

使用Intel VT-x技术,可以通过VMCS的Unconditional I/O exiting、Use I/O bitmaps、I/O bitmap进行配置,选择性地让I/O访问产生VM-Exit而陷入VMM中。这样,对于不需要模拟的I/O端口,可以让客户机直接访问。该优化属于条件优化。

MSR位图

x86包括很多MSR寄存器,使用Intel VT-x和I/O控制一样,可以通过use MSR bitmaps、 MSR bitmap来控制对MSR的访问是否触发VM-Exit。该优化属于条件优化。

VCPU的退出

和进程一样,VCPU作为调度单位不可能永远运行,总会因为各种原因退出,例如执行了特权指令、发生了物理中断等。这种退出在VT-x中表现为发生VM-Exit。

对VCPU退出的处理是VMM进行CPU虚拟化的核心,例如模拟各种特权指令。

下图描述了VMM处理VCPU退出的典型流程。可以归纳为下列几个步骤。

  1. 发生VM-Exit,CPU自动进行一部分上下文切换。
  2. 当CPU切换到根模式开始执行VM-Exit的处理函数后,进行另一部分上下文的切换工作。

VM-Exit的处理VM-Exit的处理

根据VM-Exit信息域获得发生VM-Exit的原因,并分发到对应的处理模块处理。例如,原因是执行了特权指令,则调用相应指令的模拟函数进行模拟。

上图列举了一些较为典型的VCPU退出的原因,总结起来,VCPU退出的原因大体上有三类。

  1. 访问了特权资源,对CR和MSR寄存器的访问都属于这一类。
    对于此类VM-Exit,VMM通过特权资源的虚拟化来解决。特权资源虚拟化的要点在于解决客户机与VMM在特权资源控制权的矛盾。即客户机认为自己完全拥有特权资源,可以自由读写,而特权资源的实际拥有者是VMM,不能允许客户机自由读写。VMM通过引入“虚拟特权资源”和“影子特权资源”来解决这个矛盾。“虚拟特权资源”是客户机看到的特权资源,VMM允许客户机自由地读写。“影子特权资源”是客户机运行时特权资源真正的值,通常是VMM在“虚拟特权资源”的基础上经过处理得到的,因此称其为“影子”。
    下图以特权寄存器为例,展示了特权寄存器的虚拟化过程。


    特权寄存器的虚拟化特权寄存器的虚拟化
    当VCPU读特权寄存器时,VMM将“虚拟寄存器”的值返回。例如,对于MOV EAX, CR0指令,VMM将Virtual CR0的值赋给EAX,然后VM-Entry返回。当VCPU写特权寄存器时,VMM首先将值写入“虚拟寄存器”,然后根据“虚拟寄存器”的值以及虚拟化策略来更新“影子寄存器”,最后将“影子寄存器”的值应用到VCPU上,将值写入VMCS“客户机状态域”的对应字段并且VM-Entry返回。这里的虚拟化策略是因特权虚拟器而异的,例如对于下面指令:
    1
    2
    MOV EAX, 0x00000001
    MOV CR0, EAX
    假设原来的Virtual CR0 = 0x80000001,VMM比较之后会发现试图将CR0的第31位(CR0.PG:页模式)清掉,即关掉CPU的页模式。为了实现内存的隔离,VT-x是不允许客户机的页模式关掉的。因此,VMM会将Virtual CR0按照客户机要求设置为0x00000001,但是影子CR0依然设置为0x80000001(相应VMCS中的Guest CR0字段也会被设置)。此外VMM会通知内存虚拟化模块有关客户机页模式的变化,内存虚拟化模块会做相关处理,如不再使用客户机的页表等。
  2. 客户机执行的指令引发了异常,例如缺页错误。
    客户机指令导致的异常,很多是不需要虚拟化的,可以直接交由客户操作系统处理,例如“除0错误”、“溢出错误”和“非法指令”等异常。这些都可以通过前面提到的Exception bitmap设置。
    对于需要虚拟化的异常,没有一个通用的方法,VMM会对不同的异常做不同的处理。以缺页错误为例,VMM会首先分析产生错误的原因:如果是因为访问MMIO地址导致的异常,则可以知道客户机在做I/O操作,VMM会调用I/O虚拟化模块处理;如果是在基于软件完全虚拟化中的影子页表导致的异常,VMM会调用内存虚拟化模块处理;如果所有的原因都不是,那么就是客户机正常的缺页错误(即不需要VMM处理的缺页错误),该异常会被注入给客户机,由客户机操作系统自己处理。

  3. 发生了中断。这可以分成两种情况,一种是真正的物理中断;一种是客户机的虚拟设备发生了虚拟中断,并通过VMM提供的接口使客户机发生VM-Exit。对于前者,VMM首先读取VMCS的VM-Exit interruption information字段来获得中断向量号,然后调用VMM中对应的中断处理函数。对于后者,VMM在感知到虚拟中断发生时,会用某种方法把该虚拟中断的目标VCPU拖到VMM中,常用的方法是发一个IPI给运行该VCPU的物理CPU。然后,VMM在IPI的处理函数中将该虚拟中断注入给客户机,由客户操作系统处理。

VCPU的再运行

VMM在处理完VCPU的退出后,会负责将VCPU投入再运行。从VT-x的角度来看,有几点需要额外考虑。

  1. 如果VCPU继续在相同的物理CPU上运行,可以用VMRESUME来实现VM-Entry。VMRESUME比VMLAUNCH更轻量级,执行效率更高。因此,作为优化,VMM调度程序通常会尽量将VCPU调度在同一个物理CPU上。
  2. 如果由于某种原因(如负载均衡),VCPU被调度程序迁移到了另外一个物理CPU上,那么VMM需要做如下几件事情。
    1. 将VCPU对应的VMCS迁移到另一个物理CPU,这通常可以由一个IPI中断实现。
    2. 迁移完成后,在重新绑定的物理CPU上执行VMLAUNCH发起VM-Entry。

此外,某些异常和中断是需要注入给客户机,这也是VCPU运行时进行的。通过“事件注入机制”,可以很容易地让VCPU在运行后直接进入到相应的中断/异常处理函数中执行。整个虚拟化的内容就是在VMM -> 客户机 -> VMM -> … …中完成的。再细化一下,客户机的顺利运行,就是在VCPU运行 -> VCPU退出 -> VCPU再运行 -> … …的过程中完成的。

进阶

CPU模式的虚拟化

CPU常见的运行模式有实模式、保护模式、SMM模式和虚拟8086模式。除此之外,还有64位CPU用的IA-32e模式。其中,保护模式又包括分页打开和分页关闭两种情况。在一个物理机器上,可能会同时运行一个实模式的虚拟机、一个保护模式的虚拟机和一个IA-32e模式的虚拟机,以及其他组合。为此,VMM必须有模拟各种CPU运行模式的能力。

然而,由于客户机物理地址空间和机器真实的物理地址空间并不相同,而且客户机物理地址空间占用的真实物理页面通常是不连续的,因此,目前VT-x技术要求物理CPU处于非根模式时,分页机制必须是开启的,而不考虑客户机当前的运行模式。也就是说,即使客户机运行在实模式,其所在物理CPU的分页机制也是开启的。由于实模式使用的内存访问模式和保护模式不同,VMM需要大量的工作对客户机的实模式进行模拟。随着硬件技术的发展,硬件很可能会直接支持客户机的实模式内存访问,从而大大简化对客户机运行模式的虚拟化。

客户机看到CPU模式实际是VCPU中设置的模式,即VCPU结构中CR0寄存器值反映的模式,该模式和物理CPU的真正模式可能不同,所以CPU模式的虚拟化包括如下两个方面。

  1. 对标志CPU模式的控制寄存器(如CR0的PE/PG位)的虚拟化。
  2. 对CPU运行环境的虚拟化,如指令的操作数长度等。

当客户机运行在实模式时,由于物理CPU运行在保护模式,两者在指令、运行环境方面都不同,通常VMM是对客户机的指令进行模拟执行。

当客户机运行在分页关闭的保护模式时,而物理CPU实际运行在分页开启的保护模式下。此时,VMM要负责模拟客户机实模式的内存访问机制。

当客户机运行在分页开启的保护模式时,和物理CPU的运行模式一样,此时不需要就模式的虚拟化做额外的工作。

下表总结了客户机运行模式和物理CPU的实际模式之间的对应关系,并总结了模拟客户机运行模式的方法。

虚拟机认为的CPU模式 物理CPU实际运行模式 CPU模拟虚拟化手段
实模式 分页打开的保护模式 指令模拟
分页关闭的保护模式 分页打开的保护模式 VMM提供额外的页表
分页打开的保护模式 分页打开的保护模式 不需要
IA-32e模式 IA-32e模式 不需要

需要指出的是,客户机运行在何种模式和VMM在何种模式运行没有必然关系。例如,基于对大内存支持的需求,可以将VMM运行在IA32-e模式下。在该VMM上,可以运行一个IA-32保护模式的虚拟机。当发生VM-Exit和VM-Entry引起客户机和VMM切换时,硬件会通过装载“客户机状态域”/“宿主机状态域”,而自动完成CPU模式的转换。

多处理器虚拟机

随着多核技术的发展,今天大部分的计算机都具备了多个CPU。可以通过将客户机配置为多CPU的虚拟平台,来提高客户机的计算能力。所谓配置多CPU的虚拟平台,本质上就是给客户机分配多个VCPU,并通过调度器让它们共享一个物理CPU分时执行或分散到多个物理CPU同时执行。这和操作系统中多线程任务采用的是同样的思想。

与单VCPU客户机相比,多VCPU客户机在实现上还有几点需要注意。

  1. 多VCPU发现的问题。在物理机器上,操作系统需要知道平台所有CPU的信息,同样,需要让客户机操作系统知道它所拥有的VCPU的信息,例如VCPU的个数、每个VCPU的ID号等。这些信息再物理平台上市通过BIOS提供的。在虚拟环境下,客户机的虚拟BIOS负责这项工作。
  2. 多个VCPU初识化的问题。物理平台上,多处理器的初始化通常会遵循一个规范。硬件通常会选择一个CPU作为主CPU,称为BSP(Boot Strap Processor),来执行BIOS,其他的从CPU,称为AP(Application Processor),处于Wait-for-SIPI状态,等待BSP发SIPI(Start-up IPI)唤醒它们之后再开始执行。客户机同样需要遵循这个规范,包括挑选一个VCPU作为BSP执行BIOS,将其他VCPU置于Wait-for-SIPI状态等待BSP发SIPI,只是这个挑选工作是由VMM来完成的。
  3. VCPU的同步问题。在物理多CPU系统中,各个CPU都是同时在运行的。但是,对于拥有多个VCPU的客户机,在某一个时刻,可能一部分VCPU正在运行,一部分则处于睡眠或阻塞的状态。这对于VCPU之间的通信和同步都造成了一些延时问题。例如,当客户机的两个VCPU都运行着竞争自旋锁的代码时,考虑下面这段两个VCPU通过自旋锁进行同步的代码:
    1
    2
    3
    acquire_spinlock(lock)
    critical section
    release_spinlock(lock)
    当VPCU0上运行的代码通过acquire_spinlock(lock)取得自旋锁,进入临界区后,VCPU0可能会被调度出去。其后,当另一段运行在VCPU1上的代码尝试获取这个自旋锁时,它将因为得不到自旋锁而不得不进行等待,直到VCPU0被重新调度执行,完成临界区并执行release_spinlock(lock)释放自旋锁。这样,VCPU1因为VCPU0被调度出去的缘故,额外增加了同步的时延。

为了解决这些VCPU间的通信和同步的延迟问题,一种解决方案是对一个多VCPU客户机的多个CPU进行群体调度(Gang Scheduling),即它们要么同时在多个物理CPU上同时运行,要么同时不运行。群体调度带来的限制是一个多VCPU客户机的VCPU个数可以超过物理平台的物理CPU个数。

中断虚拟化

概述

对于今天拥有五花八门外设的计算机而言,中断系统的作用是至关重要的。在虚拟的环境下,虚拟机有诸多的设备,包括VMM模拟的虚拟设备和直接分配给客户机的物理设备,这些设备都需要发送中断给VCPU,以便得到处理。因此,VMM需要提供中断虚拟化的支持。

外部中断的流程如下图。首先,I/O设备通过中断控制器(I/O APIC或者PIC)发出中断请求,中断请求经由PCI总线发送到系统总线上,最后目标CPU的Local APIC部件接收中断,CPU开始处理中断。

物理平台的中断架构物理平台的中断架构

在虚拟机的环境中,VMM也需要为客户机操作系统展现一个与物理中断架构类似的虚拟中断架构。下图展现了虚拟机的中断架构。

虚拟机中断架构虚拟机中断架构

和物理平台一样,每个VCPU都对应一个人虚拟Local APIC用于接收中断。虚拟平台也包含了虚拟I/O APIC或者虚拟PIC用于发送中断。和VCPU一样,虚拟Local APIC、虚拟I/O APIC和虚拟PIC都是VMM维护的软件实体。当虚拟设备需要发送中断时,虚拟设备会调用虚拟I/O APIC的接口发送中断。虚拟Local APIC进一步利用VT-x的事件注入机制将中断注入到相应的VCPU。

由此可以看出,中断虚拟化的主要任务是实现上图中描述的虚拟机中断架构,具体来说包括虚拟PIC、虚拟I/O APIC和虚拟Local APIC,并且实现中断的生产、采集和注入的整个过程。

此外,PCI/PCIe设备还支持另一种中断方式MSI(Message Signalled Interrupts),MSI可以允许设备直接发送中断到Local APIC,不需要通过中断控制器(I/O APIC)。

虚拟PIC

PIC本质上是芯片组的一个设备,虚拟PIC的实现,就是根据PIC硬件规范,在软件上模拟出虚拟PIC,为虚拟机提供和物理PIC一样的接口。

虚拟PIC首先要虚拟出和物理PIC一样的软件接口。PIC为软件提供了如下接口用于操作PIC。4个初始化命令字(Initialization Command Words):ICW1~4,用于初始化操作;3个操作命令字(Operation Command Words):OCW1~3,用于操作PIC。

在IA32平台上,PIC的ICW1~4和OCW1~3都是通过I/O端口访问的。因此,在VT-x的帮助下,VMM很容易就可以实现这些接口的虚拟化。具体而言,这些接口是通过I/O端口0x20/0x21以及0xA0/0xA1来访问,因此,VMM可以设置VMCS的I/O bitmap中的相应位,使得客户机在访问这些端口时发生VM-Exit,便于VMM截获。

VMM在截获这些接口的访问之后,下一步就是按照PIC硬件规范对这些接口的定义,实现相应的逻辑。举例来说,接口OCW1的功能是用于操作IMR寄存器,控制指定中断是否被屏蔽。因此,VMM会分析客户机的OCW1命令,判断出是对哪个中断进行屏蔽或者解除屏蔽,VMM继而在内部逻辑中记录指定中断是否被屏蔽。如果指定中断被屏蔽了,相应的虚拟中断就不会被提交。

此外,虚拟PIC除了为客户机提供正确的虚拟接口以外,还要为虚拟设备提供接口用于发送中断请求。这个在物理表现上为I/O设备和PIC之间的电气连线,在虚拟环境中由于设备和PIC都是虚拟的,因而两者的交互表现为直接的函数调用。

虚拟PIC最终会向虚拟Local APIC提交中断,这个在物理上表现为PIC和CPU之间的电气连线。同样的再虚拟环境中由于设备和PIC都是虚拟的,因而两者的交互表现为直接的函数调用。

虚拟PIC接口的完整实现是一个相对复杂的过程,VMM通常会为虚拟PIC维护一个内部的状态机来驱动虚拟PIC的行为。

虚拟I/O APIC

PIC只适用于但CPU系统,对于多CPU,必须通过I/O APIC来发送中断,因此,对于对CPU虚拟平台,必须实现虚拟I/O APIC。

和虚拟PIC的实现类似,虚拟I/O APIC在VMM中也是一个虚拟设备。VMM根据其硬件规范来实现虚拟设备。和PIC类似,虚拟I/O APIC也会根据硬件规范实现相应的接口内部逻辑,也会为虚拟设备提供接口用于发送中断请求。虚拟I/O APIC最后也是同过调用虚拟Local APIC的接口来提交中断。

操作系统通过MMIO的方式访问I/O APIC,因此,VMM的实现和虚拟PIC有所不同。VMM会将虚拟PIC的MMIO地址对应的页表项置为“该页不存在”,因此,当客户机访问对应的MMIO寄存器时,就会发生原因为Page Fault的VM-Exit。这样,VMM就能截获客户机对虚拟I/O APIC的访问,进而正确地虚拟化。

虚拟Local APIC

Local APIC是CPU上一个内部部件,负责接收中断。此外,还提供了产生中断的功能,例如Local APIC Timmer Interrupt和处理器间中断IPI。

和虚拟PIC、虚拟I/O APIC一样,虚拟Local APIC在VMM中被实现为一个模拟设备。和I/O APIC一样,Local APIC提供给软件的接口是MMIO寄存器,因此,VMM也通过Page Fault来截获客户机对Local APIC的访问,进一步实现内部逻辑。

虚拟Local APIC的最主要功能是向VCPU注入中断。在VT-x的帮助下,虚拟Local APIC可以借助VM-Entry事件注入机制简单地实现这个功能。

中断采集

中断的采集是指如何将虚拟机的设备中断请求送入虚拟机中断控制器。在虚拟机环境里,客户机中断有两种可能来源。

  1. 来自于软件模拟的虚拟设备,例如一个模拟出来的串口,可以产生一个虚拟中断。
  2. 来自于直接分配给客户机的物理设备的中断,例如一块物理网卡,可以产生一个真正的物理中断。

采集这两种中断的方法大不相同,前者比较简单,后者则相对复杂。

对于虚拟设备而言,它们是一个软件模块。当虚拟设备需要发出中断请求,可以通过虚拟中断控制器提供的接口函数法术中断请求,例如使用虚拟PIC或者虚拟I/O APIC提供的接口。

采集直接分配给客户机的物理设备发出的中断请求要复杂得多。一个物理设备被直接分配给一个客户机,意味着当设备发生中断时,该物理中断的处理函数位于客户操作系统中。而在虚拟化环境中,物理中断控制器由VMM控制,且中断发生时CPU的IDT表通常不是客户机的IDT表,因此,物理中断需要首先由VMM的中断处理函数接收,再注入给客户机。

下面通过一个例子概要地介绍物理中断采集过程。

  1. 物理设备发生中断,假定设备的IRQ号为14,对应的中断向量号为0x41。
  2. CPU收到中断,执行标准的中断处理流程,例如应答PIC、过中断门中断自动屏蔽等。最后,CPU跳转到IDT表中0x41表项所指定的处理函数。注意,该处理函数是VMM提供的,其目的是将物理中断注入给客户机。
  3. VMM的中断处理函数对中断进行检查,发现该中断时分配给客户机的设备产生的,因此,VMM调用虚拟中断控制器的接口函数,将中断发送给虚拟Local APIC。之后,虚拟Local APIC就会在适当的时机将该中断注入给客户机,由客户操作系统的处理函数处理。
  4. 在将中断事件通知客户机以后,VMM会进行后续处理,例如开中断等。

在上述过程中,有两点信息需要在创建客户机的过程中提供的。

  1. 设备的分配信息。在第(3)步中,VMM必须了解,中断0x41所对应的设备是否被分配给了某个客户机以及哪个客户机。通常这是在创建客户机的时候由用户决定,用户通过管理工具通知VMM相关的绑定信息。
  2. 设备在客户机平台上的管脚信息。在第(3)步中,VMM在调用虚拟中断控制器的接口函数时,需要提供IRQ号,即管脚号。需要值得注意的是,虚拟中断控制器的输入管脚与物理中断控制器的输入管脚并不一定相同。物理的输入管脚是由物理平台决定,而虚拟中断控制器的输入管脚是由VMM所提供的虚拟平台决定,通常在创建客户机的时候就已经确定了。VMM负责在两者之间做转换。

中断注入

中断注入负责将虚拟中断控制器采集到的中断请求按照优先级,逐一注入客户机虚拟处理器。这里有两个问题需要解决,首先是如何取得需要注入的最高优先级中断的相关信息,其次是如何才能将一个中断注入客户机VCPU。

对于第一个问题,虚拟中断控制器会负责将中断按照优先级排序,VMM只需要调用虚拟中断控制器提供的接口函数,就可以获得当前最高优先级中断的信息。

对于第二个问题,虚拟Local APIC提供了将中断注入客户机VCPU最基本的功能。VMM可以调用虚拟Local APIC的接口来实现中断注入。在这里,VMM的虚拟中断注入逻辑需要考虑下面几个问题。

  1. 如果目标VCPU正在物理CPU上运行,如何注入中断。只能在VM-Entry的时候将中断注入客户机,因此,为了保证中断的及时注入,需要强迫VCPU发生VM-Exit,这时就可以在VM-Entry返回客户机的时候注入中断。常用的使客户机发生VM-Exit的方法是向VCPU所在的物理CPU发送IPI中断。
  2. 如果目标VCPU目前无法中断,例如VCPU目前正处于关中断的状态(客户机的EFLAGS.IF为0),如何注入中断。Intel的VT-x技术对这种情况提供了一个解决机制,即使用中断窗口(Interrupt Windows)。该机制通过设置VMCS的一个特定字段告诉物理CPU,其当前运行的客户机VCPU有一个中断需要注入。一旦客户机VCPU开始可以接收中断,例如进入开中断状态,物理CPU会主动触发VM-Exit,从客户机陷入到VMM中,虚拟中断注入模块就可以注入等待的中断了。
  3. 什么时候来触发中断注入。通常的方法是,当中断采集逻辑调用虚拟中断控制器接口请求发出中断后,虚拟中断控制器会根据内部的状态,如虚拟IMR/ISR寄存器的值,来决定是否需要注入中断给客户机。其判断过程和物理中断控制器判断是否提交中断给CPU一样。
中断注入的过程中断注入的过程

案例分析

简单示例

物理平台上的可编程中断时钟PIT会定期产生时钟中断。VMM模拟的虚拟PIT也一样,可以定时为客户机产生虚拟时钟中断。接下来以虚拟PIT为例,说明虚拟可编程时钟中断是如何被注入到客户机的。

  1. 虚拟PIT产生中断请求时,调用虚拟中断控制器提供的接口通知虚拟PIC或I/O APIC自己有一个中断需要处理。
  2. 虚拟PIC或I/O APIC记录下这个中断请求,并检查内部寄存器,如IMR、IRR和ISR等,以决定是否需要将中断注入给客户机。如果不需要,就将这个中断请求保存在虚拟PIC或I/O APIC的内部逻辑中。当虚拟PIC内部状态改变后(通常是客户机写PIC或I/O APIC的寄存器时),虚拟PIC或I/O APIC会检查内部是否有等待处理的中断请求并重复这个过程。
  3. 如果此时需要注入中断,调用中断注入逻辑。
  4. VMM检查客户机VCPU是否正在运行,如果是,则发一个IPI强制其进入VMM上下文。
  5. 在客户机VCPU重新运行前,VMM会检查发现该VCPU有中断需要注入,接着VMM会检查当前客户机VCPU是否能被注入中断。如果能,使用中断控制器提供的接口,获取最高优先级中断的信息,设置好VMCS中的相应字段,使得当VCPU投入运行时自动执行相应矢量号的中断处理函数。否则,设置中断窗口,等待下次VM-Exit之后再注入。

复杂示例

当被直接分配给客户机的物理设备发生中断时,VMM需要将该中断注入给对应的客户机。这种情况相对来说比较复杂。

假定物理设备产生中断,设备中断管脚连接到I/O APIC的管脚0x12,对应中断重定向表的矢量号为0x41,并假定系统使用物理APIC,客户机使用虚拟APIC。同时,物理设备中断发生时,物理CPU的中断时开启的。

直接分配设备的中断虚拟化直接分配设备的中断虚拟化

上图给出了具体的流程。

  1. 物理设备发生中断,将中断发送给物理I/O APIC的管脚0x12。
  2. 物理I/O APIC收到后,将管脚0x12转化为中断向量0x41发送到Local APIC。
  3. Local APIC将中断0x41注入到CPU,CPU跳转到IDT表中0x41表项所指定的处理函数。同时,Local APIC的ISR寄存器对应0x41的位被设置为1,后面的等于或低于0x41的中断被屏蔽。
  4. VMM的相关中断处理函数被执行。
  5. 中断处理函数检查发现这个中断是属于客户机的设备产生的中断,故调用虚拟中断控制器的接口函数。在将中断时间注入客户机以后,VMM通过设置物理I/O APIC第0x12个RTE的屏蔽位为1,屏蔽后续的物理中断。VMM向物理Local APCI写入EOI,以清掉Local APIC的ISR寄存器0x41位,从而其他的中断也可以被接受。

VMM中断处理函数对物理APIC的操作到此结束,下面是虚拟APIC工作的流程。

  1. 虚拟的I/O APIC中断控制器调用虚拟的Local APIC的接口函数,并将虚拟I/O APIC中相应的矢量号传入。虚拟Local APIC的ISR相关bit为上被置1。
  2. 虚拟的Local APIC通过中断注入逻辑模块将中断注入到客户机。
  3. 客户机执行相关中断处理函数。
  4. 客户机中断处理函数处理物理设备的中断。
  5. 客户机向虚拟Local APIC写入EOI,EOI操作被VMM截获。虚拟Local APIC的ISR为被清掉,同时,虚拟Local APIC通知VMM客户已经完成对中断0x21的处理,VMM清除物理I/O APIC第0x12个RTE的屏蔽位。

内存虚拟化

概述

内存虚拟化的主要任务是实现地址空间的虚拟化,内存虚拟化通过两次地址转化来支持地址空间的虚拟化,即客户机虚拟地址GVA -> 客户机物理地址GPA -> 宿主机物理地址HPA的转换。其中GVA -> GPA的转换是由客户机软件决定的,通常是客户机操作系统通过VMCS中客户机状态域CR3指向的页表来指定;GPA -> HPA的转换是由VMM来决定的,VMM在将物理内存分配给客户机时就确定了GPA -> HPA的转换,VMM通常会用内部数据结构来记录这个映射关系。

传统的IA32架构只支持一次地址转换,即通过CR3指定的页表来实现“虚拟地址” -> “物理地址”的转换。这和内存虚拟化所要求的两次地址转换产生了矛盾。可以通过将两次装换合并为一次转换来解决这个问题,即VMM根据GVA -> GPA -> HPA的映射关系,计算出GVA -> HPA的映射关系,并将其写入“影子页表”。类似“影子页表”这样的软件方法尽管能够解决问题,但是缺点也很明显。首先是实现非常复杂,例如需要考虑各种各样页表同步情况等,这导致开发、调试和维护都比较困难。此外,“影子页表”的内存开销也很大,因为需要为每个客户机进程对应的页表都维护一个“影子页表”。

为解决这个问题,VT-x提供了Extended Page Table(EPT)技术,直接在硬件上支持GVA -> GPA -> HPA的两次地址转换,大大降低了内存虚拟化的难度,也进一步提高了内存虚拟化的性能。

此外,为了进一步提高TLB的使用效率,VT-x还引入了Virtual Processor ID(VPID)功能,进一步增加了内存虚拟化的性能。

EPT

EPT原理

下图描述了EPT的基本原理。在原有的CR3页表地址映射的基础上,EPT引入了EPT页表来实现另一次映射。这样,GVA -> GPA -> HPA两次地址装换都由CPU硬件自动完成。

EPT原理EPT原理

这里假设客户机页表和EPT页表都是4级页表,CPU完成一次地址转接的基本过程如下。

CPU首先会查找Guest CR3指向的L4页表。由于Guest CR3给出的是GPA,因此CPU需要通过EPT页表来实现Guest CR3 GPA -> HPA的转换。CPU首先会查看硬件的EPT TLB,如果没有对应的转换,CPU会进一步查找EPT页表,如果还没有,CPU则抛出EPT Violation异常由VMM来处理。

获得L4页表地址后,CPU根据GVA和L4页表项的内容,来获取L3页表项的GPA。如果L4页表中GVA对应的表项显示为“缺页”,那么CPU产生Page Fault,直接交由Guest Kernel处理。注意,这里不会产生VM-Exit。获得L3页表项的GPA后,CPU同样要通过查询EPT页表来实现L3 GPA -> HPA的转换,过程和上面一样。

同样的,CPU会依次查找L2、L1页表,最后获得GVA对应的GPA,然后通过查询EPT页表获得HPA。从上面的过程可以看出,CPU需要5次查询EPT页表,每次查询都需要4次内存访问,因此最坏的情况下总共需要20次内存访问。EPT硬件通过增大EPT TLB来尽量减少内存访问。

EPT的硬件支持

为了支持EPT,VT-x规范在VMCS的“VM-Execution控制域”中提供了Enable EPT字段。如果在VM-Entry的时候该位被置上,EPT功能就会被启用,CPU会使用EPT功能进行两次转换。

EPT页表的基地址是由VMCS“VM-Execution控制域”的Extended page table pointer字段来指定的,它包含了EPT页表的宿主机物理地址。

EPT是一个多级页表,每级页表的表格式是相同的,如下图所示。

EPT页表的表项格式EPT页表的表项格式

EPT页表转换过程和CR3页表转换是类似的。下图展现了CPU使用EPT页表进行地址转换的过程。

EPT页表转换EPT页表转换

EPT通过EPT页表中的SP字段支持大小为2MB或者1GB的超级页。下图给出了2MB超级页的地址转换过程。和上图不同点在于,当CPU发现SP字段为1时,就会停止继续向下遍历页表,而是直接转换了。

EPT页表转换:超级页EPT页表转换:超级页

EPT同样会使用TLB缓冲来加速页表的查找过程。因此,VT-x还提供了一条新的指令INVEPT,可以使EPT的TLB项失效。这样,当EPT页表有更新时,CPU可以执行INVEPT使旧的TLB失效,使CPU使用新的EPT表项。

和CR3页表会导致Page Fault一样,使用EPT之后,如果CPU在遍历EPT页表进行GPA -> HPA转换时,也会发生异常。

  1. GPA的地址位数大于GAW。
  2. 客户机试图读一个不可读的页(R=0)。
  3. 客户机试图写一个不可写的页(W=0)。
  4. 客户机试图执行一个不可执行的页(X=0)。

发生异常时,CPU会产生VM-Exit,退出的原因为EPT Violation。VMCS的“VM-Exit信息域”还包括如下信息。

  • VM-Exit physical-address information:引起EPT Violation的GPA。
  • VM-Exit linear-address information:引起EPT Violation的GVA。
  • Qualification:引起EPT Violation的原因,如由于读引起、由于写引起等。

EPT的软件使用

要使用EPT,VMM需要做如下事情.

首先需要在VMCS中将EPT功能打开,这个只需要写VMCS相应字段即可。
其次需要设置好EPT的页表。EPT页表反应了GPA -> HPA的映射关系。由于是VMM负责给虚拟机分配物理内存,因此,VMM拥有足够的信息来建立EPT页表。此外,如果VMM给虚拟机分配的物理内存足够连续的话,VMM可以在EPT页表中尽量使用超级页,这样有利于提高TLB性能。

当CPU开始使用EPT时,VMM还需要处理EPT Violation。通常来说,EPT Violation的来源有如下几种。

  1. 客户机访问MMIO地址。这种情况下,VMM需要将请求转给I/O虚拟化模块。
  2. EPT页表的动态创建。有些VMM采用懒惰方法,一开始EPT页表为空,当第一次使用发生EPT Violation时再建立映射。

由此可以看出,EPT相对于传统的“影子页表”方法,其实现大大简化了。而且,由于客户机内部的Page Fault不用发生VM-Exit,也大大减少了VM-Exit的个数,提高了性能。此外,EPT只需要维护一张EPT页表,不像“影子页表”那样需要为每个客户机进程的页表委会一张影子页表,也减少了内存的开销。

VPID

TLB是页表项的缓存,对地址转换的效率至关重要。TLB需要和对应的页表一起工作才有效。因此,当页表发生切换时,TLB原有的内容也就消失了,CPU需要使用INVLPG指令使其所有项失效,这样才不会影响之后页表的工作。例如,进程切换时需要切换进程地址空间(通过切换页表的起始物理地址CR3),使前一个进程TLB项全部失效。

类似地,在每次VM-Entry和VM-Exit时,CPU会强制TLB内容全部失效,以避免VMM以及不同虚拟机虚拟处理器之前TLB项的混用,因为硬件无法区分一个TLB项是属于VMM还是某一特定的虚拟机处理器。

VPID是一种硬件级的对TLB资源管理的优化。通过在硬件上为每个TLB项增加一个标志,来标识不同的虚拟处理器地址空间,从而区分开VMM以及不同虚拟机的不同虚拟处理器的TLB。换而言之,硬件具备了区分不同的TLB项属于不同虚拟处理器地址空间(对应于不同对的虚拟处理器)的能力。这样,硬件可以避免在每次VM-Entry和VM-Exit时,使全部TLB失效,提高了VM切换的效率。并且,由于这些继续存在的TLB项,硬件也避免了VM切换后的一些不必要的页表遍历,减少了内存访问,提高了VMM以及虚拟机的运行速度。

VT-x通过在VMCS中增加两个域来支持VPID。第一个是VMCS中的Enable VPID域,当该域被置上时,VT-x硬件会启用VPID功能。第二个是VMCS中的VPID域,用于标识该VMCS对应的TLB。VMM本身也需要一个VPID,VT-x规定虚拟处理器标志0被指定用于VMM自身,其他虚拟机虚拟处理器不得使用。

因此,在软件上使用VPID非常简单,主要做两件事情。首先是为VMCS分配一个VPID,这个VPID只要是非0的,而且和其他VMCS的VPID不同就可以了;其次是在VMCS中将Enable VPID置上,剩下的事情硬件会自动处理。

I/O虚拟化的硬件支持

概述

基于软件I/O虚拟化技术的“设备模拟”和“类虚拟化”有各自的优点,以及与生俱来的缺点。前者通用性强,但性能不理想;后者性能不错,却又缺乏通用性。为此,英特尔公司发布了VT-d技术(Intel(R) Virtualization Technology for Directed I/O),以帮助虚拟软件开发者实现通用性强、性能高的新型I/O虚拟化技术。

量化I/O虚拟化技术的两个指标————性能和通用性。性能,越接近无虚拟环境下的I/O性能越好;通用性主要是和全虚拟化挂钩,使用的I/O虚拟化技术对客户操作系统越透明,则通用性越强。通过VT-d技术,可以很好地实现这两个指标,无须像“设备模拟”和“类虚拟化”两种技术一样,为了提高某个指标而使另一个指标打折。

对于高性能,最直接的方法就是让客户机直接使用真实的硬件设备,这样客户机的I/O操作路径几乎和无虚拟机环境下的I/O路径相同,获得高性能是理所当然的;对于通用性,就是要用全虚拟化的方法,让客户机操作系统能够使用自带的驱动程序发现设备、操作设备。

客户机直接操作设备面临如下两个问题。

  1. 如何让客户机直接访问到设备真实的I/O地址空间(包括端口I/O和MMIO)。
  2. 如何让设备的DMA操作直接访问到客户机的内存空间。设备不管系统中运行的是虚拟机还是真实的操作系统,它只管用驱动提供给它的物理地址做DMA。

通用性面临的问题和(1)是类似的,要有一种方法把设备的I/O地址空间告诉给客户操作系统,并能让驱动通过这些地址访问到设备真实的I/O地址空间。VT-x技术已经解决带一个问题,可以允许客户机直接访问物理的I/O空间。Intel的VT-d技术则让第二个问题的解决成为可能,它提供了DMA重映射技术,以帮助VMM的实现者达到目标。

VT-d技术通过在北桥(MCH)引入DMA重映射硬件,以提供设备重映射和设备直接分配的功能。在启用VT-d的平台上,设备所有的DMA传输都会被DMA重映射硬件截获。根据设备对应的I/O页表,硬件可以对DMA中的地址进行转换,使设备只能访问到规定的内存。使用VT-d后,设备访问内存的架构如下图所示

使用VT-d后访问内存架构使用VT-d后访问内存架构

上图(a)中是没有VT-d的平台,此时设备的DMA可以访问整个物理内存。图(b)是启用VT-d的情况,此时,设备只能访问指定的物理内存。这和使用页表将进程的线性地址空间映射到指定物理内存区域的思想一样,只不过对象换成了设备。

VT-d技术

设备对系统中运行的软件是一无所知的,在进行DMA时,设备唯一做的是从驱动程序告知的“物理内存”复制(读取)数据。在内存虚拟化中,虚拟机环境下客户机使用的是GPA,则客户机的驱动直接操作设备时也是用GPA。而设备进行DMA,需要用HPA,如何在DMA时将GPA转换成HPA就成了关键问题。因为,通常无法通过软件的方法截获设备的DMA操作,VT-d技术提供的DMA重映射就为解决这个问题而提出的。

DMA重映射(DMA Remapping)

PCI总线结构,通过BDF可以索引到任何一条总线上的任何一个设备。同样,DMA的总线传输中包含一个BDF以标识该DMA传输是由哪个设备发起的。在VT-d技术中,标识DMA操作发起者的结构称为源标识符(Source Identifier)。对于PCI总线,VT-d使用BDF作为源标识符,在下面的内容提到的BDF均代表源标识符。

除了BDF外,VT-d还提供了两种数据结构来描述PCI架构,分别是根条目(Root Entry)和上下文条目(Context Entry)。

  1. 根条目:用于描述PCI总线,每条总线对应一个根条目。由于PCI架构支持最多256条总线,故最多可以有256个条目。这些根条目一起构成一张表,称为根条目表(Root Entry Table)。有了根条目表,系统中每一条总线都会被描述到。下图是根条目的结构。
    根条目结构根条目结构

主要字段如下:

  • P:存在位。为0时条目无效,来自该条目所代表总线的所有DMA传输被屏蔽。为1时,该条目有效。
  • CTP(Context Table Pointer,上下文表指针):指向上下文条目表。
  1. 上下文条目:用于描述某个具体的PCI设备,这里的PCI设备是指逻辑设备.一条PCI总线上最多有256个设备,故有256个上下文条目,它们一起组成上下文条目表(Contex Entry Table)。通过上下文条目表,可描述某条PCI总线上所有的设备。下图是上下文条目表的结构。
    上下文条目结构上下文条目结构

主要字段如下。

  • P:存在位。为0时条目无效,来自该条目所代表设备的所有DMA传输被屏蔽。为1时,表示该条目有效。
  • T:类型,表示ASR字段所指数据结构的类型。目前,VT-d技术中该字段为0,表示多级页表。
  • ASR(Address Space Root,地址空间根):实际是一个指针,指向T字段所代表的数据结构,目前该字段指向一个I/O页表。
  • DID(Domain ID,域标识符):相当于作用于唯一标识该客户机的标识符,例如Guest ID。

根条目表和上下文条目表一起构成了下图所示的两级结构。

根条目表和上下文条目表构成的两级结构根条目表和上下文条目表构成的两级结构

当DMA重映射硬件捕获一个DMA传输时,通过其中BDF的bus字段索引根条目表,可以得到产生该DMA传输的总线对应的根条目。由根条目的CTP字段可以获得上下文条目表,用BDF中的{ dev: func}索引该表,可以获得发起DMA传输的设备对应的上下文条目。从上下文条目的ASR字段,可以寻址到该设备对应的I/O页表,此时,DMA重映射硬件就可以做地址转换了。通过这样的两级结构,VT-d技术可以覆盖平台上所有PCI设备,并对它们的DMA传输进行地址转换。

I/O页表

I/O页表是DMA重映射硬件进行地址转换的核心。它的思想和CPU中分页机制的页表类似,与之不同的是,CPU通过CR3寄存器就可以获得当前系统使用的页表的基地址,而VT-d需要借助根条目和上下文条目才能获得设备对应的I/O页表。VT-d也使用硬件查页表机制,整个转换过程对于设备、上层软件都是透明的。与CPU使用的页表相同,I/O页表页支持几种粒度的页面大小,其中最典型的4KB页面地址转换过程如下图所示。

DMA重映射的4KB页面地址转换过程DMA重映射的4KB页面地址转换过程

通过I/O页表中GPA到HPA的映射,DMA重映射硬件可以将DMA传输中的GPA转换成HPA,从而使设备访问客户机的内存区域。

VT-d硬件缓存

VT-d硬件使用了大量的缓存以提高效率。其中,和地址转换相关的缓存称为IOTLB,它和CPU中的TLB功能一样。此外,对于上下文条目,VT-d硬件提供了上下文条目表。当软件修改了I/O页表、上下文条目表之后,要负责对这些缓存进行刷新。

VT-d对两种缓存分别提供三种粒度的刷新操作。

  1. 全局刷新(Global Invalidation):整个IOTLB或上下文条目表中所有条目无效。
  2. 客户机粒度刷新(Domain-Selective Invalidation):IOTLB中后上下文条目表中和指定客户机相关的地址条目或上下文条目无效。
  3. 局部刷新:对于IOTLB,称为Domain vPage-Selective Invalidation,指定客户机某一地址范围内的页面映射条目无效。对于上下文条目表,称为Device Selective Invalidation,和某个指定设备相关的上下文条目无效。

硬件可以实现上述三种刷新操作的一种或多种,对于系统软件来说,它并不知道自己发起的刷新操作被硬件使用哪一种粒度的刷新操作完成。

VT-d硬件的探测

和所有硬件一样,在使用DMA重映射硬件之前需要对它进行探测。VT-d通过BIOS的ACPI表向上层软件会报DMA重映射硬件的情况,硬件由3个主要数据结构描述。

  • DMAR(DMA Remapping Reporting):该结构汇报平台VT-d相关硬件的整体情况,可以看作一个总表。

    字段名 描述
    Length 以字节数表示DMAR表占用的内存大小
    HAW 该平台支持DMA操作可寻址的最大物理地址空间
    DMA Remapping Structures 指向下一级硬件描述结构,包括DHRD和RMMRR两种

DMA Remapping Structures为DHRD时。

  • DHRD(DMA Remapping Hardware Unit Definition):用于描述DMA重映射硬件,一个DHRD结构对应一个DMA重映射硬件。典型的实现是平台只有一个DMA重映射硬件并管辖所有设备,但VT-d技术也支持一个平台多个DMA重映射硬件。DHRD的主要字段如下图所示。

    DHRD结构DHRD结构
  • DSS(Device Scope Structure):藐视DHRD所管辖的设备。DHRD的Device Scope指向的数组中的每个元素以DSS结构表示。该结构可以代表两种类型的设备,一种是PCI中断设备,一种是PCI桥设备。该结构有三个重要字段,如下图所示。

    DSS结构DSS结构

三种数据结构构成了下图所示的层次。

DMAR、DRHD和DSS的层次DMAR、DRHD和DSS的层次

其中,第一级是ACPI表,从中获得DMAR,然后根据各个结构的各字段,可以解析出平台每个DMA重映射硬件的所有信息,例如该硬件的寄存器地址、该硬件管辖的设备等等。

I/O虚拟化的实现

设备直接分配

在“设备模拟”和“类虚拟化”两种I/O虚拟化技术中,所有客户机都共享平台硬件设备。考虑这样一种情况,当VMM运行在一台拥有10块网卡的服务器上时,前两种技术完全可能只使用一块网卡来满足所有客户机的网络I/O需求,这必然导致了低性能和资源的浪费。设备直接分配技术很好地解决了这个问题。可以把某个设备直接分配给一个客户机,让客户机的I/O访问至二级访问到设备的I/O地址空间,从宏观上看,这个客户机直接操作了平台的硬件设备。

I/O有如下3个方面。

  1. 驱动程序通过I/O地址空间操作设备,即设备直接分配技术解决的问题。
  2. 设备通过DMA读取/复制数据。
  3. 中断,由VT-d中的DMA重映射技术解决。

设备直接分配的一个问题是如何阻止来自未分配到该设备的客户机的I/O访问。例如,系统中有三个客户机(0、1、2),其中客户机1分配到了网卡A,则要阻止客户机0、客户机2对网卡A的访问,一个最直接的方法是隐藏,让客户机0、客户机1认为网卡A根本就不存在。实际上,无论是“设备模拟”还是“类虚拟化”技术,平台的硬件对于客户机都是透明的,所谓隐藏主要是针对运行“设备模拟器”的客户机/宿主机,或拥有“类虚拟化”后端驱动的客户机/宿主机而言的。隐藏的方式视具体的情况而定,例如可以在拥有硬件设备的客户机/宿主机加载驱动程序前,先给要分配出去的设备加载一个伪驱动作为占位符。由于没有真正的驱动程序,该设备就不会被访问到。

设备直接分配的另一个问题是如何让客户机的I/O操作直接访问到设备真实的I/O地址空间。

可以通过设备直接分配将某一设备直接分配给某个客户机,并让客户机直接访问操作。

设备I/O地址空间的访问

PCI设备在设备直接分配的情况下,客户机是如何直接访问设备的真实I/O地址空间。

PCI设备的I/O地址空间通过PCI BAR(Base Address Register)报告给操作系统。为此有两种选择供设备直接分配技术使用。

  1. 将设备的真实PCI BAR报告给客户机,并通过VMCD的I/O bitmao和EPT使客户机的端口I/O和MMIO都不引起VM-Exit,则客户操作系统的驱动程序可以直接访问设备的I/O地址空间。
  2. 建立转换表,报告虚拟的PCI BAR给客户机,当客户机访问到虚拟的I/O地址空间时,VMM负责截获操作,并通过转换表把I/O请求转发到设备的I/O地址空间。

两种方法中,方法(1)是高效的(不引起VM-Exit)和简单的(直接报告真实的PCI BAR给客户机),但在实际运行中会存在一些问题。通常VMM产品会使用多种I/O虚拟化技术,客户机的I/O请求,可能一部分由“设备模拟”技术满足,一部分由设备直接分配技术满足。例如一个客户机,它的显卡是由“设备模拟器”模拟的,但网卡又是操作系统直接使用的。已知,设备的PCI BAR通常是BIOS配置并且由操作系统直接使用的。那在这种情况下,由“设备模拟器”提供的设备的PCI BAR由PCI BAR由虚拟BIOS配置,而真实设备的PCI BAR由平台的BIOS配置,两者之间就可能产生冲突。这种情况发生时,在操作系统看到就是资源冲突,很可能停用其中一个设备而满足另一个设备。此外,操作系统是有权里修改设备的PCI BAR的,但应该阻止客户机直接修改真实设备的PCI BAR,这是为了防止真实设备之前的PCI BAR冲突,以及在客户机销毁时吧设备分配给其他客户机使用。由于这些原因,在实现设备直接分配技术时,通常采用的是方式(2),即建立转换表。根据I/O地址空间的划分,转换表分为Port I/O转换表和MMIO转换表。

对于端口I/O,可以通过I/O bitmap来控制客户机访问某个端口是否引起VM-Exit。这样,完全可以使用“设备模拟器”的虚拟BIOS(或是其他手段,取决于VMM使用的I/O虚拟化技术)为分配给客户机的真实设备产生虚拟的PCI BAR,将它报告给客户操作系统,并修改I/O bitmap使客户机在访问这些I/O端口时产生VM-Exit。同时,VMM维护一张以虚拟PCI BAR到真实PCI BAR的映射表。当客户机通过虚拟的PCI BAR发起I/O操作时,会因为VM-Exit陷入到VMM中,VMM即可以通过转换表获得真实设备的I/O端口,帮客户机将请求转发给真实硬件。

对于MMIO,其访问方式和内存访问无异。完全可以使用内存虚拟技术来解决这个问题。在虚拟BIOS产生虚拟PCI BAR之后,只需要将虚拟的MMIO地址空间映射到设备真实的MMIO地址空间上,当客户机通过虚拟的MMIO地址空间访问设备时,内存虚拟机制会处理一切。举个例子,如果当前VMM使用EPT,则客户机在第一次访问虚拟MMIO地址空间会陷入到VMM。此时,可以修改EPT页表建立起虚拟MMIO地址空间到设备真实MMIO地址空间的映射,则在以后的访问中,客户机对该虚拟MMIO地址空间的访问不会再陷入到VMM。

除了解决客户机直接访问设备I/O地址空间的问题外,转换表还可以满足客户机修改设备PCI BAR的情况。此时,只需要修改虚拟的PCI BAR并委会修改后的值到真实PCI BAR的映射即可。

设备发现

VMM通常会同时使用多种I/O虚拟化技术,其中一项必然会虚拟PCI总线(一般来说,这是“设备模拟器”的工作),所以只需将真实设备“挂接”到这条虚拟的PCI总线上,客户操作系统枚举PCI设备时必然会发现分配它。PCI设备暴露给操作系统的接口是PCI配置空间,一个很自然的想法是将真实设备的PCI配置空间暴露给客户操作系统。对于PCI配置空间中的PCI BAR通常使用的是虚拟BIOS生成。那么跟进一步,可以为设备生成整个虚拟的PCI配置空间。

为了让客户操作系统正确地识别分给它的设备,这个虚拟的PCI配置空间中,表示设备标识的前16个字节需要使用真实的信息,这是没有关系的,这些信息不会被客户操作系统修改,也不会引起冲突。将生成的PCI配置空间以一个虚拟设备的形式挂接在虚拟PCI总线上,当客户操作系统枚举总线时,即可以发现该设备并加载正确的驱动程序。

配置DMA重映射数据结构

对于VMM的实现者来说,使用DMA重映射技术的关键是为所分配的设备正确设置根条目和上下文条目,以及建立I/O页表。每个客户机都有一张I/O页表,通常在客户机创建初期根据客户机的内存大小、VT-d硬件支持的页表级数、页大小创建。

下面用一个例子说明如何配置设备对应的根条目、上下文条目。假设Guest ID为1,I/O页表已经创建好位于地址A,根条目表和上下文条目表已经在VMM加载初期创建好,要分配设备的BDF为{00 : 30 : 00}。

  1. 找到设备对应的DHRD结构。
  2. 获得该结构的根条目表,通过BDF的bus字段获得设备对应根条目(以下称Root Entry0)。
  3. 通过Root Entry0的CTP字段获得上下文条目表,用BDF的dev : func字段索引该表,获得设备对应的上下文条目。如果该上下文条目不存在,分配一个并将地址填入Root Entry0的CTP字段。
  4. 将I/O页表的地址A填入上下文条目的ASR字段,在DID字段中填入Guest ID1,在p字段中填入1。
  5. 刷新上下文条目的缓存。

上述步骤中,只介绍了主要字段的配置,其余字段也要根据格式正确配置。在刷新操作之后,该设备的DMA请求就会被DMA重映射硬件截获并进行地址转换。

设备中断虚拟化

DMA最后一个步骤往往是设备用中断报告驱动程序操作完成。在设备直接分配给客户机,以及DMA重映射到客户机内存的情况下,设备的中断也需要注入给客户机。

时间虚拟化

操作系统的时间概念

操作系统使用两种方式的时钟,周期性时钟(Periodic Timer)和单次计时时钟(One-shot Timer)。时钟虚拟化的关键是正确模拟时钟中断。

两个操作系统的时间概念。

  • 绝对时间(Wall Time):又称墙上时间。即操作系统启动后到目前为止的总运行时间,它是个单调递增的值。
  • 相对时间:指两个时间之间的间隔。例如,两次时钟中断的间隔、两次使用RDTSC指令读取TSC间的间隔。

硬件定时器如RTC、PIT或者HPET等都能够以某种频率触发时钟中断,触发的频率可以由软件编程控制。通常,操作系统会将频率设定为一个给定的值(例如10ms)。同时,硬件定时器也会提供计数器(Counter)的功能,操作系统可以知道两次读取计数器之间的时间差,从而得到相对时间概念。

操作系统在启动的时候会读取CMOS的实时时钟,或通过NPT协议,得到系统启动时的绝对时间。同时,系统通过维护相对时间,可以知道系统总共运行的时间,从而操作系统可以得到任意时刻点的绝对时间,如下面的公式所示。

1
当前绝对时间 = 系统启动时的时间 + 系统启动后运行的时间

下图描述了操作系统的时钟概念。

操作系统的时间概念操作系统的时间概念

系统在时间t0的时候启动,当实际时间到达t1的时候,系统内部维护的运行时间为t1 - t0,而系统内部的绝对时间为t0 + (t1 - t0),从保证了内部时间与实际时间的一致性。每次时钟中断发生时(t1、t2、t3等),操作系统都会更新内部的时间概念。而在t1 ~ t2时间内,操作系统可以通过读时间设备的计数器得到相对时间。

客户机的时间概念

在硬件辅助的虚拟环境下,客户机操作系统仍然需要维护正确的时间概念,包括相对时间和绝对时间。这意味着VMM需要为客户机提供系统硬件时钟设备的仿真,包括PIT、HPET和TSC等。

在虚拟环境下,由于客户机是和其他的客户机以及VMM共享物理平台,客户机只能得到部分的处理器时间(即使当前只有一个客户机,VMM本身运行也需要占用CPU时间)。在这种情况下,“正确的时间概念”是随着应用的不同有着不同的含义,如何维护正确的时间概念存在诸多问题。

下图给定了两种不同的客户机时间概念的实现。

假定客户机在t1时刻处于运行状态,t2的时候被调度进入睡眠状态,t3的时候重新被调度执行,直到t5的时候才再次被调度出去。如果客户机内的某一个程序(操作系统内核或应用程序)希望t4和t1之间的相对时间,那么返回值是多少呢。

显然对于不同的情况,返回值应该是不同的,考虑下面两种应用。

  1. 进程记账:主要用于统计某一个进程的执行时间。假定客户机内的某个进程在t1被调度执行,在t4的时候被调度出去(不要和客户机本身的调度混淆)。显然,进程记账程序希望得到的时间是进程真正的运行时间,也就是(t4 - t3) + (t2 - t1)。这种情况下,客户机的时间和实际时间的关系如图(a)所示。
  2. 网络速度检测程序:网络速度检测程序通过向远端服务器发送数据包,远端服务器收到数据包以后,会发送一个应答包回来,通过计算发送数据包和收到应答包的时间,就可以大概了解网络速度。假定程序在t1时向远程的服务器发送数据包,并在t4的时刻得到服务器的反馈。显然,网络速度检测程序希望得到的是真正的时间,也就是t4 - t1。在这种情况下,客户机的时间和实际时间的关系如图(b)所示。

在实际需求中,大多数的应用都是项网络速度间这样的应用,因此,通常时间虚拟化的策略都是给客户机呈现与实际时间相同的时间概念。后面的讨论均是基于客户机时间与实际时间相等的情况。对于需要客户机时间与实际时间不同的情况,会在最后进行简单的介绍。

操作系统通过系统中的时钟设备(包括PIT、HPET和TSC等)得到自己的绝对时间或相对时间,因此首先介绍时钟设备虚拟化的实现方法,然后再讨论如何给客户机提供与实际时间相等的时间。

时钟设备仿真

x86系统中的时间设备包括PIT、HPET、ACPI PM Timer和TSC等。客户机不会被调度出去的情况下,PIT设备是如何虚拟化的。

PIT其功能主要是为操作系统提供定时的时钟中断和时钟计数器。操作系统通过对PIT设备的I/O端口读写,设定时钟中断的触发频率,设置和读取时钟计数器。

为了实现时间设备的虚拟化,VMM必须要提供软件定时器机制,使得程序可以设定在某个未来的时间执行一段代码,同时还需要提供接口,使得程序可以了解当前的实际时间。

假定客户机操作系统设定PIT时钟中断频率为10ms,VMM截获这一设定,并通知PIT设备模型。PIT设备模型会向VMM注册一个间隔为10ms的软件定时器,并提供回调函数,这个回调函数的功能就是想客户机注入一个时钟中断。如果客户机不被调度出去,每隔10ms,VMM都会调用这个回调函数,向客户机注入一个时钟中断。

当客户机读取PIT的Counter寄存器时,PIT设备模型通过VMM了解当前的实际时间,并减去PIT的时间计数器被初始化时的实际时间,以得到这之间所流逝的时间,经过PIT频率的转换后返回给客户机。

对于HPET和ACPI PM Timer,其基本的实现方法相同,不同点在于,客户机读取PIT设备是通过I/O实现的,而对于HPET和ACPI PM Timer是通过MMIO截获实现的。同时,各个时间设备的中断信号并不相同。

由于操作系统可以依赖于多个时钟设备实现内部时间的维护,因此,当VMM提供多个时钟设备的仿真时,需要保证各个设备模型之间的时间一致性。

实现客户机时间概念的一种方法

当客户机被调度出去的情况下,如何通过设备仿真实现客户机的时间概念,以使得客户机时间等于实际的时间。

如下图所示,假定客户机在t2的时候被调度出去,并在t5的时候被调度进来。在这个过程中,根据客户机操作系统对虚拟PIT的设置,在t3和t4的时候需要插入时钟中断,使得客户机操作系统能够维持内部的时间技术。然而,在t3和t4的时候客户机并没有运行,因此,VMM没有机会把中断注入给客户机。

客户机被调度情况下的时钟实现客户机被调度情况下的时钟实现

通常的做法是,当客户机在t5时刻被调度回来以后,VMM连续把t3、t4时刻丢掉的两个时钟中断连续地注入给客户机。“连续”的意思是,当客户机处理完t3的时钟中断后,立刻把t4时刻的时钟中断注入给客户机,在客户操作系统看来就是一个时钟中断处理完后另一个时钟中断紧接着就发生了。由于这个过程中没有其他的程序被运行,因此,当应用程序或内核中需要时间服务的程序运行的时候,t3和t4丢失的时钟中断都已经补偿给客户机了。这样,VMM保证了客户机内部时间与实际时间一致。当然,如果在操作系统的时钟中断处理函数中,有需要时钟服务的代码运行,那么这些代码仍然会得不到正确的时间。但是,操作系统为保证中断的快速反应,通常并不会出现这种情况。

下图给出里实现这一过程的微观示意。当客户机在t5时刻被调度运行时,VMM立刻注入t3’时刻的时钟中断给客户机,使得客户机的时钟概念跳变到了t3’,客户机在t5’的时候执行完成t3’中断的处理函数后,VMM立刻注入t4’时刻的时钟中断,使得客户机的时钟跳变到t4’。由于客户机的时钟处理函数执行速度都很快,因此t5’ - t5远远小于t6 - t5。

客户机被调度情况下中断注入的微观示意图客户机被调度情况下中断注入的微观示意图

设备模型中技术器的实现方法。在上图中,在t5’后,由于所有的中断都已经被注入到客户机内,客户机的时钟已经调整到和实际时间一致,设备模型中计数器的值与实际时间也是一致的。而在t5到t5’的过程中,由于客户机的时钟概念仍然停留在t3的时刻,因此计数器的返回值也应该在t3和t4之间。由于t5到t5’的时间非常短,因此,具体在t3和t4之间的那个点依赖于具体的实现。

实现客户机时间概念的另一种方法

前面描述了实现时间虚拟化的一种常见的方法。但是,这种实现方法也存在一些问题。例如,由于需要客户机被调度出去时的时钟中断补偿回客户机,因此它对系统的性能会有一定的影响,特别是在运行的客户机数量比较多的情况下,时钟中断的补偿回消耗很多的时间。其次,在SMP的情况下,各个VCPU之间tsc的同步存着在一些问题。

时钟虚拟化的主要目的是保证客户机内部的时间概念的正确性,因此,可以针对操作系统特定的时间概念维护机制,修改VMM时间虚拟化的方法。

在硬件平台上,虽然时钟中断丢失的情况非常少见,但还是存在这种可能性,通常的例子是因为操作系统关闭中断的时间过长,然而,有些操作系统已经考虑了时钟中断丢失的情况,它们在收到时钟中断后,会读取时钟设备中的技术器,并根据计数器的值进行修正。

如下图所示,假定操作系统没有收到t3时刻的时钟中断,当t4时刻时钟中断注入的时候(注意,这是在物理机器上,因此t3时刻的中断并没有机会补偿给操作系统),中断处理函数会读取时钟设备中的计数器,发现和上一次时钟中断的发生已经超过了10ms,因此操作系统会认为自己错过了时钟中断,并根据时钟设备中的计数器来修改自己的时钟概念。

考虑时钟中断丢失情况下的操作系统实现考虑时钟中断丢失情况下的操作系统实现

针对这种操作系统,时钟虚拟化不再需要将错过的中断补偿给客户机,以图5-25为例子来说明,。在t5时刻客户机被重新调度调度运行的时候,VMM并不需要将t3/t4时刻的时钟中断都注入客户机,而只需要注入一次。当客户机的时钟中断处理函数读取时钟设备的计数器时,直接返回实际时间。客户机操作系统通过检查计数器返回值,就可以知道错过了t4/t3的时钟中断,并将自己的时间概念更新到t5。

如何满足客户机时间不等于实际时间的需求

前面讨论了如何通过设备仿真,使得客户机内部的时间概念与实际的时间相等。然而,正如前面所说,有些应用需要客户机时间等于客户机实际运行时间,例如进程记账软件。对于这种需求,一种做法是在客户机内部引入PV的时间模块,使得客户机被调度出去的时间(也就是图5-25中的t2~t5)被计算的PV的时间模块,而不是当前进程上。