4. GEM 内存管理:GEM 基础对象、Buffer Object 管理、Dumb Buffer 与 Prime 共享
GEM(Graphics Execution Manager)是 DRM 框架中负责图形缓冲区管理的核心基础设施。它提供了一套统一的内存分配、共享与生命周期管理机制,使得用户空间应用(如 Mesa、Wayland Compositor)与内核驱动之间能够高效地传递显存对象。本章将深入剖析 GEM 的基础对象模型、Buffer Object 的生命周期、Dumb Buffer 的简易分配路径以及跨设备/跨进程的 Prime 共享机制。
4.1 GEM 基础对象模型
GEM 的核心抽象是 GEM Object,它在内核中对应一个 struct drm_gem_object 实例。每个 GEM Object 代表一块由 DRM 驱动管理的物理或虚拟内存区域,并关联一个全局唯一的句柄(Handle)供用户空间引用。
gem_create_object 回调分配,并关联到具体的 dma_buf 或后备存储(Backing Store)。用户空间通过 drmIoctl(DRM_IOCTL_GEM_OPEN) 获取句柄,内核维护引用计数以管理生命周期。
4.2 Buffer Object 生命周期管理
Buffer Object(BO)是 GEM Object 在驱动层面的具体化,通常包含物理内存页的分配与绑定。其生命周期遵循“创建 → 绑定 → 使用 → 解绑 → 销毁”的严格流程。
| 阶段 | 关键操作 | 内核函数/IOCTL | 说明 |
|---|---|---|---|
| 创建 | 分配 GEM Object 与后备内存 | drm_gem_create_object() |
驱动可重写 gem_create_object 回调 |
| 绑定 | 将物理页映射到 GPU 地址空间 | drm_gem_handle_create() |
生成用户空间句柄 |
| 使用 | CPU/GPU 访问缓冲区 | mmap / drm_gem_prime_export() |
通过 VMA 或 DMA-BUF 共享 |
| 解绑 | 释放句柄与映射关系 | drm_gem_handle_delete() |
用户空间调用 close() 触发 |
| 销毁 | 释放物理内存与 GEM Object | drm_gem_object_free() |
引用计数归零时自动触发 |
DRM_IOCTL_GEM_CLOSE 关闭句柄时,内核仅减少引用计数。只有当所有进程的句柄以及内核内部引用(如 GPU 提交)都释放后,GEM Object 才会被真正销毁。务必注意 内存泄漏 与 UAF(Use-After-Free) 问题。
4.3 Dumb Buffer:简化分配路径
Dumb Buffer 是 GEM 提供的一种简化内存分配机制,专为不需要 GPU 复杂内存管理的显示应用(如 FBDEV、简单 KMS 测试)设计。它通过 DRM_IOCTL_MODE_CREATE_DUMB 分配连续或高效的内存块,并自动关联 GEM Object。
// 用户空间创建 Dumb Buffer 示例
struct drm_mode_create_dumb create = {
.width = 1920,
.height = 1080,
.bpp = 32,
};
int ret = drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
if (ret) {
perror("Failed to create dumb buffer");
return -1;
}
printf("Created dumb buffer: handle=%u, pitch=%u, size=%llu\n",
create.handle, create.pitch, create.size);
4.4 Prime 共享:跨设备与跨进程
Prime 是 GEM 的扩展机制,基于 Linux 内核的 DMA-BUF 框架实现。它允许将 GEM Object 导出为 dma_buf 文件描述符(FD),从而实现跨 DRM 设备、跨进程甚至跨内核子系统的缓冲区共享。
DRM_IOCTL_PRIME_HANDLE_TO_FD 将 GEM Handle 转换为 FD;导入端调用 DRM_IOCTL_PRIME_FD_TO_HANDLE 将 FD 转换为本地 GEM Handle。内核通过 dma_buf 的 attach() / detach() 回调管理设备间映射。
| 操作 | IOCTL 命令 | 输入 | 输出 | 典型场景 |
|---|---|---|---|---|
| 导出 | DRM_IOCTL_PRIME_HANDLE_TO_FD |
GEM Handle | 文件描述符 (FD) | GPU 渲染结果传递给显示控制器 |
| 导入 | DRM_IOCTL_PRIME_FD_TO_HANDLE |
文件描述符 (FD) | 本地 GEM Handle | 显示控制器读取 GPU 渲染的帧缓冲 |
| 同步 | DMA_BUF_IOCTL_SYNC |
FD + 同步标志 | 无 | CPU 访问前确保缓存一致性 |
gem_prime_export 与 gem_prime_import 回调。对于不支持硬件缓存的设备,需要显式调用 dma_buf_begin_cpu_access() 和 dma_buf_end_cpu_access() 来保证数据一致性。
4.5 总结与对比
| 特性 | GEM Object | Dumb Buffer | Prime 共享 |
|---|---|---|---|
| 分配方式 | 驱动自定义 | 内核通用回调 | 基于现有 GEM Object |
| GPU 加速 | 支持 | 不支持 | 支持(需驱动配合) |
| 跨进程 | 需手动传递 Handle | 仅单进程 | 通过 FD 原生支持 |
| 跨设备 | 不支持 | 不支持 | 支持 |
| 典型用途 | 3D 渲染、合成 | FBDEV、简单显示 | GPU→Display 零拷贝 |
理解 GEM 内存管理的这三个层次,是掌握 DRM 显示驱动开发的基础。Dumb Buffer 提供了快速上手的路径,GEM Object 是灵活性的核心,而 Prime 共享则是实现高性能多设备协作的关键。在实际驱动开发中,需要根据硬件能力和应用场景选择合适的分配与共享策略。