IOMMU(六)-post interrupt

2022-04-28 17:45:32 浏览数 (1)

post interrupt

post interrupt是intel提供的一种硬件机制,不用物理cpu从root模式exit到non-root模式就能把虚拟中断注入到non-root模式里,大概实现就是把虚拟中断写到post interrupt descriptor,预定义了一个中断号,然后给non-root模式下的cpu发送这个中断,non-root模式下cpu收到这个中断触发对virtual-apic page的硬件模拟,从post interrupt descriptor取出虚拟中断更新到virtual-apic page中,虚拟机中读virtual-access page,就能取到虚拟中断,处理中断,然后写EOI,触发硬件EOI virtulization,就能把virtual-apic page和post interrupt descriptor中数据清除。

post interrupt descriptor的格式如下:

PIR总共256位,一位代表一个虚拟中断。ON代表预先定义的中断已经发出,如果已经为1就不要再发预先定义的中断了。SN代表不要再发中断。NV就是预先定义好的那个中断号,NDST就是物理CPU的local apic id。

vt-x post interrupt

VMCS中增加如下字段控制post interrupt。

kvm用post interrupt模式给vcpu注入中断,kvm修改这个vcpu的post interrupt descriptor,然后给这个vcpu所运行的物理cpu发送中断号是post interrupt notification vector的中断。kvm只用到了post interrupt descriptor中的ON,用到的notification vector存放在VMCS中而不是post interrupt descriptor中,主要是kvm运行在另一个物理cpu上,一个vcpu有没有运行,运行在哪个物理cpu上,这个vcpu可不可以接收中断,kvm很好判断,如果没有运行或者不能接收中断kvm在把虚拟中断存放在其它地方。

vt-d post interrupt

IOMMU硬件单元也可以借用post interrupt机制把passthrough设备产生的中断直接投递到虚拟机中,不需要虚拟机exit出来,不需要VMM软件介入,性能非常高。这种情况下设备产生的中断从原来interrupt remapping格式变成post interrupt格式,IRTE中内容也变了,它中存放post interrupt descriptor的地址和虚拟中断vector,物理中断到了IOMMU,IOMMU硬件单元直接把IRTE中的虚拟中断vector写到post interrupt descriptor中pir对应的位,然后给vcpu所在的物理cpu发送一个中断,中断号就是post interrupt descriptor中的NV。

一个passthrough给虚拟机的外设,虚拟机里driver给外设分配虚拟中断,qemu拦截到对外设pci config space的写,然后把虚拟中断更新到kvm的irq routing entry中,kvm再调用update_pi_irte把post interrupt descriptor地址和虚拟中断号更新到IRTE中。

代码语言:javascript复制
update_pi_irte
  └─irq_set_vcpu_affinity
       └─intel_ir_set_vcpu_affinity
             └─modify_irte

vt-x posted interrupt就是另一个CPU更新了vcpu的post interrupt descriptor,发送一个ipi给vcpu运行的物理CPU。vt-d posted interrupt就是IOMMU硬件单元更新了vcpu的post interrupt descriptor。vt-x和vt-d post interrupt都不会导致vcpu运行的物理CPU从non-root模式exit到root模式,而且能把vcpu的中断注入到guest。但vt-d相比vt-x就弱智多了,一个vcpu有没有运行,运行在哪个物理cpu上,这个vcpu可不可以接收中断,或者vcpu从一个物理cpu迁移到另一个物理cpu,vt-d IOMMU都不能自己判断,只能通过kvm告诉它,所以kvm就把这些信息写到post interrupt descriptor的其它位中,IOMMU来读,这些位就是SN,NDST和NV。

vcpu转换为运行状态,vmx_vcpu_pi_load清除SN,更新NDST。

代码语言:javascript复制
vcpu_load
  └─preempt_notifier_register

context_switch
  └─finish_task_switch
      └─fire_sched_in_preempt_notifiers
          └─ __fire_sched_in_preempt_notifiers
               └─kvm_sched_in
                   └─kvm_arch_vcpu_load
                      └─kvm_x86_vcpu_load
                           └─vmx_vcpu_pi_load

vcpu暂时挂起,设置SN。

代码语言:javascript复制
vmx_vcpu_put
    └─vmx_vcpu_pi_put

虚拟机执行hlt指令vcpu暂停,保留原先运行的物理cpu到NDST,设置NV为wakeup vector

代码语言:javascript复制
vcpu_block
    └─vmx_pre_block
         └─pi_pre_block

如果此时IRTE中URG为1,IOMMU就给物理cpu发送wakeup vector,pi_wakeup_handler让vcpu开始运行。

代码语言:javascript复制
DEFINE_IDTENTRY_SYSVEC(sysvec_kvm_posted_intr_wakeup_ipi)
{
	ack_APIC_irq();
	inc_irq_stat(kvm_posted_intr_wakeup_ipis);
	kvm_posted_intr_wakeup_handler();
}
kvm_set_posted_intr_wakeup_handler(pi_wakeup_handler);

vcpu开始运行,更新NDST,理想NV为post interrupt vector。

代码语言:javascript复制
vmx_post_block           
  └─pi_post_block

pv ipi

虚拟机中一个vcpu要向另一个vcpu发送ipi或者向其它vcpu广播ipi,怎么利用post interrupt?

首先源vcpu需要把ipi的目的vcpu的local apic id写到apic寄存器,再写icr寄存器,写icr寄存器就会导致vcpu exit,然后kvm就可以利用vt-x posted interrupt把虚拟中断注入到另一个vcpu。如果是广播ipi,那么源vcpu要exit出来很多次。

代码语言:javascript复制
kvm_lapic_reg_write->kvm_apic_send_ipi

所以腾讯云李万鹏就想了招,通过hypercall传bitmap一次把所有目的vcpu都传出来,这样源vcpu就可以少exit出来几次。

代码语言:javascript复制
kvm_emulate_hypercall->kvm_pv_send_ipi

有些虚拟机中的业务会大量用到ipi,导致虚拟机exit出来很多很多次,性能影响太大。有人就想到把源vcpu所运行的物理cpu的lapic的icr寄存器透传给vcpu,把其它vcpu的post interrupt descriptor也透传给虚拟机源vcpu,源vcpu要给目的vcpu发送ipi,源vcpu修改目的vcpu的post interrupt descriptor,源vcpu给真正的硬件寄存器icr写post interrupt notification vector,这样源vcpu不用exit出来。问题就是有点不安全,一个有问题的虚拟机可以频繁给其它物理cpu发送ipi,造成其它物理cpu ipi DDOS攻击,私有云可以用,公有云不行。

参考文献

Intel SDM 3c

Intel vt-d spec

https://dl.acm.org/doi/abs/10.1145/3381052.3381317

https://patchwork.kernel.org/project/kvm/patch/0C23CC2D-B770-43D0-8215-20CE591F2E8F@bytedance.com/

0 人点赞