4. DMA-BUF核心概念

在 Vblank 中断与显示/渲染联合调优的链路中,DMA-BUF 是连接 GPU、显示控制器、视频编解码器与摄像头等异构硬件的“通用货币”。它解决的核心问题是:同一块物理内存如何在不复制数据的前提下,被多个设备驱动安全、有序地共享。理解 DMA-BUF,必须抓住四个关键词:共享模型、exporter/importer、fence 同步、文件描述符传递。

一、缓冲区共享模型:从“各自拷贝”到“零拷贝引用”

传统做法中,摄像头采集的数据要经过 CPU 拷贝到 GPU 可用的缓冲区,再由显示控制器读取,链路中多次 memcpy 既耗带宽又增延迟。DMA-BUF 引入的共享模型是:由一方分配并导出缓冲区,其他方以“引用”方式导入,物理内存只有一份。内核中用一个 struct dma_buf 对象代表这块共享缓冲区,它携带 size、flags、exporter 私有数据以及一组操作函数。

共享模型的关键约束是“生命周期由引用计数管理”。只要还有 importer 持有引用,缓冲区就不会被释放;当所有引用释放后,exporter 才回收内存。这避免了“显示还在扫描,GPU 已释放”的悬空访问。

二、Exporter 与 Importer:角色与职责

Exporter 是缓冲区的“生产者与管理者”,通常是 GPU 驱动、ION/CMA 分配器、V4L2 视频节点或 DRM 驱动。它负责分配内存、实现 dma_buf_ops、导出文件描述符。Importer 是“使用者”,如显示控制器驱动、视频编码器、GPU 渲染管线,它通过 fd 导入缓冲区,并调用 dma_buf_attach / dma_buf_map_attachment 获取设备可访问的 scatterlist 或 DMA 地址。

典型角色划分:
Exporter:DRM GPU 驱动、V4L2、ION、udmabuf
Importer:DRM 显示驱动、V4L2、GPU、VPU、ISP
中介:dma_buf 对象 + dma_buf_ops + fence

三、dma_buf_ops:共享缓冲区的“操作契约”

dma_buf_ops 是 exporter 必须实现的一组回调,importer 通过它完成映射、同步与释放。核心成员包括:

  • attach:把 importer 设备绑定到缓冲区,建立 device 与 buffer 的关系。
  • map_dma_buf / unmap_dma_buf:为 importer 设备建立/拆除 DMA 映射,返回 sg_table。
  • begin_cpu_access / end_cpu_access:CPU 访问前后的 cache 一致性处理。
  • mmap:支持用户态映射。
  • release:引用归零时释放底层内存。
  • vmap / vunmap:内核虚拟地址映射。

调优时常见问题是 importer 反复 attach/detach 导致映射开销大,或 cache 操作缺失导致画面撕裂、花屏。正确做法是缓存 sg_table,并在 CPU 与设备交替访问时严格配对 begin/end_cpu_access。

四、Fence 同步:让“谁先谁后”有据可依

共享缓冲区最大的风险是数据竞争:GPU 还没写完,显示控制器就开始扫描。DMA-BUF 用 fence 表达“这块缓冲区何时可用”。Exporter 在导出时附带一个 dma_fence,importer 在访问前等待该 fence 信号。fence 可以串联成链,形成跨设备的依赖顺序。

/* 简化示意:导出时附带 fence */
struct dma_buf *dbuf = dma_buf_export(&exp_info);
/* importer 侧等待 */
dma_resv_wait_timeout(dma_buf_resv(dbuf), false,
                      DMA_RESV_USAGE_READ, timeout);

在 Vblank 调优中,fence 与显示时序配合尤为关键:若 fence 在 Vblank 之后才 signal,就会错过当前帧,造成掉帧。因此要尽量让生产者提前完成,或使用“延迟提交 + fence 就绪即翻转”的策略。

五、文件描述符传递:跨进程共享的通道

DMA-BUF 对用户态暴露为一个文件描述符。进程 A 导出 fd 后,可通过 Unix domain socket 的 SCM_RIGHTS 辅助消息把 fd 传给进程 B,B 用 mmap 或 ioctl 导入。内核中 fd 只是句柄,真正的共享对象是 dma_buf,因此传递 fd 不会复制数据。

/* 发送 fd */
struct msghdr msg = {0};
char cmsg_buf[CMSG_SPACE(sizeof(int))];
msg.msg_control = cmsg_buf;
msg.msg_controllen = sizeof(cmsg_buf);
cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
sendmsg(sock, &msg, 0);
误区:把 fd 当成“内存地址”直接跨进程使用。fd 只在进程内有效,必须通过 SCM_RIGHTS 传递;且接收方要负责关闭 fd,否则引用计数泄漏,缓冲区永不释放。

六、案例与复盘:一次花屏问题的定位

某项目在 4K 视频播放时出现间歇性花屏。排查发现:VPU 解码输出通过 DMA-BUF 给显示控制器,但 importer 在 map_dma_buf 后未等待 fence,直接开始扫描;同时 CPU 侧在 begin_cpu_access 前就读取了缓冲区。修复方案是:在显示提交前调用 dma_resv_wait_timeout 等待写 fence,并补齐 cache 同步。修复后花屏消失,Vblank 命中率从 92% 提升到 99.6%。

概念作用调优关注点
dma_buf共享缓冲区内核对象引用计数、生命周期
exporter分配并导出缓冲区ops 实现完整、释放及时
importer导入并映射缓冲区缓存 sg_table、减少 attach 次数
fence表达缓冲区可用时机与 Vblank 对齐、避免超时
fd 传递跨进程共享句柄SCM_RIGHTS、及时 close
DMA-BUF 共享模型:exporter / importer / fence / fd Exporter GPU / V4L2 / ION 分配内存 + dma_buf_ops 导出 fd + 附带 fence dma_buf 对象 size / flags / priv 引用计数 + dma_resv 物理内存仅一份 Importer Display / VPU / ISP attach + map_dma_buf 等待 fence 后访问 export import fd 传递(SCM_RIGHTS) 进程 A 导出 fd → Unix socket 发送 → 进程 B 接收 fd → mmap / ioctl 导入,数据零拷贝 fence 同步 生产者 signal → 消费者 wait → 与 Vblank 对齐 dma_buf_ops 契约 attach / map / begin_cpu_access / release

总结来看,DMA-BUF 的共享模型是“一份内存、多方引用”,exporter 与 importer 通过 dma_buf_ops 建立契约,fence 保证访问顺序,fd 传递打通进程边界。四者缺一不可,任何一环处理不当,都会在 Vblank 调优中表现为掉帧、花屏或延迟抖动。