ID_AA64MMFR0_EL1寄存器用于查询处理器对内存系统相关特性的支持,系统可能在启动阶段会读取该寄存器,Hypervisor可以向Guest OS呈现一个不同的虚拟值;
当vCPU读取该寄存器时,触发异常,Hypervisor在trap_handler中进行处理,设置一个虚拟值,并最终返回给vCPU;
通过trap来虚拟化一个操作需要大量的计算,包括触发异常、捕获,模拟、返回等一系列操作,像ID_AA64MMFR0_EL1寄存器访问并不频繁,这种方式问题不大。但是当需要频繁访问的寄存器,比如MIDR_EL1和MPIDR_EL1等,出于性能的考虑,应该避免陷入到Hypervisor中进行模拟处理,可以通过其他机制,比如提供VPIDR_EL2和VMIDR_EL2寄存器,在进入VM前就设置好该值,当读取MIDR_EL1和MPIDR_EL1时,硬件就返回VPIDR_EL2和VMIDR_EL2的值,避免了陷入处理;
2.4 Virtualizing exceptionsHypervisor对虚拟中断的处理比较复杂,Hypervisor本身需要机制来在EL2处理中断,还需要机制来将外设的中断信号发送到目标虚拟机VM(或vCPU)上,为了使能这些机制,ARM体系架构包含了对虚拟中断的支持(vIRQs,vFIQs,vSErrors);
处理器只有在EL0/EL1执行状态下,才能收到虚拟中断,在EL2/EL3状态下不能收到虚拟中断;
Hypervisor通过设置HCR_EL2寄存器来控制向EL0/EL1发送虚拟中断,比如为了使能vIRQ,需要设置HCR_EL2.IMO,设置后便会将物理中断发送至EL2,然后使能将虚拟中断发送至EL1;
有两种方式可以产生虚拟中断:1)在处理器内部控制HCR_EL2寄存器;2)通过GIC中断控制器(v2版本以上);其中方式一使用比较简单,但是它只提供了产生中断的方式,需要Hypervisor来模拟VM中的中断控制器,通过捕获然后模拟的方式,会带来overhead,当然不是一个最优解。
让我们来看看GIC吧,看过之前中断子系统系列文章的同学,应该见过下图:
Hypervisor可以将GIC中的Virtual CPU Interface映射到VM中,从而允许VM中的软件直接与GIC进行通信,Hypervisor只需要进行配置即可,这样可以减少虚拟中断的overhead;
来个虚拟中断的例子吧:
外设触发中断信号到GIC;
GIC产生物理中断IRQ或者FIQ信号,如果设置了HCR_EL2.IMO/FMO,中断信号将被路由到Hypervisor,Hypervisor会检查中断信号转发给哪个vCPU;
Hypervisor设置GIC,将该物理中断信号以虚拟中断的形式发送给某个vCPU,如果此时处理器运行在EL2,中断信号会被忽略;
Hypervisor将控制权返回给vCPU;
处理器运行在EL0/EL1时,虚拟中断会被接受和处理
ARMv8处理器中断屏蔽由PSTATE中的比特位来控制(比如PSTATE.I),虚拟化时比特位的作用有些不一样,比如设置HCR_EL2.IMO时,表明物理IRQ路由到EL2,并且对EL0/EL1开启vIRQs,因此,当运行在EL0/EL1时,PSTATE.I比特位针对的是虚拟vIRQs而不是物理的pIRQs。
2.5 Virtualizing the Generic Timers先来看一下SoC的内部:
简化之后是这样的:
ARM体系架构每个处理器都包含了一组通用定时器,从图中可以看到两个模块:Comparators和Counter Module,当Comparators的值小于等于系统的count值时便会产生中断,我们都知道在操作系统中timer的中断就是系统的脉搏了;
下图展示虚拟化系统中运行的vCPU的时序: