2. DRM Prime 框架介绍
DRM Prime 是 DRM 子系统中用于实现 DMA-BUF 导出与导入的核心机制。它解决了传统图形栈中跨设备、跨驱动共享显存时存在的内存拷贝与同步开销问题,是现代嵌入式图形系统(如基于 DRM/KMS 的显示控制器与 GPU 协作)的基础设施。
2.1 DRM Prime 的由来
在早期的 Linux 图形栈中,不同设备(如 GPU、显示控制器、视频编解码器)之间的缓冲区共享通常依赖以下方式:
- V4L2 导出/导入:仅适用于视频设备,与 DRM 驱动不通用。
- ION 内存管理器:Android 专用,未合入主线内核。
- 用户态拷贝:通过 CPU 在用户空间搬运数据,性能差、功耗高。
上述方案均存在 跨驱动框架不兼容 或 性能瓶颈 的问题。DRM Prime 应运而生,它基于内核通用 DMA-BUF 框架,为 DRM 驱动提供了一套标准化的缓冲区共享接口:
- 导出 (Export):将 DRM GEM 对象转换为一个
dma_buf文件描述符,供其他驱动或用户态使用。 - 导入 (Import):从文件描述符获取
dma_buf,并将其绑定到本地 GEM 对象,实现零拷贝访问。
dma_buf 接口实现跨设备共享。
2.2 DRM Prime 与 DMA-BUF 的关系
理解 DRM Prime 必须首先理解 DMA-BUF。DMA-BUF 是内核提供的一个通用框架,用于在设备驱动之间共享 DMA 缓冲区(物理连续或通过 IOMMU/SMMU 映射的缓冲区)。DRM Prime 是 DMA-BUF 在 DRM 子系统中的 封装与扩展。
| 维度 | DMA-BUF(通用框架) | DRM Prime(DRM 实现) |
|---|---|---|
| 定位 | 内核级缓冲区共享基础设施 | DRM 驱动对 DMA-BUF 的标准化接口 |
| 核心对象 | struct dma_buf |
struct drm_gem_object + struct dma_buf |
| 用户态接口 | 无直接用户态接口 | DRM_IOCTL_PRIME_HANDLE_TO_FDDRM_IOCTL_PRIME_FD_TO_HANDLE |
| 主要使用者 | V4L2、ALSA、RDMA、DRM 等 | GPU、Display Controller、Codec |
| 生命周期管理 | 引用计数 + 文件描述符 | GEM 对象引用 + DMA-BUF 引用 |
下图展示了 DRM Prime 在 DMA-BUF 框架中的位置:
2.3 核心数据结构
DMA-BUF 框架的核心由三个数据结构组成:struct dma_buf、struct dma_buf_attachment 和 struct sg_table。DRM Prime 在此基础上增加了 GEM 对象的关联。
2.3.1 struct dma_buf
struct dma_buf 是 DMA-BUF 框架的 根对象,代表一个被共享的 DMA 缓冲区。它封装了缓冲区的元数据、访问操作和生命周期管理。
struct dma_buf {
size_t size; // 缓冲区大小(字节)
struct file *file; // 关联的文件对象(用于引用计数和 fd 映射)
struct list_head attachments; // 所有 attachment 的链表头
const struct dma_buf_ops *ops; // 操作函数指针(map, unmap, release 等)
struct mutex lock; // 保护 attachments 链表的锁
void *priv; // 私有数据(DRM 中指向 drm_gem_object)
// ... 其他字段(如 resv 用于同步)
};
| 字段 | 说明 | DRM Prime 中的角色 |
|---|---|---|
size |
缓冲区大小,导出时由 GEM 对象决定 | 与 drm_gem_object->size 一致 |
file |
文件对象,用户态通过 fd 引用 | 通过 dma_buf_fd() 创建 |
attachments |
记录所有导入该缓冲区的设备 | 每个导入设备创建一个 attachment |
ops |
底层操作函数,由导出驱动实现 | DRM 驱动通过 gem_prime_export 回调提供 |
priv |
指向导出驱动的私有数据 | 指向 drm_gem_object |
2.3.2 struct dma_buf_attachment
struct dma_buf_attachment 代表一个 导入设备 对 dma_buf 的绑定关系。每个导入设备在访问缓冲区之前,必须先创建一个 attachment,并通过它获取 scatterlist(用于 DMA 操作)。
struct dma_buf_attachment {
struct dma_buf *dmabuf; // 指向关联的 dma_buf
struct device *dev; // 导入设备的 struct device
struct list_head node; // 挂入 dma_buf->attachments 链表
// ... 其他字段(如 dma_map_attrs, priv 等)
};
| 字段 | 说明 | 典型值/用途 |
|---|---|---|
dmabuf |
所属的 dma_buf 对象 |
由 dma_buf_attach() 自动设置 |
dev |
导入设备的 device 结构体 | 用于 IOMMU/SMMU 地址空间映射 |
node |
链表节点,用于挂入 dmabuf->attachments |
遍历所有导入者时使用 |
dma_buf 可以被多个设备同时导入,每个导入设备对应一个 dma_buf_attachment。通过 dma_buf_map_attachment() 获取的 sg_table 是设备相关的,不同设备可能得到不同的物理地址映射(取决于 IOMMU 配置)。
2.3.3 struct sg_table
struct sg_table 是 DMA-BUF 框架中用于描述 物理内存布局 的核心结构。它包含一个 scatterlist 数组,每个条目描述一段连续的物理内存区域。
struct sg_table {
struct scatterlist *sgl; // scatterlist 数组
unsigned int nents; // 实际使用的条目数
unsigned int orig_nents; // 原始条目数(可能包含合并前的条目)
};
在 DRM Prime 中,sg_table 的获取流程如下:
- 导出驱动调用
dma_buf->ops->map_dma_buf()生成sg_table。 - 导入驱动通过
dma_buf_map_attachment()获取该sg_table。 - 导入驱动使用
sg_dma_address()和sg_dma_len()获取 DMA 地址和长度。
2.4 数据结构关系图
下图展示了 DRM Prime 中核心数据结构之间的关联:
2.5 DRM Prime 操作流程概览
基于上述数据结构,DRM Prime 的导出与导入操作遵循以下步骤:
| 步骤 | 导出 (Export) | 导入 (Import) |
|---|---|---|
| 1 | 用户态调用 DRM_IOCTL_PRIME_HANDLE_TO_FD |
用户态调用 DRM_IOCTL_PRIME_FD_TO_HANDLE |
| 2 | DRM Core 查找 GEM 对象 | DRM Core 通过 fd 获取 dma_buf |
| 3 | 调用 dma_buf_export() 创建 dma_buf |
调用 dma_buf_get() 增加引用计数 |
| 4 | 通过 dma_buf_fd() 获取文件描述符 |
创建新的 GEM 对象并关联 dma_buf |
| 5 | 返回 fd 给用户态 | 返回 GEM handle 给用户态 |
dma_buf_attachment 建立设备与缓冲区的映射关系。实际的 DMA 操作(如 GPU 渲染、显示扫描)由导入设备通过 sg_table 发起,整个过程无需 CPU 介入数据搬运。
2.6 小结
本章介绍了 DRM Prime 的由来、与 DMA-BUF 的关系以及三个核心数据结构:
- struct dma_buf:代表共享缓冲区本身,是跨设备共享的根对象。
- struct dma_buf_attachment:代表导入设备与缓冲区的绑定关系,每个导入设备一个。
- struct sg_table:描述物理内存布局,供导入设备进行 DMA 操作。