3、ARM虚拟化扩展:EL2异常级别、VHE、Stage-2页表、虚拟中断(vGIC)
好,咱们今天聊聊ARM虚拟化扩展的核心机制。这部分内容,说白了就是Hypervisor能跑起来的“地基”。你想想看,一个Hypervisor要在硬件上同时管多个虚拟机,没有芯片级的支持,光靠软件模拟,性能肯定惨不忍睹。
我个人习惯,讲虚拟化先从异常级别讲起。因为这是ARM给虚拟化专门划出来的“特权空间”。
3.1 EL2异常级别:Hypervisor的专属领地
ARMv8架构引入了四个异常级别(Exception Level),从EL0到EL3。EL0跑应用,EL1跑操作系统(比如Linux、Android),EL3跑安全监控(Secure Monitor)。而EL2,就是专门给Hypervisor准备的。
为什么需要EL2?
你想,如果没有EL2,Hypervisor就得跟Guest OS挤在EL1里。那怎么隔离?怎么保证Guest OS不能乱动硬件?根本做不到。EL2的存在,让Hypervisor拥有了比Guest OS更高的权限。Guest OS在EL1里以为自己掌控一切,其实它看到的“硬件”都是Hypervisor在EL2里模拟出来的。
核心要点:EL2是Hypervisor的“上帝模式”。所有虚拟机的资源访问,最终都要经过EL2的检查和转发。
我在项目中遇到过一个问题:某个客户在移植旧版KVM时,发现虚拟机老是莫名其妙地崩溃。查了很久,最后发现是EL2的异常向量表配置错了。嗯,这里要注意,EL2有自己的VBAR_EL2寄存器,跟EL1的VBAR_EL1是分开的。你如果忘了配置,或者配错了,异常就会跑飞。
3.2 VHE(虚拟化主机扩展):让Host OS“偷懒”
VHE是ARMv8.1引入的特性。它的作用,说白了就是让Host OS(比如Linux内核)可以直接在EL2上跑,而不需要再经过一层EL1的转换。
为什么会这样?
在VHE出现之前,Linux内核作为Host,是跑在EL1的。每次要执行虚拟化操作(比如切换虚拟机),都得通过SMC或者HVC指令陷入EL2。这中间的开销,虽然不大,但积少成多,对性能还是有影响的。
VHE做了什么?
- 它让EL2和EL1的寄存器视图变得“几乎一样”。比如,你原来在EL1访问TTBR0_EL1,现在在EL2可以直接访问TTBR0_EL2,而且行为跟EL1完全兼容。
- 它引入了一个HCR_EL2.E2H位。把这个位置1,就开启了VHE模式。
我的建议:如果你在做新的虚拟化方案,尽量用支持VHE的硬件(ARMv8.1及以上)。它能让你的Host OS代码更简洁,性能也更好。我曾经在一个老项目里被迫用非VHE模式,那代码里到处都是EL1/EL2切换的hack,维护起来真是头疼。
3.3 Stage-2页表:给虚拟机一个“假”内存
Stage-2页表,是ARM虚拟化里最巧妙的设计之一。它实现了“内存虚拟化”。
你想想,每个虚拟机都认为自己有完整的物理内存(比如0x80000000开始)。但实际上,这些“物理地址”都是虚拟的(IPA,Intermediate Physical Address)。Hypervisor通过Stage-2页表,把IPA映射到真正的物理地址(PA)。
这个过程分两步:
- Stage-1:Guest OS把虚拟地址(VA)翻译成中间物理地址(IPA)。这一步由Guest OS自己控制,跟普通OS一样。
- Stage-2:Hypervisor把IPA翻译成真正的物理地址(PA)。这一步由Hypervisor控制,Guest OS完全不知道。
所以,Guest OS以为自己掌控了内存,其实它看到的只是Hypervisor给它的一张“地图”。
代码示例:配置Stage-2页表的关键寄存器
// 设置Stage-2页表基地址
// VTTBR_EL2 是 Stage-2 的页表基地址寄存器
// 注意:VTTBR_EL2 的格式跟 TTBR0_EL1 不一样,有 VMID 字段
uint64_t vttbr = (uint64_t)stage2_pgd | (vmid << 48);
asm volatile("msr VTTBR_EL2, %0" : : "r" (vttbr));
// 启用Stage-2地址翻译
// HCR_EL2 的 VM 位(bit 0)控制 Stage-2 是否生效
uint64_t hcr = read_sysreg(HCR_EL2);
hcr |= HCR_VM; // 开启 Stage-2
write_sysreg(hcr, HCR_EL2);
避坑指南:我曾经犯过一个低级错误——忘了配置VMID。VMID是虚拟机ID,Stage-2页表里每个条目都关联一个VMID。如果你不配,或者配错了,不同虚拟机的内存就会互相串扰。那调试起来,简直是一场噩梦。
3.4 虚拟中断(vGIC):让中断也“虚拟化”
中断虚拟化,是另一个难点。你想,虚拟机里的设备产生中断,这个中断怎么通知到虚拟机里的Guest OS?
ARM的方案是vGIC(虚拟化通用中断控制器)。它把物理GIC的能力,通过硬件虚拟化,直接暴露给虚拟机。
vGIC的核心机制:
- LR(List Register):物理GIC里有一组LR寄存器。Hypervisor可以把虚拟中断“注入”到LR里。物理GIC看到LR里有中断,就会把它当成一个普通中断,发送给虚拟机。
- HCR_EL2.IMO/FMO:这两个位控制着中断的“路由”。IMO控制IRQ,FMO控制FIQ。如果置1,中断就会直接路由到EL2(由Hypervisor处理);如果置0,中断就路由到EL1(由Guest OS处理)。
- EOI(End of Interrupt):虚拟机处理完中断后,需要写GICC_EOIR寄存器来通知GIC。在虚拟化环境下,这个写操作会被trap到EL2,由Hypervisor来模拟完成。
我个人的经验是,vGIC的调试是最麻烦的。因为中断的流程涉及物理GIC、Hypervisor、虚拟机三层。我曾经遇到一个中断丢失的问题,查了三天,最后发现是LR寄存器里的优先级配置跟物理GIC的优先级配置不一致,导致中断被物理GIC屏蔽了。
| 组件 | 作用 | 关键寄存器/位 |
|---|---|---|
| EL2 | Hypervisor运行的特权级别 | VBAR_EL2, HCR_EL2 |
| VHE | 让Host OS直接在EL2运行 | HCR_EL2.E2H |
| Stage-2页表 | 实现内存虚拟化(IPA→PA) | VTTBR_EL2, VTCR_EL2 |
| vGIC | 实现中断虚拟化 | LR寄存器, HCR_EL2.IMO/FMO |
好了,这一章的内容就到这里。EL2、VHE、Stage-2、vGIC,这四个概念是ARM虚拟化的基石。你把这四个搞明白了,后面讲CPU虚拟化、内存虚拟化、中断虚拟化的具体实现,就会轻松很多。
下一章,我们聊聊如何用这些硬件特性,搭建一个最小的Hypervisor原型。