无头显示、欺骗器与 RustDesk 排障总结
这篇文章记录一次比较完整的无头显示排障过程。目标很简单:在 Linux 主机没有实体显示器的情况下,依然保持图形桌面可用,并让 RustDesk 能稳定连接。
最后的结论并不复杂,但中间确实绕了不少路:
- 问题核心不在 RustDesk 本身,而在显示链路。
- 在“独显 + DP 转 HDMI + HDMI 欺骗器”的方案下,Linux 的 DRM 层和 Xorg/RandR 层对输出状态的判断并不一致。
- RustDesk 关心的是图形桌面里有没有活跃显示器,不是
/sys/class/drm里某个 connector 看起来是不是connected。 - 改成核显输出后,问题直接简单了很多。
初始问题
最开始的现象是:
- 某 HDMI 欺骗器在 Windows 下工作正常;
- 在 Linux 下表现异常;
- 目标是让 Linux 在无实体显示器时仍能保持桌面可用,并供 RustDesk 远程连接。
一开始直觉上会怀疑很多地方:
- EDID 读取失败;
- 输出口没有被内核识别为
connected; - 图形会话没有真正绑定到这条输出链路。
这个方向后来证明基本是对的,只是问题并不在同一层。
早期诊断:先看 DRM 和 EDID
最开始用 get-edid | parse-edid 看过一次,结果并不好:
- 多个 i2c bus 都读不到 EDID;
- VBE 回退接口也失败。
这说明当时 Linux 没有从这条“显示器/欺骗器”链路上拿到稳定可用的 EDID。遇到这种情况,继续围着 get-edid 打转通常意义不大,应该直接去看 DRM 层和内核日志。
当时主要用到的命令如下:
1 | |
排查过程中出现过几种很关键的现象:
- 某些 HDMI/DP connector 在
/sys/class/drm/.../status中全部显示disconnected; - 某个 DP 输出在 DRM 层显示
connected; - 但同一时期,Xorg/RandR 层不一定能看到对应的可用输出。
这其实已经提前暴露了核心问题:不同图形层面对“有没有显示器”的判断并不总是一致。
问题链路:DP 转 HDMI 再接欺骗器
后来逐渐确认,实际使用的不是一条纯 HDMI 路径,而是:
DP -> HDMI 转接器 -> HDMI 欺骗器
这点非常重要。因为一旦中间多了转接器,事情就不再只是“插了个假显示器”那么简单,而是会引入:
- 转接器本身的兼容性;
- 显卡驱动对该链路的识别;
- DRM/KMS 层与 Xorg 层之间的状态传递问题。
曾经一度确认到某个 cardX-DP-Y:
status: connectedmodes里能看到2560x1440、1920x1080等分辨率;edid也是非空的。
这说明什么?
说明内核的 KMS/DRM 层并不是完全看不见这条链路。问题在于,内核能认到,不代表当前图形桌面一定会把它当成有效显示器。
中途试过 VKMS
中间还走过一段 VKMS 路线,也就是虚拟 KMS 显示器。
当时检查 VKMS 的方式大概是:
1 | |
那时候已经能看到:
vkms模块加载成功;- 存在
cardX-Virtual-1; status: connected;modes里也有多种分辨率,甚至包括 2560x1440。
但最后没有把 VKMS 作为主方案,原因也很现实:
- 目标并不是“有个软件虚拟屏就行”;
- 还希望它尽量接近真实显示器;
- RustDesk 和 GNOME/Xorg 的配合要尽量稳定;
- 后来尝试在 GDM/Xorg 层面做全局固定配置时,还一度把图形会话弄得不太正常。
所以 VKMS 更像一条技术上可行、但不够符合实际使用目标的路线。
真实 EDID 固定方案:只解决了一半
为了尽量模拟真实显示器,后面又尝试过把真实显示器的 EDID 固定给指定输出口。
思路是:
- 从真实显示器抓原始 EDID;
- 放到
/lib/firmware/edid/; - 通过 GRUB 内核参数绑定到目标输出。
示意命令如下:
1 | |
GRUB 参数类似这样:
1 | |
更新启动环境:
1 | |
这一阶段的结论很关键:
- 真实显示器接上时,GNOME/Xorg 工作正常;
- 换成欺骗器后,DRM 层有时仍能看到
connected; - 但 Xorg 中所有 RandR 输出依然可能全部显示
disconnected。
也就是说,固定 EDID 可以让 DRM 层更像“真的插了显示器”,却不能保证 Xorg 一定接受这条独显输出链。
核心定位:RustDesk 看的是 Xorg 层
真正把问题打透的,是在 Xorg 会话环境里直接跑 xrandr --query。
大致命令如下:
1 | |
对比非常明显。
接真实显示器时:
xrandr --query里能看到类似DP-N connected primary 2560x1440+0+0的输出。
换成独显链路上的欺骗器时:
xrandr --query中所有输出都可能是disconnected;- 桌面尺寸虽然可能还保留着之前的数值,但实际上没有活跃输出。
这就是 RustDesk 报“没有显示器”的直接原因。
RustDesk 关心的是当前图形桌面里有没有活跃显示器,而不是 /sys/class/drm 里某个 connector 是否被底层驱动勉强识别成 connected。
进一步用 xrandr --listproviders 看过之后,又确认当前 GNOME/Xorg 桌面跑在独显 provider 上,而核显是另一套输出路径。于是问题就更清楚了:
- DRM 层识别到某条链路,不等于当前桌面真的在使用它;
- 如果桌面、输出和欺骗器不在同一条 provider 路径上,RustDesk 还是会认为“没有显示器”。
关键转折:改走核显输出
后面的转折其实很简单:
- 不再继续折腾独显输出;
- 把 DP 适配器/欺骗器改插到核显的 DP 输出口。
结果是几乎立刻见效:
- 不需要额外复杂配置;
- 图形桌面可以正常工作;
- RustDesk 也能正常识别显示器。
这一步实际上把很多原本叠加在一起的问题一起绕开了:
- 独显输出链路和转接器的兼容性;
- Xorg provider 与当前桌面绑定关系;
- DRM 层和 Xorg 层状态不一致带来的额外复杂度。
所以这次排障最重要的判断,不是某个参数怎么配,而是及时放弃错误路线。
如果长期目标是“无头 + GNOME + RustDesk + 尽量接近真实显示器”,那么核显输出通常比继续死磕独显路径更合理。
当前剩下的问题:分辨率和刷新率偏低
切到核显路径后,系统已经能稳定工作了,剩下的问题只剩显示模式不够理想。
新的 DP 适配器默认能力偏保守:
- 2K 只有 30Hz;
- 4K 只有 17Hz。
看到这种情况,判断重点是:到底是 EDID 太保守,还是链路带宽本身就不够。
如果只是 EDID 保守,那么还有调整空间:
- 先用
xrandr手工添加更高模式; - 如果稳定,再考虑做自动化脚本或者自定义 EDID 固化。
如果链路带宽本来就不够,那么即使强行加模式,也大概率会出现:
- 模式设置失败;
- 黑屏;
- 闪回低刷新率;
- 根本点不亮。
换句话说,所谓“强制调”只能释放硬件本来就有的能力,不能突破物理上限。
用 xrandr 手工加一个 2K60 模式
当前最值得做的一步,是先手工测试一次 2560x1440@60。
先确认当前输出名:
1 | |
生成 2560x1440@60 的时序:
1 | |
一般会得到类似下面这样的 modeline:
1 | |
然后注意一个很容易踩的坑:xrandr --newmode 后面不要把 Modeline 这个词原样带上。
正确写法:
1 | |
完整模板就是:
1 | |
错误示例则是:
1 | |
这种写法会把 Modeline 当成参数的一部分,直接导致解析错误。
这次排障的核心经验
整个过程里,我觉得最值得记下来的不是具体命令,而是判断顺序:
- 先分清楚自己在看的是 DRM/KMS 层,还是 Xorg/RandR 层;
- 不要把
/sys/class/drm里的connected直接等同于“桌面里真的有显示器”; - RustDesk 的报错往往是在反映图形会话状态,而不是在反映底层内核状态;
- 遇到“独显 + 转接器 + 欺骗器 + 多 provider”这种链路时,要尽早怀疑兼容性问题;
- 如果换一条更简单、更一致的输出路径就能解决,通常比继续堆配置更值得。
到这里,其实问题已经从“系统级图形链路不通”,收缩成了“怎么把默认模式调到更理想的分辨率/刷新率”。
这类问题至少已经不是架构性故障,而只是参数和硬件能力边界的问题了。