Intel SDM Chapter 29: APIC Virtualizaton & Virtual Interrupts

下文中出现的VMCS[x]并不是指在VMCS中偏移量为x的位置存放的数据。此处应将x理解为VMREADVMWRITE指令的操作数,VMCS[x]就是VMREAD xVMWRITE x会访问到的那个Field。之所以必须如此,是因为VMCS中的Field具体的偏移量是Implementation Defined的,只能通过手册第三卷附录B中提供的编码访问。

VMX扩展中提供的功能,其是否存在不是通过CPUID指令查询,而是通过MSR查询。由于特性位较多,在正文中不一一叙述,需要时查手册第三卷附录A即可。

本章中用于优化APIC虚拟化的技术统称为APICv

Pin-Based VM-Execution Controls位于VMCS[0x4000](32 bit)
Primary Processor-Based VM-Execution Controls位于VMCS[0x4002](32 bit)
Secondary Processor-Based VM-Execution Controls位于VMCS[0x401E](32 bit)
VM-Exit Controls位于VMCS[0x400C](32 bit)

Virtual APIC State

为了让Guest对其(虚拟)APIC的访问不必引起VM Exit,引入了Virtual-APIC Page的概念。它相当于是一个Shadow APIC,Guest对其APIC的部分甚至全部访问都可以被硬件翻译成对Virtual-APIC Page的访问,这样就不必频繁引起VM Exit了。我们可以通过Virtual-APIC Address(VMCS[0x2012]/VMCS[0x2013](64 bit full/high))指定Virtual-APIC Page的物理地址。

我们知道,x86 CPU中一共有三种访问APIC的方式:

  • 通过CR8来访问TPR寄存器(仅IA32-e即64位模式)
  • 通过MMIO来访问APIC的寄存器,这是xAPIC模式提供的访问方式
  • 通过MSR来访问APIC的寄存器,这是x2APIC模式提供的访问方式

无论哪种方式,Guest对APIC的访问都可以配置为访问Virtual-APIC Page,下面几节会分别介绍三种访问方式的具体配置和虚拟化方式。

Virtual-APIC Page中的寄存器的偏移量,与APIC中对应的寄存器的偏移量相同,例如TPR寄存器位于APIC_Page[0x80],则Virtual TPR寄存器位于vAPIC_Page[0x80]

Virtual APIC Accesses

CR8-Based TPR Accesses

默认情况下,Non-root模式下进行MOV CR8操作,会直接操作CPU硬件的CR8寄存器从而操作其APIC的TPR寄存器的第4-7位。

为了实现CR8寄存器的虚拟化,可以利用Primary Processor-Based VM-Execution Controls.CR8-Load Exiting[bit 19]Primary Processor-Based VM-Execution Controls.CR8-Store Exiting[bit 20]这两个位,分别可以让Guest在Mov-to-CR8和Mov-from-CR8时发生VM Exit(VM Exit No.28 Control-Register Accesses),从而实现Trap-and-Emulate。

另一种更好的方式是设置Primary Processor-Based VM-Execution Controls.Use TPR Shadow[bit 21] = 1,这样Guest在访问CR8时实际上会访问Virtual-APIC Page中的Virtual TPR (VTPR)寄存器的第4-7位。

Memory-Mapped APIC Accesses

默认情况下,Non-root模式下CPU访问APIC的MMIO地址(HPA, Host Physical Address),会直接访问到CPU的APIC。

为了不让Guest访问到Host的APIC,早期采取的做法是利用Hypervisor对Shadow Page Table或EPT的控制,令Guest在访问落在APIC的MMIO区间的GPA (Guest Physical Address)时VM Exit,从而实现Trap-and-Emulate。

现在已经有了更好的做法:当Secondary Processor-Based VM-Execution Controls.Virtualize APIC Accesses[bit 0] = 1时,可以通过设置APIC-Access Address(VMCS[0x2014]/VMCS[0x2015](64 bit full/high))指定一个APIC-Access Page,Non-root模式下对HPA落在APIC-Access Page的内存访问默认的处理是引起一个VM Exit(VM Exit No.44 APIC Access)。

Virtualize APIC Accesses 功能可以单独开启,此时它的作用仅仅是将Guest APIC访问的VM Exit从EPT Violaton改为了APIC Access,对Guest的APIC访问仍需要以Trap-and-Emulate方式实现。但是,若进一步开启其他APIC虚拟化功能,则可以将Guest APIC访问转变为访问Virtual-APIC Page中的虚拟寄存器。

APIC Access VM Exit发生在Pagewalk之后,更进一步说是在页表项的A/D位被设置后,因为Pagewalk完成前还未获知HPA。另外,APIC-Access Page不能位于大页,否则可能失效。

这里所说的内存访问指的是所谓的「Linear Access」,不包括Pagewalk时产生的内存访问等不使用Linear Address进行的内存访问,软件最好保证不要让Non-Linear Access访问到APIC-Access Page。

Memory Reads

若满足下列任意条件,则对APIC-Access Page的读取会引起APIC Access VM Exit:

  • Use TPR Shadow = 0
  • 读取访问是一个取指令操作
  • 读取访问的Size大于32位
  • 导致读取的指令已经进行了一次对APIC-Access Page的写入的虚拟化
  • 没有访问到合法的寄存器(e.g. vAPIC_Page[0x80] - vAPIC_Page[0x83]是有效的,而vAPIC_Page[0x84] - vAPIC_Page[0x8F]是非法的)

否则,将有机会避免VM Exit,读到Virtual-APIC Page中的值,这将根据Secondary Processor-Based VM-Execution Controls.APIC-Register Virtualization[bit 8]Secondary Processor-Based VM-Execution Controls.Virtual-Interrupt Delivery[bit 9]两个位的取值决定:

  • APIC-Register Virtualization = 0, Virtual-Interrupt Delivery = 0,则只有对TPR的访问不会引起APIC Access VM Exit
  • APIC-Register Virtualization = 0, Virtual-Interrupt Delivery = 1,则只有对TPR、EOI Register和ICR_Low(ICR的低32位)的访问不会引起APIC Access VM Exit
  • APIC-Register Virtualization = 1,则对下列寄存器的访问不会引起APIC Access VM Exit
    • APIC ID Register、APIC Version Register
    • TPR、LDR、DFR
    • Spurious-Interrupt Vector Register
    • IRR、ISR、TMR、EOI Register
    • Error Status Register
    • ICR
    • LVT Entries、Initial Count Register、Divide Configuration Register
    • 换句话说除了只读的PPR和Current Count Register,APIC中其余所有寄存器都允许从Virtual-APIC Page中读取

Memory Writes

若满足下列任意条件,则对APIC-Access Page的写入会引起APIC Access VM Exit:

  • Use TPR Shadow = 0
  • 写入访问的Size大于32位
  • 导致写入的指令已经进行了一次对APIC-Access Page的写入的虚拟化
  • 没有访问到合法的寄存器

否则,将有机会避免VM Exit,写入到Virtual-APIC Page:

  • APIC-Register Virtualization = 0, Virtual-Interrupt Delivery = 0,则只有对TPR的访问不会引起APIC Access VM Exit
  • APIC-Register Virtualization = 0, Virtual-Interrupt Delivery = 1,则只有对TPR、EOI Register和ICR_Low(ICR的低32位)的访问不会引起APIC Access VM Exit
  • APIC-Register Virtualization = 1,则对下列寄存器的访问不会引起APIC Access VM Exit
    • APIC ID Register
    • TPR、LDR、DFR
    • Spurious-Interrupt Vector Register
    • EOI Register
    • Error Status Register
    • ICR
    • LVT Entries、Initial Count Register、Divide Configuration Register
    • 相比读取请求,删去了只读的APIC Verson Register以及IRR、ISR、TMR

值得注意的是,在完成对Virtual-APIC Page的写入后,对APIC寄存器的写入操作产生的副作用可能仍需要模拟。硬件提供了对部分副作用的模拟,称为APIC-write Emulation,对另一些访问则需要Hypervisor模拟,此时会在写入完成后再触发一个VM Exit(VM Exit No.56 APIC Write):

  • 对TPR的写入,会引起TPR virtualization(详下)
  • 对EOI Register的写入
    • Virtual-Interrupt Delivery = 1,则引起EOI virtualization(详下
    • 否则,引起APIC Write VM Exit
  • 对ICR_Low的写入
    • Virtual-Interrupt Delivery = 1,且写入的值满足以下条件,则引起Self-IPI virtualization(详下):
      • Vector的高4位(bit 4-7)不为0
      • Delivery Mode(bit 8-10)为000b(Fixed)
      • Delivery Status(bit 12)为0
      • Trigger Mode(bit 15)为0(Edge)
      • Destination Shorthand(bit 18-19)为01b(Self)
      • 保留位为0
    • 否则,引起APIC Write VM Exit
  • 对ICR_High的写入,不需要任何特殊处理
  • 对其余寄存器的写入,都会引起APIC Write VM Exit

对于APIC AccessAPIC Write,Exit Qualification(VMCS[0x6400](Natural Width))的第0-11位会记录导致VM Exit的访问在APIC-Access Page中的偏移量,从而令Hypervisor可以实现Trap-and-Emulate

MSR-Based APIC Accesses

要实现对x2APIC的MSR寄存器的虚拟化,一种方式是利用MSR Bitmap(通过MSR Bitmap Address(VMCS[0x2004]/VMCS[0x2005](64 bit full/high))指定),令对这些寄存器的访问都产生VM Exit(VM Exit No.31 RDMSRVM Exit No.32 WRMSR),从而实现Trap-and-Emulate。

Secondary Processor-Based VM-Execution Controls.Virtualize x2APIC Mode[bit 4] = 1,则对于x2APIC MSR的访问会得到特殊处理。

对于RDMSR:

  • APIC-Register Virtualization = 0,则只有对TPR的访问会读取Virtual-APIC Page,其余访问会Passthrough(若硬件APIC处于x2APIC模式,则访问对应寄存器,若处于xAPIC模式,则产生#GP)
  • APIC-Register Virtualization = 1,则对所有寄存器的访问都会读取Virtual-APIC Page

对于WRMSR:

  • 对TPR的写入,会写入Virtual-APIC Page,并引起TPR virtualization(详下)
  • 对EOI Register的写入
    • Virtual-Interrupt Delivery = 1,则会写入Virtual-APIC Page,并引起EOI virtualization(详下
    • 否则,访问会Passthrough
  • 对Self-IPI Register的写入
    • Virtual-Interrupt Delivery = 1,则
      • Vector的高4位非0,则引起Self-IPI virtualization(详下
      • 否则引起APIC Write VM Exit,其Exit Qualification中报告的偏移量为0x3F0
    • 否则,访问会Passthrough
  • 对其余寄存器的写入都会Passthrough

🔥 Conclusion

一图胜千言,我们用一张图来总结APICv对APIC访问的虚拟化: APICv Overview

首先,对于MMIO方式的访问,可以通过Virtualize APIC Access令对APIC-Access Page的访问,产生APIC Access VM Exit,该功能是对MMIO访问进行进一步虚拟化的前提,它可以独立于Use TPR Shadow开启。

其次,Use TPR Shadow 是CR8、MMIO、MSR三种访问方式的虚拟化的总开关,必须开启才能将Guest的APIC访问重定向到Virtual APIC Page:

  • 对CR8访问方式,Use TPR Shadow 直接控制其虚拟化
  • 对xAPIC模式的MMIO访问,开启Use TPR Shadow才能令对部分或全部寄存器的访问重定向到Virtual APIC Page,否则只会产生APIC Access VM Exit
  • 对x2APIC模式的MSR访问,Virtualize x2APIC Mode 控制其虚拟化,而启用该功能的前提是Use TPR Shadow已经启用
  • 另外需要注意Virtualize APIC AccessVirtualize x2APIC Mode不能同时启用

开启Use TPR Shadow后仅仅是启用了对TPR寄存器的虚拟化,APIC-Register VirtualizationVirtual-Interrupt Delivery提供了进一步的控制,这两个功能都必须在Use TPR Shadow启用后才能使用:

  • 开启APIC-Register Virtualization就会虚拟化对所有APIC寄存器的读取
  • 开启Virtual-Interrupt Delivery就会虚拟化对TPR、EOI Register、Self IPI (ICR_Low/Self-IPI Register)的写入
  • 另外开启Virtual-Interrupt Delivery还要求Pin-Based VM-Execution Controls.External-Interrupt Exiting[bit 0] = 1

对xAPIC模式的虚拟化,我们可以认为相比x2APIC模式增加了以下规则:

  • 开启APIC-Register Virtualization就会虚拟化对所有APIC寄存器的写入
  • 开启Virtual-Interrupt Delivery就会虚拟化对TPR、EOI Register、ICR_Low的读取

Autotriggered Behaviors

FlexPriority

在APICv推出之前,还有一个过渡性的技术,称为VT-x FlexPriority,它引入了Shadow TPR,即VTPR寄存器(Virtual-APIC Page也是此时引入的)。此时,Virtual-APIC Page中仅实现了VTPR一个寄存器,并且尚未发明APIC Write VM Exit,因此对VTPR寄存器的写入会起到类似APIC-Write Emulation的效果,不会引起APIC Write VM Exit。

Virtual-Interrupt Delivery = 0时,在写入完VTPR寄存器后

  • VTPR[7:4] < TPR Threshold,则产生一个VM Exit(VM Exit No.43 TPR Below Threshold
  • 否则,不会有任何副作用,也不会产生APIC Write VM Exit

这里的TPR Threshold位于VMCS[0x401C](32 bit),其0-3位表示VTPR的Task Prority Class(即高4位)允许的最小值,若小于TPR Threshold,则禁止进入Guest:

  • 若当前处于Non-root模式,写入了VTPR导致VTPR[7:4] < TPR Threshold,则立即产生TPR Below Threshold VM Exit
  • 若当前处于Root模式,且已满足VTPR[7:4] < TPR Threshold,则VM Entry后会立即产生TPR Below Threshold VM Exit

也就是说,TPR Threshold 的作用是强制令Task Priority较低的vCPU停止执行。

Virtual Interrupt Delivery

Virtual-Interrupt Delivery = 1时,Virtual Interrupt Delivery功能就会开启,此时会引入一个Guest Non-Register State.Guest Interrupt StatusVMCS[0x810](16 bit)),它由两个8位的Field构成:

  • 低8位为Requesting Virtual Interrupt (RVI),表示正在等待处理的中断中最大的Vector,相当于IRRV
  • 高8位为Servicing Virtual Interrupt (SVI),表示正在处理的中断中最大的Vector,相当于ISRV

PPR virtualization

从前文可知,VPPR寄存器不允许Guest读取,未启用Virtual-Interrupt Delivery时,它等同于不存在。由于Virtual-Interrupt Delivery功能需要判断Guest的虚拟PPR,因此它在VM Entry、TPR virtualization和EOI virtualization时会自动更新VPPR寄存器的值:

  • VTPR[7:4] >= SVI[7:4],则VPPR = VTPR & 0xFF
  • 否则,VPPR = SVI & 0xF0

这基本上和APIC中PPR根据TPR和ISRV更新的规则相同,即取两者中大者(优先级更高者),从而屏蔽优先级低于两者中任意一个的中断。

VPPR更新的三个时机,选择的依据如下:

  • VM Entry时,VTPR和SVI的值都可能已被Hypervisor修改过
  • TPR virtualization发生时,VTPR的值被Guest改变
  • EOI virtualization发生时,Guest完成了其当前正在处理的中断,VISR发生变化,从而SVI发生变化(详下)

Interrupt Delivery

为了解释Virtual-Interrupt Delivery机制,本节需要介绍一些手册中不涉及的背景知识,以及手册前几章节的内容

顾名思义,Virtual-Interrupt Delivery引入了对Interrupt Delivery功能的(硬件辅助)虚拟化支持。我们首先回顾通常情况(无虚拟化)下的中断处理:

  • IPI以及外源中断首先是由System Bus上的中断消息到达CPU的APIC而触发的,若CPU发现自己是该中断消息的目标,则会根据中断消息的Vector设置IRR中的相应位(不考虑NMI、SMI、INIT、ExtINT和Start-IPI)。
  • 对于来自本地中断源的中断,省略判断是否接收中断的步骤(因为必定接收),根据LVT表中配置的Vector设置IRR中的相应位
  • 对于IRR中Pending的最高优先级中断,若其优先级高于PPR的优先级,则清空IRR中的对应位,设置ISR中的对应位,并将中断分发给CPU
  • 对于NMI、SMI、INIT、ExtINT以及Start-IPI,直接分发给CPU

为了厘清它们在虚拟化中的实现,我们将它们分解成几个步骤:

  • 对于IPI和外源中断,第一步是中断路由(Interrupt Routing),这一步可以在软件中模拟(非Passthrough情形),例如QEMU会模拟一个设备产生虚拟中断,然后经过INTx来到IOAPIC最后发送到LAPIC的过程
  • 随后的步骤是设置IRR,将中断加入等待队列(不考虑NMI、SMI、INIT、ExtINT和Start-IPI),我们将这一步称为Interrupt Acceptance(中断接受)
  • 对于将中断分发给CPU执行IDT中注册的中断处理例程这一行为,我们称之为Interrupt Delivery(中断交付)
  • 对于NMI、SMI、INIT、ExtINT以及Start-IPI,可以直接进行Interrupt Delivery
  • 否则要先从IRR中取出最高优先级的中断,设置ISR中对应位,这个过程不妨称之为Interrupt Acknowledgement(中断确认),然后才能进行Interrupt Delivery

通常来说,外部中断(此处的外部中断指的是除NMI、SMI、INIT和Start-IPI外的所有中断,与CPU内部异常相对)和发送给Guest的虚拟中断之间没有必然联系,除了它们可能可以通过上述中断路由的过程间接地联系起来。一个外部中断首先由Host处理,然后可能导致Hypervisor生成一个或多个虚拟中断,并经软件模拟的中断路由,最终注入到vCPU(虚拟CPU)中,当然,也可能不产生任何虚拟中断。对于Passthrough给Guest的设备产生的中断,情况略有不同,其物理中断和虚拟中断基本上是一一对应的关系,并且对中断路由的模拟有所简化甚至完全省略。不论如何,虚拟中断的产生和路由都是System-Specific的,对于不同的虚拟化解决方案、不同的虚拟设备都有所不同,这里我们不关心这一步骤是如何完成的。

对于Interrupt Acceptance,一般来说总是需要Hypervisor进行模拟,也就是虚拟中断到达vCPU的虚拟APIC时,要由Hypervisor手动设置VIRR寄存器,这一步即使使用了Virtual-Interrupt Delivery也是一样的(除非使用了Posted Interrupt)。

真正的区别在于Interrupt Acknowledgement和Interrupt Delivery:

首先来看传统的处理方法,Hypervisor会手动设置VIRR和VISR的位,然后通过Event Injection机制在紧接着的下一次VM Entry时注入一个中断向量号,调用Guest的IDT中注册的中断处理例程。如果Guest正处在屏蔽外部中断的状态,即Guest的RFLAGS.IF = 0Guest Non-Register State.Interruptibility StateVMCS[0x4824](32 bit))的Bit 0 (Blocking by STI)和Bit 1 (Blocking by MOV-SS)不全为零,将不允许在VM Entry时进行Event Injection。为了向vCPU注入中断,可以临时设置Primary Processor-Based VM-Execution Controls.Interrupt-Window Exiting = 1,然后主动VM Entry进入Non-root模式。一旦CPU进入能够接收中断的状态,即RFLAGS.IF = 1Interruptibility State[1:0] = 0,便会产生一个VM Exit(VM Exit No.7 Interrupt Window),此时Hypervisor便可注入刚才无法注入的中断,并将Interrupt-Window Exiting重置为0。

Virtual-Interrupt Delivery解决了上述做法中的两个问题,第一个是需要Hypervisor手动模拟Interrupt Acknowledgement、Interrupt Delivery,第二个是有时需要产生Interrupt Window VM Exit以正确注入中断。

具体来说,在进行VM Entry、TPR virtualization、EOI vitualization、Self-IPI virtualization以及Posted-Interrupt proessing时,会进行一个称为evaluate pending virtual interrupts的行为:

  • Interrupt-Window Exiting = 0,且RVI[7:4] > VPPR[7:4],则确认一个pending virtual interrupt
  • 一旦确认,硬件会持续检查vCPU是否能够接收中断(即RFLAGS.IF = 1Interruptibility State[1:0] = 0)
    • 若当前已经能接收中断,则立即进行Virtual-Interrupt Delivery
    • 否则令vCPU继续执行,直到vCPU能够接收中断时,进行Virtual-Interrupt Delivery

而所谓的Virtual-Interrupt Delivery,实际上就是进行如下操作:

  1. 根据RVI,清除VIRR中对应位,设置VISR中对应位,设置SVI = RVI
  2. 设置VPPR = RVI & 0xF0
  3. 若VIRR中还有非零位,设置RVI = VIRRV,即VIRR中优先级最高的Vector号,否则设置RVI = 0
  4. 根据RVI提供的Vector调用Guest的IDT中注册的中断处理例程

基本上,Virtual-Interrupt Delivery就是用硬件自动执行了我们前文定义的Interrupt Acknowledgement和Interrupt Delivery两个步骤,除了它还需要同步RVI、SVI与VIRRV、VISRV。

现在来考察一下Virtual-Interrupt Delivery的用法:

  • 我们仍旧需要在Hypervisor中模拟Interrupt Acceptance,即设置VIRR寄存器
  • 但只要设置了RVI,VM Entry时就可以自动进行Interrupt Acknowledgement和Interrupt Delivery,并且硬件会自动阻塞Virtual-Interrupt Delivery直至vCPU允许接收中断,从而避免了此前使用的Interrupt Window VM Exit
  • 如果vCPU尚未被调度到,则我们可以在其VIRR寄存器中设置多个位,从而积累多个pending的中断,只要设置RVI为其中优先级最高者,VM Entry后vCPU就会自动对所有pending的中断进行Virtual-Interrupt Delivery,而无需每发送一个中断就VM Exit一次
    • 这里的原理是每当vCPU进行EOI时,都会触发EOI virtualization,从而引起下一次Virtual-Interrupt Delivery,整个过程不需要VM Exit。而原本每次进行EOI都要VM Exit到Hypervisor,重新向Guest注入中断。

APIC-write Emulation

为了在Non-root模式中不退出地处理VIRR中累积的中断,Virtual-Interrupt Delivery功能引入了APIC-write Emulation,即Guest写入TPR、EOI寄存器以及进行Self-IPI时,除了对Virtual-APIC Page写入,还会进行以下操作:

TPR virtualization:

  1. 首先进行PPR virtualization,更新VPPR寄存器的值
  2. 其次根据RVI和VPPR的值evaluate pending virtual interrupts

EOI virtulization:

  1. 根据SVI,清除VISR中对应位
  2. 若VISR中还有非零位,设置SVI = VISRV,即VISR中优先级最高的Vector号,否则设置SVI = 0
  3. 然后进行PPR virtualization,更新VPPR寄存器的值
  4. 检查EOI-Exit Bitmap[Vector],其中Vector为SVI的旧值,即被EOI的中断
    • 若该位为1,则引起VM Exit(VM Exit No.45 Virtualized EOI),Exit Qualification的第0-7位会记录引起VM Exit的Vector值
    • 否则evaluate pending virtual interrupts
    • EOI-Exit Bitmap由4个64位寄存器构成,分别位于VMCS[0x201C]/VMCS[0x201D](64 bit full/high)VMCS[0x201E]/VMCS[0x201F]VMCS[0x2020]/VMCS[0x2021]VMCS[0x2022]/VMCS[0x2023]

Self-IPI virtualization:

  1. 根据Self-IPI的Vector,设置VIRR中的对应位
  2. 设置RVI = max(RVI, Vector),其中Vector为Self-IPI的中断向量号
  3. 然后evaluate pending virtual interrupts

Posted Interrupt

Posted Interrupt是对Virtual-Interrupt Delivery的进一步发展,让我们可以省略Interrupt Acceptance的过程,直接令正在运行的vCPU收到一个虚假中断,而不产生VM Exit。它可以向正在运行的vCPU注入中断,配合VT-d的Posted Interrupt功能,还可以实现Passthrough设备的中断直接发给vCPU而不引起VM Exit。

我们可以通过Pin-Based VM-Execution Controls.Process Posted Interrupts[bit 7]开启Posted Interrupt功能,要启用该功能必须先开启Virtualize-Interrupt Delivery以及VM Exit Controls.Acknowledge Interrupt on Exit[bit 15]

在介绍Posted Interrupt之前,首先来回顾一下,Non-root模式下对外部中断(指除NMI、SMI、INIT和Start-IPI外的所有中断)的处理:

External-Interrupt Exiting = 1时,Non-root模式下接收到外部中断后,会产生VM Exit(VM Exit No.1 External Interrupt),将中断交给Host处理。开启Virtual-Interrupt Delivery的前提就是开启External-Interrupt Exiting。根据Acknowledge Interrupt on Exit的取值,Host对中断有不同的处理:

  • Acknowledge Interrupt on Exit = 0,则VM Exit后该中断还在IRR中pending,Host应该开中断(即取消中断屏蔽)以调用其IDT中注册的中断处理例程
  • 否则,VM Exit时会进行Interrupt Acknowledgement,但不会进行EOI,中断的向量号会存储在VM-Exit Interruption InformationVMCS[0x4404](32 bit))中,Host应该读取该向量号然后作出相应的处理

回到Posted Interrupt,它引入了一个Posted-Interrupt Notification Vector(VMCS[0x0002](16 bit),仅最低8位有效)和一个64字节(恰好占满一个Cache Line)的Posted-Interrupt Descriptor。后者位于内存中,其地址(HPA)通过Posted-Interrupt Descriptor Address(VMCS[0x2016]/VMCS[0x2017](64 bit full/high))指定,格式如下:

  • 第0-255位为Posted-Interrupt Requests (PIR),是一个Bitmap,每个位对应一个Vector
  • 第256位为Outstanding Notification (ON),取1表示有一个Outstanding的Notificaton Event(即中断)尚未处理
  • 第257-511位为Available,即允许软件使用的Ignored位

当Non-root模式下收到一个外部中断时,CPU首先完成Interrupt Acceptance和Interrupt Acknowledgement(因为开启Posted Interrupt必先开启Acknowledge Interrupt on Exit),并取得中断的Vector。然后,若Vector与Posted-Interrupt Notification Vector相等,则进入Posted-Interrupt Processing,否则照常产生External Interrupt VM Exit。Posted-Interrupt Processing的过程如下:

  1. 清除Descriptor的ON位
  2. 向CPU的EOI寄存器写入0,执行EOI,至此在硬件APIC上该中断已经处理完毕
  3. VIRR |= PIR,并清空PIR
  4. 设置RVI = max(RVI, PIRV),其中PIRV为PIR的旧值中优先级最高的Vector
  5. 最后evaluate pending virtual interrupts

其中步骤1和步骤3都是针对Posted-Interrupt Descriptor所在的Cache Line的原子操作,这就是Descriptor正好占满一个Cache Line的原因。

现在来考察一下Posted Interrupt的用法:

假设现在想给一个正在运行的vCPU注入中断,除非该vCPU正在处理中断,否则仅凭Virtual-Interrupt Delivery,仍需要令其VM Exit并设置RVI,以便在VM Entry时触发Virtual-Interrupt Delivery。若使用Posted Interrupt,则可以设置PIR中对应位,然后给vCPU所在的CPU发送一个Notification Event,即中断向量号为Posted-Interrupt Notification Vector的中断,这样vCPU无需VM Exit就可以被注入一个甚至多个中断。