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);
六、案例与复盘:一次花屏问题的定位
某项目在 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 通过 dma_buf_ops 建立契约,fence 保证访问顺序,fd 传递打通进程边界。四者缺一不可,任何一环处理不当,都会在 Vblank 调优中表现为掉帧、花屏或延迟抖动。