代码分析
el2_setup继续执行就到了EL2异常等级的虚拟化部分的解析和处理部分
#ifdef CONFIG_ARM64_VHE
/*
* Check for VHE being present. For the rest of the EL2 setup,
* x2 being non-zero indicates that we do have VHE, and that the
* kernel is intended to run at EL2.
*/
mrs x2, id_aa64mmfr1_el1
ubfx x2, x2, #ID_AA64MMFR1_VHE_SHIFT, #4
#else
mov x2, xzr
#endif
/* Hyp configuration. */
mov_q x0, HCR_HOST_NVHE_FLAGS
cbz x2, set_hcr
mov_q x0, HCR_HOST_VHE_FLAGS
set_hcr:
msr hcr_el2, x0
isb
/*
* Allow Non-secure EL1 and EL0 to access physical timer and counter.
* This is not necessary for VHE, since the host kernel runs in EL2,
* and EL0 accesses are configured in the later stage of boot process.
* Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout
* as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined
* to access CNTHCTL_EL2. This allows the kernel designed to run at EL1
* to transparently mess with the EL0 bits via CNTKCTL_EL1 access in
* EL2.
*/
cbnz x2, 1f
mrs x0, cnthctl_el2
orr x0, x0, #3 // Enable EL1 physical timers
msr cnthctl_el2, x0
1:
msr cntvoff_el2, xzr // Clear virtual offset
从 id_aa64mmfr1_el1 获取硬件是否支持 VHE
第一行关于 CONFIG_ARM64_VHE,我们先可以在Linux menuconfig里面看一下含义
Symbol: ARM64_VHE [=y] │
│ Type : bool │
│ Defined at arch/arm64/Kconfig:1473 │
│ Prompt: Enable support for Virtualization Host Extensions (VHE) │
│ Location: │
│ -> Kernel Features │
│ (1) -> ARMv8.1 architectural features
根据网上的解释,其含义如下
"Enable support for Virtualization Host Extensions (VHE)" 是一个功能选项,它允许在支持该特性的处理器上,让操作系统内核直接在 EL2(Exception Level 2)级别运行,而不是传统的 EL1(Exception Level 1)。这项特性由 ARMv8.1 架构引入,旨在简化对第二代虚拟化技术(Type-2 hypervisors)的支持,并且能够显著减少宿主和客户机之间共享的系统寄存器数量,从而降低虚拟化的性能开销。
VHE 通过在 HCR_EL2 寄存器中设置特定的位来控制,其中包括 E2H 位用于控制是否启用 VHE,以及 TGE 位用于控制当启用 VHE 时,EL0 是作为宿主(Host)还是客户机(Guest)。启用 VHE 后,可以在 EL2 提供类似于 EL1 的虚拟内存功能和计时器功能,同时保持与原来在 EL1 运行的代码的兼容性,如寄存器、中断路由等。
此外,VHE 还支持在 EL2 运行时,通过重定向客户机虚拟化控制访问,允许客户机虚拟化软件在 EL1 运行,而宿主机虚拟化软件在 EL2 运行,从而实现嵌套虚拟化。
在 Linux 内核配置中,可以通过设置 CONFIG_ARM64_VHE 选项来启用对 VHE 的支持。这个选项在 Linux 内核版本 4.1 及以后的版本中可用,并且在运行时可以检测 VHE 特性,不会影响那些不实现此特性的处理器。
VHE 的引入,对于运行 KVM(Kernel-based Virtual Machine)的系统来说,可以提高性能,因为它减少了宿主操作系统和客户机操作系统之间进行上下文切换的成本。此外,VHE 还允许物理中断在宿主上被处理,而不需要进行完整的保存/恢复操作,这可能导致中断延迟的减少。
总的来说,"Enable support for Virtualization Host Extensions (VHE)" 是一个针对 ARM 架构的优化特性,它通过允许操作系统内核在 EL2 运行,来提高虚拟化技术的性能和效率。
如果我们的内核使能了这个选项的话,我们看一下第三到五行的含义:检查硬件是否支持VHE,将结果放在x2寄存器里面,如果x2寄存器的值非0, 则表示支持VHE,那么后续操作系统将在EL2异常等级运行,根据网上的解释,这样虚拟化性能更高。那么我们继续分析如何查看硬件是否支持VHE。
第七行里面提到了id_aa64mmfr1_el1寄存器,先将该寄存器的值存放到x2寄存器中
第八行中有如下宏定义 ID_AA64MMFR1_VHE_SHIFT,其值定义如下:
#define ID_AA64MMFR1_VHE_SHIFT 8
该句话的含义是:紧接着将x2寄存器的bit8开始的4bit数提取出来,再放回到x2寄存器中。
我们猜测代码的含义,id_aa64mmfr1_el1寄存器的bit[11:8]存放VHE是否支持的依据。
为了证明我们的推测,我们在芯片手册里面查找id_aa64mmfr1_el1寄存器,跟我们猜想的一样,
id_aa64mmfr1_el1(Identification Register: AArch64 Memory Model Feature Register 1, EL1)是ARMv8-A架构中的一个系统寄存器,
它提供了关于ARMv8-A处理器在EL1(Exception Level 1,即操作系统级别)下支持的内存管理特性的信息。
这个寄存器对于操作系统和虚拟机管理程序(VMM)来说非常重要,因为它们需要了解底层硬件的内存管理功能来正确配置和管理内存。
芯片手册明确如果支持VHE,那么其值只可能为1
芯片手册对id_aa64mmfr1_el1寄存器的bit[11:8]定义如下
VH, bits [11:8]
From ARMv8.1:
Virtualization Host Extensions. Defined values are:
0b0000 Virtualization Host Extensions not supported.
0b0001 Virtualization Host Extensions supported.
All other values are reserved.
ARMv8.1-VHE implements the functionality identified by the value 0b0001.
From ARMv8.1, the only permitted value is 0b0001.
也就是说,x2的值为1时,表示硬件芯片支持VHE,否则,表示不支持或者内核没有打开CONFIG_ARM64_VHE编译选项。
配置 HCR_EL2
第十四到十八行表示,根据x2也就是根据芯片是否支持VHE,对hcr_el2寄存器设置不同的配置值。
HCR_EL2 全称 Hypervisor Configuration Register,该寄存器的含义如下:
Provides configuration controls for virtualization, including defining whether various operations are trapped to EL2
也就是说,该寄存器用于给虚拟化提供配置控制,包括定义哪些行为需要陷入到EL2异常等级处理。
HCR_HOST_NVHE_FLAGS和HCR_HOST_VHE_FLAGS是ARM架构中用于配置Hypervisor(虚拟机监控器)控制寄存器(HCR_EL2)的两个重要标志集合,它们分别对应于不同的虚拟化模式:
传统的分离Hyp(Non-Virtualization Host Extensions)模式;
虚拟主机扩展(Virtualization Host Extensions,VHE)模式。
其宏定义以及对应的bit位定义如下:
/* Hyp Configuration Register (HCR) bits */
#define HCR_ATA (UL(1) << 56)
#define HCR_FWB (UL(1) << 46)
#define HCR_API (UL(1) << 41)
#define HCR_APK (UL(1) << 40)
#define HCR_TEA (UL(1) << 37)
#define HCR_TERR (UL(1) << 36)
#define HCR_TLOR (UL(1) << 35)
#define HCR_E2H (UL(1) << 34)
#define HCR_ID (UL(1) << 33)
#define HCR_CD (UL(1) << 32)
#define HCR_RW_SHIFT 31
#define HCR_RW (UL(1) << HCR_RW_SHIFT)
#define HCR_TRVM (UL(1) << 30)
#define HCR_HCD (UL(1) << 29)
#define HCR_TDZ (UL(1) << 28)
#define HCR_TGE (UL(1) << 27)
#define HCR_TVM (UL(1) << 26)
#define HCR_TTLB (UL(1) << 25)
#define HCR_TPU (UL(1) << 24)
#define HCR_TPC (UL(1) << 23)
#define HCR_TSW (UL(1) << 22)
#define HCR_TAC (UL(1) << 21)
#define HCR_TIDCP (UL(1) << 20)
#define HCR_TSC (UL(1) << 19)
#define HCR_TID3 (UL(1) << 18)
#define HCR_TID2 (UL(1) << 17)
#define HCR_TID1 (UL(1) << 16)
#define HCR_TID0 (UL(1) << 15)
#define HCR_TWE (UL(1) << 14)
#define HCR_TWI (UL(1) << 13)
#define HCR_DC (UL(1) << 12)
#define HCR_BSU (3 << 10)
#define HCR_BSU_IS (UL(1) << 10)
#define HCR_FB (UL(1) << 9)
#define HCR_VSE (UL(1) << 8)
#define HCR_VI (UL(1) << 7)
#define HCR_VF (UL(1) << 6)
#define HCR_AMO (UL(1) << 5)
#define HCR_IMO (UL(1) << 4)
#define HCR_FMO (UL(1) << 3)
#define HCR_PTW (UL(1) << 2)
#define HCR_SWIO (UL(1) << 1)
#define HCR_VM (UL(1) << 0)
/*
* The bits we set in HCR:
* TLOR: Trap LORegion register accesses
* RW: 64bit by default, can be overridden for 32bit VMs
* TAC: Trap ACTLR
* TSC: Trap SMC
* TSW: Trap cache operations by set/way
* TWE: Trap WFE
* TWI: Trap WFI
* TIDCP: Trap L2CTLR/L2ECTLR
* BSU_IS: Upgrade barriers to the inner shareable domain
* FB: Force broadcast of all maintenance operations
* AMO: Override CPSR.A and enable signaling with VA
* IMO: Override CPSR.I and enable signaling with VI
* FMO: Override CPSR.F and enable signaling with VF
* SWIO: Turn set/way invalidates into set/way clean+invalidate
* PTW: Take a stage2 fault if a stage1 walk steps in device memory
*/
#define HCR_GUEST_FLAGS (HCR_TSC | HCR_TSW | HCR_TWE | HCR_TWI | HCR_VM | \
HCR_BSU_IS | HCR_FB | HCR_TAC | \
HCR_AMO | HCR_SWIO | HCR_TIDCP | HCR_RW | HCR_TLOR | \
HCR_FMO | HCR_IMO | HCR_PTW )
#define HCR_VIRT_EXCP_MASK (HCR_VSE | HCR_VI | HCR_VF)
#define HCR_HOST_NVHE_FLAGS (HCR_RW | HCR_API | HCR_APK | HCR_ATA)
#define HCR_HOST_VHE_FLAGS (HCR_RW | HCR_TGE | HCR_E2H)
以下是两者之间的主要不同点:
HCR_HOST_NVHE_FLAGS:
对应于传统的分离Hyp虚拟化模式。
在此模式下,宿主操作系统(Host OS)运行在EL1(异常级别1),而客户操作系统(Guest OS)也运行在EL1,但通过一个中间层(如KVM)进行虚拟化。
客户OS陷入到EL2后,需要宿主OS帮助完成相关功能后,再次陷入EL2然后返回客户OS,即CPU需要两次陷入和四次上下文切换才能完成一次对客户OS的服务。这样运行性能很低。
HCR_HOST_VHE_FLAGS:
对应于虚拟主机扩展(VHE)模式。这是针对于前者来数,ARM64的改进方案,
在此模式下,宿主操作系统运行在EL2,而客户操作系统运行在EL1。
客户OS可以直接陷入EL2的宿主OS完成相关功能,然后直接返回客户OS,即CPU只需要一次陷入和两次上下文切换就可以完成一次对客户OS的服务。
所以,对应的标志位设置如下:
HCR_HOST_NVHE_FLAGS 通常包括以下几个标志位(具体可能因实现而异):
HCR_RW:设置EL1为aarch64模式。
HCR_API:允许访问APn寄存器(Application Program Interrupt)。
HCR_APK:允许访问PKn寄存器(Process Key)。
HCR_HOST_VHE_FLAGS 通常包括以下几个标志位(具体可能因实现而异):
HCR_RW:同样设置EL1为aarch64模式。
HCR_TGE:将原本路由到EL1的各类中断(包括快速中断FMO、中断IMO、错误中断AMO)全部路由到EL2来处理。
HCR_E2H:控制VHE模式的行为,如果打开,则由TGE决定当前是VM还是host。
基于上述的分析,我们可以分析出设置标记位对性能的影响
HCR_HOST_NVHE_FLAGS 下的Hyp模式由于需要更多的上下文切换和陷入/退出操作,可能在某些情况下对性能有一定影响。
HCR_HOST_VHE_FLAGS 下的VHE模式由于减少了上下文切换和陷入/退出操作的次数,通常可以提供更好的性能。
时钟相关寄存器处理
第二十二行到第二十九行是注释,是对第三十一行到第三十六行代码的讲解。
它的含义是:
允许EL1和EL0异常模式下访问物理定时器和计数器。
但是这些访问对于VHE来说没有必要,因为host kernel运行在EL2异常模式,并且在后续的启动阶段会配置EL0对与物理定时器和计数器的访问。
需要关注的是:当HCR_EL2.E2H为1的时候,CNTHCTL_EL2与CNTHCTL_EL1的值完全相同;访问CNTHCTL_EL1等同于访问CNTHCTL_EL2。
This allows the kernel designed to run at EL1 to transparently mess with the EL0 bits via CNTKCTL_EL1 access in EL2.
这句话不理解,字面意思是 这使得设计为在EL1运行的内核能够通过在EL2中访问CNTKCTL_EL1来透明地操作EL0的位。
代码的第31行表示如果x2非0,则跳转到第36行,也就是如果当前是VHE模式,则将寄存器cntvoff_el2 Counter-timer Virtual Offset register清零。
在芯片手册里,该寄存器的含义如下:
Holds the 64-bit virtual offset. This is the offset between the physical count value visible in CNTPCT_EL0 and the virtual count value visible in CNTVCT_EL0.
具体含义如下:
在ARM64架构中,cntvoff_el2寄存器是一个关键的系统控制寄存器,它位于EL2(虚拟机管理级别)的权限层级。该寄存器的具体含义和作用主要涉及虚拟化环境中的时间管理和偏移控制。
主要含义
cntvoff_el2寄存器用于定义物理时间(由系统计数器的值定义)和虚拟时间之间的偏移量。在支持虚拟化扩展的ARM64处理器上,这个偏移量允许虚拟机监控器(Hypervisor)为运行在EL1(操作系统级别)或更低级别的虚拟机(Guest VMs)提供独立的虚拟时间视图,而不会干扰到物理时间或其他虚拟机的时间。
功能和作用
虚拟化时间偏移:
通过设置cntvoff_el2寄存器的值,Hypervisor可以为每个虚拟机配置一个独特的虚拟时间偏移量。这样,即使多个虚拟机同时运行,它们也可以拥有各自独立的时间流,而不会相互干扰。
时间隔离:
在虚拟化环境中,时间隔离是一个重要的安全特性。cntvoff_el2寄存器通过提供虚拟时间偏移,帮助实现虚拟机之间的时间隔离,确保每个虚拟机只能看到自己的虚拟时间,而无法访问或修改其他虚拟机的时间。
性能优化:
在某些情况下,通过调整cntvoff_el2寄存器的值,Hypervisor可以优化虚拟机的性能。例如,它可以通过为特定虚拟机设置适当的虚拟时间偏移来减少虚拟机之间的时间同步开销。
使用场景
在ARM64的虚拟化环境中,cntvoff_el2寄存器通常由Hypervisor在初始化阶段进行配置。Hypervisor会根据虚拟机的需求和安全策略来设置每个虚拟机的虚拟时间偏移量。这样,当虚拟机运行时,它们就可以通过访问相应的虚拟时间寄存器(如CNTVCT_EL0)来获取自己的虚拟时间,而无需担心与其他虚拟机或物理时间发生冲突。
也就是说,该寄存器是一个计数器偏移寄存器。
代码的第32行到34行是将CNTHCTL_EL2的bit[1:0]赋值为3
CNTHCTL_EL2, Counter-timer Hypervisor Control register
该寄存器的含义是:Controls the generation of an event stream from the physical counter, and access from EL1 to the physical counter and the EL1 physical timer.
翻译过来就是
cnthctl_el2是ARM64架构中的一个系统控制寄存器,它位于EL2(虚拟机管理级别)的权限层级。该寄存器主要用于控制Hypervisor(虚拟机管理器)对物理和虚拟定时器的行为,特别是在虚拟化环境中。
主要功能有如下两个
第一个:
cnthctl_el2寄存器允许Hypervisor启用或禁用对物理定时器和虚拟定时器的访问。这对于确保虚拟机(VM)之间的隔离以及管理虚拟机的定时器资源至关重要。
通过设置cnthctl_el2中的特定位,Hypervisor可以控制哪些异常级别(EL0、EL1)可以访问物理定时器(如CNTPCT_EL0)和虚拟定时器(如CNTVCT_EL0)。
第二个:
在支持虚拟化的ARM64系统中,cnthctl_el2是确保虚拟机能够安全、有效地使用定时器资源的关键。通过精确控制对定时器的访问,Hypervisor可以防止虚拟机之间的干扰,并确保系统的整体稳定性和性能。
正如前面注释描述的,在非VHE模式下,允许EL1和EL0异常模式下访问物理定时器和计数器
我们看一下芯片手册的描述:
EL1PCEN, bit [1]
Traps EL0 and EL1 accesses to the EL1 physical timer registers to EL2 when it is enabled for the current Security state
0b1 This control does not cause any instructions to be trapped.
EL1PCTEN, bit [0]
Traps EL0 and EL1 accesses to the EL1 physical counter register to EL2 when it is enabled for the current Security state.
0b1 This control does not cause any instructions to be trapped.
我的理解是,如果EL2异常等级使能了安全模式,那么当在EL0和EL1异常等级下访问EL1的物理定时器和计数器,则会陷入到EL2异常模式
如果该寄存器对应的bit位设置为1,那么不会存在陷入EL2异常模式的情况
参考
参考1
arch/arm64/kernel/head.S文件——el2_setup函数分析
https://blog.csdn.net/lsshao/article/details/107462240
参考2
聊聊SOC启动(十) 内核启动先导知识
https://zhuanlan.zhihu.com/p/522195519
参考3
飞腾CPU虚拟化相关代码分析(一)
https://blog.csdn.net/lsshao/article/details/108404486