GEM(Graphics Execution Manager,图形执行管理器)是 Linux DRM 中常见的图形缓冲对象管理抽象,用来让内核驱动和用户态驱动共同管理 GPU 可访问的 buffer object。
它关注的不是“如何画三角形”,而是“这块图形资源是谁创建的、谁能访问、什么时候能被 GPU 读写、能否安全共享给另一个进程”。
核心问题
图形应用会频繁创建和共享图像、纹理、顶点缓冲、命令缓冲和显示缓冲。这些资源不像普通堆内存:
- GPU 和 CPU 都可能访问同一块内存。
- 资源可能在显存、系统内存或经过 IOMMU 映射的页里。
- 一个进程创建的图像可能要传给合成器显示。
- GPU 工作异步完成,缓冲不能在还被使用时释放或覆盖。
- 内核需要防止进程伪造地址读取别人的图形数据。
GEM 把这些资源抽象成内核跟踪的对象,让用户态通过句柄、fd 和 ioctl 操作它们。
核心对象
| 对象 | 含义 | 工程关注点 |
|---|---|---|
| GEM object | 内核中的图形缓冲对象 | 大小、内存位置、引用计数、同步状态 |
| handle | 进程内引用 GEM object 的整数 | 只在当前 DRM fd 上有效,不能跨进程直接使用 |
| dma-buf fd | 可跨进程共享的缓冲文件描述符 | 导出、导入、权限、生命周期 |
| mmap mapping | CPU 映射缓冲的地址空间 | 缓存一致性、写合并、失效/刷新 |
| fence | GPU 工作完成信号 | 防止读写冲突和提前复用 |
| reservation object | 缓冲上的同步记录 | 跟踪当前读者、写者和等待条件 |
核心机制
GEM 典型流程如下:
- 创建对象:用户态驱动请求创建某个大小和用途的 GEM object。
- 获得 handle:内核返回当前进程可用的 handle。
- 映射或填充数据:CPU 可以通过 mmap 写入顶点、纹理或命令数据。
- 提交 GPU 工作:命令提交时引用这些 handle,内核检查对象是否存在且权限合法。
- 记录同步关系:如果 GPU 会写这个对象,内核会把对应 fence 记录到对象上。
- 跨进程共享:需要交给合成器或视频管线时,把 GEM object 导出成 dma-buf fd。
- 释放引用:进程关闭 handle 或 fd 后,引用计数减少;没有引用且没有未完成 GPU 工作时才可回收。
最小生命周期可以写成:
gem_object.refcount = handles + exported_fds + in_flight_gpu_uses
free(gem_object) only if refcount == 0handles是进程内 DRM handle 对对象的引用。exported_fds是跨进程 dma-buf fd 的引用。in_flight_gpu_uses是还没有完成的 GPU 读写。refcount == 0才能释放,不是“用户态 close 了 handle 就马上释放”。
工程用途
- 用户态图形驱动用 GEM 管理纹理、渲染目标、命令缓冲和 staging buffer。
- 合成器用 dma-buf 接收应用渲染结果,减少 CPU 拷贝。
- 视频解码、相机、显示控制器和 GPU 可以共享同一块缓冲,形成零拷贝管线。
- DRM 驱动用 GEM 对象实现显存分配、pin/unpin、mmap 和同步。
调试时常见线索包括 GEM object 泄漏、dma-buf fd 未关闭、fence 等待超时、mmap 缓存不一致,以及显示端拿到的缓冲格式或 stride 不匹配。
边界与常见坑
- GEM 不是图形 API:它不等价于 OpenGL 或 Vulkan,只是 DRM 里的资源管理层。
- handle 不是 fd:handle 只在当前 DRM 连接中有效;跨进程共享通常要导出成 dma-buf fd。
- 共享不等于同步:把缓冲传给另一个进程后,仍要用 fence 或显式同步保证对方不会读到未完成内容。
- CPU mmap 不等于普通内存访问:GPU 缓冲可能需要 cache flush/invalidate 或特殊映射属性。
- 生命周期错误会表现为随机花屏:缓冲提前释放、复用或覆盖,症状可能是偶发帧损坏,而不是稳定崩溃。