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 介入拷贝。
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。核心步骤包括:
- 分配物理内存:通过
dma_alloc_coherent()或shmem分配连续/非连续物理页。 - 填充导出参数:构造
struct dma_buf_export_info,指定名称、操作回调、大小、标志位等。 - 调用导出函数:
dma_buf_export()返回struct dma_buf *对象。 - 转换为 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 示例代码
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 高效协作的前提。