# ArceOS + AxVisor 虚拟化技术指南

如需从环境搭建开始，按完整课程顺序执行，请进入 [HANDBOOK.md](HANDBOOK.md)。

> 本指南以 ARMv8-A/AArch64 为主，用于配合 16 周学习计划。AxVisor 和 ArceOS 的目录、符号与配置格式可能随版本变化，因此应理解职责和调用关系，并根据当前源码重新定位具体函数。

## 1. 系统目标与边界

最终系统由三层组成：

1. AxVisor 在 EL2 控制真实 CPU、内存、中断和设备。
2. Linux 与 RTOS 分别作为独立 VM 的客体内核运行。
3. Linux 启动完成后，由用户态 `init` 自动拉起至少两个程序；RTOS 启动调度器并运行两个实时任务。

AxVisor 并不直接运行 Linux 用户程序。正确路径是：

```text
AxVisor 创建 Linux VM
  → vCPU 进入 Linux 内核
  → Linux 建立页表、驱动和根文件系统
  → Linux 执行 /init
  → /init 启动 app-a 和 app-b
```

## 2. ARM 异常级和虚拟化扩展

### 2.1 异常级

| 异常级 | 本项目中的典型软件 | 主要职责 |
|---|---|---|
| EL2 | AxVisor | 管理 VM、Stage-2、中断和客体陷入 |
| EL1 | Linux/RTOS 客体内核 | 管理客体自身的内存、进程和驱动 |
| EL0 | Linux 用户程序 | 通过系统调用使用 Linux 服务 |

Hypervisor 使用 EL2 寄存器决定哪些 EL1 行为可以直接执行，哪些行为需要陷入 EL2。一次 VM Exit 通常意味着客体触发了同步异常、中断、HVC、系统寄存器陷入或 Stage-2 fault，CPU 把控制权交给 EL2 异常向量。

### 2.2 关键寄存器

- `HCR_EL2`：控制虚拟化与陷入策略，包含 Stage-2 使能等关键配置。
- `VTTBR_EL2`：保存当前 VM 的 Stage-2 页表根和 VMID。
- `VTCR_EL2`：描述 Stage-2 地址宽度、页粒度和页表层级。
- `ESR_EL2`：记录异常类别 EC 和原因 ISS。
- `ELR_EL2`：客体发生异常时的 PC，也是处理完成后的返回位置。
- `SPSR_EL2`：保存异常发生前的处理器状态。
- `FAR_EL2`：地址异常相关的虚拟地址。
- `HPFAR_EL2`：Stage-2 fault 相关的中间物理地址信息。

调试 VM Exit 时建议至少输出：

```text
vm_id, vcpu_id, ESR_EL2, ELR_EL2, SPSR_EL2, FAR_EL2, HPFAR_EL2
```

## 3. 完整启动路径

### 3.1 AxVisor 自身启动

1. CPU 从复位向量或固件指定地址开始执行。
2. 固件准备 DRAM、设备树等信息，并把控制权交给 AxVisor。
3. 入口汇编建立启动栈，清零 BSS，保存启动参数。
4. 建立 Hypervisor 自身页表，配置 MMU 与缓存。
5. 初始化串口、堆、页分配器、per-CPU 数据、中断控制器和定时器。
6. 唤醒或初始化次级 CPU。
7. 解析 VM 配置并创建客体。

早期启动排查必须记录：

```text
CurrentEL
PC / SP
镜像链接地址与实际装载地址
BSS 与栈范围
UART 基址
MMU 开启前后的地址关系
```

### 3.2 VM 创建

创建 VM 通常需要完成：

- 分配 VM ID。
- 建立 VM 对象、设备表和 vCPU 集合。
- 检查分配给 VM 的物理 CPU、内存与中断是否冲突。
- 为客体建立 Stage-2 页表。
- 把内核、DTB、initramfs 或 RTOS 镜像装入指定 IPA。
- 设置 vCPU 初始 PC、PSTATE 和启动参数。

### 3.3 首次进入客体

概念流程如下：

```text
选择 vCPU
  → 写入该 VM 的 VTTBR_EL2
  → 恢复客体通用寄存器和系统寄存器
  → 设置 ELR_EL2 为客体入口
  → 设置 SPSR_EL2 为客体初始状态
  → ERET 进入客体 EL1
```

## 4. Stage-2 地址转换

### 4.1 两阶段转换

客体使用自己的 Stage-1 页表完成：

```text
Guest Virtual Address (VA) → Intermediate Physical Address (IPA)
```

Hypervisor 使用 Stage-2 页表完成：

```text
Intermediate Physical Address (IPA) → Real Physical Address (PA)
```

最终过程为：

```text
VA → [Guest Stage-1] → IPA → [AxVisor Stage-2] → PA
```

两个 VM 可以拥有相同 IPA，例如都认为 RAM 从 `0x4000_0000` 开始，因为它们使用不同 Stage-2 地址空间。但它们的最终 PA 不得重叠。

### 4.2 映射属性

建立映射时必须检查：

- IPA、PA 与长度是否页对齐。
- RAM 与 MMIO 是否使用正确的 Normal/Device 内存类型。
- 代码、只读数据、可写数据的权限是否合理。
- 不同 VM 的最终 PA 是否重叠。
- 修改映射后是否执行所需屏障与 TLB invalidation。

### 4.3 Stage-2 fault

客体访问未映射或权限不允许的 IPA 时，会触发 Stage-2 fault。排查顺序：

1. 解码 `ESR_EL2` 的 EC 和 ISS。
2. 读取 `ELR_EL2` 确定触发访问的指令。
3. 结合 `FAR_EL2`、`HPFAR_EL2` 恢复故障 IPA。
4. 检查该 IPA 应属于 RAM、镜像、DTB 还是 MMIO。
5. 查找 VM region 配置和页表项。
6. 判断是漏映射、权限错误、属性错误还是客体访问了错误地址。

## 5. vCPU 和 VM Exit

### 5.1 vCPU 状态

一份可恢复的 vCPU 上下文通常包含：

- 通用寄存器 `x0–x30`。
- PC、SP、PSTATE。
- EL1 地址空间相关寄存器，如 TTBR、TCR、MAIR、SCTLR。
- 异常相关状态，如 VBAR、ESR、FAR、SPSR。
- 虚拟计时器状态。
- 虚拟 GIC 或其他中断控制器状态。

### 5.2 运行循环

```text
安装 VM 的 Stage-2
  → 恢复 vCPU 上下文
  → 进入客体
  → 客体发生 Exit
  → 保存 vCPU 上下文
  → 解码 Exit
  → 模拟设备、处理 HVC 或报告错误
  → 调整返回 PC/寄存器
  → 再次进入客体
```

不是所有 Exit 都应推进 PC。例如某些故障需要重新执行原指令，而已完成模拟的 MMIO/系统寄存器访问通常需要跳过已模拟指令。必须根据异常类别处理，不能统一 `PC += 4`。

## 6. 中断与设备虚拟化

### 6.1 常见方式

- Trap-and-emulate：客体 MMIO 触发陷入，Hypervisor 模拟设备寄存器。
- 半虚拟化：客体使用 virtio 或 hypercall 等约定接口。
- 设备直通：设备和 IRQ 固定分配给一个 VM。
- 静态分区：不同 VM 拥有不重叠的设备集合。

### 6.2 虚拟中断路径

以 RTOS 的系统 tick 为例：

```text
计时器到期
  → 物理中断或虚拟计时器事件
  → AxVisor/GIC 确定目标 VM 与 vCPU
  → 设置 pending vIRQ
  → vCPU 运行时注入
  → 客体 EL1 中断向量
  → RTOS tick handler
  → 调度器决定是否切换任务
```

RTOS 能输出启动日志但任务不切换时，应检查计时器频率、比较值、IRQ 号、GIC 使能、目标 vCPU 和 EOI/ack 流程。

## 7. ArceOS 技术结构

ArceOS 通过组件组合提供内核能力。常见组件职责如下：

| 组件 | 典型职责 |
|---|---|
| `axhal` | 架构和平台硬件抽象、入口、页表、中断与计时器 |
| `axruntime` | 组织系统启动和组件初始化 |
| `axalloc` | 页分配与堆分配 |
| `axtask` | 任务、调度、同步和 per-CPU 运行状态 |
| `axdriver` | 设备探测和驱动框架 |
| `axfs` | 文件系统抽象 |
| `axnet` | 网络栈接口 |
| `axstd` | 面向应用的类标准库接口 |

推荐源码阅读顺序：

1. 从构建命令确认架构、平台和 feature。
2. 找到链接脚本、入口符号和镜像地址。
3. 追踪第一条日志之前的调用路径。
4. 找到内存、驱动、任务调度器初始化的位置。
5. 从一个官方示例反向追踪它使用的组件。
6. 修改一个组件或 feature，观察镜像、日志和行为差异。

## 8. AxVisor 技术结构

应沿一台 VM 的生命周期建立源码索引：

```text
VM 配置
  → VM 对象
  → 内存区域与设备
  → 客体镜像装载
  → Stage-2
  → vCPU 对象
  → 架构上下文
  → 首次运行
  → Exit 分发
```

建议定位以下职责：

- AxVisor 主入口和 per-CPU 初始化。
- VM ID 与 VM 配置结构。
- VM 内存 region 管理。
- Guest image/DTB 装载。
- Stage-2 页表实现。
- vCPU 创建、进入和上下文切换。
- EL2 异常向量与 Exit 解码。
- MMIO 区域、虚拟设备和 IRQ 注入。

第一项代码修改建议是增加结构化日志：

```text
[vm-create] vm=0 ipa=0x40000000 pa=0x90000000 size=512M
[vcpu-enter] vm=0 vcpu=0 pc=0x40200000
[vm-exit] vm=0 vcpu=0 ec=0x24 elr=... far=... hpfar=...
[vcpu-resume] vm=0 vcpu=0 next_pc=...
```

## 9. Linux 客体启动

### 9.1 所需文件

- AArch64 Linux `Image`。
- 与虚拟硬件一致的 DTB。
- 可选 initramfs。
- 合适的内核命令行。

### 9.2 启动契约

对 ARM64 Linux，通常需要：

- 内核镜像被装载到符合 Linux boot protocol 的地址。
- vCPU 入口 PC 指向正确内核入口。
- `x0` 保存 DTB 的 IPA，其他启动寄存器按协议初始化。
- DTB 位于有效且可读的 Guest IPA 区域。
- 客体看到的 CPU、RAM、UART、Timer 与 GIC 描述正确。

具体要求应对照当前 Linux 内核的 `Documentation/arch/arm64/booting.rst`。

### 9.3 initramfs 与两个程序

学习阶段建议 App A、App B 使用静态链接，减少动态库问题。概念性 `/init`：

```sh
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys

/bin/app-a &
/bin/app-b &

echo "[PASS] linux apps started"
exec /bin/sh
```

常见故障：

- 完全无日志：检查 `earlycon`、UART DTB 节点和 MMIO 映射。
- 解压后卡住：检查 Image 地址、DTB、CPU 模式和内存区域。
- 找不到 rootfs：检查 initramfs 地址和 bootargs。
- `No working init found`：检查 `/init` 是否存在、可执行、架构匹配以及动态库。

## 10. RTOS 客体启动

RTOS 通常需要匹配：

- ELF/raw image 的链接地址、装载地址和入口地址。
- 栈、BSS、堆和异常向量的内存布局。
- UART 基址。
- 计时器频率。
- 中断控制器类型和 IRQ 号。

推荐步骤：

1. 优先运行 AxVisor 官方已经支持的 RTOS/unikernel 示例。
2. 只启动一个 vCPU。
3. 先获得第一条串口日志。
4. 再初始化 timer 和 GIC。
5. 创建两个不同周期的任务。
6. 最后再尝试 SMP、网络或复杂设备。

RT-Thread、Zephyr 或 ArceOS RT 负载的选择必须以当前 AxVisor 和目标平台的支持情况为准。

## 11. 多客体资源隔离

学习阶段建议静态资源分区：

| 资源 | Linux VM 示例 | RTOS VM 示例 |
|---|---|---|
| pCPU | CPU 0–1 | CPU 2 |
| PA RAM | 0x9000_0000 / 512 MiB | 0xB000_0000 / 64 MiB |
| Guest IPA RAM | 0x4000_0000 | 0x4000_0000 |
| 设备 | 虚拟 UART、块设备 | Timer、独立或复用 UART |

示例地址只用于说明，必须根据平台内存图调整。

验证隔离性：

1. 输出所有 VM 的 pCPU、PA region、IRQ 和设备归属。
2. 自动检查 PA region 是否相交。
3. 让 Linux 主动 panic，观察 RTOS 是否继续运行。
4. 让 RTOS 访问未映射地址，观察 Linux 是否继续运行。
5. 保存 Hypervisor 对故障 VM 的处理日志。

## 12. 分层调试方法

| 现象 | 优先检查 |
|---|---|
| AxVisor 无输出 | 入口 PC、CurrentEL、SP、UART、镜像地址 |
| VM 创建后卡住 | Guest entry、镜像地址、Stage-2、vCPU 初始状态 |
| Stage-2 fault | ESR/FAR/HPFAR、IPA region、权限和内存类型 |
| Linux 无串口 | DTB UART、earlycon、MMIO 模拟、IRQ |
| Linux 无法执行 init | initramfs、`/init` 权限、程序架构和动态库 |
| RTOS 无 tick | Timer、IRQ 路由、GIC、vCPU 注入状态 |
| 客体互相影响 | PA/IRQ/设备重叠、错误的 VMID/VTTBR 切换 |

调试原则：

- 只追踪最后一个成功边界之后的代码。
- 每次只改变一个变量。
- 保存完整命令、配置、commit 和原始日志。
- 在日志中始终包含 VM ID 和 vCPU ID。
- 先验证地址与寄存器，再怀疑复杂调度算法。

## 13. 必做编码实验

### 实验 1：异常解码器

读取 `ESR_EL2`，输出 EC/ISS、ELR、FAR、HPFAR，并区分 HVC、系统寄存器、Instruction Abort 与 Data Abort。

### 实验 2：Stage-2 region 检查器

遍历所有 VM 的内存区域，检查页对齐、PA 重叠、权限和内存属性。故意制造重叠时必须拒绝启动。

### 实验 3：vCPU 生命周期日志

为 create、enter、exit、resume、stop 增加结构化日志，能够从日志还原完整运行循环。

### 实验 4：Linux 双应用

编写两个静态 Linux 程序，由 `/init` 自动启动，周期性输出不同心跳并包含 PID。

### 实验 5：RTOS 双任务

创建两个不同周期的任务，输出 tick 和任务 ID，验证运行频率符合预期。

### 实验 6：故障隔离

让一个 VM 主动 panic 或访问未映射 IPA，Hypervisor 应明确报告并停止或重启故障 VM，另一个 VM 必须继续运行。

## 14. 最终技术验收

- 能画出上电到 Linux App A/App B 和 RTOS Task 运行的完整流程。
- 能解释 `HCR_EL2`、`VTTBR_EL2`、`ESR_EL2`、`ELR_EL2` 的作用。
- 能从 Stage-2 fault 日志定位故障 IPA 和对应 VM region。
- 能指出 ArceOS 中平台、运行时、内存、任务和驱动组件的边界。
- 能定位 AxVisor 的 VM 创建、vCPU 进入、异常处理和 IRQ 注入路径。
- Linux 与 RTOS 同时稳定运行 30 分钟。
- Linux 自动启动两个用户程序，RTOS 运行两个周期任务。
- 一个客体故障后，另一个客体继续运行。
- 项目可以通过脚本在干净环境中重新构建并复现。
