本文将介绍VT-x中的”acknowledge interrupt on exit” feature。

definition

sdm中的相关描述:

description

The “acknowledge interrupt on exit” VM-exit control in the controlling VMCS controls processor behavior for external interrupt acknowledgement. If the control is 1, the processor acknowledges the interrupt controller to acquire the interrupt vector upon VM exit, and stores the vector in the VM-exit interruption-information field. If the control is 0, the external interrupt is not acknowledged during VM exit. Since RFLAGS.IF is automatically cleared on VM exits due to external interrupts, VMM re-enabling of interrupts(setting RFLAGS.IF = 1) initiates the external interrupt acknowledgement and vectoring of the external interrupt through the monitor/host IDT.

总结

  • 当“acknowledge interrupt on exit” VM-exit control位为0:

当vCPU在non-root mode时,external interrupt会导致VM Exit,此时VM Exit interruption information field is marked as invalid。在root mode下,lapic的IRR对应的bit位会被置上。在root mode下,硬件会完成interrupt evaluation和interrupt recognition,当hypervisor设置RFLAGS.IF后,就会发生interrupt delivery,处理器就调用IDT对应的中断处理函数。

  • 当“acknowledge interrupt on exit” VM-exit control位为1:

当vCPU在non-root mode时,external interrupt会导致VM Exit,此时VM Exit interruption information field is marked as valid,并且会记录external interrupt的vector号,此时lapic的IRR对应的bit位并没有被置上,但lapic ISR对应的bit位会被置上(logical processor acknowledges the interrupt controller)。在root mode下,由于lapic的IRR对应的bit位并没有被置上,此时就不会走interrupt evaluation和interrupt recognition这条路径了,也就不会发生interrupt delivery了,处理器不会通过IDT调用中断处理函数。此时需要hypervisor手动调用IDT的中断处理函数。

代码解析

本文参考的内核版本为v5.0

1
2
3
4
static int (*kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
...
[EXIT_REASON_EXTERNAL_INTERRUPT] = handle_external_interrupt,
...
1
2
3
4
5
static __always_inline int handle_external_interrupt(struct kvm_vcpu *vcpu)
{
++vcpu->stat.irq_exits;
return 1;
}

由代码可知,external interrupt的handler只是增加统计信息而已,并没有处理外部中断。

最终是vmx_handle_external_intr进行了外部中断的真正处理。

1
2
3
vcpu_run
└── vcpu_enter_guest
└── vmx_handle_external_intr[kvm_x86_ops->handle_external_intr]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void vmx_handle_external_intr(struct kvm_vcpu *vcpu)
{
u32 exit_intr_info = vmcs_read32(VM_EXIT_INTR_INFO);

/* 如果中断的类型是外部中断 */
if ((exit_intr_info & (INTR_INFO_VALID_MASK | INTR_INFO_INTR_TYPE_MASK))
== (INTR_INFO_VALID_MASK | INTR_TYPE_EXT_INTR)) {
unsigned int vector;
unsigned long entry;
gate_desc *desc;
struct vcpu_vmx *vmx = to_vmx(vcpu);
#ifdef CONFIG_X86_64
unsigned long tmp;
#endif


/* 取得外部中断的vector值,
* (这是因为处理器的"acknowledge interrupt on exit"特性会自动ACK,就自动拿到了vector,
* 但是这个特性使能之后,处理器不会再通过IDT调用中断处理函数,而是使用vmx handler
* vmx handler在这里构造中断栈帧,然后根据vector的值到IDT中找到真正的处理函数完成中断的处理
* 注意,这里会将中断栈帧中的IF置位,这样中断处理完成的时候,就会自动的开启中断了)
*/
vector = exit_intr_info & INTR_INFO_VECTOR_MASK;

/*
* 找到中断描述符,并得到门入口
*/
desc = (gate_desc *)vmx->host_idt_base + vector;
entry = gate_offset(desc);
asm volatile(
#ifdef CONFIG_X86_64
"mov %%" _ASM_SP ", %[sp]\n\t"
"and $0xfffffffffffffff0, %%" _ASM_SP "\n\t"
"push $%c[ss]\n\t"
"push %[sp]\n\t"
#endif
"pushf\n\t"
__ASM_SIZE(push) " $%c[cs]\n\t"
CALL_NOSPEC /*调用真正的中断处理函数*/
:
#ifdef CONFIG_X86_64
[sp]"=&r"(tmp),
#endif
ASM_CALL_CONSTRAINT
:
THUNK_TARGET(entry),
[ss]"i"(__KERNEL_DS),
[cs]"i"(__KERNEL_CS)
);
}
}

参考资料:

  1. KVM: VMX: enable acknowledge interupt on vmexit
  2. 关于KVM中处理外部中断的处理代码
  3. kvm对外部中断的处理
  4. Interrupt and Interrupt Virtualization