4. DMA-BUF框架入门:dma-buf共享内存模型、文件描述符与同步机制

DMA-BUF(Direct Memory Access Buffer)是Linux内核提供的一种跨驱动、跨设备、跨进程的共享内存框架。在显示驱动场景中,它允许GPU、显示控制器(Display Controller)、视频编解码器、相机ISP等硬件模块直接访问同一块物理内存,而无需通过CPU进行数据拷贝。本章将深入剖析DMA-BUF的核心模型、文件描述符传递机制以及关键的同步原语。

核心价值: DMA-BUF是连接GPU渲染与Vblank刷新的“零拷贝”桥梁。理解其模型是进行联合调优的前提。

4.1 dma-buf共享内存模型

DMA-BUF的本质是一个内核对象,它代表了一块物理连续或通过IOMMU/SMMU映射的物理内存区域。该模型围绕“生产者-消费者”模式构建:

  • 生产者(Exporter): 分配并导出DMA-BUF。例如,GPU驱动分配显存后,将其导出为一个DMA-BUF。
  • 消费者(Importer): 导入并使用DMA-BUF。例如,显示控制器驱动导入该DMA-BUF,并将其地址配置为帧缓冲区。
  • Buffer对象: 内核中的 struct dma_buf 结构体,包含引用计数、附件列表、操作函数集(dma_buf_ops)等。
  • 附件(Attachment): 每个消费者在导入时创建一个 struct dma_buf_attachment,代表该设备与Buffer的一次绑定关系,并管理设备特定的映射(如IOMMU页表)。
关键数据结构关系:
struct dma_buf {
    size_t          size;           // Buffer大小
    struct file     *file;          // 关联的文件描述符
    struct list_head attachments;   // 附件链表
    const struct dma_buf_ops *ops;  // 操作函数
    // ... 其他字段
};

struct dma_buf_attachment {
    struct dma_buf  *dmabuf;        // 所属的dma-buf
    struct device   *dev;           // 绑定的设备
    struct list_head node;          // 链表节点
    // ... 映射状态
};

下图展示了DMA-BUF在GPU与Display之间的典型共享模型:

DMA-BUF 共享内存模型 DMA-BUF 共享内存模型:GPU → Display 生产者 (Exporter) GPU / 渲染驱动 消费者 (Importer) Display Controller struct dma_buf 物理内存 / 共享内存 引用计数 | 附件链表 Attachment (GPU) IOMMU映射 Attachment (Display) IOMMU映射 零拷贝路径:GPU渲染完成后,Display直接读取同一块物理内存,无需CPU拷贝。

4.2 文件描述符(FD)传递机制

DMA-BUF通过文件描述符(File Descriptor, FD)实现跨进程和跨驱动边界的安全传递。这是Unix哲学“一切皆文件”在DMA-BUF中的体现。

操作 系统调用 / API 说明
导出FD dma_buf_fd() 生产者将 struct dma_buf* 转换为一个整数FD,并返回给用户空间。
导入FD dma_buf_get() 消费者通过FD从内核获取对应的 struct dma_buf* 指针,增加引用计数。
关闭FD close() 用户空间关闭FD,内核减少引用计数;当计数归零时,释放DMA-BUF内存。
传递FD sendmsg() + SCM_RIGHTS 通过Unix Domain Socket在进程间传递FD,实现跨进程共享。
生命周期管理陷阱: FD是引用计数的句柄。生产者导出后应立即关闭本地FD,避免内存泄漏。消费者导入后,使用完毕也需调用 dma_buf_put() 或关闭FD。错误的引用计数管理会导致内存泄漏或UAF(Use-After-Free)。

下图展示了FD在用户空间与内核空间之间的流转过程:

DMA-BUF 文件描述符传递流程 DMA-BUF 文件描述符传递流程 用户空间 (Userspace) 进程A (GPU) 持有 FD = 7 进程B (Display) 持有 FD = 3 内核空间 (Kernel) struct dma_buf refcount = 2 dma_buf_fd() dma_buf_get() sendmsg(SCM_RIGHTS) FD传递不复制数据,仅传递内核对象的引用,实现零拷贝共享。

4.3 同步机制:Fence与DMA-BUF

在多设备共享内存时,必须解决生产者-消费者同步问题。例如,GPU正在渲染帧时,Display不能读取该帧。DMA-BUF本身不提供同步,而是通过内核的 DMA-Fence 框架实现。

DMA-Fence 核心概念:
  • Fence: 一个轻量级的同步对象,表示“某个事件是否已完成”。通常由硬件(如GPU)在提交作业时创建,并在作业完成时触发信号。
  • Explicit Fencing(显式同步): 用户空间显式传递Fence FD给驱动。驱动在访问DMA-BUF前必须等待Fence信号。
  • Implicit Fencing(隐式同步): 内核自动管理Fence,通常通过 dma_resv 结构体关联到DMA-BUF。

下表对比了两种同步模式在显示驱动中的典型应用:

特性 Explicit Fencing Implicit Fencing
控制粒度 用户空间精细控制,每个提交可指定多个Fence 内核自动管理,对用户空间透明
性能开销 较低,避免不必要的等待 可能引入额外等待,但实现简单
适用场景 Android HWC、现代DRM驱动(atomic API) 传统V4L2、简单显示驱动
API接口 drm_syncobj、sync_file dma_resv、dma_fence

下图展示了Explicit Fencing在GPU渲染与Vblank刷新之间的同步流程:

Explicit Fencing 同步流程 Explicit Fencing 同步流程 时间 1. GPU提交渲染 创建 Fence A 2. 传递Fence FD 用户空间传递 3. Display等待 等待 Fence A 信号 4. Vblank刷新 读取DMA-BUF Fence A 信号 关键点: Display Controller 在 Vblank 中断到来前,必须确保 Fence A 已信号化,否则会出现画面撕裂(Tearing)。 联合调优的目标:最小化 Fence 等待时间,使渲染完成与 Vblank 中断精确对齐。

4.4 本章小结

  • 共享内存模型: DMA-BUF通过 struct dma_buf 和 dma_buf_attachment 实现多设备零拷贝共享。
  • 文件描述符: FD是跨进程传递DMA-BUF引用的安全句柄,生命周期由引用计数管理。
  • 同步机制: DMA-Fence(显式/隐式)是解决生产者-消费者竞争的关键,Explicit Fencing在显示驱动中提供更优的性能控制。
下一阶段预告: 在掌握DMA-BUF基础后,我们将深入分析如何将DMA-BUF与Vblank中断进行联合调优,包括Fence等待时间测量、Buffer切换策略优化以及CPU-GPU-Display流水线延迟分析。