Minos 虚拟化: ARMv8-A 虚拟化基础

本文介绍 Minos Hypervisor 虚拟化的基础知识。

1. EL 层级与异常模型

1.1 概念

ARMv8-A 定义 4 个异常等级(Exception Level):

EL 典型用途
EL0 用户态(Apps)
EL1 操作系统内核(Guest Linux / Minos OS)
EL2 Hypervisor(Minos 运行于此)
EL3 Secure Monitor(固件/安全世界,本项目中不用)

异常(Exception)发生的本质:处理器因事件暂停当前执行,切到更高或同级 EL,
从异常向量表取指执行
。三个关键寄存器:

  • SPSR_ELx:保存被中断时 PSTATE(含返回的 EL 编码、DAIF 中断屏蔽位、执行状态)
  • ELR_ELx:保存被中断指令的返回地址(eret 时回到这里)
  • ESR_ELx:异常综合寄存器(Exception Syndrome Register),描述异常原因,
    最关键的是 EC 字段(Exception Class),标识这是哪种异常(HVC/SVC/DABT/WFx…)

异常返回用 eret 指令:把 ELR_ELx → PC,把 SPSR_ELx → PSTATE。

1.2 Minos 代码位置

位置 内容
arch/aarch64/core/boot.S:76-91 CurrentEl 检测当前 EL;EL3 → panic;CONFIG_VIRT 下非 EL2 → panic
arch/aarch64/core/boot.S:146-151 构造 SPSR_EL2 值 + ELR_EL2 后 eret 降级到 EL1
arch/aarch64/core/vector.S:71-76 异常入口把 SP_EL0 / SPSR_EL2 / ELR_EL2 压栈
arch/aarch64/core/aarch64_sync.c:87-92 bad_mode():未知模式 panic 出口
arch/aarch64/core/aarch64_sync.c:46-85 esr_class_str[]:ESR EC 字段 → 字符串的全表

1.3 精读 boot.S:76-151(当前 EL 检测与降级)

1
2
3
4
5
6
7
8
9
mrs	x0, CurrentEl        // 读当前异常等级
mov x1, x0, lsr #2 // EL 编码在 [3:2] 位
and x1, x1, #3
cmp x1, #3
b.eq minos_panic // 不能在 EL3 运行

#ifdef CONFIG_VIRT
cmp x1, #2
b.ne minos_panic // 虚拟化模式下必须从 EL2 启动

要点:

  • 启动时 Minos 已经由 U-Boot(跑在 EL3 或 EL2 上层)通过 PSCI 带到了 EL2
  • 虚拟化必须从 EL2 开始,因为只有 EL2 才有 HCR_EL2.VM、Stage2 MMU 等能力
1
2
3
4
5
6
drop_to_el1:
adr x1, minos_os_start
msr ELR_EL2, x1 // 返回地址 = minos_os_start
mov x1, #(AARCH64_SPSR_EL1h | AARCH64_SPSR_F | AARCH64_SPSR_I | AARCH64_SPSR_A)
msr SPSR_EL2, x1 // 返回后的 PSTATE = EL1h,且屏蔽 F/I/A
eret // eret:PC=ELR_EL2,PSTATE=SPSR_EL2

要点:

  • CONFIG_VIRT 构建(纯 OS 模式)时 Minos 会主动从 EL2 降级到 EL1 运行
  • AARCH64_SPSR_EL1h 表示返回后处于 EL1 + SP_EL1(不是 SP_EL0)
  • F/I/A = 屏蔽 FIQ/IRQ/SError,确保进入 C 世界前中断全关
  • 这是软件主动用 eret 完成降级的经典写法

1.4 要点

  1. 异常 = 控制流切换,由硬件自动完成保存(写 SPSR/ELR),由软件决定处理
  2. Minos 必须运行在 EL2(虚拟化需要),这是 Type-1 Hypervisor 的基石
  3. ESR 的 EC 字段是异常分发的钥匙,贯穿整个 Phase 3(trap 机制)
  4. eret 是唯一从异常返回的指令,SPSR+ELR 配对使用

2. HCR_EL2:虚拟化控制寄存器

2.1 概念

HCR_EL2(Hypervisor Configuration Register)是 EL2 控制虚拟化的总开关
它决定:哪些来自 EL1/EL0 的操作会被 trap(陷入) 到 EL2 由 Hypervisor 处理,
以及虚拟化的基本行为(VM 使能、中断路由等)。

2.2 Minos 代码位置

arch/aarch64/virt/arch_virt.c:187 arch_vcpu_state_init(),代码自带逐位中文注释
是学习 HCR_EL2 最好的入门材料:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* HVC : enable hyper call function
* TWI : trap wfi - default enable, disable by dts
* TWE : trap wfe - default disable
* TIDCP : Trap implementation defined functionality
* IMP : physical irq routing
* FMO : physical firq routing
* BSU_IS : Barrier Shareability upgrade
* FB : force broadcast when do some tlb ins
* PTW : protect table walk
* TSC : trap smc ins
* TACR : Trap Auxiliary Control Registers
* AMO : Physical SError interrupt routing.
* RW : low level is 64bit, when 0 is 32 bit
* VM : enable virtualzation
*/
value = read_sysreg64(HCR_EL2);
context->hcr_el2 = value | HCR_EL2_VM |
HCR_EL2_TIDCP | HCR_EL2_IMO | HCR_EL2_FMO |
HCR_EL2_BSU_IS | HCR_EL2_FB | HCR_EL2_PTW |
HCR_EL2_TSC | HCR_EL2_TACR | HCR_EL2_AMO;

/* 通常 guest 会有很多 WFI,关掉 WFI trap 避免大量陷入 */
if (!(vcpu->vm->flags & VM_FLAGS_NATIVE_WFI))
context->hcr_el2 |= HCR_EL2_TWI;

2.3 关键位作用速查表

置位含义 Minos 用法
VM 使能 Stage2 地址翻译(虚拟化核心开关) 必置
RW EL1 使用 AArch64(0=AArch32) 64 位 guest 必置
TWI trap 所有 WFI/WFE 指令 默认置位;NATIVE_WFI 的 VM 关闭
TSC trap SMC 指令 置位,SMC → EL2 处理
IMO/FMO 物理 IRQ/FIQ 路由到 EL2 置位(虚拟化中断的前提)
AMO 物理 SError 路由到 EL2 置位
TIDCP trap IMPLEMENTATION DEFINED 功能 置位
BSU_IS barrier 指令共享属性升级 置位
FB TLB 维护指令强制广播 置位
PTW Stage1 页表访问被 trap(保护表遍历) 置位
TACR trap 辅助控制寄存器 置位

2.4 要点

  1. HCR_EL2 是 per-VM 的——每个 vCPU 的 context->hcr_el2 独立保存,
    由 vmodule 机制在上下文切换时恢复(Phase 7)
  2. 这些 trap 位决定了哪些指令会”掉进” handle_vcpu_sync_exception()(Phase 3 精读)
  3. trap 是虚拟化的根本手段:guest 想访问敏感资源 → 被 HCR trap 到 EL2 →
    Hypervisor 模拟 → 返回 guest。HCR_EL2 就是这份”trap 清单”

3. Stage1 地址翻译

3.1 概念

地址翻译是把 CPU 看到的虚拟地址(VA)变成物理地址(PA)的过程。
ARMv8-A 支持两级:

  • Stage1:VA → IPA(Intermediate Physical Address,中间物理地址)。
    由 OS 自身管理(页表基址 TTBR0/TTBR1)。
  • Stage2:IPA → PA。由 Hypervisor 管理(VTTBR_EL2)。

Stage1 需要 4 个控制寄存器配合:

寄存器 作用
TTBR0/TTBR1 两级页表基址(TTBR0=低位 VA 空间,TTBR1=高位)
TCR 翻译控制:T0SZ/T1SZ(VA 大小)、TGn(粒度)、PS(物理大小)等
MAIR 内存属性索引表:把页表描述符里的 AttrIdx 翻译成实际内存属性(Normal/Device/缓存策略)
SCTLR 总开关(M=MMU 使能,C=dcache,I=icache)

3.2 Minos 代码位置

位置 内容
arch/aarch64/core/boot.S:198-204 设置 TTBR0/TTBR1 指向 __stage1_page_table
arch/aarch64/core/boot.S:206-221 设置 MAIR = 0xbbff440c0400(附注释:MT_* 属性表)
arch/aarch64/core/boot.S:223-239 ID_AA64MMFR0_EL1 取 PA range,动态调整 TCR 的 IPS/PS 位
arch/aarch64/core/boot.S:302-315 SCTLR 置 M/C/I 位 → 开 MMUbr x26 跳到 mmu_on
arch/aarch64/core/stage1.h 全部 Stage1 描述符布局宏
arch/aarch64/core/stage1.c Stage1 页表建立/映射函数

3.3 精读 boot.S:198-239(页表基址 + MAIR + TCR)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ldr	x1, = __stage1_page_table
asm_vtop x1 // 物理地址(MMU 还没开,必须用物理地址写寄存器)
msr ARM64_TTBR0, x1 // TTBR0 = 低地址空间页表基址
msr ARM64_TTBR1, x1 // TTBR1 = 高地址空间页表基址(映射到同一张表)

ldr x1, =0xbbff440c0400
msr ARM64_MAIR, x1 // 内存属性表(见下)

mrs x0, ID_AA64MMFR0_EL1 // 查询硬件支持的最大 PA 位数
and x0, x0, #0xf // PaRange 字段
mov x2, x0, lsl #16 // 填入 TCR.IPS 位
ldr x1, =ARM64_TCR_VALUE // 基础 TCR 值
and x1, x1, x3 // 清掉旧 IPS
orr x1, x1, x2
msr ARM64_TCR, x1 // 写 TCR

MAIR 值 0xbbff440c0400 分解(每 8bit 一个 AttrIdx):

1
2
3
4
AttrIdx0=0x00  Device-nGnRnE    AttrIdx4=0x44  Normal, WB, Inner/Outer
AttrIdx1=0x04 Device-nGnRE AttrIdx5=0xff Normal, WB (write-through 无效时)
AttrIdx2=0x0c Device-GRE
AttrIdx3=0x44 Normal-NC

这与 stage1.h:51-56MT_* 宏一一对应。

3.4 stage1.h 描述符要点

1
2
3
4
5
6
7
8
9
10
11
12
#define S1_DES_FAULT	(0b00 << 0)   /* 无效描述符 */
#define S1_DES_BLOCK (0b01 << 0) /* Block 描述符(L1/L2,覆盖大块) */
#define S1_DES_TABLE (0b11 << 0) /* Table 描述符(指向下一级表) */
#define S1_DES_PAGE (0b11 << 0) /* Page 描述符(L3,4K 页) */

#define S1_AP_RW (0b00 << 6) /* EL2 视角:可读写 */
#define S1_AP_RO (0b10 << 6) /* 只读 */

#define S1_SH_INNER (0b11 << 8) /* 内层共享(多核缓存一致性) */
#define S1_AF (1 << 10) /* Access Flag:访问标志位 */

#define S1_ATTR_IDX(n) ((n & 0xf) << 2) /* AttrIdx → 查 MAIR */

属性宏如 S1_PAGE_NORMAL

1
#define S1_PAGE_NORMAL	(S1_DES_PAGE | S1_AF | S1_NS | S1_SH_INNER | S1_ATTR_IDX(MT_NORMAL))

3.5 要点

  1. MMU 开启前的关键动作:所有写系统寄存器必须用物理地址asm_vtop),
    因为此时还没走翻译
  2. identity map 开 MMU:开 MMU 后 PC 不能变,必须保证当前 PC 在翻译前后
    指向同一个物理地址,所以先 identity map,再跳到 mmu_on
  3. MAIR 是”属性字典”,页表描述符只存索引(AttrIdx),实际属性查 MAIR
  4. TCR 的 IPS 由硬件 ID_AA64MMFR0_EL1 动态决定,保证跟 CPU 能力匹配

4. Stage2 地址翻译

4.1 概念

Stage2 是 Hypervisor 给 guest 用的第二层翻译:guest 的虚拟地址先过 Stage1
(guest 自己管)得 IPA,再过 Stage2(Hypervisor 管)得 PA。每个 VM 有独立的
Stage2 页表,通过 VMID 隔离。

寄存器 作用
VTTBR_EL2 Stage2 页表基址 + VMID(VMID 在 [63:48])
VTCR_EL2 Stage2 翻译控制(T0SZ=IPA 大小、SL0、TG0=粒度、PS=物理大小)
HCR_EL2.VM Stage2 翻译的总开关(前面已讲)

4.2 Minos 代码位置

位置 内容
arch/aarch64/virt/arch_virt.c:131-149 generate_vtcr_el2():40bit IPA、4K 粒度、1TB PA
arch/aarch64/virt/arch_virt.c:151-155 generate_vttbr_el2():`base
arch/aarch64/virt/stage2.h 全部 Stage2 描述符/索引宏
arch/aarch64/virt/stage2.c(500 行) Stage2 映射/解映射/huge page/stage2_ipa_to_pa

4.3 精读 arch_virt.c:131-161

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static inline uint64_t generate_vtcr_el2(void)
{
value |= (24 << 0); // T0SZ = 24 → IPA 宽度 = 64-24 = 40 bit
value |= (0x01 << 6); // SL0 = 1 → 4KB 粒度时从 Level1 开始
value |= (0x1 << 8); // 内存属性 Inner WBWA
value |= (0x1 << 10); // 内存属性 Outer WBWA
value |= (0x3 << 12); // Inner Shareable
value |= (0x0 << 14); // TG0 = 4K
value |= (2 << 16); // PS = 1TB 物理地址空间
value |= (0x0 << 19); // VMID 宽度 8bit
}

static inline uint64_t generate_vttbr_el2(uint32_t vmid, unsigned long base)
{
return (base | ((uint64_t)vmid << 48));
}

要点:

  • T0SZ=24 → 40bit IPA:guest 最多映射 1TB 的中间物理地址
  • VMID << 48:VTTBR 高 16 位是 VMID,用于 TLB 中不同 VM 条目的隔离
  • PS=1TB 表示物理地址空间是 1TB(对应 stage2.h 的 S2_PHYSICAL_SIZE

4.4 stage2.h 与 stage1.h 的差异(易错点)

Stage1 Stage2
AP 编码 S1_AP_RW=0b00<<6(EL2 只用 AP[2]) S2_AP_RW=0b11<<6(两 bit 都要)
共享属性 在描述符里(SH 字段) 同样有 SH 字段
内存属性 AttrIdx → 查 MAIR 直接在描述符里写 MemAttr(S2_MEMATTR_*),不查 MAIR
权限含义 AP[2:1] 分 EL1/EL0 S2_AP[1:0]:01=RO 10=WO 11=RW 00=无权限
1
2
3
4
5
6
#define S2_AP_NON	(0b00 << 6)  /* 无权限 */
#define S2_AP_RO (0b01 << 6)
#define S2_AP_WO (0b10 << 6)
#define S2_AP_RW (0b11 << 6)

#define S2_PAGE_NORMAL (S2_DES_PAGE | S2_AF | S2_SH_INNER | S2_MEMATTR_NORMAL_WB)

注意:这就是之前踩过的坑(LEARNING_PLAN.md 中 Stage2 permission fault 修复)——
Stage2 缺 RW 权限时 default 分支给 S2_AP_NON(无权限),guest 访问直接 fault。

4.5 分级索引

1
2
3
4
#define S2_PGD_SHIFT	39   /* Level0 → 512G */
#define S2_PUD_SHIFT 30 /* Level1 → 1G */
#define S2_PMD_SHIFT 21 /* Level2 → 2M */
#define S2_PTE_SHIFT 12 /* Level3 → 4K */

40bit IPA 用 4 级表(STAGE2_PGTABLE_LEVELS=3 表示从 PUD 开始,实际 walk 3 级)。

4.6 要点

  1. 两套翻译串行:guest VA –Stage1–> IPA –Stage2–> PA,都由硬件自动完成
  2. VMID 隔离是硬件级的:不同 VM 的同地址不会互相串扰(TLB 按 VMID 区分)
  3. Stage2 用 S2 自己的描述符格式,内存属性直接内嵌(不查 MAIR),AP 编码与 Stage1 不同
  4. generate_vtcr_el2 由 hypervisor 统一生成,所有 VM 用相同的 IPA 布局

5. 异常向量表与调用关系图

5.1 概念

ARMv8-A 的异常向量表是固定布局:基址(VBAR_EL2)+ 偏移。共 4 组 × 4 类型 = 16 入口,每个入口间隔 0x80 字节(128B)。

分组依据两个条件:

  1. 异常来自哪个 EL:当前 EL 还是更低 EL
  2. 使用的栈(仅当前 EL 相关):SP0(执行状态与当前 EL 不同的栈)还是 SPx
偏移 含义
0 0x000 Current EL, SP0
1 0x200 Current EL, SPx
2 0x400 Lower EL, AArch64
3 0x600 Lower EL, AArch32

每组内再按异常类型分 4 个:

类型 偏移
Synchronous(同步异常:DABT/IABT/HVC/SVC…) +0x000
IRQ(普通中断) +0x080
FIQ(快速中断) +0x100
SError(异步系统错误) +0x180

5.2 Minos 代码位置

  • arch/aarch64/core/entry.S:36-84:16 入口布局(elx_vectors
  • arch/aarch64/core/vector.S:各入口的具体处理(寄存器保存/跳 C handler)
  • arch/aarch64/core/boot.S:173-174msr VBAR_EL2, elx_vectors 设置向量表基址

5.3 精读 entry.S(向量表布局)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	.global elx_vectors
.section __el2_vectors, "ax"
.align 12 /* 2^12 = 4096 = 0x1000 字节对齐 */

elx_vectors:
c0sync: // Current EL with SP0
BAD_MODE VECTOR_C0_SYNC /* 当前EL+SP0 同步异常 → bad_mode */
.balign 0x80
c0irq:
BAD_MODE VECTOR_C0_IRQ
.balign 0x80
...
cxsync:
b __sync_exception_from_current_el /* 当前EL+SPx 同步异常 */
.balign 0x80
cxirq:
b __irq_exception_from_current_el /* 当前EL+SPx IRQ */
.balign 0x80
...
l64sync:
b __sync_exception_from_lower_el /* 更低EL(AArch64) 同步异常 */
.balign 0x80
l64irq:
b __irq_exception_from_lower_el
...

要点:

  • BAD_MODE 宏(entry.S:30-34)把 mode 号放 x29 后跳 __bad_modebad_mode() panic
  • 虚拟化相关的真正处理在:cxsync/cxirq/l64sync/l64irq/l32sync/l32irq

5.4 vector.S 的关键宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.macro __SAVE_GP_REGS          /* 异常入口:把 31 个通用寄存器 + SP0/SPSR/ELR 压栈 */
stp x27, x28, [sp, #-16]!
... /* 逐个压栈,共 33 个 64bit 值 */
mrs x0, SP_EL0
str x0, [sp, #-8]!
mrs x0, ARM64_SPSR
str x0, [sp, #-8]!
mrs x0, ARM64_ELR
str x0, [sp, #-8]!
dsb nsh
.endm

.macro LOAD_GP_REGS /* 返回前恢复(顺序与保存相反) */
ldr x0, [sp], #8
msr ARM64_ELR, x0
ldr x0, [sp], #8
msr ARM64_SPSR, x0
ldr x0, [sp], #8
msr SP_EL0, x0
...
.endm

这个压栈布局就是 gp_regs 结构include/minos/gp_regs.h),C 侧 handler 拿到的参数。

5.5 ★ 异常调用关系图(Phase 1 验证目标)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
                     guest EL1 / minos EL1 发生异常


VBAR_EL2 → entry.S elx_vectors(16 入口)

┌──────────────┬──────────────┼──────────────┬──────────────┐
│ │ │ │ │
C0 组(SP0) CX 组(SPx) L64 组(lower64) L32 组(lower32) (FIQ/SERR → bad_mode)
sync/irq sync/irq sync/irq sync/irq
│ │ │ │
▼ ▼ ▼ ▼
BAD_MODE __sync_exception __sync_exception __sync_exception
(→panic) _from_current_el _from_lower_el _from_lower_el
│ │ │ │
│ │ ┌───────┼────────┐ │
│ │ │ │ │ │
│ SVC64? task_exit_from_user task_exit_from_user
│ / \ │ │
│ 调度路径 sync_exception_ sync_exception_
│ from_current_el() from_lower_el()
│ │ │
│ ▼ ┌──┴────────────┐
│ handle_sync_exception() │ vCPU 任务? │
│ │ │ │
│ process_sync_descs[EC] ▼ ▼
│ │ handle_vcpu_ handle_sync_
│ ▼ sync_exception() exception()
│ handler(regs,ec,esr) │
│ │ guest_sync_descs[EC]
│ ▼ │
│ (当前EL异常, ▼
│ 内核自己的异常) handler(regs,ec,esr)
│ │
│ ┌─────────────────────┤
│ │ │
▼ ▼ ▼
__bad_mode irq_from_current_el irq_from_lower_el
│ │ │
▼ ▼ ▼
bad_mode() exception_return ←────────┴───────┐
│ │ │
▼ ▼ ▼
panic() exception_return_handler() LOAD_GP_REGS + eret
│ (回到 guest 继续执行)

调度 / 返回 guest

C 侧 handler 对照表(trap.c / aarch64_sync.c)

EC(异常类) handler(guest 路径,trap.c:344-365) handler(当前EL路径,aarch64_sync.c:112-114)
WFx (WFI/WFE) wfi_wfe_handlervcpu_idle
HVC64/HVC32 aarch64_hypercall_handler SVC64 → 调度
SMC64/SMC32 aarch64_smccall_handler
SYS64 (MSR/MRS) access_system_reg_handler(模拟系统寄存器)
DABT_LOW dataabort_tfl_handler(MMIO 模拟入口) kernel_mem_fault
IABT_LOW insabort_tfl_handler kernel_mem_fault
其他 unknown_handler unknown_trap_handler

5.6 要点

  1. 向量表布局是硬性规定:必须 0x1000 对齐,16 入口各 0x80 间隔,不能错位
  2. 异常进入时由汇编负责保存全部寄存器(gp_regs),C 只负责处理,返回时汇编恢复
  3. guest 的同步异常走 handle_vcpu_sync_exception()(trap.c:392),
    按 EC 查表分发给 22 个 handler——这就是 Phase 3 的主角
  4. dataabort_tfl_handler(trap.c:241)是设备模拟(MMIO)的唯一入口
    guest 访问模拟设备地址 → Stage2 fault → 这里 → vdev_mmio_emulation

6. GICv3 中断虚拟化

6.1 概念

GIC(Generic Interrupt Controller)v3 是 ARM 标准中断控制器。核心概念:

概念 说明
GICD Distributor(分发器),全局控制 SPI 等
GICR Redistributor(重分发器),per-CPU,控制 PPI/SGI,含 GICR_SGI(SGI 触发)
*ICC_(CPU 接口) 系统寄存器(ICC_IAR1/EOIR1/SGI1R…),per-CPU
IRQ 类型 SGI(0-15) 核间中断 / PPI(16-31) 私有外设中断 / SPI(32+) 共享外设中断
LR(List Register) ICH_LR0-15:硬件把”要注入给 guest 的中断”放这里,guest 直接响应
优先级 数值越小优先级越高,GICD/GICR_IPRIORITYR 配置

6.2 Minos 代码位置

物理 GIC(hypervisor 自己用的):

  • drivers/irq-chips/gicv3.c(548 行)
    • gicv3_init:GICD/GICR 初始化
    • gicv3_read_irq(70):ICC_IAR1_EL1 读中断号
    • gicv3_eoi_irq(58):ICC_EOIR1_EL1 结束中断
    • gicv3_set_irq_priority(129):GICD/GICR_IPRIORITYR
    • gicv3_set_irq_affinity(143):GICD_IROUTER
    • __gicv3_send_sgi_list(157):ICC_SGI1R 发 SGI

虚拟 GIC(给 guest 的):

  • virt/virq_chips/vgicv3.c(1075 行)
    • gicv3_send_virq:把 virq 写入 ICH_LR(硬件注入路径)
    • vgic_mmio_handler(416):GICD/GICR 的 MMIO 读写模拟(软件路径)
    • gicv3_state_save/restore:切换时保存/恢复 LRs + APR + VMCR

6.3 两种虚拟化路径(Phase 5 的预习)

路径 A:硬件注入(快)

1
2
host 收到物理 IRQ → gicv3_send_virq() 把 vINTID 写入 ICH_LR
→ guest 进入时硬件自动把 LR 中断呈现给 guest → guest 的 GIC 驱动直接响应

路径 B:软件模拟(MMIO trap)

1
2
guest 访问 GICD_* / GICR_* 地址 → Stage2 fault → dataabort_tfl_handler
→ vdev_mmio_emulation → vgic_mmio_handler() 模拟寄存器读写

vgicv3.c:221-283 里能看到 GICD_ISENABLER/ICENABLER/IPRIORITYR/ICFGR 等的模拟。

6.4 要点

  1. LR 是硬件虚拟化的核心:Hypervisor 把中断”排队”进 LR,硬件替它注入给 guest
  2. GIC 虚拟化 = 物理中断转发(LR)+ 控制寄存器模拟(MMIO)两条腿
  3. vGIC 是 vmodule 之一:进出 guest 时 state_save/restore 保存 LR/APR/VMCR(Phase 7)
  4. 物理 GIC(hypervisor 自己用)和虚拟 GIC(guest 用)是两套独立实现

7. Generic Timer 定时器虚拟化

7.1 概念

ARM generic timer 提供:

  • CNTP:物理计数器/定时器(EL1 可见,hypervisor 自己的时间基准)
  • CNTV:虚拟计数器/定时器(guest 用的”虚拟时间”)

虚拟化关键机制:

  • CNTVOFF_EL2:虚拟时间偏移。CNTVCT = 物理时间 - CNTVOFF
    Hypervisor 可通过改 CNTVOFF 给每个 guest 制造不同的虚拟时间
  • CNTV_CVAL_EL0:比较值。CNTVCT >= CNTV_CVAL 时触发虚拟定时器中断
  • CNTV_CTL_EL0:控制(ENABLE / IMASK / ISTATUS)
  • trap:guest 访问 CNTP 系列寄存器被 trap 到 EL2,由 hypervisor 用软件 timer 模拟

7.2 Minos 代码位置

arch/aarch64/virt/vtimer.c(341 行):

  • vtimer_state_restore(97):写 CNTVOFF_EL2(104) + CNTV_CVAL_EL0(105) + CNTV_CTL_EL0(106)
  • vtimer_state_save(110):读 CNTV_CVAL/CTL 存到 context,然后清零 CNTV_CTL
  • phys_timer_expire_function(73):物理定时器到期 → send_virq_to_vcpu
  • virt_timer_expire_function(84):虚拟定时器到期 → wake() 唤醒 vCPU
  • 虚拟定时器通过 core/timer.c 的软件定时器机制模拟

7.3 精读 vtimer.c:97-120

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void vtimer_state_restore(struct vcpu *vcpu, void *context)
{
struct vtimer_context *c = (struct vtimer_context *)context;
struct vtimer *vtimer = &c->virt_timer;

stop_timer(&vtimer->timer);

write_sysreg64(c->offset, ARM64_CNTVOFF_EL2); // 设置虚拟时间偏移
write_sysreg64(vtimer->cnt_cval, ARM64_CNTV_CVAL_EL0); // 恢复比较值
write_sysreg32(vtimer->cnt_ctl, ARM64_CNTV_CTL_EL0); // 恢复控制
isb();
}

static void vtimer_state_save(struct vcpu *vcpu, void *context)
{
vtimer->cnt_cval = read_sysreg64(ARM64_CNTV_CVAL_EL0);
vtimer->cnt_ctl = read_sysreg32(ARM64_CNTV_CTL_EL0);
write_sysreg32(0, CNTV_CTL_EL0); // 关掉定时器,防止切走后被误触发
isb();
}

7.4 要点

  1. **CNTVOFF 实现”虚拟时间”**:给 guest 一个可偏移的时间视图(可以加速/倒流)
  2. vtimer 是 vmodule:进出 guest 时保存/恢复 CNTV_CVAL/CTL/CNTVOFF
  3. 物理定时器(CNTP)被 hypervisor 自己占用;guest 的虚拟定时器用软件 timer 模拟
  4. 虚拟定时器到期走 virt_timer_expire_functionwake(),等 vCPU 被调度回来
    再通过硬件 CNTV 重新触发,避免在 EL2 里直接注入

8. Barrier / TLB / Cache

8.1 概念

内存屏障(Memory Barrier)解决多核/内存系统的顺序问题:

指令 全称 作用
DSB Data Synchronization Barrier 等所有之前的访存/维护指令完成(最严格)
DMB Data Memory Barrier 保证之前访存按序,但不等待完成
ISB Instruction Synchronization Barrier 刷流水线,保证之后指令能看到之前的系统寄存器/页表修改

TLB(Translation Lookaside Buffer):地址翻译缓存。修改页表后必须失效对应 TLB,
否则旧映射残留。

Cache 维护指令:ic(指令缓存)、dc(数据缓存),配合 set/way 或 by-VA 操作。

8.2 Minos 代码位置

位置 内容
arch/aarch64/include/asm/barrier.h isb/dmb/dsb 及全套 mb/rmb/wmb/smp_* 变体
arch/aarch64/core/boot.S:181-191 inv_dcache_all + tlbi alle2/vmalle1
arch/aarch64/core/cache.S flush_dcache_range / inv_dcache_range / cache 维护宏
arch/aarch64/core/boot.S:318-320 ic ialluis(开 MMU 后失效 icache)

8.3 barrier.h 精读

1
2
3
4
5
6
7
8
9
10
11
#define __isb()	asm volatile("isb" : : : "memory")
#define __dmb(opt) asm volatile("dmb " #opt : : : "memory")
#define __dsb(opt) asm volatile("dsb " #opt : : : "memory")

#define mb() __dsb(sy)
#define rmb() __dsb(ld)
#define wmb() __dsb(st)

#define smp_mb() __dmb(ish) /* inner-shareable */
#define smp_rmb() __dmb(ishld)
#define smp_wmb() __dmb(ishst)

选项说明:

  • sy:全系统(full system)
  • ld/st:只等读/只等写
  • ish/ishld/ishst:inner-shareable(多核一致域内)

8.4 要点

  1. 修改系统寄存器/页表后必须 isb/dsb:boot.S 里几乎每个 msr 后面都跟 dsb + isb
  2. TLB 失效范围:tlbi alle2(EL2 全部)/ vmalle1(EL1 全部)/ 按 VMID 失效
  3. guest 修改自己的 Stage1 页表,由 guest 自己管 TLB;Hypervisor 改 Stage2 才需要刷
  4. 共享内存通信(vmbox ring)依赖 mb()/wmb()/rmb() 保证双端可见性

9. Phase 1 验证清单

对应 LEARNING_PLAN.md Phase 1 checklist。

  • EL0-EL3、异常模型、SPSR/ELR/ESR —— 见本笔记第 1 节
  • HCR_EL2 各位作用 —— 见第 2 节(arch_virt.c:187 注释)
  • 地址翻译:TTBR/TCR/MAIR、Stage1 4 级 —— 见第 3 节
  • Stage2:VTTBR_EL2、VTCR_EL2、VMID、IPA —— 见第 4 节
  • 异常向量表布局(4 组 × 4 类型)—— 见第 5 节
  • 画出”异常向量表 → 处理函数”调用关系图 —— 见第 5.5 节
  • GICv3:GICD/GICR/LR/优先级 —— 见第 6 节
  • ARM generic timer:CNTVOFF/CNTV_CTL/CVAL —— 见第 7 节
  • barrier/TLB/cache 指令 —— 见第 8 节

动手建议(可加深理解)

  1. 用 gdb 连 QEMU,在 handle_vcpu_sync_exception()(trap.c:392)打断点,
    观察 guest 触发一个 HVC 后栈上的 gp_regs
  2. 在 guest 里跑 sleep 1,在 vtimer_state_restore(vtimer.c:97)打断点,
    观察 CNTVOFF 的值
  3. 阅读 boot.S 时对照 asm-offset.c 理解 PCPU_* 偏移量来源

附:核心寄存器速查表

寄存器 一句话作用 精读位置
SPSR_EL2 保存被中断的 PSTATE boot.S:149, vector.S:73
ELR_EL2 异常返回地址 boot.S:148, vector.S:75
ESR_EL2 异常原因(EC 字段=分发钥匙) aarch64_sync.c:137, trap.c:403
VBAR_EL2 异常向量表基址 boot.S:174, entry.S
HCR_EL2 虚拟化总开关(trap 清单) arch_virt.c:187
TTBR0/1_EL2 Stage1 页表基址 boot.S:201-202
MAIR_EL2 内存属性表 boot.S:221
TCR_EL2 Stage1 翻译控制 boot.S:237
VTTBR_EL2 Stage2 页表基址 + VMID arch_virt.c:151
VTCR_EL2 Stage2 翻译控制(IPA 宽度/粒度) arch_virt.c:131
CNTVOFF_EL2 虚拟时间偏移 vtimer.c:104
ICH_LR0-15 注入给 guest 的中断 vgicv3.c:583
ICC_IAR1/EOIR1 CPU 接口:ack/eoi gicv3.c:58,70