Intel VT-d实现概述
Intel VT-d是IOMMU技术在intel上的实现,全称是Intel Virtualization Technology for Direct I/O,它是Intel虚拟化技术的一部分,主要针对的是I/O子系统,它的实现主要是通过在硬件上引入重定向单元,该硬件重定向单元用于对I/O子系统的DMA操作和中断传递进行重定向,从而辅助VMM(Virtual Machine Monitor)实现I/O子系统的虚拟化。也就是说VT-d技术的关键分为两部分:DMA重映射(DMA remapping)和中断重映射(interrupt remapping)。
一般情况下VMM支持I/O虚拟化可以通过以下四种方式实现:
(1)纯软件模拟。即VMM的软件模拟一个现有的I/O设备,这种方式具有较好的兼容性,但是纯软件模拟在性能和功能上就表现得比较差了。
(2)还是纯软件模拟,但是引入新的I/O操作接口。如virtio方式,这些接口针对I/O虚拟化进行一定的优化,这样虽然能够解决一定的性能问题,但是兼容性问题又出现了,因为需要使用新的操作接口。
(3)设备直通(passthough),直接将I/O设备分配给某个VM(Virtual Machine),这样只有指定的VM能够使用该I/O设备,并且I/O设备的驱动位于VM中,并且VM能够直接对I/O设备进行操作,这样性能和兼容性都能达到最佳,但是一个I/O设备只能给一个VM使用。
(4)I/O设备分享(SR-IOV),这是硬件分配方式的一种扩展,主要还是需要I/O设备本身需要支持一定的功能,如能够同时提供多个功能接口,比如PCIe设备的SR-IOV功能,即PCIe设备本身能够将一个物理PCIe设备,变成多个逻辑设备,这多个逻辑设备共享该PCIe设备上的物理资源,并且可以独立地分配给不同的VM。
以上I/O虚拟化的一个通用要求就是要求VMM能够将属于不同VM的I/O设备安全地隔离起来,即需要满足以下两方面的要求:
属于一个VM的vCPU无法访问到属于另外一个VM的I/O设备,这可以通过VMX(Virtual Machine Extension)中的EPT(Extended Page Table)功能来实现,即VMM可以通过软件配置,将虚拟机物理内存,即Guest Physcial Address映射到不同的主机内存区域,即Host Physical Address。
属于一个VM的I/O设备不能访问到属于其他VM的内存或者向属于其他VM的中断控制器发送中断。如果是纯软件模拟的方式,则VMM也可以通过纯软件的方式来控制I/O设备访问内存(如DMA)和发送中断的行为。但是如果采用硬件分配的方式,则就需要在硬件上对I/O设备访问内存和发送中断的行为进行拦截,然后重定向到指定的VM中,这就是VT-d派上用场的时候了。
VT-d是一个位于CPU、内存和I/O设备之间的硬件设备,通常位于PCI设备树的根部,或者类似的位于I/O子系统的根部,当VT-d重定向硬件设备启用的时候,它会拦截位于它下面的所有I/O设备产生的中断请求和通过DMA方式对虚拟机内存访问的请求,然后通过查找中断重定向表或者I/O页表的方式(类似分页机制)来重新定位中断转发的目标LAPIC或者是I/O设备访问的目标主机物理内存地址。如下图所示:
VMM软件负责I/O设备的分配,即将指定I/O设备和相应的VM对应起来,并且负责建立中断重定向关系表和I/O地址转换页表,并将这些转换关系的配置设置到VT-d硬件设备上,而I/O设备发起的中断请求或者DMA内存访问请求中带有相应设备的ID,这样VT-d硬件单元就可以通过硬件查找的方式将不同的I/O设备中断和内存访问请求重定向到相应的VM上,从而达到隔离不同VM的I/O设备的目的。
总的来说,VT-d的主要功能就是将I/O设备的DMA访问请求和中断请求重定向到VMM设定好的VM中。
DMA Remapping
负责DMA重定向硬件一般位于Root Complex(RC)中,Root-Complex是PCIe系统中引入的概念,它将CPU、内存子系统和PCIe子系连接起来。如下图所示:
而Root Complex则经常被集成到CPU芯片上、MCH(Memory Controller Hub)上或者是IOH(I/O hub)上。
DMA重定向硬件将来自于I/O子系统的内存访问请求分为两类:
不带地址空间ID的请求(Request without Process Address Space Identifier,即Request-without_PASID),相当于GPA(Guest Physical Address),这是一般Endpoint设备发出的内存访问请求,这类请求通常会表明该请求的类型(读、写或原子操作),DMA目标的地址、大小和发起请求的源设备的ID。
带有地址空间ID的请求(Request with Process Address Space Identifier,即Request-with-PASID),相当于GVA(Guest Virtual Address),能够发出这类请求的源PCI设备需要拥有virtual address capability,该请求带有额外的信息用于定位目标地址空间和一些其他信息。
不同虚拟机之间的隔离是通过防止分配到其他虚拟机的资源(CPU、I/O设备)访问到本虚拟机的物理地址。每个虚拟机都会有自己独立的物理地址空间,即GPA(Guest Physical Address)空间,该空间不同于主机物理地址空间,即HPA(Host Physical Address)空间。DMA重定向硬件将从I/O设备发过来的访问请求中包含的地址看做是DMA地址,根据不同的使用配置,该DMA地址可能是GPA;可能是跟PASID(Process Address Space ID)相关的VA(application Virtual Address);也可能是由软件定义的I/O虚拟地址(IOVA)。不管怎样,DMA重定向硬件将DMA地址最终转化为HPA(Host Physical Address)实现最终主机物理地址的访问。
如下图所示,系统中存在两个Domain,1和2,也可以理解为存在两个虚拟机,这两个虚拟机发出的内存请求通过在CPU上的内存管理单元(MMU),在x86 CPU上可以理解为分页机制和EPT(Extended Page Table)的组合,将发出的内存请求地址最终转化为主机的物理内存地址,即HPA,对应到主机物理内存上。而Device 1和2,则可以理解为分别分配给Domain 1和2的I/O设备,虽然它们发出访问请求的地址数值一样,但是由于它们所属的Domain不一样,导致DMA Memory Management将会使用不同的地址转换页表(IOMMU),将其分别转换到不同Domain所对应的HPA。VMM/Hypervisor负责对DMA Memory Management所使用的I/O地址转换页表进行创建和维护,同时需要对DMA重定向和I/O设备进行配置,协商好使用什么类型的地址,GPA或者GVA。
每个DMA重定向硬件的实现可以是一个硬件单元包含整个PCI Segment,也可以是多个硬件单元,每个硬件单元各自包含PCI Segment中的部分PCI设备。系统的BIOS或者UEFI负责在系统启动的时候对VT-d硬件进行检测,并分配相应的地址空间,让系统软件能够访问到VT-d硬件及其配置寄存器。BIOS/ACPI以ACPI表的子表(DMAR:DMA Remapping Reporting ACPT Table)的形式将VT-d硬件资源描述出来,这样VMM只需要找到DMAR表,就可以对相应的VT-d硬件进行访问或配置了。DMAR表的组织形式以后再详细讲。
每个从I/O设备往上传输经过重定向硬件的数据包都会包含source-id用于定位源I/O设备,不同的I/O设备其source-id的实现可能不一样,对于PCI设备而言,其source-id是传输层头部中包含的requester Identifier,由Bus、Device、Function组成,其格式如下所示:
所谓的重定向就是对目标地址进行转换或更改,DMA重定硬件向利用分层页表结构对地址进行转化。属于不同Domain的I/O设备请求需要被分配到不同的转换页表中,该索引、分配过程以I/O设备请求的source-id为输入源,对于PCI设备发出的Request-without-PASID而言,它将会使用请求包中包含的PCI Bus、Device和Function号作为索引值。在进行这样的索引之前,VMM需要在内存中建立好一个4KB的Root-table,该table包含256个entry,每个entry对应一个PCI Bus,每个Root-entry中包含一个指针,该指针指向一个4KB的Context-table,该Context-table中包含了256个entry,对应到该PCI Bus下的所有Device和Function。每个Context-entry都包含一个指向该PCI Function所对应的Domain的地址转换页表的指针。其结构如下图所示:
当找到相应的地址转换页表后,硬件才开始正常的地址分级页表转换,即所谓的page-walk,将请求中的GPA地址转化为HPA,实现最终的物理内存访问,该转化过程即称为Second-Level-Translation。Root-table的地址需要VMM在启动VT-d硬件的时候,将Root-table的地址写到VT-d硬件相应的寄存器(Root Table Address Register)上,为VT-d硬件提供一个入口。
对于Request-with-PASID而言,其请求中包含的地址转换页表入口索引,即(PCI、Bus、Device和Function值)是一样的,但是其包含的请求地址类型不一样,是GVA,而不是GPA,故需要现将GVA转化为GPA(即为First-Level-Translation),然后再将得到的GPA转化为HPA,即Second-Level-Translation,才能实现最终的物理内存访问。
该方法需要有两次索引地址转换页表的入口地址,所以需要用到Extended-root-table,该4KB的table每个Ext-root-entry中,包含两部分,分别包含Upper-context-table的指针和Lower-contex-table的指针,Upper-context-table指针指向的是PASID-Table,PASID-Table则根据PASID来对该表进行索引,每个PASID-entry都包含一个指向分级页表的入口,该分级页表用于完成First-Level-Translation,即将请求中包含的GVA转化为GPA。Sencond-Level-Translation分级页表的索引和前面Request-without-PASID一样。当Frist-Level-Translation完成后得到的GPA,将作为Second-Level-Translation作为输入地址,最终得到HPA,完成主机物理内存的访问。
不管是first-level translation (requests-with-PASID)还是second-level translation (request-without-PASID)。对于request-with-PASID而言,会先使用first-level translation table将DMA地址转换为一个不带PASID的地址,然后再将这个不带PASID的地址作为second-level-translation table的输入进行转换得到最终的物理地址。查询页表完成地址转换的方式就根普通的分页机制完全一致。页表的层数会根据具体使用的页框大小而变化,页框大小可以是4KB,2MB和1GB,下图以4KB为例。
由此可见要完成一次DMA重定向访问到真正的主机物理地址,中间会有很多的内存访问,为了加快这些内存访问,VT-d硬件中,会引入各种各样的Cache加快这些物理地址的转换,或者是主机物理内存的访问。这也是SRIOV方式和原始设备直通性能差异的一个原因。
Interrupt Remapping
讲完DMA重映射后我们再来看中断重映射(interrupt remapping),中断重映射单元让系统软件能够对I/O设备产生的中断(包括从I/O APIC发送过来的中断,I/O设备产生的以MSI、MSI-X形式传递的中断,不包含中断重映射硬件本身产生的中断)的传输进行控制,而不仅仅取决于硬件的连接。
对于VT-d硬件而言,中断请求就是从外面发送进来对物理地址范围0xFEEX-XXXXh的写请求。VT-d中,中断重映射功能由Extended Capability Register寄存器来决定,该寄存器的bit3表示Interrupt Remapping Support,如果为1,则表示支持中断重映射,如果为0,则表示不支持中断重映射。Extended Capability Register寄存器字段含义如下图。
为了实现同一物理系统中,不同Domain之间的隔离,需要VT-d的中断重映射硬件对接收到的中断请求提取其中断的来源(一般是PCI设备的Bus、Device和Function号之类的信息),然后根据不同设备所属的Domain,将该中断请求转发到相应的Domain中,实现不同Domain之间,中断请求的隔离。
在Intel x86架构中,中断请求的格式有两种:兼容格式和可重映射格式。并且每个请求中都包含了访问的地址和数据,格式的选择由地址信息的bit 4(Interrupt Format)来决定。
对于兼容格式而言,bit 4(Interrupt Format)为0,表示兼容格式,这种格式的中断直接向上传递到CPU的LAPCI,不会被重新映射。兼容格式的中断请求消息格式如下。
对于可重映射的中断请求消息格式而言,bit 4(Interrupt Format)为1,其具体格式如下所示:
消息分为Address和Data两部分,其中Address信息中,bit 19 ~ bit 5和bit 2共同组成了16bit的Handle,并且在Address bit 3(SHV)为1的情况下,Data区域的bit 15 ~ bit 0包含了Sub-Handle,这些值用于索引中断重映射表,后面会讲到。
中断重映射硬件利用一张位于内存的单层表,即中断重映射表,来确定中断请求需要被如何重新生成并转发。该表由VMM配置,并且该表的物理地址将会被写到VT-d硬件中的Interrupt Remap Table Address Register,用于告知硬件中断重映射表的位置,其格式如下图。并且低4 bit用于表示中断重映射表中包含的entry个数,即2的(1+S)次方,最高达2的16次方,即64K。
用来记录中断重映射表物理地址的寄存器
中断重映射表中的每个表项大小为128 bit,即16 Byte,称为Interrupt Remap Table Entry(IRET),其格式如下所示:
中断重映射表表项
主要包含了重映射目标的Destination ID、Vector和一些其他中断传输相关的信息(对于兼容格式的中断而言,其中断的属性都是在中断请求中说明,而可重映射的中断,其中断属性则在IRTE中说明)。另外bit 15必须为0,该bit表示IRTE Mode,如果为0,则表示为Remapped Interrupt,如果为1,则表示Posted Interrupt(下一节讲解)。中断重映射硬件,将以前面提到的中断请求中包含的Handle和Sub-Handle计算索引值,对中断重映射表进行索引。其算法如下所示:
从硬件的角度来看,整个中断重映射的过程为:硬件检测到向0xFEEX-XXXXh地址DWORD的写请求,判定其为中断请求,并将其拦截。如果中断重映射的功能没有打开(Global Status Register的IRES为0),则所有的中断请求都以兼容格式的中断来处理。如果中断重映射功能被打开(Global Status Register的IRES为1),则查看中断请求的格式,如果是兼容格式,则直接跳过中断重映射硬件,继续中断请求的传递。如果是可重映射格式,则检测中断请求中的数据正确性,计算Interrupt_index,读取相应的IRTE,检测IRTE是否存在及其正确性,如果一切正常则中断重映射硬件将会根据读取的IRTE产生一个新的中断请求,并向上传递。其基本流程如下所示:
对于软件而言,为了实现中断重映射,则需要进行如下操作:
如果中断重映射表没有被分配,则在内存中分配一块区域作为中断重映射表,并将该表的位置告诉给中断重映射硬件,即将该表的位置写到Interrupt Remap Table Address Register。
找到一个可用的IRTE(Interrupt Remapping Table Entry),然后设置需要重新转发的中断的一些属性,如传递的目标、中断模式,中断向量等。
对中断源(一般是I/O设备或者是I/O APIC)进行设置,让中断源能够产生可重映射格式的中断,并且由其Handle、Sub-Handle、SHV等区域计算出来的interrupt_index正好匹配到之前设置好的IRTE。不同的I/O设备的设置方法会有区别。
对于I/O xAPIC而言,系统软件通过设置设置I/O xAPIC的Redirection Table Entries(RTEs)来设置I/O xAPIC产生的可重映射中断。I/O xAPIC的RTE的格式如下所示:
将Interrupt Format设置为1表示产生的中断为可重映射中断,并且可以设置Interrupt_Index、Vector、Trigger Mode等信息。
对于可以产生MSI(Message Signaled Interrupt)或者MSI-X的设备而言,对产生中断的设置包括Address和Data寄存器,其格式如下所示:
当Address的bit 4(Interrupt Format)为1的时候,则表示产生的中断为可重映射中断,并且可以设置Interrupt_Index和SHV等值。对于支持多个(必须是2的n次方)Vector中断的MSI/MSI-X消息而言,其Data寄存器的低n位对应到具体的Vector数值,并且SHV(Sub-Handle Valid)为1,这时候IRTE的索引值就是Interrupt_index[15:0]的值加上Vector的值。
总的来说,VT-d的中断重映射就是指VT-d会拦截其下面挂载的I/O设备产生的中断,然后根据接收到的中断请求索引中断重映射表,根据找到的中断重映射表的表项产生新的中断请求,上传到CPU的LAPIC。VT-d就是通过这个重映射动作,实现了同一物理系统中不同Domain的I/O设备中断请求的隔离。同时为了让I/O设备能够产生可重映射的中断,并对中断重映射表进行正确的索引,系统软件还需要对I/O设备的中断请求生成进行配置。
Interrupt Posting
Interrupt-posting是VT-d中中断重映射功能的一个扩展功能,该功能也是针对可重映射的中断请求。Interrupt-posting功能让一个可重映射的中断请求能够被暂时以一定的数据形式post(记录)到物理内存中,并且可选择性地主动告知CPU物理内存中暂时记录着pending的中断请求。
在x86处理器的虚拟化中,Interrupt-posting再加上APIC Virtualization让VMM能够更加高效地处理分配各虚拟机的设备产生的中断请求。VT-d重映射硬件是否支持Interrupt-posting功能可以通过查询Capability Register的bit 59 Posted Interrupt Support(PI)知道硬件是否支持该功能。
对于VT-d而言,所有可重定向的中断都需要经过IRTE(Interrupt Remapping Table Entry)的处理,在进行处理之前会先通过IRTE的bit 15(IRTE Mode)判断该IRTE的模式,如果为0,则VT-d硬件将会以Remapped Interrupt的形式来解析该IRTE(前一篇文章讲的中断重映射),如果为1,则VT-d硬件将会以Posted Interrupt的形式来解析该IRTE,如下图所示:
Posted Interrupt格式的IRTE的定义会有一些不同,主要是:
(1)多了一个Posted Descriptor Address Low/High,该区域保存一个指向内存的指针,该指针指向的位置就是保存中断(Posted Interrupt)的结构体。
(2)Urgent位,该位用于表示该中断是否是紧急的,是否需要目标CPU的立即响应。
(3)Vector用于设置Posted Interrupt Descriptor数据结构中相应的位,而不是用于设置直接产生的中断的vector值。
每个Posted Interrupt Descriptor的大小为64 Byte,用于记录VT-d硬件那边post过来的中断,即在内存中暂时记录一些中断请求,等待CPU的处理,而不是主动打断CPU的运行,让其跳转过来处理,其格式如下所示:
(1)Posted Interrupt Requests (PIR),一共256 bit,每个bit对应一个中断向量(Vector),当VT-d硬件将中断请求post过来的时候,中断请求相应的vector对应的bit将会被置起来。
(2)Outstanding Notification(ON),表示对于该Posted Interrupt Descriptor当前是否已经发出了一个Notification Event等待CPU的处理。当VT-d硬件将中断请求记录到PIR的时候,如果ON为0,并且允许立即发出一个Notification Event时,则将会将ON置起来,并且产生一个Notification Event;如果ON已经被置起来,则VT-d硬件不做其他动作。
(3)Suppress Notification(SN),表示当PIR寄存器记录到non-urgent的中断时,是否不发出Notification Event,如果该位为1,则当PIR记录到中断的时候,则不发出Notification Event,并且不更改Outstanding Notification位的值。
(4)Notification Vector(NV)表示如果发出Notification Event时,具体的Vector值。
(5)Notification Destination(NDST),表示若产生Notification Event时,传递的目标逻辑CPU的LAPIC ID(系统中以逻辑CPU的LAPIC ID来表示具体的逻辑CPU,BIOS/UEFI其初始化系统的时候,会为每个逻辑CPU分配一个唯一的LAPIC ID)。
在硬件上整个Posted Interrupt的处理过程如下所示:
当VT-d硬件接收到其旗下I/O设备传递过来的中断请求时,会先查看自己的中断重定向功能是否打开,如果没有打开则,直接上传给LAPIC。如果中断重定向功能打开,则会查看中断请求的格式,如果是不可重定向格式,则直接将中断请求提交给LAPIC。如果是可重定向的格式,则会根据算法计算Interrupt_Index值,对中断重定向表进行索引找到相应的IRTE。然后,查看IRTE中的Interrupt Mode,如果为0,则该IRTE的格式为Remapped Format,即立即根据IRTE的信息产生一个新的中断请求,提交到LAPCI。如果Interrupt Mode为1,则表示该IRTE的格式为Posted Format,根据IRTE中提供的Posted Interrupt Descriptor的地址,在内存中找到相应Posted Interrupt Descriptor,并根据其ON、URG和SN的设置判断是否需要立即产生一个Notification Event(Outstanding Notification为0,并且该中断为Urgent(URG=1)或者不抑制该Notification(SN==0)),如果不需要,则只是将该中断信息记录到Posted Interrupt Descriptor的PIRR(Posted Interrupt Request Register)字段,等待CPU的主动处理。如果需要立即产生一个Notification Event,则根据Posted Interrupt Descriptor(会提供目标APIC ID、vector、传输模式和触发模式等信息)产生一个Notification Event,并且将该中断请求记录到PIRR字段。
硬件在对Posted Interrupt Descriptor进行修改的时候,要保证该修改是原子操作,即对Posted Interrupt Descriptor的读取、修改和写入必须是原子操作,并且在写入之后,要保证相应内存在各个cache agent之间的一致性,即所有的CPU应该立马能够看到该内存修改。
从软件的角度来看,VMM可能会对Interrupt Posting做如下设置和操作:
1. 对于每个vCPU而言,VMM都会分配一个对应的Posted Interrupt Descriptor用于记录和传递经过重定向,并且目的地为对应vCPU的所有中断请求。
2. VMM软件为所有的Notification Event分配两个物理中断vector:
(1)第一个称作Active Notification Vector(ANV),该Vector对应到当中断目标vCPU当前正在被逻辑CPU执行(即vCPU的状态为active)时,Notification Event所使用的中断vector。
(2)第二个称作Wake-up Notification Vector(WNV),该Vector对应到中断目标vCPU当前不在逻辑CPU上被执行时,由于Urgent被置起来产生的Notification Event所使用的中断Vector。
3. 对于从直接分配给VM的I/O设备产生的中断而言,VMM会为每个这样的中断分配一个IRTE。并且VMM可能会为vCPU使能硬件上的APIC Virtualization,APIC Virtualization主要包括两方面功能:virtual-interrupt delivery和process posted-interrupts,其主要工作形式表现在:
(1)当一个vCPU被VMM调度即将执行的时候,该vCPU的状态为active,该状态的一个表现形式是VMM会将Posted Interrupt Descriptor的Notification Vector字段设置为ANV(Active Notification Vector)。这样就允许当这个vCPU在逻辑CPU上执行的时候,所有指向该vCPU的中断都能够直接被该vCPU处理,不需要经过VMM。vCPU通过将记录在Posted Interrupt Descriptor中的中断直接转移到Virtual-APIC Page中,并直接将中断信号传递给vCPU,让vCPU直接获取到该中断信号的方式来处理Notification Event。
(2)当一个vCPU被抢占后,即vCPU的状态为ready-to-run,该状态的一个表现形式是VMM会将Posted Interrupt Descriptor的Notification Vector字段设置为WNV(Wake-up Notification Vector),并且SN(Suppress Notification)设置为1。只有当接收到的中断请求为Urgent的时候,才会发出Notification Event,触发VMM的执行,让VMM调度目标vCPU处理该紧急中断。
(3)当一个vCPU处于Halt状态的时候,逻辑CPU执行VMM软件,VMM将该vCPU标记为halted状态。该状态的一个表现形式就是将Posted Interrupt Descriptor的Notification Vector字段设置为WNV(Wake-up Notification Vector),并且SN(Suppress Notification)设置为0,即任何到达该Posted Interrupt Descriptor的中断请求都会触发Notification Event,让VMM唤醒vCPU,让vCPU处理中断请求。
4. 当VMM调度并执行一个vCPU的时候,VMM会对被记录到Posted Interrupt Descriptor的中断请求进行处理:
(1)首先,VMM会通过将Posted Interrupt Descriptor的Notification Vector字段的值改为ANV将vCPU的状态变为active。
(2)VMM检测在Posted Interrupt Descriptor中是否有待处理的中断请求。
(3)如果有待处理的中断请求,则VMM会给当前CPU发送一个sefl-IPI中断(即CPU自己给自己发送一个中断),并且中断向量值为ANV。当vCPU一使能中断的时候,就能够立马识别到该中断。该中断的处理类似于vCPU处于active状态时,接收到了Active Notification Vector的中断请求,vCPU可以直接对其进行处理,不需要VMM的参与。
5. 同样VMM可以可以利用Posted Interrupt的处理机制,通过设置Posted Interrupt Descriptor向vCPU注入中断请求。
总体上来说,Interrupt Posting的功能就是让系统可以选择性地将中断请求暂存在内存中,让CPU主动去获取,而不是中断请求一过来就让CPU进行处理,这样在虚拟化的环境中,就能够防止外部中断频繁打断vCPU的运行,从而提高系统的能能。