惠伟:IOMMU(五)-interrupt remmapingzhuanlan.zhihu.com
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攻击,私有云可以用,公有云不行。