English

文章

keelOS: 强化VM的硬核技术,PCIe直通,SR-IOV,一个都不能少!

为什么 VM 主机离不开直通

一台放在家里或小办公室的服务器,往往同时跑着 NAS、路由、Windows 桌面和开发机。大多数设备用虚拟网卡、虚拟磁盘(virtio)就够了,但有两类需求绕不开真实硬件:

  • 存储要整块给 NAS:ZFS 这类文件系统要直接看到每一块盘,看得到 SMART、写缓存和真实的错误。隔着一层虚拟磁盘,这些都会打折扣。
  • 网络要接近线速:10G、25G 的网卡,经过虚拟交换机转发会吃掉大量 CPU,延迟也更高。

PCIe 直通把一块物理设备整个交给一台 guest,由 guest 自己的驱动直接操作。SR-IOV 则把一块网卡在硬件里切成多个“虚拟功能”(VF),每个 VF 都能单独直通,一块卡就能同时服务多台 guest。

keelOS 的目标很明确:直通不是附加功能,而是一等公民。更进一步,它要和 keelOS 的招牌能力一起工作:宿主系统重启或升级时,guest 不停机。

keelOS 是什么,直通在这里为什么特别难

keelOS 是一个最小的 hypervisor(hyp)加一个 FreeBSD 宿主系统(root)。guest 由 root 里的 bhyve 运行,但 guest 的内存来自 hyp 的内存池,VMX 指令也由 hyp 执行。

这样拆分的好处是:root 可以重新加载或升级内核,而 guest 只暂停二十几秒。root 重启前把 guest 挂起,内存留在 hyp 的池里;新 root 起来后再恢复,TCP 连接不断。

直通恰恰和这件事冲突:

  • 直通设备会自己做 DMA,靠 IOMMU(Intel VT-d)把它的访问限制在 guest 的内存里。普通做法是宿主系统管 IOMMU,但 keelOS 的 root 随时会重启。
  • 设备在 root 重启期间还在跑:它随时可能写内存、发中断。新 root 接手时还不能复位它,否则 guest 里的磁盘 I/O 就丢了。
  • 业界的现状是:ESXi 的 DirectPath 和 Hyper-V 的 DDA 都不能挂起带直通设备的 VM,Xen 不能 save,KVM 只有少数有厂商迁移驱动的设备(如 mlx5)可以迁移。

整块设备直通:NVMe、SATA 控制器,认准了再交出去

开发机上已经直通运行的有:Intel P3600 NVMe(整块)、板载的 Intel C62x SATA 控制器、X710 网卡的 VF。一台叫 nas 的 FreeBSD guest 拿着整块 NVMe 和一个 VF,在上面跑 ZFS 并用 NFS 共享出去。

几个关键设计:

  • 按身份认设备,不按插槽。keel.json 里的直通设备用 vendor、device、subvendor、subdevice 加序列号来认。换了插槽照常工作;换了一块盘,它不会被误交给 guest,用它的 guest 不启动并写明原因。
  • 开机就预留。内核模块 keelppt.ko 在开机早期按身份接管这些设备,让 root 的驱动没机会先挂上。否则 root 可能自动导入 NAS guest 的 ZFS 池,两边同时写,数据就毁了。
  • 一个设备只归一台 guest。第二台要同一块设备时,创建或启动直接被拒绝。
  • 小于一页的 BAR 也能直通。C62x SATA 控制器有一个只有 256 字节的 BAR,原版 bhyve 直接拒绝。keelOS 像 QEMU/VFIO 那样把这一段拦下来,由 bhyve 代为访问,而且只放行这 256 字节。

VT-d 归 hyp 管

IOMMU 交给了不会重启的 hyp,root 重启期间,直通设备的地址映射始终有效。原来 FreeBSD 的 vmm.ko 自己驱动 VT-d;现在它的 IOMMU 操作改成调用 hyp,hyp 里是一个约 580 行的 VT-d 驱动。

  • 宿主域:root 自己的设备看到的是除 hyp 以外的全部内存,由 hyp 建好并保管。
  • guest 域:只映射这台 guest 在池里的内存,永远碰不到 hyp 自己。
  • 停放的域:guest 挂起、root 重启时,它的域交给 hyp 保管,设备的 DMA 一直有效;新 root 恢复 guest 时再接回来。

这也是 Amazon 在 Xen Live Update 里采用的思路:更换底层软件时,IOMMU 页表在交接期间保持有效。

最硬核的一步:带直通设备的 guest,跨 root 重启不停

拿着整块 NVMe 的 NAS guest,在 root 重新加载期间只暂停 21–23 秒,磁盘 I/O 不丢、不超时、不复位。做法是让设备在整个交接过程中一直运行:

  1. guest 挂起时,只屏蔽设备的 MSI-X 中断。设备照常完成手上的 I/O,中断暂存在它自己的 PBA 里。
  2. guest 的内存和 IOMMU 域都留在 hyp 里,设备的 DMA 一直有效。
  3. 新 root 的 ppt 驱动发现设备在停放的域里,就“接管”而不初始化:不做 FLR 复位,不关总线主控。
  4. bhyve 按 guest 原来的 MSI-X 设置重新接好中断,暂存的中断随即送进 guest。
root 重启期间直通设备的交接:5 个阶段
root 重启期间直通设备的交接:5 个阶段

设备从没停过:整个 root 重启期间它的 DMA 一直有效,只是中断先存在设备里,等 guest 恢复后再送进去。

实测(开发机,2026-09-29):

测试 负载 root 重启次数 结果
NVMe 直通的 NAS guest 不停地 fsync 写盘 4 每次暂停 21–23 秒,无错误
同上,重负载 4 个写 + 1 个读,写约 600–700 MB/s 10 无超时、无复位,ZFS scrub 0 错误
一台 guest 带两块设备 P3600 + 板载 SATA 控制器 5 通过

累计跨过 14 次 root 重启没有一次出错之后,整块设备直通的 guest 在 root 重启时默认就是“挂起”,而不是关机。

SR-IOV:一块网卡分给多台 guest,老平台也不落下

X710 的 VF 已经在开发机上直通运行。guest 的配置里可以写固定的 VF 编号,也可以写 auto:启动时自动挑一个空闲的 VF,调整 VF 数量后 guest 不用改配置。VF 被重建后 MAC 地址不变,guest 里的网络配置照常有效。

SR-IOV 最容易出问题的是异常情况,所以我们列了 13 项测试:VF 不够、两台 guest 抢同一个 VF、运行中调整 VF 数量、网卡坏了或换了、PF 在 root 里被关掉、root 重启等。其中 9 项实测通过,2 项已有处理,还剩真拔网线和“VF 创建失败后的恢复”两项待测。

i350:FreeBSD 没有的,我们自己写

Intel i350 是服务器上最常见的千兆网卡之一,很多老平台板载就是它。Linux 下它能开 SR-IOV(每个口最多 7 个 VF),但 FreeBSD 只有 guest 里用的 VF 驱动,没有宿主这一侧的 PF 功能,iovctl 根本找不到设备。

keelOS 把它补上了:

  • 照 Intel 数据手册写,不用 Linux 代码。Linux 的 igb 驱动是 GPLv2;我们依据 i350 数据手册和 FreeBSD 自己的代码写了约 700 行的 PF 驱动,包括虚拟化模式、PF 与 VF 之间的邮箱协议、VF 复位、MAC 防伪造和端口 VLAN。
  • 不换内核。它是一个开机时加载的模块,按更高的优先级接管内核自带的 igb 驱动。
  • 状态:在模拟的 82576(同一家族、同样的 PF 逻辑)上全部测试通过:VF 与外部、VF 与 PF、VF 与 VF 之间都能通,9000 字节巨帧能通,VF 申请 VLAN 能通,7 个 VF 反复创建销毁 5 轮正常。真机 i350 的验证正在进行。

我们做了哪些工作:修正 8 个 FreeBSD 的问题,补上 5 项缺失的功能

keelOS 建立在 FreeBSD 和 bhyve 之上。把直通、挂起恢复和 root 重启凑到一起时,我们碰到了原版里的不少问题。每一处修改都是对 FreeBSD 源码的一个补丁脚本,源码一变就会报错,不会静默地失效。

原版行为有错、已经修正的 8 个问题:

模块 原版的问题 后果
vmm 本地 APIC 挂起时丢掉已到期、还没触发的定时器中断 Linux guest 恢复后定时器停摆,约十次挂起碰到一次
vmm 虚拟 HPET 计数器跳过比较值后,下一次中断要等计数器绕一圈(最多 256 秒) Windows 的一个 vCPU 空转读 HPET,guest 卡死
bhyve 电源管理 ACPI 电源状态不在快照里 恢复后按“电源键”会直接杀掉 bhyve
bhyve PCI 恢复时 BAR 还在旧地址,没有按快照里 guest 的设置移过去 UEFI 移到 4 GB 以上的设备(如 NVMe)恢复后访问不到
bhyve 传统中断(INTx) 中断线的状态不在快照里 恢复后设备中断风暴(e1000、AHCI、virtio)
bhyve 串口(LPC) 不管有没有配置,总向 ACPI 声明 COM1–COM4,两两抢同一个独占 IRQ Windows 里 4 个串口全是代码 12
igb VF 驱动 VF 和 PF 共用一个 iflib 描述,没有标成 VF iovctl -D 删不掉 VF(Device busy)
igb VF 驱动 VF 去读只有 PF 才有的统计寄存器,越过了 VF 的寄存器区 宿主里直接 panic,或读到垃圾数据

FreeBSD 没有、我们补上的 5 项功能:

  1. 虚拟 NVMe 控制器的快照:原来带 NVMe 盘的 guest 根本不能挂起。
  2. 小于一页的 BAR 的直通:原来 Intel C62x SATA 控制器直通不了。
  3. 直通设备跨 root 重启:ppt 驱动的“接管”模式、bhyve 直通设备的快照。
  4. VT-d 交给 hyp:vmm.ko 的 IOMMU 操作改成调用 hyp。
  5. i350 / 82576 的 SR-IOV PF 驱动。

还没做完的

  • 中断重映射还没开。直通设备理论上可以被 guest 驱动去伪造中断、打扰 root。个人和小办公室的场景里 guest 都是自己人的,暂时可以接受。
  • 带 VF 的 guest 在 root 重启时还是关机再开。让它也能挂起的代码(原样重建 VF,由 guest 驱动自己恢复)已经写好,待实测。
  • VT-d 的收尾:VT-d 硬件的寄存器还对 root 可见;“root 的设备无法用 DMA 写进 hyp”还要实测一遍;需要 RMRR 的设备还不能直通。
  • i350 真机还在验证。另外,FreeBSD 自己的 igb VF 驱动在 PF 复位后不会自动重新握手,要在 guest 里把网卡 down/up 一次;Linux 的 igbvf 没有这个问题。
  • X710 的一个原版问题还在绕开:VF 创建失败后再执行 iovctl -D 会让宿主 panic,keel 不这样操作,改为提示经固件重启。

结语

直通和 SR-IOV 本身不是新技术,难的是让它们和“宿主重启、guest 不停”同时成立。keelOS 现在做到了:拿着整块 NVMe 的 NAS,在满负载写盘时跨过 14 次 root 重启,没有一次超时或复位,ZFS scrub 零错误。

下一步是让带 VF 的 guest 也能跨重启不停,并在更多硬件上验证:i350、板载 SATA 带盘、HBA 卡。

← 文章 · 为什么选 keelOS