English日本語

文章

keelOS: 显卡直通,人民群众的刚性要求,我们要满足!

为什么说是刚性要求

在家里或小公司用虚拟化的人,迟早会问同一个问题:显卡能不能给虚拟机用?跑本地大模型、转码、剪视频、云游戏、给设计师一台远程工作站,都要一块真显卡。模拟出来的显卡做不了这些,所以要把整块物理显卡交给一台虚拟机,这就是显卡直通(GPU passthrough)。

这篇文章讲三件事:显卡直通背后到底要做哪些工作;主流的虚拟化平台让用户怎么做;keelOS 现在做到了什么程度,包括还没做好的地方。

显卡直通要做哪些工作

直通的想法很简单:把设备交给虚拟机,让虚拟机里的驱动直接操作硬件。但宿主要把下面八件事都做对。少一件,虚拟机里看到的就是黑屏、Code 43,或者驱动初始化超时。

要做的事 为什么 做错了是什么样子
预留设备 宿主的驱动一旦初始化了显卡,就很难把它干净地交出去,所以开机时就要拦住 宿主占着显卡不放,或者交出去的显卡状态不对
IOMMU(Intel VT-d、AMD-Vi) 显卡自己读写内存(DMA),用的是虚拟机里的地址。IOMMU 把这些地址翻译到这台虚拟机的内存,并且不让它碰别处 没有 IOMMU 就不能直通;映射错了,显卡会写坏别人的内存
隔离 同一张卡的显示和 HDMI 声音、同一个桥后面的几个设备,可能绕过 IOMMU 互相访问,要一起给同一台虚拟机 两台虚拟机能互相读写对方的设备
固定虚拟机的内存 显卡随时可能做 DMA,虚拟机的内存不能被换出或回收 显卡写到已经挪作他用的内存上
映射寄存器和显存窗口(BAR) 显卡有几个大的地址窗口(1080 Ti 是 16 MB、256 MB、32 MB),要直接映射进虚拟机才快。虚拟机的固件和系统还会挪它们的地址,映射要跟着挪 驱动读到全 1,或者访问落到别的设备上
中断 设备的 MSI、MSI-X 中断要送进虚拟机;老式的线中断(INTx)要另做一套 驱动等不到中断,Windows 里是 Code 43
复位 虚拟机关机、重启或被杀之后,显卡要回到干净的状态才能再用。很多显卡不支持最方便的那种复位(FLR) 第一次启动正常,第二次显卡起不来,只能重启宿主
显卡初始化和厂商的特殊行为 物理机上由主板固件运行显卡的 VBIOS;虚拟机里要么把 VBIOS 交给虚拟机的固件运行,要么靠驱动自己初始化。各家显卡还有自己的怪脾气,要逐个照顾 开机没有画面;驱动读到宿主的配置而不是虚拟机的

这些事里,IOMMU、内存和中断是所有直通设备共有的。复位和最后一项是显卡特别难的地方:显卡的内部状态多,厂商的驱动又默认自己跑在物理机上。

业界怎么做

底层的机制各家差不多,都是上面那八件事。差别在于有多少要用户自己动手。

平台 宿主上要做的 虚拟机上要做的
Proxmox VE(KVM + VFIO) 改内核启动参数打开 IOMMU(较新的内核默认已开);加载 vfio 模块;把宿主的显卡驱动(nouveau、nvidia、radeon)加进黑名单;把显卡的设备 ID 写给 vfio-pci;重新生成 initramfs;重启;检查 IOMMU 分组 机型选 q35,固件选 OVMF;添加 PCI 设备,勾选“主 GPU”“PCI-Express”;有的卡要自己导出 VBIOS 文件;AMD 部分型号要另装 vendor-reset 模块解决复位
Unraid(KVM + VFIO) 在系统设备页面勾选显卡,“开机时绑定到 VFIO”,重启 在虚拟机设置里选显卡;做主显卡时常要提供 VBIOS 文件
VMware ESXi(DirectPath I/O) 在硬件页面把设备切换为直通(老版本要重启);复位方式在 passthru.map 里按型号配置 内存全部预留;添加 PCI 设备;显存窗口大的卡要加 64 位 MMIO 的高级参数
Hyper-V(DDA) 只在 Windows Server 上提供,只能用 PowerShell:停用设备,再用 Dismount-VMHostAssignableDevice 把它从宿主卸下 Set-VM 设置 MMIO 空间大小,自动停止动作改为关闭;Add-VMAssignableDevice
原版 FreeBSD bhyve 在 loader.conf 里按总线地址写 pptdevs,重启 命令行加 passthru 和 -S(固定内存);没有 FLR 的显卡只做电源状态循环,下文会讲这为什么不够

几个共同点:

  • 预留基本都要重启宿主,而且多数按总线地址或设备型号来认设备。按型号认,两张同型号的卡就分不开;按地址认,加一张卡地址可能就变了。
  • 复位是老大难。KVM 的 vfio-pci 在打开和关闭设备时做 FLR 或总线复位,还为个别设备写了专门的复位办法;ESXi 要用户按型号挑复位方式;Hyper-V 直接要求设备自己能正确复位。
  • 带直通设备的虚拟机不能挂起、不能带内存快照、一般也不能迁移,各家都一样。
  • NVIDIA 的 Code 43 曾经是各平台共同的麻烦:老的 GeForce 驱动发现自己在虚拟机里就拒绝工作,用户要想办法把虚拟机的特征藏起来。2021 年的 465 版驱动起,NVIDIA 正式允许消费级显卡在虚拟机里用,这一条才成为历史。

这些平台都能把显卡直通做成,网上的教程也很多。但教程多本身就说明问题:这件事要用户懂内核参数、驱动黑名单和 VBIOS。

keelOS 怎么做:开箱即用

先交代一下 keelOS 的结构。它是一个最小的 hypervisor(hyp)加一个 FreeBSD 宿主系统(root)。虚拟机由 root 里的 bhyve 运行,内存来自 hyp 的内存池,IOMMU 也归 hyp 管。hyp 常驻不动,root 可以重新加载或升级,虚拟机只是暂停一下,不用关机。

在 keelOS 上把显卡给虚拟机,用户要做的是三步,都在网页上:

  1. 打开“配置 → PCI 直通”,找到显卡和它自带的 HDMI 声音,点“保留给直通”,勾上“立即生效”。
  2. 点“加到虚拟机…”,选一台虚拟机。
  3. 启动虚拟机,在里面装显卡驱动。

不用改内核参数,不用写驱动黑名单,不用导出 VBIOS,也不用重启宿主。前面那八件事,keelOS 是这样替用户做掉的:

  • 预留:内核模块 keelppt 在开机早期接管列表里的设备,宿主的驱动没有机会先挂上。设备按身份认(型号加序列号),不按总线地址;同型号又没有序列号的,退回按插槽认。勾了“立即生效”就在运行中交接,不等下次开机。
  • IOMMU:hyp 开机就接管 VT-d,没有开关要打开。每台虚拟机一个域,只映射它自己的内存。
  • 隔离:PCI 直通页面列出每个设备的隔离分组,同组的设备会提醒“一起给同一台虚拟机,或者都留给主机”。主机自己在用的设备(启动盘的控制器、存储池的盘)直接拒绝。
  • 内存:虚拟机的内存本来就在 hyp 的池里,不会被换出;带直通设备时自动标成不回收。
  • BAR:由 bhyve 映射并跟着虚拟机挪动。keelOS 补上了小于一页的 BAR(原版直接拒绝)。
  • 中断:MSI 和 MSI-X 直接送进虚拟机。老式的线中断也接上了:宿主收到后先让设备停止发中断,把中断转给虚拟机,等虚拟机处理完再放开。
  • 复位:虚拟机拿到和交还设备时自动复位。有 FLR 的用 FLR;没有的,在显卡上游的桥上做总线复位;都不行才退回电源状态循环。
  • 厂商的特殊行为:NVIDIA 显卡的处理内置在 keelOS 的 bhyve 里,用户不用配置。不需要 VBIOS 文件。

实测(2026-10-02)

测试机是一台双路 Xeon E5-2667 v3 的 Supermicro X10DRU-i+,32 GB 内存,显卡是 GTX 1080 Ti。这张卡没有 FLR,是比较难伺候的一类。

虚拟机 驱动 结果
Debian 13,4 vCPU,8 GB nvidia-driver 550.163.01(Debian 自带的包) nvidia-smi、OpenCL、hashcat 正常。跑着 hashcat 时把虚拟机杀掉再启动,连着三轮,每轮显卡都正常
Windows 10 Pro 22H2,4 vCPU,8 GB 560.94(Windows Update 自动装的) 不用在虚拟机里做任何设置,设备管理器里显卡和 HDMI 声音都正常,nvidia-smi 正常,虚拟机重启后依旧正常;HDMI 口能输出 1920×1080 60 Hz 的画面

Windows 这一行目前还带着一个条件(系统盘不能用模拟的 NVMe),列在“还没做完的”一节里。

路上的四个坑

开箱即用不是白来的。下面四个问题,每一个都让显卡在虚拟机里起不来。现在四个都由系统自己处理。

1. 第一次正常,第二次起不来

现象:宿主开机后第一次启动虚拟机,显卡正常。虚拟机被杀掉或者挂住之后再启动,驱动报 RmInitAdapter failed,nvidia-smi 找不到设备,只有重启宿主才恢复。

走过的弯路:我们一开始以为是显卡复位后没有人运行它的 VBIOS,于是从显卡里读出 VBIOS,分别交给 UEFI 固件和 SeaBIOS 去跑。这条路没有解决问题。

原因:我们做了一个跟踪开关,把虚拟机对显卡寄存器的每一次访问都记下来,再拿正常和失败的两次启动对比。结果是 VBIOS 跑完了,驱动也把固件传给了显卡上的管理处理器(PMU),但 PMU 启动不了:它还停在上一台虚拟机留下的状态里。原版 FreeBSD 对没有 FLR 的设备只做电源状态循环(D3hot 再回 D0),对这张卡来说这不算复位。

修正:在显卡上游的桥上做总线复位(secondary bus reset),这才是真正的复位。它会复位这条总线上的所有设备,所以有条件:总线上的设备都是直通设备,而且没有一个在别的虚拟机手里。显卡和它的 HDMI 声音在同一条总线上,虚拟机拿到第一个时复位一次,交还最后一个时再复位一次。改完之后不需要 VBIOS,也不需要重启宿主。

2. 驱动读到的是宿主的配置

现象:带上 VBIOS 启动时,虚拟机的 UEFI 固件卡死在显卡的初始化程序里。

原因:NVIDIA 的显卡在自己的寄存器空间里又给出了一份 PCI 配置空间,VBIOS 和驱动都从这里读写。寄存器空间是直接映射给虚拟机的,所以虚拟机从这条路读到的是宿主的 BAR 地址和宿主的中断设置,和它从正常途径看到的对不上。

修正:照 QEMU 的做法,把这一页单独拦下来,访问它时返回虚拟机自己的配置。显卡其余的寄存器照旧直接映射,不影响速度。

3. 带直通设备的虚拟机,内存加起来不能超过 2.5 GB

现象:一台 2 GB 的 NVMe 直通虚拟机在跑的时候,8 GB 的显卡虚拟机启动不了,报内存不足。去掉直通设备就能启动。

原因:直通要求固定虚拟机的内存。FreeBSD 对可以固定的内存有个上限,按宿主自己的内存来算。keelOS 的宿主只留了 3.7 GB 给自己,上限大约 2.5 GB,而虚拟机的内存明明在 hyp 的池里,也被算了进去。

修正:池里的内存本来就不会被换出,不再计入这个上限。之后两台直通虚拟机(2 GB 加 8 GB)一起运行正常。

4. Windows 里的 Code 43

现象:Windows 装好驱动后,设备管理器里显卡是 Code 43。同一张卡在 Linux 虚拟机里正常。

原因:这不是当年驱动检查虚拟机的那个 Code 43。NVIDIA 的 Windows 驱动默认用线中断(INTx),而 bhyve 只给直通设备 MSI 和 MSI-X,驱动等不到中断。Linux 驱动默认就用 MSI,所以没事。

修正:给直通设备补上线中断,照的是 KVM/VFIO 的做法。宿主接住设备的中断线,收到中断后用命令寄存器里的 DisINTx 位让设备先停止发中断,再把中断送进虚拟机;虚拟机处理完之后放开,设备还有事就再发一次。改完之后,Windows 里什么都不用设置,显卡装好驱动就是正常的。我们在三种虚拟机里验证了这条路:Windows 10 的 NVIDIA 驱动;Linux 的 NVIDIA 驱动(强制用线中断);FreeBSD 的 NVMe 驱动(强制用线中断,四块盘写入再读回,校验全对,带着读写挂起再恢复三轮没有出错)。线中断比 MSI 慢一些:NVMe 的 4K 单队列读大约是 MSI-X 的一半,顺序读没有差别。所以驱动能用 MSI 时还是用 MSI。

和物理机比:性能差多少

直通的意义在于虚拟机里的显卡要和物理机上一样快,所以我们在同一台机器上做了对比(2026-10-02)。

做法是让两边只差“有没有虚拟化”这一件事:先在 keelOS 的虚拟机里装好 Debian 13 和 NVIDIA 驱动,跑一遍测试;再把这台虚拟机的磁盘原样拷到一块 NVMe 上,让物理机直接从它启动,用同一个内核、同一个驱动、同一个脚本再跑一遍。虚拟机是 4 个 vCPU、8 GB 内存,放在显卡所在的那一路 CPU 上;物理机的测试也绑在同一路。测试程序是 clpeak(OpenCL 的算力、显存带宽、数据拷贝)和 hashcat。

虚拟机的成绩占物理机的百分比:显卡上的计算 97–104%,主机和显卡之间拷数据 71–76%
虚拟机的成绩占物理机的百分比:显卡上的计算 97–104%,主机和显卡之间拷数据 71–76%

来源:tools/bench/results/2026-10-02 的 gpu-keelos.json 和 gpu-bare.json · 13 项,每边三次取中位数 · GTX 1080 Ti,Debian 13

显卡上的事和物理机没有差别:算力、显存带宽、hashcat 都在物理机的 97% 到 104% 之间,而两边各自的三次成绩之间本来就有 1% 到 5% 的波动。这符合直通的原理:显卡的寄存器和显存直接映射给虚拟机,计算时不经过宿主。

差距在主机内存和显卡之间拷数据这一项,虚拟机比物理机慢四分之一左右。这不是噪声,三次的成绩都很接近。原因还没有查清,可能和 IOMMU 页表用 4K 小页有关,我们把它记进了性能版本的计划。

测试中还有一个比这更大的因素:NUMA。在双路机器上,物理机的测试如果跑在离显卡远的那一路 CPU 上,拷贝速度只有 5.2 和 6.4 GB/s,比虚拟机还慢。所以给显卡虚拟机选对 CPU 比虚拟化本身的开销更要紧。keelOS 里这是虚拟机的一个设置(NUMA 节点),目前要手工选,还不会按显卡的位置自动选。

还没做完的

开箱即用说的是宿主这一侧:预留、IOMMU、复位、显卡的特殊处理,都不用用户动手。虚拟机这一侧还有几处没做完,照实列在这里。

  • 线中断还有两种情况不支持。一种是没有 DisINTx 位的老设备(PCI 2.3 以前的),另一种是不用 I/O APIC、还在用 8259 中断控制器的老系统虚拟机。这两种情况下直通设备仍然只有 MSI 和 MSI-X。
  • 带直通显卡的 Windows 虚拟机,系统盘暂时不能用模拟的 NVMe。启动一两分钟后盘会停住,Windows 蓝屏。换成 AHCI 盘就正常,不带显卡也正常。原因还没有找到,这是 1.0 之前必须查清的问题,因为 Windows 模板默认用的就是 NVMe 盘。
  • Linux 虚拟机里只有计算是通的。加载 NVIDIA 的显示模块之后,虚拟机关机时会卡住,所以目前不加载它们。Windows 下的显示输出是正常的。
  • NUMA 节点要手工选。keelOS 还不会按显卡所在的位置自动放置虚拟机。
  • 主机和显卡之间拷数据比物理机慢四分之一左右,原因待查。
  • 只测了一张卡。GTX 1080 Ti 之外的显卡,包括 AMD 和 Intel 的,都还没有测过。

结语

显卡直通的机制各家都一样,难的是那些机制之外的事:显卡不肯复位,驱动从后门读配置,中断方式对不上。别的平台把这些留给用户和教程,keelOS 想把它们收进系统里,让用户只做三步:保留、加到虚拟机、启动。

今天做到的是:一张没有 FLR 的 GTX 1080 Ti,在 Linux 和 Windows 10 虚拟机里都能用,反复杀掉再启动也正常,显卡上的计算和物理机一样快。宿主系统重新加载时,带显卡的 Windows 虚拟机只暂停了 11 秒,没有重启,显卡照常工作。接下来要做的是查清 NVMe 的问题,再测更多的显卡。

← 文章 · 为什么选 keelOS