yvisor 架构总览(2026-07 快照)

本页基于当前代码库(含最新拆分/重构后的 boot/cpu/svm/devices/ 模块)生成,帮助你建立完整的心智模型。

Boot / 启动编排 CPU / SVM 虚拟化核心 Devices / 设备总线 Memory / 内存与页表 Linux Guest 加载 本次新增/拆分的文件

1. 整体分层架构

yvisor 现在是一个清晰的四层结构:启动编排层驱动虚拟化核心层反复执行客户机,退出原因交给设备总线层模拟,客户机内核本身由加载层一次性构建好。

Boot 层 — src/boot/(编排 + 状态机,本次重构的重灾区)
mod.rs
hypervisor_main() 主入口
SVM检测→NPT→ACPI→分页→VMCB→主循环
loop_state.rs
GuestLoopState 结构体
循环间可变状态(计数器/标志位)
tick.rs
tick_pit / check_rx
service_virtio / maybe_log_diag
inject.rs
inject_pending(中断注入)
handle_hlt / handle_exception
guest.rs
load_guest_kernel
ELF→bzImage→raw 三态分发
vmcb_setup.rs
setup_vmcb_save/control
build_kernel_pdpts (按需分页)
embedded.rs
include_bytes! 载荷
GUEST_BZIMAGE/INITRD/DISK
asm.rs
boot32.S / boot64.S
global_asm! 引导存根
debug.rs
IDT/TSS dump
CEA guard 救援
CPU / SVM 虚拟化核心 — src/cpu/(本次拆分为 4 个子模块)
vcpu.rs
VcpuBackend 抽象
InterruptEvent / InterruptType
idt.rs
宿主机(L1) IDT
捕获 yvisor 自身异常
lapic.rs
宿主机 LAPIC 定时器
抢占 CPU-bound 客户机
svm/vcpu.rs
SvmVcpu / GuestGprs
ExitStats,SVM 使能
svm/handlers.rs
handle_cpuid/ioio/msr
handle_npf/handle_rdtsc
svm/vmrun.rs
SvmVcpu::run() 主循环
vmrun_trampoline (naked asm)
svm/vmcb.rs
Vmcb 结构体定义
控制区+保存区,4096B对齐
svm/mmio.rs
x86-64 指令解码器
MOV 型 MMIO 模拟
svm/vmexit.rs
兼容重导出
pub use vcpu::SvmVcpu
Devices 层 — src/devices/(新增 io_dispatch / mmio_dispatch 拆分)
bus.rs
DeviceBus 结构体
拥有所有设备实例
io_dispatch.rs
io_in / io_out
端口范围匹配 + virtio BAR 发现
mmio_dispatch.rs
mmio_read / mmio_write
LAPIC/IOAPIC/HPET/virtio-mmio
serial.rs
16550A UART
COM1 直通主机
pic.rs / pit.rs
i8259 级联 PIC
i8254 PIT(基于TSC)
lapic.rs
客户机 LAPIC
纯软件状态机
i8042.rs / ioapic.rs
PS/2 控制器存根
IOAPIC 存根
pci.rs
PCI 配置空间读取
发现 virtio BAR 基址
virtio_blk.rs
virtio-mmio 块设备
legacy v1,完整实现
virtio_blk_pci.rs
virtio_net.rs
virtio PCI 代理
used ring 轮询+IRQ合成
Memory 层 — src/mem/
layout.rs
全部固定物理地址常量
无堆分配器
npt.rs
Nested Page Table 构建
identity map + MMIO holes
page_table.rs
页表遍历工具
(未来动态映射用)
alloc.rs
PhysAllocator
(预留,暂未接入热路径)
Linux Guest 加载层 — src/linux/
elf.rs
ELF64 (vmlinux) 加载
段拷贝 + entry 提取
bzimage.rs
bzImage setup header 解析
load_addr 选择
boot_param.rs
boot_params + e820
build / build_minimal
cmdline.rs
内核命令行构建
acpi.rs
最小 ACPI 表
RSDP + RSDT + MADT

2. 启动流程(Boot Flow)

boot32.S
multiboot1
32位入口
boot64.S
长模式跳转
hypervisor_main()
boot/mod.rs
SVM 检测/启用
svm::vcpu
TSC 校准
time::calibrate
NPT 构建
mem::npt
ACPI 安装
linux::acpi
客户机分页/GDT
boot/mod.rs
load_guest_kernel()
boot::guest
VMCB 配置
boot::vmcb_setup
DeviceBus::new/init
devices::bus
主 VMEXIT 循环
loop { vcpu.run() }

load_guest_kernel() 内部三态分发(src/boot/guest.rs)

1

elf::is_elf(GUEST_BZIMAGE) 为真 → load_elf_kernel():直接加载 ELF 段,build_kernel_pdpts() 预建按需分页表,构造最小 boot_params。

2

否则尝试 bzimage::parse() 成功 → load_bzimage_kernel():解析 setup header,选择加载地址,拷贝保护模式内核,构造完整 boot_params + e820。

3

两者都失败 → load_raw_blob():整段二进制原样拷贝到 GUEST_CODE_PAboot_params_pa = 0 表示"raw guest 模式"(主循环会对其注入固定向量 0x20)。

3. 主 VMEXIT 循环 — 谁在驱动客户机?

这是本次重构的核心:原来集中在 boot/mod.rs 里的一大段循环逻辑,被拆分到 loop_state.rs(状态)+ tick.rs(周期性任务)+ inject.rs(中断注入与异常处理)三个文件,mod.rs 现在只剩编排。

state.tick_pit(&mut bus)tick.rs:基于真实 TSC 节拍,按客户机 PIT channel-0 reload 值计算周期,到点就 bus.pic.raise_irq(0)

state.check_rx(&mut bus)tick.rs:排空主机 UART,若客户机开启 RX 中断则 raise IRQ4。VMRUN 前后都调用一次,避免错过窗口。

state.service_virtio(&mut bus)tick.rs:轮询 virtio-net/virtio-blk-pci 的 used ring(未采用中断驱动,因为嵌套 KVM 下物理中断投递不可靠),完成时合成 IRQ11。

state.inject_pending(&mut vcpu, ...)inject.rs:按优先级 LAPIC 向量 > PIC 向量(仅IF=1时) 写入 VMCB EVENTINJ 字段。

vcpu.run(&mut bus)svm/vmrun.rs:执行 VMRUN,内联快速处理 CPUID/IOIO/MSR/RDTSC/NPF,遇到 HLT/NPF-unhandled/Exception/Shutdown 才返回给外层循环。

外层 match exit 分支:Intr(宿主定时器抢占,continue)、Nmi(吞掉)、Hltstate.handle_hlt()Npf(未处理则打诊断后break)、Exceptionstate.handle_exception()(PML4按需补页/CEA救援/重新注入客户机IDT)。

4. SVM 虚拟化核心:VMRUN 循环细节

src/cpu/svm/ 原来是一个大文件,现按职责拆成 4 层:

文件职责关键点
vcpu.rs核心数据结构SvmVcpu(持有 &'static mut Vmcb + GuestGprs + ExitStats),SVM 使能(EFER.SVME + VM_HSAVE_PA MSR)
handlers.rs各类 VMEXIT 的模拟逻辑handle_cpuid(屏蔽虚拟化特征位)、handle_ioio(转发DeviceBus)、handle_msr(合成/持久化)、handle_npf(指令解码+MMIO分发)、handle_rdtsc(见下方⚠️)
vmrun.rs主循环 + 世界切换SvmVcpu::run() 内联快速路径;vmrun_trampoline 裸函数手工保存/恢复 GPR,CLGI/STGI 包裹 VMLOAD/VMRUN/VMSAVE
vmexit.rs向后兼容重导出只有 pub use super::vcpu::SvmVcpu;,让旧的 crate::cpu::svm::vmexit::SvmVcpu 导入路径继续可用
⚠️ 发现的一处代码不一致(值得你关注)src/boot/vmcb_setup.rs 的注释明确写道 "RDTSC/RDTSCP are intentionally NOT intercepted",且 intercept_misc1 位掩码里确实没有设置 RDTSC(14) / RDTSCP(msr里) 位。但 src/cpu/svm/handlers.rs 里仍保留了完整的 handle_rdtsc() 确定性虚拟 TSC 实现,且 vmrun.rsmatch 分支里也还在处理 VmexitCode::Rdtsc | VmexitCode::Rdtscp。由于没有设置拦截位,这两个分支在当前配置下应该是死代码——客户机的 RDTSC 会直接在硬件上执行,不会走这条路径。这看起来是从"确定性虚拟TSC"设计切换到"原生TSC"设计后遗留下来的代码,如果继续按当前架构演进,可以考虑清理或者明确注释说明其为保留/兼容路径。

5. 设备总线(DeviceBus)分发路径

bus.rs 现在只保留 DeviceBus 结构体定义和 new()/init();实际的分发逻辑被拆到两个新文件:

io_dispatch.rs
IOIO VMEXIT → io_in(port,size) / io_out(port,val,size)

匹配顺序:串口范围 → PIC → PIT → PORT61 → CMOS → i8042 → PORT92 → VIRTIO_WINDOW(动态发现BAR) → 兜底 host passthrough
mmio_dispatch.rs
NPF VMEXIT → mmio_read(gpa,width) / mmio_write(gpa,val,width)

匹配区间:LAPIC(0xFEE00000) → IOAPIC存根 → HPET存根 → virtio-mmio(0x28000000)

端口 I/O 完整路径示例(串口写入一个字节)

1

客户机执行 out 0x3f8, al → SVM 因 IOPM 全 1 拦截 → VMEXIT(IOIO)

2

svm/vmrun.rsrun() 匹配 VmexitCode::Ioio → 调用 handlers.rshandle_ioio(bus)

3

handle_ioioexitinfo1 解析方向/端口/大小 → 调用 bus.io_out(0x3f8, val, 1)

4

io_dispatch.rs 匹配 SERIAL_RANGE → 转发到 Serial::io_out() → 真正写入宿主机 COM1

5

RIP 用 exitinfo2(IOIO 规范要求)直接赋值,回到主循环,继续下一次 tick

6. 内存布局(Host-Physical Address Map)

·
0x0000_0000
0x0010_0000
0x0090_0000
0x0091_0000
0x00A0_0000
0x0100_0000
0x0400_0000
0x0800_0000
0x2100_0000
0x2200_0000
0x2800_0000
0xFEC0_0000
0xFED0_0000
0xFEE0_0000
BIOS / IVT(未使用) 0x0 - 0x100000
yvisor 引导映像 boot32/64.S 0x100000
VMCB / HSAVE / NPT表 / CEA / vmemmap PDPT 0x900000, 4K粒度
客户机 PML4/PDPT/GDT(Guest CR3) 0x910000
LOW_MEM_BACKING(ACPI/boot_params/cmdline 后备) 0xA00000, 2MiB
Guest RAM(512MB,NPT identity-map) 0x1000000 - 0x21000000
yvisor .text/.rodata/.data/.bss 0x4000000(在guest RAM内,需从e820排除)
initrd 加载区 0x8000000(128MiB处)
IOPM bitmap(12KiB,全1除PCI配置口) 0x21000000
CEA_BACKING(cpu_entry_area 后备页) 0x22000000, 2MiB
virtio-mmio 块设备 0x28000000, 512B, NPT hole
IOAPIC(存根) 0xFEC00000, NPT hole
HPET(存根) 0xFED00000, NPT hole
LAPIC(软件状态机) 0xFEE00000, NPT hole

注:VMEMMAP_BACKING_PA(0x21000000起16MiB)、CEA_BACKING_PA 等是本次代码中出现的新地址,专门用于 Linux ELF 直接启动路径下手工补全内核缺失的页表映射(vmemmap / cpu_entry_area),详见下节。

7. 一个精妙的机制:按需分页 + PML4 补丁

yvisor 不是把所有客户机页表建好后一次性交给客户机,而是采用"故意留空 + #PF 时补丁"的策略,这是从 boot/inject.rshandle_exception 里学到的关键设计:

a

初始 build_guest_paging()boot/mod.rs)只填 PML4[0](低地址identity)、PML4[273](direct-map 起点)、PML4[511](内核高地址)。

b

客户机运行到需要 PML4[273]/[493]/[511] 以外尚未存在的映射时触发 #PF → VMEXIT(Exception, vec=14)。

c

handle_exception()boot/inject.rs)检查故障是否为内核态 #PF 且对应 PML4 槽为空,若是则按 idx 匹配写入预先建好的 KPGT_PDPT_LOW/HIGH_PAVMEMMAP_PDPT_PAtlb_ctl=1(FLUSH_ALL)后 continue(重新执行故障指令,此时页表已补全)。

d

如果不是可修补的情况,则把 CR2 设为 exitinfo2,通过 vcpu.inject_event() 把异常重新注入客户机自己的 IDT 处理程序(Linux 的 #PF handler)。

这套机制专门针对 ELF 直接启动路径(跳过标准 bzImage 解压器,没有走 Linux 自带的 head_64.S 建页表逻辑),因此 yvisor 需要"代管"内核在正常引导时会自建的几处页表槽位:vmemmap(struct page 数组)与 cpu_entry_area(IST 栈、异常处理用)。CEA 部分还有专门的"guard page 救援"逻辑(debug::try_patch_cea_guard)用于诊断 die-handler 场景。

8. 组件依赖关系图

main.rs
 └─ boot::hypervisor_main()
      ├─ cpu::idt::init()                    (宿主IDT)
      ├─ cpu::svm::vcpu::SvmVcpu::{is_supported,enable_svm}
      ├─ time::calibrate()
      ├─ mem::npt::build_identity_npt()
      ├─ linux::acpi::install_tables()
      ├─ boot::guest::load_guest_kernel()
      │     ├─ linux::elf / linux::bzimage    (格式探测)
      │     ├─ linux::cmdline / linux::boot_param
      │     └─ boot::vmcb_setup::build_kernel_pdpts()
      ├─ cpu::svm::vcpu::SvmVcpu::new(vmcb)
      ├─ boot::vmcb_setup::{setup_vmcb_save,setup_vmcb_control}
      ├─ devices::bus::DeviceBus::new(disk).init()
      ├─ cpu::lapic::init()                   (宿主抢占定时器)
      └─ loop {
            boot::loop_state::GuestLoopState
              ├─ tick.rs      : tick_pit / check_rx / service_virtio / maybe_log_diag
              ├─ inject.rs    : inject_pending / handle_hlt / handle_exception
              └─ cpu::svm::vmrun::SvmVcpu::run(bus)
                    ├─ handlers.rs : handle_cpuid/ioio/msr/npf/rdtsc
                    │      └─ devices::bus::{io_dispatch, mmio_dispatch}
                    │             └─ devices::{serial,pic,pit,lapic,virtio_*,pci,i8042}
                    └─ vmrun_trampoline (naked asm: VMLOAD/VMRUN/VMSAVE)
         }

9. 本次相较基础版本的结构性变化总结

目录之前(基础版本设想)现在(本次分析结果)
src/boot/mod.rs 一个文件承载入口 + 主循环 + 内核加载 + VMCB配置拆成 7 个协作文件mod.rs(编排)、loop_state.rs(状态)、tick.rs(周期任务)、inject.rs(中断/异常)、guest.rs(内核加载)、vmcb_setup.rs(VMCB配置)、embedded.rs(载荷)
src/cpu/svm/推测为 vmexit.rs 单文件包含 SvmVcpu、handle_*、vmrun_trampoline拆成 vcpu.rs(数据结构)、handlers.rs(VMEXIT模拟逻辑)、vmrun.rs(主循环+裸函数),vmexit.rs 降级为纯重导出层保持向后兼容
src/devices/bus.rsDeviceBus + io_in/io_out + mmio_read/mmio_write 全部在一个文件bus.rs 只留结构体定义,分发逻辑拆到 io_dispatch.rsmmio_dispatch.rs
拆分的设计动机(从代码注释和结构推断):
1. 关注点分离:每个文件现在有单一、明确的职责边界(状态 vs 行为、端口 vs MMIO、数据结构 vs 处理逻辑)。
2. 可维护性GuestLoopState 把原来可能是 static mut 的一次性标志位(如 pic_first_injection, cea_diag_done, next_cea_page)收敛成结构体字段——注释里明确写了"replacing static mut",说明这是一次去除全局可变状态、转向显式状态传递的重构。
3. 向后兼容svm/vmexit.rs 保留为重导出层,说明重构者有意保持外部导入路径 crate::cpu::svm::vmexit::SvmVcpu 不变,避免连锁修改所有调用点。