在虚拟化中,为了实现vCPU,既要模拟CPU的运行,又要记录vCPU的状态(包括对vCPU运行的控制信息),在Intel x86处理器的VMX(Virtual Machine Extension)功能中,通过引入根运行模式(VMX root operation)和非根模式(VMX non-root operation),直接让vCPU运行在逻辑CPU上,在软体上省去了对vCPU运行的模拟,同时也大大提升了性能。剩下的就是对vCPU状态的记录了,为此Intel引入了VMCS(Virtual Machine Control Structure)功能。

VMCS(Virtual Machine Control Structure)是Intel x86处理器中实现CPU虚拟化,记录vCPU状态的一个关键数据结构。该数据结构主要包含如下六方面的信息:

  1. Guest-state area,即vCPU的状态信息,包括vCPU的基本运行环境(如通用寄存器等)和一些非寄存器信息,如当前vCPU是否接收中断,是否有挂起的exception,VMCS的状态等等。
  2. Host-state area,即主机物理CPU的状态信息,因为物理CPU是在主机CPU和vCPU之间来回切换运行的,所以在VMCS中既要记录vCPU的状态,也需要记录主机CPU的状态,这样vCPU才能有足够的信息恢复到原来主机CPU的状态,继续主机CPU的运行。其包含的具体信息和前面记录的vCPU的状态信息大体相同。
  3. VM-execution control fields,该方面信息主要用于对vCPU的运行行为进行控制,这是VMM对vCPU进行配置最复杂的一部分,如
    1. 控制vCPU在接收到某些中断事件的时候,是否直接在vCPU中处理掉,即虚拟机直接处理掉该中断事件还是需要退出到VMM中,让VMM去处理该中断事件。
    2. 是否使用EPT(Extended Page Table)功能。
    3. 是否允许vCPU直接访问某些I/O口,MSR寄存器等资源。
    4. vCPU执行某些指令的时候,是否会出发VM Exit等等。
  4. VM-exit control fields,即对VM Exit的行为进行控制,如VM Exit的时候对vCPU来说需要保存哪些MSR寄存器,对于主机CPU来说需要恢复哪些MSR寄存器。
  5. VM-entry control fields,即对VM Entry的行为进行控制,如需要保存和恢复哪些MSR寄存器,是否需要向vCPU注入中断和异常等事件(VM Exit的时候不需要向主机CPU注入中断/异常事件,因为可以让那些事件直接触发VM Exit)。
  6. VM-exit information fields,即记录下发生VM Exit发生的原因及一些必要的信息,方便于VMM对VM Exit事件进行处理,如vCPU访问了特权资源造成VM Exit,则在该区域中,会记录下这个特权资源的类型,如I/O地址,内存地址或者是MSR寄存器,并且也会记录下该特权资源的地址,好让VMM对该特权资源进行模拟。

VMCS有点类似于Linux进程中的上下问,只是它保存的是vCPU/CPU的状态和控制信息,而不是进程/线程的状态和控制信息。

每个vCPU都有自己的一份VMCS数据结构,并且在同一个VM中的vCPU可能会通过VMCS中的指针共享一些数据,如I/O bitmap,MSR bitmap和EPT表等。VMCS的大小需要查看VMX capability MSR IA32_VMX_BASIC寄存器来决定,不同的处理器可能要求的不一样,最大不超过4KB。

每个VMCS都会有一个8位元组的头部,前面4位元组主要用来表示VMCS的版本号并且表示VMCS是否为shadow VMCS。然后从偏移量8开始,就是VMCS的数据区,不同版本号的VMCS数据区分布会不一样。

为了为不同版本的VMCS(数据分布有区别)提供统一的、安全的访问介面,VMX功能提供了VMREAD和VMWRITE两个指令,用于对VMCS数据结构进行访问,该指令的第一个操作数就是VMCS数据结构中各个数据域的索引(注意:并不是偏移量),在Intel SDM(Software Developer Manuel)手册上,会定义各个数据区域的索引值。

当VMCS数据结构被load到逻辑CPU上(即执行VMPTRLD指令)后,处理器并没法通过普通的内存访问指令去访问VMCS,如果那样做的话,会引起处理器报错,唯一可用的方法就是通过VMREAD和VMWRITE指令去访问。

VMCS数据结构也会记录下当前vCPU的状态,基本上VMCS的状态由三个域来表示:

  1. Active或Inactive,表示该VMCS是否处于激活状态,即对于逻辑CPU来说,该VMCS是否是直接可用的,有点类似于Linux进程的可运行状态(虽然可运行,但并不意味著现在就必须运行)
  2. Current或Non Current,该状态表示当前逻辑CPU执行VMX指令(如VMCLEAR,VMLAUNCH,VMRESUME等)时的目标VMCS(或者可以理解为目标vCPU),这些VMX指令的目标都是状态为Current的VMCS所代表的vCPU。
  3. Clear或者Launched,用于表示VMCS所对应的vCPU是否有在逻辑CPU上执行过,如果没有,则为Clear状态,如果有,则为Launched状态,Launched状态的VMCS也可以通过VMCLEAR指令恢复到Clear状态。当一个vCPU从一个逻辑CPU迁移到另外一个逻辑CPU的时候,需要先将相应VMCS的状态恢复到Clear,撇清该VMCS根原本逻辑CPU的关系,然后在另外一个逻辑CPU上执行VMPTRLD,将该VMCS和新的逻辑CPU建立联系。

对于VMM而言,配置并运行一个VM的最基本步骤如下所示:

  1. 根据IA32_VMX_BASIC MSR寄存器提供的VMCS region大小信息,在内存中创建一个4KB对齐的VMCS region(为了防止被直接内存访问到,可以将该page frame从页表中删除);
  2. 根据IA32_VMX_BASIC MSR寄存器提供的VMCS版本信息,初始化新创建的VMCS region中的版本信息,并且将VMCS前4位元组的bit31设置为0(表示该VMCS不是shadow VMCS);
  3. 针对新创建的VMCS region执行VMCLEAR指令,这会对新创建的VMCS region进行初始化,并且将VMCS的状态设置为Clear;
  4. 针对新创建的VMCS region执行VMPTRLD指令,该指令会将新创建的VMCS region设置为逻辑CPU的当前VMCS,即新创建的VMCS从原来的Not Current状态变为Current状态,后面所有的VMX相关指令操作的对象都将是新创建的VMCS;
  5. 通过一系列的VMWRITE指令对VMCS中的Host-state area进行初始化,该区域将记录VM返回到VMM时,或者是VM Entry不成功时,逻辑CPU的状态及执行位置等信息;
  6. 通过VMWRITE指令对VMCS中VM-Exit control field,VM-Entry control field和VM execution control field等区域进行设置;
  7. 通过WMWRITE指令对VMCS中guest state area区域进行设置,该区域表示了VM中vCPU的执行时vCPU的状态,起始执行位置等信息;
  8. 执行VMLAUNCH指令,开始vCPU的执行。通过VMM需要判断VMLAUNCH的返回结果,确定vCPU是否真正被执行,还是因为某些逻辑冲突导致vCPU没有被执行就返回。

注意:在设置VMCS的各个数据区域的时候,是有顺序讲究的,需要先设置Host-state area,这样可以防止后边步骤出现问题,CPU无法返回到正确的状态和位置,另外页可以减少后面设置对Host CPU状态的干扰。

vCPU的直奔执行逻辑如下图所示:

不同的VMCS的切换就像Linux中不同的进程在逻辑CPU上切换,通过时分复用的方式共享著物理CPU的资源。

推荐阅读:

相关文章