本页基于当前代码库(含最新拆分/重构后的 boot/、cpu/svm/、devices/ 模块)生成,帮助你建立完整的心智模型。
yvisor 现在是一个清晰的四层结构:启动编排层驱动虚拟化核心层反复执行客户机,退出原因交给设备总线层模拟,客户机内核本身由加载层一次性构建好。
elf::is_elf(GUEST_BZIMAGE) 为真 → load_elf_kernel():直接加载 ELF 段,build_kernel_pdpts() 预建按需分页表,构造最小 boot_params。
否则尝试 bzimage::parse() 成功 → load_bzimage_kernel():解析 setup header,选择加载地址,拷贝保护模式内核,构造完整 boot_params + e820。
两者都失败 → load_raw_blob():整段二进制原样拷贝到 GUEST_CODE_PA,boot_params_pa = 0 表示"raw guest 模式"(主循环会对其注入固定向量 0x20)。
这是本次重构的核心:原来集中在 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(吞掉)、Hlt→state.handle_hlt()、Npf(未处理则打诊断后break)、Exception→state.handle_exception()(PML4按需补页/CEA救援/重新注入客户机IDT)。
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.rs 的 match 分支里也还在处理 VmexitCode::Rdtsc | VmexitCode::Rdtscp。由于没有设置拦截位,这两个分支在当前配置下应该是死代码——客户机的 RDTSC 会直接在硬件上执行,不会走这条路径。这看起来是从"确定性虚拟TSC"设计切换到"原生TSC"设计后遗留下来的代码,如果继续按当前架构演进,可以考虑清理或者明确注释说明其为保留/兼容路径。
bus.rs 现在只保留 DeviceBus 结构体定义和 new()/init();实际的分发逻辑被拆到两个新文件:
io_in(port,size) / io_out(port,val,size)mmio_read(gpa,width) / mmio_write(gpa,val,width)客户机执行 out 0x3f8, al → SVM 因 IOPM 全 1 拦截 → VMEXIT(IOIO)
svm/vmrun.rs 的 run() 匹配 VmexitCode::Ioio → 调用 handlers.rs 的 handle_ioio(bus)
handle_ioio 从 exitinfo1 解析方向/端口/大小 → 调用 bus.io_out(0x3f8, val, 1)
io_dispatch.rs 匹配 SERIAL_RANGE → 转发到 Serial::io_out() → 真正写入宿主机 COM1
RIP 用 exitinfo2(IOIO 规范要求)直接赋值,回到主循环,继续下一次 tick
注:VMEMMAP_BACKING_PA(0x21000000起16MiB)、CEA_BACKING_PA 等是本次代码中出现的新地址,专门用于 Linux ELF 直接启动路径下手工补全内核缺失的页表映射(vmemmap / cpu_entry_area),详见下节。
yvisor 不是把所有客户机页表建好后一次性交给客户机,而是采用"故意留空 + #PF 时补丁"的策略,这是从 boot/inject.rs 的 handle_exception 里学到的关键设计:
初始 build_guest_paging()(boot/mod.rs)只填 PML4[0](低地址identity)、PML4[273](direct-map 起点)、PML4[511](内核高地址)。
客户机运行到需要 PML4[273]/[493]/[511] 以外尚未存在的映射时触发 #PF → VMEXIT(Exception, vec=14)。
handle_exception()(boot/inject.rs)检查故障是否为内核态 #PF 且对应 PML4 槽为空,若是则按 idx 匹配写入预先建好的 KPGT_PDPT_LOW/HIGH_PA 或 VMEMMAP_PDPT_PA,tlb_ctl=1(FLUSH_ALL)后 continue(重新执行故障指令,此时页表已补全)。
如果不是可修补的情况,则把 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 场景。
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)
}
| 目录 | 之前(基础版本设想) | 现在(本次分析结果) |
|---|---|---|
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.rs | DeviceBus + io_in/io_out + mmio_read/mmio_write 全部在一个文件 | bus.rs 只留结构体定义,分发逻辑拆到 io_dispatch.rs 和 mmio_dispatch.rs |
GuestLoopState 把原来可能是 static mut 的一次性标志位(如 pic_first_injection, cea_diag_done, next_cea_page)收敛成结构体字段——注释里明确写了"replacing static mut",说明这是一次去除全局可变状态、转向显式状态传递的重构。svm/vmexit.rs 保留为重导出层,说明重构者有意保持外部导入路径 crate::cpu::svm::vmexit::SvmVcpu 不变,避免连锁修改所有调用点。