一、总览
- 客机里是 2 张显卡:一张半虚拟化显示控制器(QXL,无 3D,供远程查看)+ 一张直通的物理卡(承担全部 3D 算力)。并列关系,不是主备。
- 因而有两条互不相干的输出路:路 A 把画面编码后送到宿主上的查看窗口;路 B 从直通卡的物理输出口直出。
- 最省损耗的是路 B 接真显示器(零回传);什么都不接就只能吃跨卡回传的损耗,是三种形态里最差的。
计算链路
【计算链路】
┌─ 虚拟机内部(Windows)────────────────────────────────────────┐
│ 游戏 / 应用 ──DirectX/Vulkan──▶ 渲染调用 │
│ │ │
│ ┌────────────────┐ │ ┌──────────────────────────┐ │
│ │ 直通显卡 │◀──┼──▶│ QXL 虚拟显示控制器 │ │
│ │ (物理显卡) │ │ │ (半虚拟化,无 3D,仅 2D) │ │
│ └───────┬────────┘ │ └───────────┬──────────────┘ │
│ │ 显存/命令队列/中断 │ 帧缓冲 │
└───────────┼───────────────────────────┼───────────────────────┘
│ PCIe 直通(OCuLink 4.0 x4)│ 纯软件路径
▼ ▼
┌─ 宿主(Linux)────────────────────────────────────────────────┐
│ KVM / QEMU 进程 │
│ · vCPU 线程 ← 宿主 CPU 调度 · VM 内存 ← 宿主 DRAM │
│ · 系统盘 ← 宿主 NVMe 上的镜像 · 直通设备 ← VFIO 交 BAR/中断
│ ※ 一旦直通,宿主不再驱动它 ⇒ 宿主侧看不到这张卡 │
└────────────────────────────────────────────────────────────────┘
算力与访存由直通卡承担,宿主只负责调度与提供内存/磁盘;QXL 不参与计算。显示链路
【显示链路】
应用渲染完成的画面
│
┌──────────────┴───────────────┐
▼ ▼
┌─ 路 A:虚拟显示(远程访问)─┐ ┌─ 路 B:直通显示(游戏直出)──┐
│ QXL 控制器 │ │ 直通显卡渲染 │
│ ▼ │ │ ▼ │
│ SPICE 编码 │ │ 显卡物理输出口 (DP/HDMI) │
│ ▼ │ │ ▼ │
│ 宿主上的查看窗口 │ │ 真显示器 │
│ (窗口里只有这一路) │ │ (不经宿主、零拷贝) │
└─────────────────────────────┘ └──────────────────────────────┘
· 路 A 与宿主接了什么显示器无关;路 B 只出现在「直通卡输出口实际连着的屏」上。
· 查看窗口里看不到路 B;路 B 也不会出现在窗口里。二、画面走向:接哪根线、怎么判断
外接屏采用「一台显示器两根线」:一根进核显、一根进外置卡。切换屏上的输入源时看到什么,取决于那根线背后的卡此刻归谁用:
| 屏的输入源 | 该卡此刻归谁 | 看到什么 |
|---|---|---|
| 核显那一路 | 始终归宿主 | 宿主桌面本身 —— 路 A 只是宿主桌面上的一扇窗 |
| 外置卡那一路 · 宿主模式 | 归宿主 | 宿主桌面的一部分(该卡驱动的扩展屏/主屏) |
| 外置卡那一路 · 直通模式 | 归虚拟机 | 客机画面(路 B)—— 客机桌面与游戏 |
三条推论:①切核显看宿主、切外置卡看客机(直通下),一台屏看两侧;②同一根线在两种模式下是两个世界;③直通下虚拟机未启动时那一路是黑的(卡已不在宿主手里),不是故障。
从哪看出画面走的是哪条路:
| 观察位置 | 判据 |
|---|---|
| 客机「显示设置」 | 能看到两个显示适配器/多台显示器;被设为主显示的那套接收桌面与游戏窗口 |
| 宿主查看窗口 | 窗口内只有虚拟那一路;直通那一路永远不会出现(它走物理输出口) |
| 宿主侧显卡与输出口 | 直通后宿主看不到这张卡的输出口;仍能看到 ⇒ 说明并没有直通 |
| 物理显示器 | 只有接在直通卡输出口上的那块会亮,与查看窗口互不影响 |
三、直通卡没接显示器时:回传与损耗
前提:显卡没有活动显示器 ≠ 不能渲染。驱动允许把它当纯渲染设备,成品帧由系统合成器跨适配器搬到「有显示的那张卡」再输出:
【回传路径】
直通卡渲染 ─▶ 成品帧 ─▶ 经 PCIe 回传到虚拟适配器 ─▶ 合成 ─▶ 编码 ─▶ 通道送出 ─▶ 查看窗口
(渲染快) (往返搬运:主要开销) (软件合成) (CPU 编码) (网络/USB 通道)| 形态 | 直通卡是否接真显示器 | 画面如何被看到 | 损耗 |
|---|---|---|---|
| 物理直出(最优) | 接 | 直接看那块屏 | 零回传损耗(渲染即输出) |
| 假负载 + 采集 | 接假负载,或在客机内装间接显示驱动造虚拟显示器 | 用采集/串流抓走该输出再送到别处看 | 渲染无损耗;采集与编码约数十毫秒延迟 |
| 跨卡回传(兜底) | 不接 | 只能从查看窗口看 | 跨适配器搬运是主要瓶颈(分辨率/刷新率越高越明显)+ 虚拟控制器只做 2D 合成 + 编码通道开销 ⇒ 帧率下降、延迟升高、可能撕裂跳帧,能玩但不适合竞技类 |
四、模式切换的设计:把「开关」从世代里拆出去
可行,且比「用系统重建管开关」合理得多。但「必须重启」绕不过 —— 它不在配置层而在驱动层;脚本能省掉的是世代堆积,不是重启。
| 判据(本机实测) | 实测 | 意义 |
|---|---|---|
| IOMMU | 已启用,设备有独立分组 | 直通前提已满足 |
| 直通所需内核模块 | 三个模块都在本机内核里 | 无需为此再声明 |
| 按设备指定期望驱动的接口 | 支持:每个 PCI 设备都有 driver_override(当前为空) | 关键:绑定决策可以不写内核参数,因而能移出世代 |
| 目标驱动占用情况 | 引用计数非零,被显示相关子模块持有 | 不可热切换的直接证据 —— 卸载它等于停掉整个图形栈,实际等同重启 |
注意:外置卡与其音频功能同属一个分组,直通必须整组给。
| 机制 | 世代开销 | 评价 | |
|---|---|---|---|
| A. 写内核参数 | 参数里写死直通哪些设备,改一次重建一次 | 每次一个世代 | 开关被焊进「配置基线」 |
| B. 期望驱动 + 启动期应用(推荐) | 系统层只搭机制:模块可用 + 一个启动期服务;开关值由脚本写进状态文件,服务开机读取并应用 | 零 | 开关是状态不是配置,与不可热切换的事实天然一致 |
两层共同点:都要重启。差别只在「切换动作会不会污染版本基线」。
【三层分工】
系统声明层:IOMMU / 模块 / 启动期应用服务 / 显示栈容错 ← 不含具体哪张卡,不随开关变
状态层 :每张卡的「期望模式」标记(系统状态目录,不在配置树里)
应用层 :开机读状态 ⇒ 写期望驱动并绑定/解绑,必须早于图形会话
命令行工具(运行时,零世代) 状态文件 启动期服务
───────────────────────── ────────────── ───────────────────
gpu 帮助
gpu list/devices ─▶ 列出显卡:当前驱动 + 期望模式 + 是否一致
gpu pass <卡> ─▶ 写「期望=直通」──┐
gpu host <卡> ─▶ 写「期望=宿主」──┤
▼
重启 ─▶ 服务读状态 ─▶ 应用绑定 ─▶ 图形会话启动五、落地要点
| 代价 | 表现 | 缓解 / 铁律 |
|---|---|---|
| 引入非声明式状态 | 「实际绑定」不再能只从配置推出 | list 并列给出「当前驱动 / 期望模式 / 是否一致」 |
| 时序敏感 | 服务晚于图形会话 ⇒ 切换静默失效 | 显式早于图形会话启动 + 在 list 里回读实际绑定自证 |
| 两处可能打架 | 系统层若又写死绑定参数,会与状态文件冲突 | 铁律:系统声明层永不写具体设备的绑定参数 |
| 调试面变宽 | 要查 状态文件 → 服务日志 → 驱动绑定 三处 | 查询面收敛为 list 一个入口 |
最容易漏掉的一处 —— 显示栈容错:卡被交给虚拟机后宿主就失去它。若宿主图形配置硬编码了「这张卡负责哪些输出」,开机就会因找不到设备而报错或掉回兜底布局。这属于系统声明层必须一起搭好的结构,脚本补救不了 —— 应让显示配置按「实际存在的设备」自适应,或启动期按状态选配置。
落点:归拢到硬件域(modules/hardware) | 职责 |
|---|---|
| 声明文件 | IOMMU 与模块可用性 + 启动期应用服务(读状态→应用绑定)+ 显示栈容错 |
| 脚本文件 | 运行时工具本体(很薄:list 读设备树、pass/host 写状态) |
一句话:系统层持久化「环境基础 + 脚本」,脚本管理「运行时」,重启生效。
切换生效后,光切到一个含直通设备的模具还不够,还有三处必须一起满足:
| # | 必须项 | 不满足会怎样 |
|---|---|---|
| 1 | 直通设备整组给 | 显卡与音频同组,只给一半 ⇒ 启动被拒(分组不可用) |
| 2 | 模具含反虚拟化隐藏项 | 客机显卡驱动识别出虚拟化环境,拒绝工作或报错 |
| 3 | 启动虚拟机之前已处于直通模式 | 模式不符 ⇒ 管理器可能自动抢绑(与状态层打架)或启动失败 |
另两处属「配套而非模具」:宿主图形栈要容忍「这张卡不见了」;客机里仍需保留虚拟显示控制器,否则查看窗口没画面。因此虚拟机侧两种做法都成立:手动切直通模具,或按当前显卡模式自动挑模具(一一对应时更省心)。
六、实测现状与边界
| 判据 | 实测 |
|---|---|
| 外置卡当前归谁 | 归宿主显卡驱动,未交给虚拟机 |
| 直通设备占用 | 无任何设备处于直通绑定状态 |
| 宿主可见的输出口 | 外置卡两个口「已连接」;核显侧内屏与另一外接口「已连接」⇒ 天然具备物理直出通路 |
| 现有虚拟机方案的显卡形态 | 全部是「虚拟控制器 + 远程显示通道」,无一含直通设备 |
| 显示拓扑 | 外接屏「一台两根线」分接核显与外置卡;另有一台屏走该屏的菊花链下游 |
落地要动三处:①把卡交给虚拟机(宿主随即失去它,显示布局需提前规划);②虚拟机的显示配置(保留虚拟控制器 + 加直通设备);③输出通路二选一(真显示器 / 客机内造虚拟显示器 + 采集),都不做就只能吃跨卡回传的损耗。
边界:全文属配置推演 —— 本机尚未执行直通,故所有直通形态的结论均按机制推出,未实测;「跨适配器回传的帧率损失」也未量化,只给构成与定性排序。多屏串联时下级屏显示什么取决于上级屏向下转发哪一路(本文按「多数显示器只转发 DP 输入」推演),切核显时下级屏可能无信号,需现场核对。半虚拟化显示控制器型号随方案可选,本文按「无 3D 的 2D 控制器」描述。本次为只读梳理:未改动任何配置,未启停任何虚拟机。
七、整体结论
- 架构判断成立:开关交给运行时工具、机制留在系统声明层,前提是不写内核参数,改用按设备的期望驱动接口。
- 「重启生效」是物理事实不是设计缺陷:绑定发生在内核探测期,且驱动被图形栈持有。要区分切换成本(消不掉)与版本污染(本方案消掉)。
- 两个子命令就够:
list一屏给出设备/当前驱动/期望模式/是否一致;pass/host只写状态;真正逻辑全在启动期服务里。 - 落地前必须先解决显示栈容错,否则第一次直通就会把宿主桌面搞挂。
- 冷插拔禁忌不变:外置通路切换全程断电,不因脚本化而放松。