4. Buffer Sharing 机制:DMA-BUF 如何实现跨驱动、跨设备的零拷贝内存共享

在多媒体与图形渲染管线中,数据需要在 GPU、Display Controller、Camera ISP、Video Codec 等多个硬件单元之间流转。传统的拷贝方式不仅消耗 CPU 带宽,还会引入延迟和功耗开销。DMA-BUF(Direct Memory Access Buffer)是 Linux 内核提供的一种通用缓冲区共享机制,它允许不同设备驱动程序通过 文件描述符(fd) 传递物理内存的访问权,从而实现真正的零拷贝共享。

核心目标: 避免数据在设备间逐级拷贝,通过共享同一块物理内存,让多个硬件单元直接访问,降低延迟与功耗。

4.1 DMA-BUF 的核心架构与对象模型

DMA-BUF 在内核中抽象为 struct dma_buf 对象,它并不直接管理物理页面,而是通过 附件(Attachment) 和 映射(SGT / Scatter-Gather Table) 机制,将物理内存暴露给不同的驱动。

DMA-BUF 核心对象模型 生产者驱动 (Camera / GPU Render) struct dma_buf (文件描述符 + 引用计数) 消费者驱动 (Display / Video Codec) dma_buf_export() dma_buf_attach() dma_buf_attachment sg_table (SGT) struct page *pages[] 关键点 fd 传递 → 跨进程/跨驱动 SGT 映射 → 设备 DMA 地址

4.2 零拷贝共享的完整生命周期

DMA-BUF 的共享流程分为 导出(Export)、附加(Attach)、映射(Map) 和 同步(Sync) 四个阶段。以下以 Camera 采集帧送给 GPU 渲染为例:

阶段 调用接口 动作描述 参与者
1. 导出 dma_buf_export() Camera 驱动分配物理页面,创建 dma_buf 对象,返回 fd Camera ISP 驱动
2. 传递 fd 通过 IPC / ioctl 传递 用户空间将 fd 从 Camera 节点传递给 GPU 节点 用户空间 / libcamera
3. 附加 dma_buf_attach() GPU 驱动创建 attachment,记录设备与内存访问权限 GPU 驱动
4. 映射 dma_buf_map_attachment() 生成 SGT,将物理页面映射到 GPU 的 IOMMU 地址空间 GPU 驱动 / IOMMU
5. 同步 dma_buf_begin/end_cpu_access() 保证 Cache 一致性,防止 CPU 与设备访问冲突 CPU / 设备驱动
性能关键: 第 4 步的 SGT 映射是零拷贝的核心。GPU 通过 IOMMU 直接访问 Camera 的物理页面,无需任何数据搬移。映射完成后,GPU 可以直接将帧数据作为纹理采样。

4.3 跨设备共享的 DMA 地址转换

不同设备可能挂载在不同的 IOMMU 域或总线(如 PCIe、AMBA)上,物理页面需要转换为设备可见的 DMA 地址。DMA-BUF 通过 Scatter-Gather Table (SGT) 为每个附件独立维护地址映射:

跨设备 DMA 地址映射(SGT 机制) 物理页面 A 物理页面 B 物理页面 C SGT for GPU GPU DMA 地址 0xA000 GPU DMA 地址 0xA100 GPU DMA 地址 0xA200 (IOMMU 映射) SGT for Display Display DMA 地址 0xB000 Display DMA 地址 0xB100 Display DMA 地址 0xB200 (IOMMU 映射) 同一组物理页面,在不同设备 SGT 中拥有不同的 DMA 地址

4.4 同步与 Cache 一致性管理

当 CPU 或设备需要访问共享缓冲区时,必须通过 DMA-BUF 同步接口 保证数据一致性。Linux 内核提供了两种同步方向:

  • DMA_TO_DEVICE:CPU 写入完成,设备即将读取(Cache 刷出)。
  • DMA_FROM_DEVICE:设备写入完成,CPU 即将读取(Cache 失效)。
  • DMA_BIDIRECTIONAL:双向访问,需要完整的 Cache 维护。
常见陷阱: 如果驱动在映射后未调用 dma_buf_end_cpu_access() 就启动 DMA 传输,可能导致设备读到过时的 Cache 数据,造成画面撕裂或计算错误。在 Vblank 中断上下文中尤其要注意同步顺序。

4.5 典型应用场景:GPU 与 Display 的帧共享

在 Android 或 Linux 桌面合成器中,DMA-BUF 是连接 GPU 渲染与 Display 扫描输出的关键桥梁:

  1. GPU 渲染完成后,将 framebuffer 导出为 DMA-BUF fd。
  2. Display Controller 驱动通过 dma_buf_attach() 获取该 fd。
  3. 在 Vblank 中断到来时,Display 驱动切换 scanout 地址到该 DMA-BUF 的物理页面。
  4. 整个过程中,framebuffer 数据从未离开物理内存,实现零拷贝合成。
GPU → Display 零拷贝帧传递 GPU 渲染 导出 dma_buf fd DMA-BUF 物理页面 (Framebuffer) Display Controller Vblank 切换 scanout 零拷贝路径 GPU 写入 → 物理页面 → Display 直接扫描,无 CPU 拷贝、无内存复制

4.6 性能对比:传统拷贝 vs DMA-BUF 共享

指标 传统拷贝(CPU memcpy) DMA-BUF 零拷贝共享
延迟 高(μs 级拷贝 + 上下文切换) 极低(仅指针传递 + IOMMU 映射)
CPU 占用 高(每次拷贝消耗 CPU 周期) 几乎为零(仅管理开销)
内存带宽 额外消耗(读 + 写) 无额外消耗
功耗 高(频繁内存访问) 低(数据不动,只动指针)
适用场景 小数据量、跨 CPU 核 大帧数据、跨设备流水线
实战建议: 在 Vblank 中断驱动的合成管线中,优先使用 DMA-BUF 传递 framebuffer。配合 dma_fence 机制,可以精确控制 GPU 渲染完成与 Display 扫描开始的时序,避免撕裂。