Skip to content
Go back

排查 VMware 虚拟机中 vmwgfx 与 wlroots 的 DMA-BUF 导入失败问题

Edit page

排查 VMware 虚拟机中 vmwgfx 与 wlroots 的 DMA-BUF 导入失败问题

最近在 VMware 虚拟机中测试 river-classic + wlroots 0.20 时, 遇到了一个比较烦人的问题: 某些图形程序无法正常启动, Xwayland 会被打死后重启, 同时日志里反复出现 importing the supplied dmabufs failed 相关报错.

下面记录这次排查过程, 以及最终采用的修复思路.

故障现象

运行环境大致如下:

启动图形程序后, 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

这条日志说明问题出现在 wlrootslinux-dmabuf 路径里.

查看 wlroots 对应代码

顺着日志里的文件路径继续查看 wlroots 源码, 最终可以定位到 types/wlr_linux_dmabuf_v1.c 中的 check_import_dmabuf() 函数.

这段逻辑的主要流程是:

  1. 对客户端传来的 dma-buf fd 调用 drmPrimeFDToHandle()
  2. 将得到的 handle 再调用 drmCloseBufferHandle() 关闭
  3. 如果关闭失败, 则认为检查失败

这一步本质上是一个 sanity check. 它的目的不是正式渲染, 而是在 buffer 真正进入后续流程前先做一次 DRM 层面的验证.

观察 vmwgfx 上的实际行为

在当前环境下, 实际行为是:

这说明至少在 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() 失败不再变成致命错误.

这个做法确实可以让应用重新启动, 但它有一个明显问题:

这样做虽然避免了直接把客户端打死, 但有机会把问题转换成持续的资源泄漏.

泄漏的不是 wlr_linux_dmabuf_v1 结构体本身

这里还需要补充一点: wlr_linux_dmabuf_v1 本身只是一个很小的结构体, 真正可能增长的是:

因此, 这个问题不应该理解成“某个 struct 一直在长大”, 而应该理解成“底层资源没有被正确释放”.

解决方案

更稳妥的思路

既然 main_device_fd 只用于这项 sanity check, 那么一个更稳妥的方案是:

  1. 第一次 drmCloseBufferHandle() 失败时, 记录日志
  2. 立即关闭 main_device_fd
  3. main_device_fd 设为 -1
  4. 后续直接跳过这项 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;
}

这个思路相比社区里那种简单的一行补丁, 有几个明显优点:

为什么不直接保持上游的 fail-fast

如果保持上游原始逻辑, 那么只要 drmCloseBufferHandle() 失败, 就会直接返回 DMA-BUF 导入失败.

这在 vmwgfx 场景下的问题是:

因此在 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 先暴露了底层异常, wlroots 再通过过于激进的错误处理把它放大成了应用无法启动和 Xwayland 重启.

验证

应用上述修复思路后, 可以从以下几个方面验证效果:

1. Xwayland 不再频繁重启

首先确认日志中不再持续出现:

XWAYLAND: [destroyed object]: error 7: importing the supplied dmabufs failed

2. Failed to close buffer handle 不再持续刷屏

如果修复思路生效, 那么这类错误最多应该在第一次失败时出现, 后续不应再持续重复出现.

3. 观察内存/资源占用变化

如果之前使用的是社区里“只删 return false”的一行补丁, 那么修复后应重点观察:

虽然这不能证明所有路径都绝对没有泄漏, 但至少可以验证这条最明显的问题路径是否已经被止损.

总结

对于 VMware 虚拟机中的 vmwgfx + wlroots 组合, 这个问题不能简单地理解成“DMA-BUF 导入失败”.

更准确的理解应该是:

因此, 相比直接 fail-fast 或简单去掉 return false, 更稳妥的做法是:

在第一次 drmCloseBufferHandle() 失败后, 关闭 main_device_fd 并禁用后续的 DMA-BUF import sanity check.

这不是一个完美的上游统一修复, 但对于 VMware 用户来说, 它是一个更现实、也更保守的折中方案.

参考资料


Edit page
Share this post on:

Previous Post
Ubuntu 基础 NIS 客户端容器从 18.04 升级到 22.04 后 `getent passwd -s nis` 失效问题排查与兼容方案
Next Post
Docker 容器内 nslcd 启动卡死 (Hang) 问题排查