排查 VMware 虚拟机中 vmwgfx 与 wlroots 的 DMA-BUF 导入失败问题
最近在 VMware 虚拟机中测试 river-classic + wlroots 0.20 时, 遇到了一个比较烦人的问题: 某些图形程序无法正常启动, Xwayland 会被打死后重启, 同时日志里反复出现 importing the supplied dmabufs failed 相关报错.
下面记录这次排查过程, 以及最终采用的修复思路.
故障现象
运行环境大致如下:
- VMware 虚拟机
- Arch Linux
river-classicwlroots 0.20- 显卡驱动为
vmwgfx
启动图形程序后, river 日志中会出现类似如下内容:
error(wlroots): [types/wlr_linux_dmabuf_v1.c:223] Failed to close buffer handle for plane 0: Invalid argument
XWAYLAND: [destroyed object]: error 7: importing the supplied dmabufs failed
info(wlroots): [xwayland/server.c:217] Restarting Xwayland
从现象上看, 很容易把问题理解成 “DMA-BUF 本身不能用”. 但顺着日志继续排查后可以发现, 真正的问题出在 wlroots 的导入前检查逻辑, 以及 vmwgfx 在这条路径上的异常行为.
排查过程
查看日志定位关键报错
首先需要确认真正有价值的错误信息. 在这类问题中, Xwayland 重启往往只是结果, 真正关键的是前面的 wlroots 日志:
Failed to close buffer handle for plane 0: Invalid argument
这条日志说明问题出现在 wlroots 的 linux-dmabuf 路径里.
查看 wlroots 对应代码
顺着日志里的文件路径继续查看 wlroots 源码, 最终可以定位到 types/wlr_linux_dmabuf_v1.c 中的 check_import_dmabuf() 函数.
这段逻辑的主要流程是:
- 对客户端传来的 dma-buf fd 调用
drmPrimeFDToHandle() - 将得到的 handle 再调用
drmCloseBufferHandle()关闭 - 如果关闭失败, 则认为检查失败
这一步本质上是一个 sanity check. 它的目的不是正式渲染, 而是在 buffer 真正进入后续流程前先做一次 DRM 层面的验证.
观察 vmwgfx 上的实际行为
在当前环境下, 实际行为是:
drmPrimeFDToHandle()成功drmCloseBufferHandle()返回EINVAL
这说明至少在 wlroots 当前的预期里, vmwgfx 在这条 handle 生命周期路径上的行为并不符合预期.
为什么会导致 Xwayland 重启
wlroots 上游当前逻辑会把 drmCloseBufferHandle() 失败直接当成 DMA-BUF 导入失败, 然后给客户端返回:
importing the supplied dmabufs failed
对 Xwayland 来说, 这会进一步变成协议错误, 最终触发 Xwayland 退出并由 river/wlroots 重新拉起.
原因分析
这不是单纯的“DMA-BUF 一定不可用”
这里最容易误判的一点是: 看到 importing the supplied dmabufs failed, 就直接认为 DMA-BUF 本身一定不能工作.
但从代码路径来看, 当前失败点其实发生在 导入前的 sanity check 阶段.
换句话说:
- 底层确实出现了异常行为
- 但这一步异常并不自动等价于“真实渲染路径一定不可用”
main_device_fd 只是检查用的 FD
进一步查看结构体定义可以发现, wlr_linux_dmabuf_v1 中的 main_device_fd 本来就是用于检查客户端传入的 fd 是否可被当前 DRM 设备接受.
它不是整个渲染链路里必须长期保留的核心 fd, 这一点很重要, 因为它决定了后续修复思路.
社区常见的一行补丁为什么不够稳妥
社区里比较常见的一种 workaround, 是直接把这里的:
return false;
删掉, 也就是让 drmCloseBufferHandle() 失败不再变成致命错误.
这个做法确实可以让应用重新启动, 但它有一个明显问题:
drmPrimeFDToHandle()已经创建了 handledrmCloseBufferHandle()失败, 说明 handle 没有正常释放- 代码仍然继续执行
- 后续每次检查仍可能继续留下新的 handle
这样做虽然避免了直接把客户端打死, 但有机会把问题转换成持续的资源泄漏.
泄漏的不是 wlr_linux_dmabuf_v1 结构体本身
这里还需要补充一点: wlr_linux_dmabuf_v1 本身只是一个很小的结构体, 真正可能增长的是:
- DRM/GEM handle
- render node 相关资源
- GPU/shm 映射
- 客户端 buffer 生命周期相关资源
因此, 这个问题不应该理解成“某个 struct 一直在长大”, 而应该理解成“底层资源没有被正确释放”.
解决方案
更稳妥的思路
既然 main_device_fd 只用于这项 sanity check, 那么一个更稳妥的方案是:
- 第一次
drmCloseBufferHandle()失败时, 记录日志 - 立即关闭
main_device_fd - 将
main_device_fd设为-1 - 后续直接跳过这项 import check
伪代码大致如下:
if (drmCloseBufferHandle(linux_dmabuf->main_device_fd, handle) != 0) {
wlr_log_errno(WLR_ERROR,
"Failed to close buffer handle, disabling future DMA-BUF import checks");
close(linux_dmabuf->main_device_fd);
linux_dmabuf->main_device_fd = -1;
break;
}
这个思路相比社区里那种简单的一行补丁, 有几个明显优点:
- 不会继续把已经表现异常的
DRM FD带入后续检查 - 不会像上游 fail-fast 那样直接把客户端和
Xwayland打死 - 更有机会把资源风险限制在第一次失败附近, 而不是持续累积
为什么不直接保持上游的 fail-fast
如果保持上游原始逻辑, 那么只要 drmCloseBufferHandle() 失败, 就会直接返回 DMA-BUF 导入失败.
这在 vmwgfx 场景下的问题是:
- 用户可见故障非常明显
Xwayland很容易被连带打死- 实际上这里失败的是检查逻辑, 不一定代表后续渲染一定不可用
因此在 VMware/vmwgfx 场景中, 这个策略过于激进.
如果追求最稳, 可以直接切到软件路径
如果当前目标是“先稳定可用”, 那么最保守的办法仍然是走软件路径, 例如:
WLR_RENDERER=pixman river
或者对单个应用使用:
LIBGL_ALWAYS_SOFTWARE=1 应用名
此外也可以尝试关闭 modifier 或 cursor 相关路径:
WLR_EGL_NO_MODIFIERS=1
WLR_DRM_NO_MODIFIERS=1
WLR_NO_HARDWARE_CURSORS=1
必要时还可以继续尝试:
WLR_DRM_NO_ATOMIC=1
社区现状
目前社区对这个问题的处理方式, 用一句话概括就是:
有 workaround, 但还没有看到一个已经在
wlroots上游正式合并, 并被普遍认为彻底解决vmwgfx这类问题的修复.
这也是为什么不同用户在 VMware 虚拟机中运行 wlroots 系 compositor 时, 往往会组合使用:
- 本地补丁
- 环境变量
- 软件渲染
来获得一个可接受的稳定性.
这个问题更像 vmwgfx 的问题, 还是 wlroots 的问题
如果一定要做归因, 我更倾向于这样描述:
底层触发点更像是
vmwgfx的兼容性问题, 但把问题放大成用户可见故障的是wlroots的处理方式.
原因是:
vmwgfx先给出了不符合预期的底层行为, 即drmPrimeFDToHandle()成功而drmCloseBufferHandle()返回EINVALwlroots再把这个异常直接升级成了客户端可见的致命错误
所以这里不能简单地下结论说“纯粹是谁的锅”, 更合适的说法是:
vmwgfx先暴露了底层异常,wlroots再通过过于激进的错误处理把它放大成了应用无法启动和Xwayland重启.
验证
应用上述修复思路后, 可以从以下几个方面验证效果:
1. Xwayland 不再频繁重启
首先确认日志中不再持续出现:
XWAYLAND: [destroyed object]: error 7: importing the supplied dmabufs failed
2. Failed to close buffer handle 不再持续刷屏
如果修复思路生效, 那么这类错误最多应该在第一次失败时出现, 后续不应再持续重复出现.
3. 观察内存/资源占用变化
如果之前使用的是社区里“只删 return false”的一行补丁, 那么修复后应重点观察:
river进程的RSS/PSSrenderD128相关映射vmwgfx占用是否仍持续上涨
虽然这不能证明所有路径都绝对没有泄漏, 但至少可以验证这条最明显的问题路径是否已经被止损.
总结
对于 VMware 虚拟机中的 vmwgfx + wlroots 组合, 这个问题不能简单地理解成“DMA-BUF 导入失败”.
更准确的理解应该是:
vmwgfx在DRM handle生命周期这条路径上存在不符合wlroots预期的行为wlroots又把这个异常当成致命错误处理- 社区常见的一行补丁虽然能恢复可用性, 但可能引入资源泄漏风险
因此, 相比直接 fail-fast 或简单去掉 return false, 更稳妥的做法是:
在第一次
drmCloseBufferHandle()失败后, 关闭main_device_fd并禁用后续的DMA-BUF import sanity check.
这不是一个完美的上游统一修复, 但对于 VMware 用户来说, 它是一个更现实、也更保守的折中方案.
参考资料
- wlroots
types/wlr_linux_dmabuf_v1.c - wlroots
include/wlr/types/wlr_linux_dmabuf_v1.h - Linux DRM /
drmPrimeFDToHandle(3) vmwgfx相关内核与 Mesa 讨论