Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kvm-arm-lab

ARM64 虚拟化实践教程:从手写 VMM 到 KVM/QEMU 源码精读。

面向具备 Linux 与 C 语言基础、希望深入 ARM64 虚拟化底层的开发者。 教程以「先运行、再溯源」为原则组织:每个概念都对应一个可复现的实验, 每个实验产生的数据都能在源码中找到出处。

English · 学习路线 · 目录结构 · 参考资料

内容

阶段 主题 产出
搭建可运行 KVM 的 ARM64 环境(嵌套虚拟化) docs/01-环境搭建.md
用 QEMU 启动虚拟机并观察其硬件构成 docs/02-QEMU启动虚拟机.md
基于 KVM ioctl API 手写约 200 行 VMM docs/03-手写VMM.md · labs/lab02-tiny-vmm/
精读 KVM 内核源码主干 docs/04-KVM源码.md · notes/
建立 QEMU 用户态侧的系统性认知 docs/05-QEMU源码.md
全景串联:完整描述一次虚拟机启动 notes/day6-终极作业-一次虚拟机启动的全过程.md

零基础读者建议先阅读 docs/00-从零理解虚拟化.md, 其中系统讲解了虚拟化原理、ARM 特权级模型与三层嵌套的成因。

快速开始

git clone https://github.com/<user>/kvm-arm-lab.git
cd kvm-arm-lab
bash scripts/check-env.sh        # 环境自检:/dev/kvm、CPU 特性、依赖工具

环境就绪后,从阶段三的实验入手最能建立直观认识:

cd labs/lab02-tiny-vmm && make && ./tiny_vmm

该程序在约 200 行内完成 VM 创建、内存注册、vCPU 初始化与主循环, 运行后输出客户机通过 MMIO 打印的字符,并统计陷出次数。

参考环境

文档中的实测数据均来自以下环境。不同硬件与内核版本下数值会有差异, 但数量级与相互关系保持一致:

项目 配置
宿主机 Apple Silicon M3 / macOS 15
L1 虚拟机 Lima + vz + 嵌套虚拟化,Ubuntu 24.04
L2 客户机 qemu-system-aarch64 -machine virt -accel kvm
内核源码 Linux v6.8(arch/arm64/kvm/
QEMU 源码 master 分支

嵌套虚拟化依赖 ARMv8.4 的 FEAT_NV2,需要 M3 及以上芯片与 macOS 15+。 M1/M2 无法运行 L2,但阶段一至阶段五的源码阅读部分不受影响。

学习路线

完整路线约 6 周,每周 10–15 小时。各阶段可独立阅读, 但阶段三的手写 VMM 是理解后续源码的基础,建议不要跳过。


0. 核心认知模型

在执行任何命令之前,先建立以下三条认知,后续所有源码分析都是它们的展开:

  1. 客户机的"物理内存",就是 VMM 进程里一段普通的 mmap 匿名内存。 GPA → HVA 的对应关系由用户态通过 KVM_SET_USER_MEMORY_REGION 告诉内核; 真正让硬件自动完成 GPA → HPA 翻译的是 ARM 的 stage-2 页表,由 KVM 按需填充。

  2. "运行虚拟机"就是一个线程在 ioctl(KVM_RUN) 里进进出出。 进去 = 硬件 eret 到客户机;出来 = 客户机干了件自己搞不定的事,陷入 EL2。 整台虚拟机的性能,本质就是「陷出次数 × 每次陷出的代价」。

  3. KVM 只做"必须在内核里做"的事,其余全丢给 QEMU。 CPU 指令、内存翻译、中断控制器(GIC)、定时器 → KVM 内核态。 磁盘、网卡、串口、PCI 拓扑、固件加载 → QEMU 用户态。 两者的边界,就是 KVM_RUN 返回时 kvm_run->exit_reason 那个枚举值。

ARM 虚拟化和 x86 的关键差异(别拿 x86 经验硬套)

维度 x86 (VMX) ARM64
特权级 root/non-root × ring0-3 EL0/EL1/EL2/EL3,Hypervisor 跑在 EL2
进出客户机 vmlaunch/vmresume#VMEXIT eret 进入 / 异常陷入 EL2
二级页表 EPT stage-2 translation(stage-1 由客户机自己管)
陷出信息 VMCS exit reason 字段 ESR_EL2 异常综合寄存器(EC 字段是分类关键)
中断控制器 APIC/APICv GIC(v2/v3/v4),vGIC 在内核里模拟
主机内核位置 始终 ring0 VHE(内核直接跑 EL2,现代主流)和 nVHE(EL1+EL2 stub)
关机/启动协议 ACPI PSCIhvc 调用)

VHE 特别重要:ARMv8.1 引入 VHE 后,Linux 内核直接运行在 EL2, 避免了 nVHE 下每次陷出都要在 EL1/EL2 之间搬运寄存器的巨大开销。 现代 Apple Silicon 上运行的几乎均为 VHE 模式,读源码时优先读 hyp/vhe/ 而不是 hyp/nvhe/


阶段一:搭出一个能跑 KVM 的 ARM64 环境(第 1 周)

1.1 为什么必须先解决环境

qemu-system-aarch64 在 macOS 上能用 HVF 加速,但那是苹果的 Hypervisor.framework, 不是 KVM,无法访问 /dev/kvm,也无法在 macOS 上读 KVM 源码做实验。 所以路线是:macOS → 一台 Linux VM(L1)→ 在里面用 KVM 跑 L2。 这需要嵌套虚拟化,而这正好是 M3+ / macOS 15+ 才有的能力(依赖 ARMv8.4 的 FEAT_NV2)。

1.2 推荐方案:Lima(最省心)

# 在 macOS 上
brew install lima

# 关键:开启嵌套虚拟化 + 使用 Apple Virtualization 框架(vz)
limactl create --name=kvmlab \
  --vm-type=vz \
  --nested-virt \
  --cpus=4 --memory=8 --disk=60 \
  template://ubuntu-24.04

limactl start kvmlab
limactl shell kvmlab

老版本 Lima 没有 --nested-virt 这个别名,等价写法是: limactl create --set '.nestedVirtualization = true' --vm-type=vz ...

进去之后第一件事就是验证

ls -l /dev/kvm          # 必须存在
sudo apt install cpu-checker && kvm-ok

看到 /dev/kvm exists 才算过关。看不到就先别往下走。

1.3 备选方案:UTM

UTM 在使用 Apple Virtualization 后端时,M3+ / macOS 15 上默认开启嵌套虚拟化, 适合喜欢图形界面的人。注意必须选 Apple Virtualization 后端,不能选 QEMU 后端

1.4 兜底方案:ARM 云主机

若本地环境始终无法就绪(或需要一个不受 macOS 更新影响的稳定环境), 用一台 ARM 裸金属/云主机,KVM 是原生的,没有嵌套的任何坑:

  • Oracle Cloud Ampere A1:有长期免费额度,4 核 24G,性价比最高
  • AWS Gravitonc7g.metal 等裸金属实例可直接用 KVM
  • 阿里云倚天 / 华为鲲鹏
  • 物理开发板:树莓派 4/5(内存小,但 KVM 完全可用)、RK3588

建议:本地 Lima 做日常代码阅读和小实验,云主机做内核编译这类重活。

1.5 安装工具链

sudo apt update
sudo apt install -y build-essential git bpftrace trace-cmd \
    qemu-system-arm qemu-utils cloud-image-utils \
    gdb-multiarch libncurses-dev flex bison libssl-dev bc \
    libglib2.0-dev libpixman-1-dev ninja-build python3-venv

跑一下自检脚本确认:

bash scripts/check-env.sh

产出docs/01-环境搭建.md —— 记录环境选型、遇到的问题及其解决过程。


阶段二:用 QEMU 启动第一台虚拟机(第 1–2 周)

2.1 ARM 启动和 x86 完全不同

x86 有 BIOS/CSM 那套历史包袱,从实模式 0xFFFF0 开始。 ARM 没有 BIOS,virt 机型上有两条路:

  • 直接内核启动:QEMU 把 kernel Image + DTB 直接塞进内存,PC 指向内核入口。适合学习。
  • UEFI 启动:加载 EDK2(QEMU_EFI.fd)到 pflash,走标准 UEFI 流程。适合跑发行版。

先玩第一种,因为它没有中间层,最容易看清"谁把什么放到了哪个地址"。

2.2 实验 A:最小化直接内核启动

# 下载一个 ARM64 内核和 initrd(也可以自己编译,见阶段五)
# Ubuntu cloud image 的 kernel/initrd 就够用

qemu-system-aarch64 \
  -machine virt,gic-version=host \
  -cpu host -accel kvm \
  -smp 2 -m 2G -nographic \
  -kernel vmlinuz \
  -initrd initrd.img \
  -append "console=ttyAMA0 rdinit=/bin/sh"

逐个参数问自己为什么(这是本阶段的核心作业):

  • -machine virt:QEMU 虚构的机型,没有任何真实硬件对应。为什么 ARM 需要它而 x86 用 pc
  • gic-version=host:让 vGIC 版本匹配宿主机。改成 gic-version=2 在 GICv3 宿主机上会怎样?
  • -cpu host:把宿主机 CPU 特性直接透传。用 KVM 时能不能写 -cpu cortex-a57?为什么不行?
  • -accel kvm:不加会怎样?(会退化成 TCG 纯软件模拟,慢 10–100 倍,但也能跑
  • console=ttyAMA0:为什么是 ttyAMA0 而不是 ttyS0?(PL011 vs 16550)

2.3 实验 B:对照实验 —— TCG vs KVM

同一个镜像,分别用 -accel tcg-accel kvm 启动,用 time 量启动耗时。

time qemu-system-aarch64 -machine virt -cpu max -accel tcg ... 
time qemu-system-aarch64 -machine virt -cpu host -accel kvm ...

这是理解"硬件辅助虚拟化到底帮了什么"最直观的一课。 TCG 是逐条翻译客户机指令;KVM 是让客户机指令直接在物理 CPU 上跑,只在必要时陷出。

2.4 实验 C:看见虚拟硬件

在客户机里:

cat /proc/cpuinfo          # CPU 型号是什么?和宿主机一样吗?
cat /proc/interrupts       # 中断控制器是 GICv2 还是 GICv3?
ls /sys/firmware/devicetree/base/    # 设备树 —— QEMU 生成的
lspci                      # virtio 设备
dmesg | grep -i virtio

用 QEMU monitor(Ctrl-A c)从宿主机视角看:

(qemu) info qtree      # 完整设备树
(qemu) info mtree      # 内存映射 —— 重点看 GPA 空间怎么划分的
(qemu) info registers  # vCPU 寄存器

对比 info mtree 的输出和客户机里的 /proc/iomem,可以看到同一份内存布局的两个视角。

产出docs/02-QEMU启动虚拟机.md —— 完整命令、参数含义、TCG/KVM 性能对比数据、info mtree 输出分析。


阶段三:手写一个 200 行的 VMM(第 2–3 周)★ 最关键的一步

这是整个学习路线里信息密度最高的一步。 读一万行 QEMU 源码,不如自己写 200 行 VMM 跑起来一次。 做完这一步,再回头阅读 QEMU 的 accel/kvm/kvm-all.c,会发现"啊,它也就是在做这些事"。

代码已经准备好并验证过可以干净编译labs/lab02-tiny-vmm/):

  • tiny_vmm.c —— VMM 主体,带详细中文注释
  • guest.S —— 运行在虚拟机里的裸机固件(打印字符串然后关机)
  • Makefile

3.1 跑起来

cd labs/lab02-tiny-vmm
make
./tiny_vmm guest.bin

预期输出:

[vmm] 1. /dev/kvm 已打开, API version = 12
[vmm] 2. 虚拟机已创建, vm_fd = 4
[vmm] 3. 已加载 68 字节固件到 GPA 0x40000000
[vmm] 4. vCPU 已创建, target = 5
[vmm] 5. 进入客户机...
--------- guest output ---------
Hello from the guest!
--------- guest output ---------
[vmm] 客户机请求关机 (type=2), 共发生 24 次用户态退出

那句 Hello from the guest! 是这样出现的:客户机执行 strb w3, [x1]0x09000000 写一个字节 → 该地址没有 stage-2 映射 → 硬件产生 data abort 陷入 EL2 → KVM 判定为 MMIO、自己处理不了 → KVM_RUN 返回用户态 → VMM 执行 putchar → 再次 KVM_RUN 继续。每一个字符都是一次完整的 VM Exit 往返。

3.2 五个必须理解的 API 步骤

步骤 ioctl 内核入口 干了什么
1 open("/dev/kvm") kvm_chardev_ops 拿到系统级句柄
2 KVM_CREATE_VM kvm_create_vm()kvm_arch_init_vm() 分配 stage-2 页表、VMID
3 KVM_SET_USER_MEMORY_REGION kvm_set_memory_region() 建立 GPA→HVA 的 memslot
4 KVM_CREATE_VCPU + KVM_ARM_VCPU_INIT kvm_arch_vcpu_create() / kvm_vcpu_set_target() 造 vCPU、定 CPU 型号和特性
5 KVM_RUN kvm_arch_vcpu_ioctl_run() 进入客户机,直到陷出

arm64 独有的坑KVM_CREATE_VCPU 之后必须调 KVM_ARM_VCPU_INIT, 否则 KVM_RUN 直接返回 ENOEXEC。x86 没有这一步。 用 KVM_ARM_PREFERRED_TARGET 由内核返回应使用的 target,别硬编码。

3.3 动手改造练习(按难度排序)

  1. 改字符串,重新 make,确认改动生效 —— 建立"我真的在控制这台虚拟机"的信心。
  2. 统计退出次数:代码里已有 exits 计数。字符串多长,退出多少次?关系是什么?
  3. 加一个"寄存器读取":客户机关机后,用 KVM_GET_ONE_REGx0x3 读出来打印。
  4. 实现一个新 MMIO 设备:比如在 0x09001000 放一个"计数器寄存器",客户机读它返回递增值。
  5. 故意制造错误:把 PC 设成一个没映射的地址,看 KVM_EXIT_FAIL_ENTRY 长什么样。
  6. 把 PSCI 关掉(去掉 KVM_ARM_VCPU_PSCI_0_2),观察 hvc 变成什么退出原因。
  7. 进阶:加载真正的 Linux kernel Image,替代当前的最小固件。 这需要自行构造 DTB、按 ARM64 boot protocol 设置 x0 指向 DTB —— 难度陡增,但收获巨大。

3.4 用 trace 观察内核在干什么

# 看 KVM 内核态的所有事件
sudo trace-cmd record -e kvm ./tiny_vmm guest.bin
sudo trace-cmd report | head -50

# 重点关注这几个 tracepoint:
#   kvm_entry / kvm_exit    —— 每次进出客户机
#   kvm_mmio                —— MMIO 访问
#   kvm_arm_setup_debug

kvm_exit 事件里的 ESR_EL2 值是 ARM 虚拟化的核心线索,它的 EC 字段(bit 31:26) 指示陷出原因:0x24 = data abort(MMIO),0x16 = HVC,0x18 = 系统寄存器访问。

产出docs/03-手写VMM.md —— 五步 API 详解、改造实验、trace 输出分析、ESR_EL2 解码笔记。


阶段四:读 KVM 内核源码(第 3–5 周)

📄 详细分日计划见 docs/04-KVM源码.md(4 天路线) 📁 源码阅读笔记(源码精读 + 校订注记)见 notes/ —— Day 1 架构与核心组件、Day 2 KVM_RUN 主循环、Day 3 世界切换与 eret

4.1 获取源码

# 用当前运行的内核版本,别用 master,避免代码和行为对不上
uname -r
git clone --depth=1 --branch v6.12 \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

建议配 VSCode + clangd,或者 make ARCH=arm64 tags 用 vim/ctags。 不建议裸看 GitHub 网页,跳转太痛苦。

4.2 阅读路线(严格按顺序,每一条对应手写 VMM 的一个 ioctl)

所有文件都在 arch/arm64/kvm/,核心是 arm.c

第一遍:跟着 ioctl 走一遍主干

对应实现 读这里 关注
KVM_CREATE_VM arm.c: kvm_arch_init_vm() stage-2 页表初始化 kvm_init_stage2_mmu()
KVM_CREATE_VCPU arm.c: kvm_arch_vcpu_create() vcpu 结构体初始化
KVM_ARM_VCPU_INIT arm.c: kvm_arch_vcpu_ioctl_vcpu_init()reset.c: kvm_reset_vcpu() target/features 怎么落到寄存器上
KVM_SET_USER_MEMORY_REGION mmu.c: kvm_arch_prepare_memory_region() memslot 的建立
KVM_RUN arm.c: kvm_arch_vcpu_ioctl_run() 整个 KVM 最重要的函数

第二遍:精读 kvm_arch_vcpu_ioctl_run() 的主循环

这个函数是一个 while (ret > 0) 循环,每轮做:

kvm_vcpu_load()              加载 vCPU 上下文到物理 CPU
  ↓
检查是否有 pending signal / 需要调度 / 有中断要注入
  ↓
kvm_arm_vcpu_enter_exit()    ← 进入客户机
  ↓ (调 __kvm_vcpu_run,最终 __guest_enter,执行 eret)
  ↓ ......客户机在跑......
  ↓ (异常陷入 EL2,__guest_exit 保存现场并返回)
  ↓
handle_exit()                ← handle_exit.c,根据 ESR_EL2 分发
  ↓
返回 > 0 就继续循环;返回 0 则退出到用户态(ioctl 返回)

第三遍:按主题深入

主题 文件 核心函数
世界切换(最硬核) hyp/vhe/switch.c __kvm_vcpu_run_vhe()__guest_enterhyp/entry.S
陷出分发 handle_exit.c handle_exit()arm_exit_handlers[] 数组
stage-2 缺页 mmu.c kvm_handle_guest_abort()user_mem_abort()
系统寄存器模拟 sys_regs.c kvm_handle_sys_reg()sys_reg_descs[]
vGIC 中断 vgic/vgic*.c vgic_v3_*kvm_vgic_inject_irq()
虚拟定时器 arch_timer.c kvm_timer_vcpu_run()
PSCI(关机流程经由此处) psci.c kvm_psci_call()
嵌套虚拟化(参考环境使用此机制) nested.cemulate-nested.c FEAT_NV2 相关

4.3 关键理解点:__guest_enter 那一刻

hyp/entry.S 里的 __guest_enter 是整个 KVM 的物理奇点:

  1. 把宿主机寄存器压到 host_ctxt
  2. 把客户机寄存器从 guest_ctxt 恢复到物理寄存器
  3. 执行 eret —— 这条指令之后,CPU 就在跑客户机代码了
  4. 客户机出事 → 硬件跳到 EL2 异常向量 → __guest_exit → 反向做 1、2 步

一个绝妙的视角:从内核角度,客户机是被 __guest_enter "调用"的一个函数; 从客户机角度,是它自己被中断了然后 hypervisor 处理完 eret 回来。 同一件事的两种叙述,理解这个对偶性,ARM 虚拟化就通了。

4.4 用 ftrace 把源码"跑给自己看"

静态读代码容易迷路,配合动态追踪效果翻倍:

# 跟踪 kvm_arch_vcpu_ioctl_run 的完整调用图,深度 3 层
sudo trace-cmd record -p function_graph \
  -g kvm_arch_vcpu_ioctl_run --max-graph-depth 3 \
  ./tiny_vmm guest.bin
sudo trace-cmd report | less

可直接观察到函数调用顺序,与静态阅读的推断是否一致一目了然。

# 或者用 bpftrace 统计各类陷出的频率
sudo bpftrace -e 'tracepoint:kvm:kvm_exit { @[args->esr_ec] = count(); }'

产出docs/04-KVM源码.md —— 用自己的语言画出 KVM_RUN 的完整流程图, 标注每个函数的文件:行号,附上 ftrace 实际调用链做印证。


阶段五:理解 QEMU —— 用户态那一半(宏观,约 3 天)

📄 详细计划见 docs/05-QEMU源码.md(3 天路线) —— Day 1 职责分界、Day 2 客户机的硬件从哪来、Day 3 为什么虚拟机不慢

定位说明:本阶段面向以 KVM 为主要方向的读者, 所以这一阶段只建立心智模型,不深入源码,也不需要编译 QEMU。 用系统自带的 qemu-system-aarch64 就能做完全部实验。

三天回答三个问题

  1. QEMU 承担什么? 分界线在 handle_exit 的返回值: >0 = KVM 自己搞定,=0 = 交给 QEMU

  2. 客户机的"硬件"从哪来? QEMU 生成设备树塞进客户机内存, 内核启动时解析 —— ARM 没有 x86 那种固定端口约定,必须有人明说

  3. 为什么虚拟机不慢? 三条 IO 路径,就是性能优化的全部套路:

    设备 路径 出内核 出用户态
    arch_timer vGIC 硬件注入
    uart-pl011 MMIO 陷出
    virtio 共享内存 + irqfd

三个零编译实验(全部用现象代替读源码):

# ① 看见 vCPU 线程 —— 它就是 while(1){ioctl(KVM_RUN)} 所在
ps -T -p $(pgrep -f qemu-system-aarch64) -o spid,comm,pcpu
#   预期看到 "CPU 0/KVM"、"CPU 1/KVM"

# ② 看见地址空间树 —— 在 QEMU monitor 里(Ctrl-A 松开再按 C 切过去)
(qemu) info mtree
#   和客户机 /proc/iomem 逐行对照,地址完全一致

# ③ 看见设备树 —— 客户机"知道有什么硬件"的原因
qemu-system-aarch64 -machine virt,gic-version=host,dumpdtb=/tmp/virt.dtb \
  -cpu host -accel kvm -smp 2 -m 2G -nographic
dtc -I dtb -O dts /tmp/virt.dtb -o /tmp/virt.dts
grep -A 8 "pl011@9000000" /tmp/virt.dts
#   reg = 0x9000000 → 对上阶段二的 /proc/iomem
#   interrupts SPI 1 → 32+1=33 → 对上阶段二的 /proc/interrupts

产出notes/day6-QEMU宏观理解.md —— 职责分界图、一个字符的完整旅程 (从 printk 到宿主机终端,覆盖三次边界穿越)、三条 IO 路径对照表 + 实测数据。

🔧 以后真需要改 QEMU 时,源码级地图没有丢,在 docs/05-QEMU源码.md附录里: 四个入口函数、编译参数、gdb 读法,配套 bash ~/kvm-arm-lab/scripts/qemu-src-map.sh 在本地源码树上生成准确行号。


阶段六:串起全景 + 专题深入(第 5–6 周)

6.1 终极作业:完整描述一次虚拟机启动

已完成notes/day6-终极作业-一次虚拟机启动的全过程.md 12 幕全景 + 名词详解 + 8 个自检问答。这份就是整个学习文档的总纲。

把从 qemu-system-aarch64 敲回车到客户机 shell 出现,中间发生的事 从头到尾讲一遍。分两大阶段,分界点是第一次 ioctl(KVM_RUN)

准备期(只有 QEMU,客户机还不存在)

  1. 解析参数:virt 机型(专为虚拟化设计的假主板)+ KVM 加速 + -cpu host
  2. 创建设备:GIC 必须最早(后面设备要往它上面接中断线)→ 串口 → 时钟 → PCIe → virtio 每个设备做两件独立的事:注册内存区域(QEMU 自己用)+ 写设备树节点(客户机用)
  3. open("/dev/kvm")KVM_CREATE_VM
  4. mmap 2 GB → KVM_SET_USER_MEMORY_REGION只注册 RAM,故意不注册设备地址 —— 这正是 MMIO 能工作的原理
  5. 加载内核(按 Image 头的 text_offset,现代内核落在 2 MiB 对齐处 ≈ 0x40200000, 实测 0x402100000x40080000 是 3.17 以前的老行为)+ 生成 DTB 塞进客户机内存
  6. KVM_CREATE_VCPUKVM_ARM_VCPU_INIT(ARM 独有)→ 设 PC = 内核入口、x0 = DTB 地址(ARM64 boot protocol,全部硬件信息的交接点)

运行期(QEMU + KVM + 客户机三方交替)

  1. 首次 KVM_RUN__guest_entereret → 内核第一条指令 ★ VHE 下这是全程唯一一次 EL 切换
  2. 每访问一个新页 → stage-2 缺页 → user_mem_abort() 填页表(几千次,不出用户态
  3. 初始化 GIC → vGIC 在内核处理,基本不陷出(实测 arch_timer 5333 次全在内核内)
  4. printk → MMIO → 4 次边界穿越 → 宿主机终端(一个字符一次往返,几微秒)
  5. 探测 virtio → 共享内存 + irqfd,中断绕过用户态
  6. init 启动 → shell 提示符

三条 IO 路径(性能优化的全部套路:让操作「少出一层」):

设备 实测 出内核 出用户态
arch_timer 5333
uart-pl011 187
virtio 0

6.2 专题(按实际工作方向挑)

  • 内存虚拟化深挖:stage-2 页表格式、大页(THP/hugetlbfs)、 内存热插拔、脏页跟踪与热迁移(KVM_MEM_LOG_DIRTY_PAGES
  • 中断虚拟化:GICv3 架构、LPI/ITS、GICv4 直接注入、irqfd/ioeventfd
  • 设备直通:VFIO、SMMU(ARM 的 IOMMU)、vfio-pci
  • 性能优化:vhost-net/vhost-user、多队列、CPU 绑核、 用 perf kvm stat 分析陷出热点
  • 安全:pKVM(Protected KVM,Android 在用)、机密计算 CCA/Realm
  • 嵌套虚拟化:本参考环境即为此情形,读 arch/arm64/kvm/nested.c 和 FEAT_NV2 规范

6.3 参与社区(可选但强烈推荐)

订阅 kvmarm@lists.linux.devqemu-devel,看真实的 patch 讨论。 从修文档 typo 开始提第一个 patch,是进入这个领域最好的敲门砖。


必备参考资料

规范文档(权威,遇到不确定就查它)

  • ARM Architecture Reference Manual for A-profile (ARM DDI 0487) —— D 章讲 EL2 和虚拟化
  • ARM Generic Interrupt Controller Architecture Spec (GICv3/v4)
  • ARM Power State Coordination Interface (PSCI) —— 关机所遵循的协议

内核文档(源码树里就有)

  • Documentation/virt/kvm/api.rst ★ —— KVM 用户态 API 完整手册,写 VMM 时常备
  • Documentation/virt/kvm/arm/ —— ARM 特有部分
  • Documentation/arch/arm64/booting.rst —— ARM64 内核启动协议

  • 《深入浅出系统虚拟化》/《QEMU/KVM 源码解析与应用》(中文,适合入门)
  • Hardware and Software Support for Virtualization(Bugnion 等,理论扎实)

在线

  • KVM Forum 历年演讲(YouTube)—— Marc Zyngier(ARM KVM 维护者)的分享必看
  • LWN.net 的 KVM/虚拟化专题文章

目录结构

kvm-arm-lab/
├── README.md / README.en.md
├── docs/                            # 学习文档,按阶段编号
│   ├── 00-从零理解虚拟化.md          # 零基础原理教程(建议先读)
│   ├── 01-环境搭建.md
│   ├── 02-QEMU启动虚拟机.md
│   ├── 03-手写VMM.md
│   ├── 04-KVM源码.md
│   ├── 05-QEMU源码.md
│   └── _template/                   # 学习笔记模板
├── notes/                           # 源码精读笔记 + 校订注记
│   ├── day1-架构与核心组件.md
│   ├── day2-KVM_RUN主循环.md
│   ├── day3-世界切换.md
│   ├── day5-stage2缺页与MMIO.md
│   └── day6-终极作业-一次虚拟机启动的全过程.md
├── labs/
│   └── lab02-tiny-vmm/              # 约 200 行的可运行 VMM
│       ├── tiny_vmm.c
│       ├── guest.S
│       └── Makefile
├── scripts/
│   ├── check-env.sh                 # 环境自检
│   ├── setup-source.sh              # 获取内核与 QEMU 源码
│   ├── kvm-src-map.sh               # 在本地源码树生成 KVM 阅读地图
│   ├── qemu-src-map.sh              # 在本地源码树生成 QEMU 阅读地图
│   ├── run-vm.sh                    # 启动 L2 客户机(KVM / TCG 对照)
│   ├── fix-kvm-perm.sh              # 修复 /dev/kvm 权限
│   └── setup-vscode-mac.sh          # 配置 VSCode 远程开发
└── tools/                           # 离线分发(自解压安装脚本)

docs/notes/ 的分工docs/ 是按阶段推进的学习路径, 说明学什么、如何验证;notes/ 是源码事实的详细记录,用作查阅手册。 笔记末尾的「校订注记」标注了与实际源码不符或仅在 nVHE 下成立的表述。


关于学习方法的三条建议

  1. 永远"先跑再读"。先让东西动起来,看到输出,再去源码里找"刚才那个输出是从哪行打出来的"。 反过来(先通读源码再动手)极易中途放弃。

  2. 每个概念都要有一个能亲手复现的实验对应。 说"stage-2 缺页"较为抽象;跑一次 trace-cmd record -e kvm:* 看到 user_mem_abort 被调用了 3000 次,就具体了。

  3. 文档边学边写,不要攒到最后。每个阶段结束立刻写完对应的 docs/ 文件, 趁记忆新鲜。写不出来的地方,就是尚未真正理解之处 —— 这是最好的自检。

About

No description, website, or topics provided.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages