第2章:系统架构基础:ARMv8/v9 异常等级模型
好,咱们正式开始聊虚拟化,得先把地基打牢。ARM的异常等级模型,说白了就是CPU的「权限分级」。
我刚开始接触ARMv8时,也被EL0、EL1这些概念绕晕过。后来在做一个多域隔离的项目时,才真正体会到这套设计的精妙。你想想看,一个系统里跑着用户App、操作系统、还有Hypervisor,如果大家都平起平坐,那不乱套了?
2.1 异常等级(Exception Level)概览
ARMv8/v9定义了四个异常等级,从EL0到EL3。数字越大,权限越高。嗯,这里要注意,EL0权限最低,EL3最高。
| 异常等级 | 典型用途 | 运行环境 |
|---|---|---|
| EL0 | 用户应用程序 | 非特权模式 |
| EL1 | 操作系统内核 | 特权模式 |
| EL2 | Hypervisor / 虚拟机监视器 | 虚拟化特权 |
| EL3 | 安全监视器(Secure Monitor) | 最高特权 |
我个人习惯把EL0到EL3想象成一栋楼的四个楼层。EL0是一楼大厅,谁都能进;EL1是办公区,有门禁;EL2是管理层,能看所有监控;EL3是楼顶的安保中心,掌握着整栋楼的最高权限。
2.2 各异常等级的作用
EL0 - 用户态
这是最底层。你的手机App、游戏、浏览器都跑在这里。EL0不能直接操作硬件,也不能访问系统寄存器。它想干点「坏事」?对不起,得先通过系统调用(SVC)请EL1帮忙。
我在项目中遇到过一个问题:某个第三方应用频繁触发系统调用,导致整体性能下降。排查后发现是它在EL0层做了大量无效的权限请求。说白了,EL0就是个「提需求」的角色,干不了重活。
EL1 - 内核态
操作系统内核(Linux、Android、RTOS)跑在EL1。它管理进程、内存、中断。EL1可以访问大部分系统寄存器,也能操作MMU(内存管理单元)。
EL1和EL0之间通过异常切换。比如EL0想分配内存,就触发一个同步异常,CPU自动切换到EL1去处理。处理完再返回EL0。这个切换过程是有代价的——上下文保存、恢复,都会消耗CPU周期。
EL2 - 虚拟化层
这就是咱们课程的核心了。EL2是Hypervisor的地盘。它负责创建和管理虚拟机(VM)。每个虚拟机都有自己的EL0和EL1。
EL2能拦截EL1对某些系统寄存器的访问。比如虚拟机里的「Guest OS」想写某个控制寄存器,EL2可以「假装没看见」,或者给它一个模拟的值。这就是虚拟化的本质——让Guest OS以为自己在独占硬件。
EL3 - 安全世界
EL3是ARM的「安全扩展」部分。它运行Secure Monitor,负责在「正常世界」(Normal World)和「安全世界」(Secure World)之间切换。
举个例子:你的手机支付指纹信息,就存储在安全世界里。普通App(EL0)和操作系统(EL1)都访问不到。只有通过EL3的Secure Monitor才能切换过去。
2.3 异常等级切换机制
异常等级不是随便跳的。ARM有一套严格的切换规则:
- 同步异常: 比如系统调用(SVC)、页错误、未定义指令。这些是主动触发的。
- 异步异常: 比如中断(IRQ/FIQ)、复位。这些是被动接收的。
切换时,CPU会自动保存当前等级的上下文(比如返回地址、PSTATE),然后加载目标等级的上下文。嗯,这里有个坑——我曾经在调试一个中断嵌套问题时,发现EL2的中断处理函数里不小心修改了EL1的寄存器,导致Guest OS崩溃。后来我养成了习惯:在EL2处理中断时,先保存所有可能被污染的寄存器。
2.4 虚拟化扩展(VHE)介绍
ARMv8.1引入了虚拟化扩展(Virtualization Host Extensions,VHE)。这东西是干嘛的?说白了,就是让Hypervisor跑得更爽。
在VHE之前,Hypervisor(EL2)和Host OS(EL1)是分开的。每次Host OS想访问硬件,都得经过EL2的「翻译」。这带来了额外的性能开销。
VHE允许Host OS直接运行在EL2。这样一来,Host OS就能直接操作硬件,不需要再经过一层「代理」。你想想看,这能省多少事?
| 特性 | 传统EL2 | VHE(EL2) |
|---|---|---|
| Host OS位置 | EL1 | EL2 |
| 硬件访问 | 需EL2代理 | 直接访问 |
| 上下文切换 | 频繁 | 减少 |
| 性能 | 有损耗 | 接近原生 |
我建议你在设计智能座舱系统时,优先考虑支持VHE的硬件。比如Cortex-A76、A78这些核心,都原生支持VHE。用上VHE后,你的Android系统跑在EL2上,Guest OS跑在EL1上,性能损耗能降到最低。
kvm-arm.mode=protected 或者 arm64_sw.feature=1。否则你可能发现EL2的某些寄存器访问不了。
2.5 实战:如何判断当前异常等级
写代码时,怎么知道自己当前在哪个EL?ARM提供了一个寄存器:CurrentEL。
// 读取当前异常等级
uint64_t current_el;
asm volatile("mrs %0, CurrentEL" : "=r" (current_el));
current_el = (current_el >> 2) & 0x3; // 低2位是EL值
switch (current_el) {
case 0: printf("当前在 EL0\n"); break;
case 1: printf("当前在 EL1\n"); break;
case 2: printf("当前在 EL2\n"); break;
case 3: printf("当前在 EL3\n"); break;
default: printf("未知等级\n");
}
这段代码我在调试Hypervisor启动流程时用过无数次。每次系统启动,我都会先打印一下当前EL,确认启动流程是否正确。
2.6 小结
这一章咱们把ARMv8/v9的异常等级模型捋了一遍。EL0到EL3,每个等级都有自己的职责。EL2是虚拟化的核心,VHE让Hypervisor跑得更高效。
下一章,咱们会深入EL2的寄存器配置,看看怎么用代码搭建一个最小的Hypervisor。到时候你会看到,这些抽象的概念是怎么落到实处的。
嗯,先消化这些吧。有问题随时问我。