3. GEM与内存管理:GEM的设计目标、Buffer Object管理、Dumb Buffer与Prime共享机制
在DRM(Direct Rendering Manager)子系统中,GEM(Graphics Execution Manager)是核心的内存管理基础设施。它负责管理GPU与CPU之间共享的图形缓冲区,提供高效、安全的内存分配与回收机制。本章深入剖析GEM的设计哲学、核心对象模型、Dumb Buffer的简单分配路径以及跨进程/跨设备的Prime共享机制。
核心概念: GEM并非一个独立的驱动程序,而是一套内存管理API和对象模型,嵌入在DRM框架中,为不同GPU(包括MIPI DSI显示控制器)提供统一的缓冲区管理接口。
3.1 GEM的设计目标
GEM的设计围绕以下几个关键目标展开,旨在解决传统图形驱动中内存管理的碎片化与低效问题:
- 统一抽象: 为所有类型的GPU(集成、独立、显示控制器)提供一套标准的内存对象模型,屏蔽底层硬件差异。
- 高效共享: 支持在多个进程(如X Server、Wayland Compositor、应用)之间安全、零拷贝地共享图形缓冲区。
- 延迟绑定: 允许在分配缓冲区时不立即绑定物理内存,而是在实际使用时(如GPU渲染或显示扫描)才进行地址映射,优化内存利用率。
- 安全隔离: 通过句柄(Handle)和文件描述符(FD)机制,防止用户态进程直接访问或破坏内核态内存。
- 硬件无关性: 驱动开发者只需实现GEM提供的钩子函数(如gem_create_object、gem_free_object),即可获得标准的内存管理能力。
3.2 Buffer Object管理
GEM的核心数据结构是Buffer Object(BO),它代表一块可被GPU和CPU访问的内存区域。每个BO在内核中由struct drm_gem_object描述,驱动可在此基础上扩展私有数据。
3.2.1 核心数据结构
| 字段 | 类型 | 说明 |
|---|---|---|
handle |
u32 | 用户态句柄,用于在进程内引用BO |
size |
size_t | 缓冲区大小(字节) |
name |
int | 全局名称(用于跨进程共享) |
dma_buf |
struct dma_buf * | 关联的DMA-BUF(用于Prime共享) |
funcs |
struct drm_gem_object_funcs * | 操作函数表(open、close、pin、unpin等) |
3.2.2 生命周期管理
BO的生命周期由引用计数控制,遵循以下流程:
- 创建: 用户态通过
DRM_IOCTL_GEM_CREATE创建BO,内核分配drm_gem_object并返回句柄。 - 映射: 通过
DRM_IOCTL_GEM_MMAP将BO映射到用户态地址空间,CPU可直接读写。 - 使用: GPU通过GEM句柄提交命令,驱动负责将句柄转换为GPU地址。
- 销毁: 用户态调用
DRM_IOCTL_GEM_CLOSE释放句柄,当引用计数归零时,内核释放物理内存。
最佳实践: 在MIPI DSI驱动中,FrameBuffer通常对应一个或多个GEM BO。驱动应在
atomic_flush回调中确保BO已正确pin到显示控制器可访问的地址空间。
3.3 Dumb Buffer:轻量级分配路径
Dumb Buffer是GEM提供的一种简化分配机制,专为不需要GPU渲染能力的显示控制器(如MIPI DSI、HDMI、VGA)设计。它分配的是连续物理内存(或通过IOMMU映射的连续虚拟地址),适合用作帧缓冲区(FrameBuffer)。
3.3.1 关键特性
- 无需GPU: 不依赖3D引擎,仅需显示控制器支持。
- 连续内存: 保证物理地址连续,满足大多数显示控制器的DMA要求。
- 简单API: 仅需三个IOCTL:
DRM_IOCTL_MODE_CREATE_DUMB、DRM_IOCTL_MODE_MAP_DUMB、DRM_IOCTL_MODE_DESTROY_DUMB。
3.3.2 典型使用流程
// 用户态示例:创建并映射Dumb Buffer
int fd = open("/dev/dri/card0", O_RDWR);
struct drm_mode_create_dumb create = {
.width = 1920,
.height = 1080,
.bpp = 32,
};
ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
// create.handle 为GEM句柄,create.size 为缓冲区大小
struct drm_mode_map_dumb map = {
.handle = create.handle,
};
ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
// map.offset 为mmap偏移量
void *fb = mmap(NULL, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
// 现在可通过fb指针直接写入像素数据
注意: Dumb Buffer仅适用于简单的显示场景。对于需要GPU渲染合成(如Android HWUI、Wayland Weston)的系统,应使用完整的GEM BO + GPU提交路径,以避免性能瓶颈。
3.4 Prime共享机制:跨设备与跨进程
Prime是GEM的扩展机制,基于Linux内核的DMA-BUF框架,实现跨设备(如GPU与显示控制器)和跨进程(如Compositor与客户端)的缓冲区共享。它解决了传统GEM共享(通过全局名称)只能在同一DRM设备内共享的限制。
3.4.1 核心概念
| 概念 | 说明 |
|---|---|
| 导出(Export) | 将GEM BO转换为DMA-BUF文件描述符(FD),FD可跨进程传递 |
| 导入(Import) | 从DMA-BUF FD创建新的GEM BO句柄,实现内存共享 |
| DMA-BUF | 内核标准框架,提供统一的缓冲区描述和同步机制 |
| 同步 | 通过DMA-BUF的Fence机制,确保读写操作的顺序正确 |
3.4.2 典型共享场景
在MIPI DSI显示系统中,Prime共享常用于以下场景:
- GPU渲染 → DSI显示: GPU将渲染结果导出为DMA-BUF FD,DSI驱动导入FD并配置为扫描输出源。
- 视频解码 → 显示: 硬件解码器输出YUV缓冲区,通过Prime直接传递给显示控制器,避免CPU拷贝。
- Wayland客户端 → Compositor: 客户端应用将绘制好的缓冲区通过Prime共享给Compositor,Compositor合成后输出到DSI。
3.5 三种分配路径对比
| 特性 | Dumb Buffer | 标准GEM BO | Prime共享 |
|---|---|---|---|
| 适用场景 | 简单显示(无GPU) | GPU渲染、通用图形 | 跨设备/跨进程共享 |
| 内存连续性 | 物理连续 | 可离散(依赖IOMMU) | 取决于源BO类型 |
| API复杂度 | 低(3个IOCTL) | 中(需GPU提交) | 高(需DMA-BUF) |
| 性能开销 | 低(CPU直接写) | 中(GPU合成) | 极低(零拷贝) |
| 跨进程支持 | 不支持 | 仅同设备(全局名称) | 支持(FD传递) |
实践建议: 在MIPI DSI驱动开发中,优先使用Dumb Buffer进行初期验证(如显示Logo、测试图案);进入产品阶段后,应切换到Prime共享路径,以支持GPU合成和视频解码等复杂场景。