中断虚拟化做了什么?

我们首先回忆一下中断的过程:中断由设备发送给了I/O APIC,随后交给CPU本地的LAPIC进行处理。LAPIC把它交给CPU,然后由操作系统根据相应的IDT表进行处理。
很明显,如果虚拟机内的操作系统,要正常的去处理中断,就必须要由VMM来完成这两个部分:
(1)虚拟化LAPIC,并虚拟化客户机对APIC寄存器的访问
(2)虚拟化到达客户机的所有中断

APIC的虚拟化

当虚拟机访问local APIC寄存器时,最终同样会对物理上的local APIC进行读写。那么VMM该如何对它进行控制,防止其干扰宿主机的环境呢?VMM有两种方式,来对local APIC进行限制:
(1)通过EPT来进行保护,处理对local APIC的读写,但这样每次都会触发EPT violation,无疑会引入开销;
(2)利用Virtual-APIC机制,guest对local APIC的访问实际上是对virtual-APIC页进行访问。

通过设置VMCS中的Virtualize APIC accesses字段为1,会开启第二种方式:当guest访问APIC-access page时,会根据APIC-register virtualization当中的设置,来决定这次访问的方式:

(1)产生APIC access VM EXIT,或者APIC write VM EXIT
(2)通过virtual-APIC机制

这里,我们详细讨论一下virtual-APIC方式。

APIC-access page和Virtual APIC accesses

Virtualize APIC accesses开启时,处理器会启用APIC-access page。此时APIC-access page对应着物理的local APIC页面,其地址保存在VMCS的APIC-access address当中。从硬件上,CPU能够帮助我们去监控这个页,这样一来,只要guest尝试线性访问local APIC,就会访问APIC-access page。那么此时,会根据“APIC-register virtualization”位的设置,进行不同的处理:

  1. 发生APIC access VM-exit,或者APIC write VM-exit
  2. 直接访问virtual-APIC page中的数据(某些寄存器)

virtual-APIC page是什么?当“use TPR shadow”为1时,会引入virtual-APIC page,它是local APIC的影子页面,其地址保存在VMCS的virtual-APIC address当中。它保存了一份TPR数据,也即virtual TPR寄存器。实际上,virtual-APIC page的唯一用处就是对寄存器进行虚拟化,这样每个VCPU都能够有不同的TPR、VEOI、VLDR等寄存器,其中TPR寄存器保存在virtual-APIC page的80H位置。

那么APIC-access pagevirtual-APIC page之间是什么关系呢?实际上是一种类似映射的关系,当guest尝试线性访问(注意,只有线性访问)APIC-access page页面时,会实际上访问vritual-APIC page页面中的数据。

在虚拟机访问APIC access page时:
(1)如果线性访问了没有虚拟化支持的offset位置,会产生APIC-access VM-exit
(2)采用guest-physical方式访问APIC-access page页中的任何offset位置,都会发生APIC-access VM-exit
(3)采用physical方式访问APIC-access page,可能发生APIC-access VM-exit

这里我们主要关注线性访问的情况,来具体说明APIC-access pagevirtual-APIC page的工作原理。
读APIC-access page
在如下情况时,访问APIC-access page时,都会产生APIC-access VM-exit:
(1)“use TPR shadow”为0
(2)尝试执行APIC-access page
(3)访问数据超过32位
(4)访问APIC-access page的动作发生在虚拟化写APIC-access page过程中
(5)尝试进行跨过local APIC寄存器边界的访问
在除上述原因之外的情况下,读取时,会根据APIC-register virtualizaion位的设置,来进行读取:
(1)如果APIC-register virtualization为0,则如果线性读取TPR,直接从virtual-APIC page返回;否则直接产生APIC-access VM-exit
(2)如果APIC-register virtualization为1,如果访问的是某些特定的偏移量,那么就会返回virtual-APIC page中的数据;否则直接产生APIC-access VM-exit
写APIC-access page
在如下情况时,访问APIC-access page时,都会产生APIC access VM-exit
(1)“use TPR shadow”为0
(2)访问数据超过32位
(3)访问APIC-access page的动作发生在虚拟化写APIC-access page过程中
(4)尝试进行跨过local APIC寄存器边界的访问
在除上述原因之外的情况下,读取时,会根据APIC-register virtualizaionvirtual-interrupt delivery位的设置,来进行写入:
(1)如果APIC-register virtualizationvirtual-interrupt deliverty都为0,那么如果线性写TPR时,会写入VTPR中(virtual-APIC page),其他位置都会产生APIC-access VM-exit
(2)如果APIC-register virtualization为0,但virtual-interrupt deliverty为1,那么线性写TPR、EOI、ICR时,会写入virtual-APIC page中对应的虚拟寄存器当中,其他位置都会产生APIC-access VM-exit
(3)如果APIC-register virtualization为1,则会允许更多的虚拟寄存器写入virtual APIC,其他位置则产生APIC-access VM-exit
但是写入是一个更为复杂的过程,在写入对应的虚拟寄存器之后,处理器还要进行后续的处理,也即APIC-write emulation:根据virtual-interrupt deliverty的设置,产生APIC-write VM-exit,或者对local APIC进行虚拟化操作(TPR、EOI、Self-IPI等)。
小结
APIC虚拟化的过程,实际上也是借助了硬件的力量。根据CPU的设置,可以把不同VCPU的虚拟寄存器保存在不同的页中。这样不仅避免了虚拟机对素主机的污染,还能够减少VM-exit的发生,并且能够让每个VCPU拥有独立的寄存器,对性能是有极大提高的。

中断的evaluation和delivery

现在,我们来看看VMM是如何判断一个中断是否应该交给虚拟机进行处理,并且将它转交给虚拟机的。
VMCS中有一个External-interrupt exiting位。当这个位设置为1时,如果CPU处于non-root模式,那么所有中断都会产生VM-exit;如果设置为0,则所有中断都会直接交给虚拟机进行处理。这无疑是很不安全的,所以一般情况下外部中断都会造成VM-exit,并且在虚拟化层处理完反回虚拟机(VM-entry)时,就需要做中断的evaluation(判断是否有中断要交给虚拟机处理)和delivery(将中断交给IDT处理)了。
在这几种情况下,会引发中断的evaluation和delivery:

(1)TPR虚拟化、EOI虚拟化、Self-IPI虚拟化。
(2)在VM-entry操作时引发。
(3)在利用“post-interrupt processing”机制处理外部中断时引发。

中断的注入,是通过由VMM将中断写入VMCS对应的信息位来实现的,当中断完成后,通过读取中断的返回信息,来分析中断是否正确。在中断注入之后,处理器会完成evaluation和delivery的工作(还有另一种方式是posted interrupt 处理)
那么evalutaion会根据什么来判断一个虚拟中断是否应该pending呢?处理器首先根据虚拟中断的优先级别进行判断,只有在RVI[7:4]大于VPPR[7:4]时,处理器才会组织一次虚拟中断pending。随后处理器还要检查虚拟中断是否被阻塞,只有不被阻塞打时候,会通过guest-IDT来进行deliver执行。
在evaluation之后,虚拟中断就会通过guest-IDT进行delivery。这时处理器将会更新virtual-APIC的状态,包括VIRR、VISR、RVI、SVI状态等,随后处理器根据虚拟中断向量号,通过guest-IDT表进行deliver执行。

posted interrupt处理

前面我们也提到了,外部中断通常都会造成VM-exit的下陷。posted interrupt处理,能够直接把中断注入到guest当中去,而不需要产生VM-exit。
假如启用了posted interrupt处理,那么处理器对外部中断的处理就不同了:
(1)一般情况下,如果external-interrupt exiting为1,那么外部中断产生VM-exit;否则直接通过guest IDT表处理;
(2)在posted interrupt机制下,如果处理器接收到的外部中断向量号是预先定义的通知向量号时,不会产生VM-exit,而是通过PIR的pending处理。
那么这个不产生VM-exit的过程是如何发生的呢?
VMM会在posted-interrupt notification vector当中设置通知向量号,一旦local APIC接到对应的外部中断,那么就会通知guest进行post-interrupt处理。此时posted interrupt描述符内的PIR请求列表会复制到VIRR(virtual-APIC页的另一个用途),形成新的虚拟中断请求列表,然后再对这个虚拟中断请求表中的中断请求进行evaluation和delivery。

而如果外部中断向量号不等于通告的向量,那么依然会触发VM-exit,处理器获取外部中断的信息,通过VMCS中的字段来进行注入中断。

小结

可以看到,中断虚拟化方式的改变,是随着硬件特性的改变而改变的。硬件的支持,主要就是为了提升效率。我们知道,VM-exit无疑会给虚拟机的性能带来损耗,而中断的特殊性,又使得它极有可能造成VM-exit。所以intel的工作,一直都致力于减少VM-exit的发生,提升虚拟机的性能。让虚拟机直接接管硬件一直是虚拟化中极为重要的一种思想,不论是IO还是中断,或者是内存,都是如此。
Ps:《处理器虚拟化技术》这本书极烂,不建议阅读。