4. GEM (Graphics Execution Manager) 内存管理
GEM(Graphics Execution Manager)是 DRM 框架中负责图形缓冲区管理的核心子系统。它提供了一套统一的 API,用于在用户空间(如 Mesa、Wayland)和内核空间之间高效地分配、共享和同步显存。本章将深入剖析 GEM 对象的生命周期、Buffer 对象的管理机制以及 DMA-BUF 在跨设备显存共享中的关键作用。
4.1 GEM 对象创建与销毁
GEM 对象是 DRM 驱动中表示一块连续物理或虚拟显存的基本单位。每个 GEM 对象在内核中由一个 struct drm_gem_object 结构体描述,并通过一个唯一的句柄(Handle)暴露给用户空间。
4.1.1 核心数据结构
| 成员 | 类型 | 描述 |
|---|---|---|
size |
size_t |
对象大小(字节) |
handle_count |
u32 |
用户空间句柄引用计数 |
dev |
struct drm_device * |
所属 DRM 设备 |
filp |
struct drm_file * |
创建该对象的文件句柄上下文 |
dma_buf |
struct dma_buf * |
关联的 DMA-BUF 导出缓冲区 |
4.1.2 创建流程
用户空间通过 DRM_IOCTL_GEM_CREATE 请求创建 GEM 对象。内核驱动会执行以下步骤:
- 参数校验:检查请求的大小是否合法(非零且不超过设备限制)。
- 分配物理内存:调用驱动实现的
gem_create_object回调,通常使用dma_alloc_attrs()或连续内存分配器(CMA)。 - 初始化对象:填充
drm_gem_object结构体,设置size、dev等字段。 - 生成句柄:通过
drm_gem_handle_create()为用户空间分配一个唯一的整数句柄。 - 返回句柄:将句柄通过 ioctl 返回值传递给用户空间。
idr 机制维护句柄到对象的映射。
4.1.3 销毁流程
当用户空间调用 DRM_IOCTL_GEM_CLOSE 或进程退出时,内核会减少句柄引用计数。当 handle_count 降为零时,触发 GEM 对象的释放:
- 调用
drm_gem_object_release()释放内核资源。 - 调用驱动实现的
gem_free_object回调,归还物理内存。 - 如果对象已导出为 DMA-BUF,则需等待所有外部引用释放。
4.2 Buffer 对象管理
Buffer 对象(BO)是 GEM 对象在驱动层面的具体实现,通常包含物理内存地址、大小、缓存模式、同步对象等附加信息。驱动需要管理 BO 的分配、映射、缓存一致性以及同步。
4.2.1 BO 管理核心操作
| 操作 | 描述 | 关键函数/回调 |
|---|---|---|
| 分配 | 从系统内存或 VRAM 中分配物理页 | drm_gem_cma_create() |
| 映射 | 将物理内存映射到用户空间或内核虚拟地址 | drm_gem_cma_vmap() |
| 缓存同步 | 确保 CPU 与 GPU 之间的缓存一致性 | dma_sync_single_for_device() |
| 绑定 | 将 BO 绑定到 GPU 的页表或显示控制器地址空间 | 驱动自定义 bind() 回调 |
4.2.2 缓存模式与一致性
嵌入式系统中,CPU 和 GPU 通常共享同一片物理内存(如 CMA 区域)。为避免缓存不一致导致的数据错误,驱动必须根据 BO 的使用场景选择合适的缓存模式:
- 非缓存(Non-cached):适用于帧缓冲区,CPU 写入后 GPU 立即可见,但性能较低。
- 写合并(Write-combine):适用于 GPU 读取、CPU 写入的场景(如纹理上传),性能优于非缓存。
- 回写(Write-back):适用于 CPU 频繁读写的临时缓冲区,需要显式缓存刷新。
pgprot_writecombine() 设置页表属性。
4.3 DMA-BUF 与显存共享
DMA-BUF 是 Linux 内核提供的一种跨设备、跨驱动共享 DMA 缓冲区的机制。在 DRM 框架中,GEM 对象可以通过 DMA-BUF 导出,使得其他设备(如 VPU、ISP、显示控制器)能够直接访问同一块物理内存,避免不必要的数据拷贝。
4.3.1 DMA-BUF 生命周期
- 导出(Export):拥有 GEM 对象的驱动调用
dma_buf_export(),创建一个struct dma_buf,并关联到 GEM 对象。 - 获取文件描述符:通过
DRM_IOCTL_GEM_DMA_BUF或dma_buf_fd()获取一个文件描述符,传递给其他进程或驱动。 - 导入(Import):接收方驱动通过文件描述符调用
dma_buf_get()获取struct dma_buf,并映射到自己的地址空间。 - 同步与访问:使用
dma_buf_begin_cpu_access()和dma_buf_end_cpu_access()管理访问同步。 - 释放:所有引用计数归零后,DMA-BUF 自动销毁,底层 GEM 对象随之释放。
4.3.2 DMA-BUF 共享架构
4.3.3 驱动实现要点
在嵌入式 LCD 控制器驱动中实现 DMA-BUF 共享时,需要关注以下回调:
// 导出回调:将 GEM 对象包装为 DMA-BUF
struct dma_buf *(*gem_export_object)(struct drm_gem_object *obj, int flags);
// 导入回调:从 DMA-BUF 创建 GEM 对象
struct drm_gem_object *(*gem_import_object)(struct drm_device *dev,
struct dma_buf *dma_buf);
// 驱动示例:使用 dma_buf_export 创建共享缓冲区
static struct dma_buf *my_gem_export(struct drm_gem_object *obj, int flags)
{
struct dma_buf_export_info exp_info = {
.exp_name = "my_drm_driver",
.owner = THIS_MODULE,
.ops = &my_dma_buf_ops,
.size = obj->size,
.flags = flags,
.priv = obj,
};
return dma_buf_export(&exp_info);
}
4.4 本章小结
GEM 内存管理是 DRM 框架的基石,它通过句柄机制隔离用户空间与内核空间的地址空间,通过 DMA-BUF 实现跨设备零拷贝共享。理解 GEM 对象的创建与销毁、Buffer 对象的缓存管理以及 DMA-BUF 的导入导出流程,是开发高效、稳定的嵌入式显示驱动的关键。