3. DMA-BUF 框架基础:Buffer共享、文件描述符传递、dma_buf_export/dma_buf_attach/dma_buf_map_attachment

DMA-BUF(Direct Memory Access Buffer)是 Linux 内核提供的一种通用缓冲区共享框架,专门用于在不同设备驱动、不同子系统之间高效、安全地共享物理内存。在 RK3399 多屏异显场景中,GPU、VPU、Camera、Display Controller 等硬件模块需要频繁交换帧缓冲区数据,DMA-BUF 正是实现这种零拷贝共享的核心机制。

3.1 DMA-BUF 核心概念与共享模型

DMA-BUF 的本质是一个物理内存块的抽象描述,它通过文件描述符(fd)在用户空间和内核空间之间传递,实现跨设备、跨进程的内存共享。其核心设计思想是:一次分配,多处映射。

关键优势: 避免数据拷贝,降低内存带宽消耗,提升显示性能。在 RK3399 多屏异显中,GPU 渲染完成的帧缓冲区通过 DMA-BUF 直接传递给 DRM/KMS 显示驱动,无需 CPU 介入拷贝。
DMA-BUF 共享模型架构图 DMA-BUF 共享模型架构 生产者 GPU / VPU / Camera 消费者 DRM/KMS / Display DMA-BUF 对象 物理内存 + 文件描述符 dma_buf_export() dma_buf_attach() 用户空间:通过 fd 传递,mmap() 映射访问 内核空间:dma_buf_ops 回调 + sg_table 管理

3.2 核心 API 详解

DMA-BUF 框架提供了一套完整的 API 用于缓冲区的生命周期管理。理解这些 API 是掌握多屏异显配置的基础。

API 函数 角色 功能描述 返回值
dma_buf_export() 生产者 创建并导出一个 DMA-BUF 对象,关联物理内存和操作回调 struct dma_buf *
dma_buf_fd() 生产者 将 dma_buf 对象转换为文件描述符,用于跨进程传递 int fd
dma_buf_get() 消费者 通过文件描述符获取 dma_buf 对象引用 struct dma_buf *
dma_buf_attach() 消费者 将 DMA-BUF 绑定到特定设备,建立设备与缓冲区的关联 struct dma_buf_attachment *
dma_buf_map_attachment() 消费者 获取设备可访问的 DMA 地址(sg_table),完成映射 struct sg_table *
dma_buf_unmap_attachment() 消费者 解除设备映射,释放 DMA 地址 void
dma_buf_detach() 消费者 解除设备与 DMA-BUF 的绑定关系 void
dma_buf_put() 双方 释放 dma_buf 引用计数,计数归零时释放内存 void

3.3 生产者流程:dma_buf_export()

生产者(如 GPU 驱动)负责分配物理内存并导出为 DMA-BUF。核心步骤包括:

  1. 分配物理内存:通过 dma_alloc_coherent() 或 shmem 分配连续/非连续物理页。
  2. 填充导出参数:构造 struct dma_buf_export_info,指定名称、操作回调、大小、标志位等。
  3. 调用导出函数:dma_buf_export() 返回 struct dma_buf * 对象。
  4. 转换为 fd:dma_buf_fd() 生成文件描述符,传递给消费者。
// 生产者导出 DMA-BUF 示例代码
struct dma_buf *dmabuf;
struct dma_buf_export_info exp_info = {
    .exp_name = "rk3399_gpu_buffer",
    .ops = &rk_gpu_dmabuf_ops,      // 自定义回调函数集
    .size = fb_size,                 // 帧缓冲区大小
    .flags = O_RDWR,                 // 读写权限
    .priv = gpu_private_data,        // 私有数据
};

dmabuf = dma_buf_export(&exp_info);
if (IS_ERR(dmabuf)) {
    pr_err("Failed to export DMA-BUF\n");
    return PTR_ERR(dmabuf);
}

// 转换为文件描述符
int fd = dma_buf_fd(dmabuf, O_CLOEXEC);
// 将 fd 通过 ioctl 或 socket 传递给消费者进程
实践要点: 在 RK3399 平台上,dma_buf_export_info.flags 通常设置为 O_RDWR,确保显示控制器和 GPU 都能读写缓冲区。如果只读共享,可设置为 O_RDONLY 以增强安全性。

3.4 消费者流程:dma_buf_attach() + dma_buf_map_attachment()

消费者(如 DRM/KMS 显示驱动)通过文件描述符获取 DMA-BUF,然后绑定到自己的设备并映射出可访问的 DMA 地址。

消费者 DMA-BUF 映射流程 消费者 DMA-BUF 映射流程 1. dma_buf_get(fd) 获取 dma_buf 对象 2. dma_buf_attach() 绑定到显示设备 3. dma_buf_map_attachment() 获取 sg_table 4. 配置显示 设置 DMA 地址 dma_buf_map_attachment() 内部机制 • 调用 dma_buf->ops->map_dma_buf() 回调 • 生成 struct sg_table,包含物理页的 DMA 地址和长度 • 根据设备 IOMMU 配置,返回设备可访问的 DMA 地址 • 在 RK3399 上,VOP 通过 IOMMU 映射得到连续的虚拟地址
// 消费者获取并映射 DMA-BUF 示例代码
int fd = msg->dmabuf_fd;  // 从生产者传递过来的文件描述符

// 1. 通过 fd 获取 dma_buf 对象
struct dma_buf *dmabuf = dma_buf_get(fd);
if (IS_ERR(dmabuf)) {
    dev_err(dev, "Failed to get dma_buf from fd\n");
    return PTR_ERR(dmabuf);
}

// 2. 将 DMA-BUF 绑定到显示设备
struct dma_buf_attachment *attach;
attach = dma_buf_attach(dmabuf, dev);
if (IS_ERR(attach)) {
    dev_err(dev, "Failed to attach dma_buf\n");
    dma_buf_put(dmabuf);
    return PTR_ERR(attach);
}

// 3. 映射得到设备可访问的 DMA 地址
enum dma_data_direction dir = DMA_TO_DEVICE;
struct sg_table *sgt = dma_buf_map_attachment(attach, dir);
if (IS_ERR(sgt)) {
    dev_err(dev, "Failed to map attachment\n");
    dma_buf_detach(dmabuf, attach);
    dma_buf_put(dmabuf);
    return PTR_ERR(sgt);
}

// 4. 使用 sg_table 中的 DMA 地址配置显示控制器
// sgt->sgl->dma_address 即为 VOP 可访问的起始地址
vop_configure_plane(vop, sgt->sgl->dma_address, fb_width, fb_height);

// 5. 使用完毕后解除映射和绑定
dma_buf_unmap_attachment(attach, sgt, dir);
dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);

3.5 文件描述符传递机制

文件描述符是 DMA-BUF 跨进程共享的桥梁。在 RK3399 多屏异显场景中,通常通过以下方式传递:

传递方式 适用场景 示例
ioctl 参数传递 同一进程内的驱动模块间 GPU 驱动通过 DRM ioctl 将 fd 传给 KMS
Unix Domain Socket 不同进程间传递 SurfaceFlinger 将 fd 传给 HWC
Binder 机制 Android 系统服务间 Camera HAL 将 fd 传给 Display HAL
memfd_create() 用户空间直接创建共享内存 测试工具或简单应用
安全注意事项: 文件描述符传递时,接收方必须验证 fd 的有效性。恶意或损坏的 fd 可能导致内核崩溃。建议在驱动层使用 dma_buf_get() 后检查返回值,并配合 fdget() 进行安全校验。

3.6 RK3399 平台 DMA-BUF 实践要点

在 RK3399 多屏异显配置中,DMA-BUF 的使用需要特别注意以下硬件特性:

  • IOMMU 一致性:RK3399 的 VOP(Video Output Processor)通过 IOMMU 访问内存。DMA-BUF 映射时需确保 IOMMU 页表已正确配置,否则显示会出现花屏或黑屏。
  • 缓存一致性:GPU 和 VOP 可能使用不同的缓存策略。在 dma_buf_map_attachment() 时,dma_data_direction 参数需正确设置(通常为 DMA_BIDIRECTIONAL),并在必要时调用 dma_sync_sg_for_device() 进行缓存同步。
  • 内存对齐:RK3399 的显示控制器要求帧缓冲区地址按 256 字节对齐。导出 DMA-BUF 时,分配的物理内存必须满足此对齐要求。
  • 引用计数管理:每个 dma_buf_get() 必须对应一个 dma_buf_put(),避免内存泄漏。在热插拔或模式切换时,务必确保所有引用已释放。
性能优化建议: 在 RK3399 双屏异显场景中,建议为每个显示平面预分配 DMA-BUF 池,避免频繁的导出/映射操作。使用 dma_buf_begin_cpu_access() 和 dma_buf_end_cpu_access() 控制 CPU 访问权限,减少不必要的缓存刷新。

3.7 本章小结

DMA-BUF 框架是 RK3399 多屏异显的基石,它通过文件描述符实现了跨设备、跨进程的零拷贝内存共享。生产者使用 dma_buf_export() 导出缓冲区,消费者通过 dma_buf_attach() 和 dma_buf_map_attachment() 获取设备可访问的 DMA 地址。理解这一流程,是后续配置多路显示输出、实现 GPU 与 VOP 高效协作的前提。