侧边栏壁纸
博主头像
蔚然小站博主等级

未来会有的,不要辜负了梦想

  • 累计撰写 39 篇文章
  • 累计创建 15 个标签
  • 累计收到 29 条评论

目 录CONTENT

文章目录

中断虚拟化:5.硬件虚拟化支持

皮蛋熊
2023-08-27 / 0 评论 / 0 点赞 / 357 阅读 / 9686 字
温馨提示:
本文最后更新于 2023-08-27,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

中断注入:是指 虚拟中断控制器采集到的中断请求,将按照VMM排定的优先级,被逐一注入到对应的虚拟CPU中。

VM exit:最常用的办法就是往虚拟CPU对应的物理CPU发送一个IPI核间中断

为什么中断注入一定要让CPU退出,这是因为intel硬件设计使然。只有在VM-Entry这个时间硬件才检查VMCS域中的VM-entry interruption-information的字段,所以要让虚机先退出,然后复现一个VM-Entry的时机。

为了让Guest对其(虚拟)APIC的访问不必引起VM Exit,引入了Virtual-APIC Page的概念。它相当于是一个Shadow APIC,Guest对其APIC的部分甚至全部访问都可以被硬件翻译成对Virtual-APIC Page的访问,这样就不必频繁引起VM Exit了。Virtual-APIC Page受VMCS管理,这是一个可以由虚拟机直接访问的内存页面。

VT-d是X86架构里面可以对中断进行重新映射或者直接投递中断的一个硬件,它根据中断映射表将外部中断投递到目标Guest。VMM拥有一个Interrupt Remap Table Address Register指向中断映射表的入口,每passthrough一个设备,VMM就在这个表中分配一个entry,打上相应的属性,由于外设已经pasthrough给Guest了,Guest里驱动写外设的PCI config space,此时VMM要拦截对PCI config space的写,配置外设MSI产生的中断是remappable格式。Guest里的driver给这个passthroug的device分配了一个vector X,然后在IDT中添加了vector X的处理函数。由于device的interrupt是external interrupt,不能直接给了Guest,host也给device分配一个vector Y,host接收到了interrupt Y转换成interrupt X,再投递给Guest,Guest用自己的函数处理,投递时用post interrupt就不会导致Guest exit出来。

中断评估:待处理的中断有没有被屏蔽?待处理的中断优先级是否比CPU正在处理的中断优先级高?等等的过程就是中断评估。

正篇开始

virtual-APIC page(虚拟中断寄存器页面)

物理LAPIC有一个APIC-access page,物理采用mmap的方式访问这些寄存器。一开的设计是vCPU准备访问这些寄存器的时候会触发从guest VM-exit到Host中,由KVM负责模拟,将Guest写给LAPIC的值写入vLAPIC,或者从vLAPIC的APIC page读入值给Guest。这其中充斥了大量的VM exit,为了优化整个过程,intel设计了virtual APIC page,CPU在Guest模式下将使用这个virtual-APIC page来维护寄存器的状态;

  • 读取vAPIC page不需要VM-exit到host
  • 写入vAPIC page需要VM-exit到host

Guest模式下CPU需要从VMCS中找vAPIC page,VMX在VMCS中设计了VIRTUAL_APIC_PAGE_ADDR字段,在CPU切入Guest模式前,KVM需要将vAPIC page的地址记录到VMCS这个字段中。

linux.git/arch/x86/kvm/vmx.c
static int vmx_vcpu_reset(struct kvm_vcpu *vcpu)
{
…
	vmcs_write64(VIRTUAL_APIC_PAGE_ADDR, 
		__pa(vmx->vcpu.arch.apic->regs));
…
}

这个功能有一个VMCS开关配置,叫Secondary Processor-Based VM-Execution Controls,打开这个配置的代码如下:

Pasted image 20220725112146

linux.git/arch/x86/kvm/vmx.c
static __init int setup_vmcs_config(struct vmcs_config *vmcs_conf)
{
	…
	opt2 = SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES |
	…
		SECONDARY_EXEC_APIC_REGISTER_VIRT;
	…
}
linux.git/arch/x86/include/asm/vmx.h
#define SECONDARY_EXEC_APIC_REGISTER_VIRT 0x00000100

该功能打开后,只有写vAPIC page会触发VM-exit,这个处理函数由handle_apic_write负责。

linux.git/arch/x86/kvm/vmx.c
static int (*const kvm_vmx_exit_handlers[])
	(struct kvm_vcpu *vcpu) = {
	…
		[EXIT_REASON_APIC_ACCESS] = handle_apic_access, 
		[EXIT_REASON_APIC_WRITE] = handle_apic_write,
	…
};

未开启该功能,访问APIC page都会触发VM-exit,读和写都由handle_apic_access进行处理。

写寄存器可能会伴随其他动作,这里这里的APIC_ICR会发送核间中断的操作,这也是为啥intel的硬件实现中写寄存器还是要触发VM-exit。

linux.git/arch/x86/kvm/lapic.c
int kvm_lapic_reg_write(struct kvm_lapic *apic, u32 reg, u32 val)
{
	...
	case APIC_ICR:
        /* No delay here, so we always clear the pending bit */
        val &= ~(1 << 12);
        apic_send_ipi(apic, val, kvm_lapic_get_reg(apic, APIC_ICR2));
        kvm_lapic_set_reg(apic, APIC_ICR, val);
        break;
    ...
}

Guest模式下中断评估

没有intel硬件层面支持的话,中断评估和中断注入都应该在VM entry时。有两种情况无法处理中断:

  1. VM entry那一刻Guest是关闭中断的
  2. Guest正在执行不能被中断的指令 这种情况会导致中断等很久,为了不让中断延时过大,当CPU打开中断且未执行无法中断的指令时,CPU应该立马VM exit,以便注入中断。

VMX提供了一种特性:Interrupt-window exiting,RFLAGS寄存器中的IF置位了,就和CPU检查中断类似,会在Guest能够处理中断,且无不可打断指令执行,会触发VM exit从而进行中断注入。

Pasted image 20220725135905

Primary Processor-Based VM-Execution Controls
commit 85f455f7ddbed403b34b4d54b1eaf0e14126a126 (历史代码)
KVM: Add support for in-kernel PIC emulation
static void vmx_intr_assist(struct kvm_vcpu *vcpu)
{
…
	interrupt_window_open =
		((vmcs_readl(GUEST_RFLAGS) & X86_EFLAGS_IF) &&
		 (vmcs_read32(GUEST_INTERRUPTIBILITY_INFO) & 3) == 0);
	if (interrupt_window_open)
		vmx_inject_irq(vcpu, kvm_cpu_get_interrupt(vcpu));
	else
		enable_irq_window(vcpu);
}
static void enable_irq_window(struct kvm_vcpu *vcpu)
{
	u32 cpu_based_vm_exec_control;
	cpu_based_vm_exec_control = vmcs_read32(CPU_BASED_VM_EXEC_CONTROL);
	cpu_based_vm_exec_control |= CPU_BASED_VIRTUAL_INTR_PENDING;
	vmcs_write32(CPU_BASED_VM_EXEC_CONTROL,
	cpu_based_vm_exec_control);
}

当guest模式下支持中断评估后,Guest模式的CPU就不仅仅在VM entry时才能进行中断评估了,其重大的不同在于运行于Guest模式的CPU也能评估中断,一旦识别出中断,在Guest模式即可自动完成中断注入,无须再触发VM exit。

Guest模式的CPU评估中断借助VMCS中的字段guest interrupt status。字段guest interrupt status长度为16位,存储在VMCS中的Guest Non-Register State区域。低8位称作Requesting virtual interrupt(RVI),这个字段用来保存中断评估后待处理的中断向量;高8位称作Servicing virtual interrupt(SVI),这个字段表示Guest正在处理的中断。

当Guest打开中断或者执行完不能被中断的指令后,CPU会检查VMCS中的字段guest interrupt status是否有中断需要处理,如果有中断pending在这,则调用Guest的内核中断handler处理中断. 中断注入则有两种方式了,

  1. VM-entry的时候处理。
  2. 记录到guest interrupt status中,进入guest模式自己处理
x86, apicv: add virtual interrupt delivery support
linux.git/arch/x86/kvm/x86.c
static int vcpu_enter_guest(struct kvm_vcpu *vcpu)
{
	…
	if (kvm_check_request(KVM_REQ_EVENT, vcpu) || req_int_win) {
	…
		if (kvm_x86_ops->hwapic_irr_update)
			kvm_x86_ops->hwapic_irr_update(vcpu,
				kvm_lapic_find_highest_irr(vcpu));
	…
		}
	}
	…
}
linux.git/arch/x86/kvm/vmx.c
static void vmx_hwapic_irr_update(struct kvm_vcpu *vcpu, …)
{
…
	vmx_set_rvi(max_irr);
}
static void vmx_set_rvi(int vector)
{
…
	if ((u8)vector != old) {
		status &= ~0xff;
		status |= (u8)vector;
		vmcs_write16(GUEST_INTR_STATUS, status);
	}
}

Guest模式的CPU中断评估支持默认关闭(需手动开启),但是KVM默认开启了这个支持。

linux.git/arch/x86/kvm/vmx.c
static __init int setup_vmcs_config(struct vmcs_config *vmcs_conf)
{
…
	opt2 = SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES |
	…
			SECONDARY_EXEC_VIRTUAL_INTR_DELIVERY; //
…
}

posted-interrupt processing

当虚拟中断芯片需要注入中断时,其将中断的信息更新到posted-interrupt descriptor中。然后虚拟中断芯片向CPU发送一个通知posted-interrupt notification(常规IPI,但核间中断向量专有,不会触发VM exit),处于Guest模式的CPU收到这个中断后,将在Guest模式直接响应中断。

当来自虚拟设备的中断到达虚拟LAPIC后,虚拟LAPIC将更新目标Guest的posted-interrupt descriptor,然后通知目的CPU评估并处理中断,目的CPU无须进行一次VM exit和VM entry。

Pasted image 20220725143827

处理模拟设备的中断过程

外部中断在一个处于Guest模式的CPU,但是目标Guest是运行于另外一个CPU上的情况。来自外设的中断落在CPU0上,而此时CPU0处于Guest模式,将导致CPU0发生VM exit,陷入KVM。KVM中的虚拟LAPIC将更新目标Guest的posted-interrupt descriptor,然后通知目的CPU1评估并处理中断,目的CPU1无须进行一次VM exit和VM entry。

Pasted image 20220725144127

处理外设中断过程

设备透传结合posted-interrupt processing机制后,中断重映射硬件单元负责更新目标Guest的posted-interrupt descriptor,将不再导致任何VM exit,外部透传设备的中断可直达目标CPU。

Pasted image 20220725144416

透传设备中断处理过程

posted-interrupt descriptor的长度为64Bytes,其格式

Pasted image 20220725144628

posted-interrupt descriptor格式

0~255位用来表示中断向量,256位用来指示是否有中断。其地址记录在VMCS中,对应的字段是posted-interrupt descriptor address。 CPU判断哪个中断是posted-interrupt notification?其中断向量记录在VMCS中。

linux.git/arch/x86/kvm/vmx.c
static int vmx_vcpu_setup(struct vcpu_vmx *vmx)
{
…
	if (vmx_vm_has_apicv(vmx->vcpu.kvm)) {
		…
		vmcs_write64(POSTED_INTR_NV, POSTED_INTR_VECTOR);
		vmcs_write64(POSTED_INTR_DESC_ADDR, __pa((&vmx->pi_desc)));
	}
…
}

函数pi_test_and_set_pir和pi_test_and_set_on分别设置posted-interrupt descriptor中的pir和notification,设置完posted-interrupt descriptor后,如果此时 CPU处于Guest模式,那么发送专为posted-interrupt processing定义的核间 中断POSTED_INTR_VECTOR;如果CPU不是处于Guest模式,那么就 发送一个重新调度的核间中断,促使目标CPU尽快得到调度,在VM entry后马上处理posted-interrupt descriptor中的中断。

linux.git/arch/x86/kvm/lapic.c
static int __apic_accept_irq(…)
{
…
	kvm_x86_ops->deliver_posted_interrupt(vcpu, vector);
…
}
linux.git/arch/x86/kvm/vmx.c
static void vmx_deliver_posted_interrupt(…)
{
	…
	if (pi_test_and_set_pir(vector, &vmx->pi_desc))
	…
	r = pi_test_and_set_on(&vmx->pi_desc);
	…
	if (!r && (vcpu->mode == IN_GUEST_MODE))
		apic->send_IPI_mask(get_cpu_mask(vcpu->cpu),
			POSTED_INTR_VECTOR);
	else
		kvm_vcpu_kick(vcpu);
}

参考文献

  1. 《深度探索Linux系统虚拟化:原理与实现》 - 王柏生 & 谢广军
  2. X86架构中断虚拟化技术解析
0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区