1. 引言:显存空间管理
在现代图形系统中,显存(GPU memory)管理是驱动开发的核心问题之一。显存空间不仅需要高效分配和回收,还要支持用户空间映射、多进程并发访问、跨设备共享等复杂需求。
GEM(Graphics Execution Manager)作为 DRM 的一部分,专门负责图形内存对象的统一管理。本文将围绕“如何管理一块显存”这一问题,深入分析 GEM 的核心数据结构、它们之间的关系,并通过原理解析和代码示例,帮助读者理解其设计思想和实际用法。
2. 问题分析:显存空间管理的需求
假设我们有一块显存空间,我们要如何完成下列功能:
-
分配和释放显存对象:支持动态分配、回收显存 buffer。
-
用户空间映射:支持 mmap,将显存映射到用户空间。
-
多进程访问隔离:每个进程只能访问自己拥有的 buffer。
-
跨进程/设备共享:支持 buffer 在不同进程或设备间共享。
-
同步与访问控制:保证并发访问的正确性和安全性。
GEM 的设计旨在解决上述问题,这是显存使用的基本需求。本文将聚焦其1-3 项功能展开论述,共享功能属于另一主题,具体可参考 “Prime 机制”。此外,上述 5 项需求需重点关注。
3. GEM核心数据结构详解
下图是显存使用示意图。注意每个框图的包含关系。图中下方长条表示显存空间,在T0时刻,显存全部可用,作为一个大的空闲节点(绿色表示空闲节点,红色表示已分配的节点)。在实际程序的运行过程中,显存空间就是被分割成了这样两个列表:hole list和allocated list。接下来看下这个图里涉及的相关概念。这些概念是理解GEM的关键,务必搞懂。按照top-down的顺序讲解概念。请注意这里的空间是逻辑空间,例如每个进程都会看到这样一个空间,甚至一个进程里多次打开设备,每个打开的设备都存在这样一个空间。

3.1. drm_gem_object —— 显存对象的抽象
drm_gem_object 是 GEM 的核心结构,代表一个显存 buffer。其成员如下:
struct drm_gem_object {
struct kref refcount;
unsigned handle_count;
struct drm_device *dev;
struct file *filp;
//空间分配与映射
struct drm_vma_offset_node vma_node;
//object大小
size_t size;
//共享
struct dma_buf *dma_buf;
//访问同步
struct dma_resv *resv;
struct dma_resv _resv;
//自定义函数
const struct drm_gem_object_funcs *funcs;
...
};
-
功能:抽象一个可被用户空间或内核访问的显存对象,管理其生命周期、引用计数、映射关系等。
- 关键成员:
-
dev:所属的drm_device,即该对象属于哪个 DRM 设备。 -
filp:后端文件对象,通常用于 shmem(匿名共享内存)实现 buffer 的物理存储。 -
size:对象大小(字节)。 -
refcount:内核内部引用计数,保证对象不会被提前释放。 -
handle_count:用户空间 handle 数量,支持多进程引用。 -
vma_node:用于 mmap 管理的 VMA 节点(见下文)。 -
dma_buf:用于buffer的共享。
-
_resv/resv:同步相关的 reservation 对象,支持 buffer 访问同步。 -
lru_node:用于 LRU 管理,支持内存回收策略。
-
作用:每个显存 buffer 都是一个 drm_gem_object 实例,驱动通过它进行分配、释放、映射等操作。该结构体是drm的核心结构体,建议多看几遍,记住这些成员变量。
3.2. drm_vma_offset_manager / drm_vma_offset_node — 映射空间管理
在 Linux DRM(Direct Rendering Manager)框架中,用户空间与 GPU 驱动之间的数据交换和缓冲区管理,常常涉及到内存映射(mmap)操作。而drm_gem_object 的 vma_node 是用户空间存储映射管理的核心。其由drm_vma_offset_manager进行管理。
先来看下官方对drm_vma_offset_manager的解释:
/**
* DOC: vma offset manager
*
* The vma-manager is responsible to map arbitrary driver-dependent memory
* regions into the linear user address-space. It provides offsets to the
* caller which can then be used on the address_space of the drm-device. It
* takes care to not overlap regions, size them appropriately and to not
* confuse mm-core by inconsistent fake vm_pgoff fields.
* Drivers shouldn't use this for object placement in VMEM. This manager should
* only be used to manage mappings into linear user-space VMs.
*
* We use drm_mm as backend to manage object allocations. But it is highly
* optimized for alloc/free calls, not lookups. Hence, we use an rb-tree to
* speed up offset lookups.
*
* You must not use multiple offset managers on a single address_space.
* Otherwise, mm-core will be unable to tear down memory mappings as the VM will
* no longer be linear.
*
* This offset manager works on page-based addresses. That is, every argument
* and return code (with the exception of drm_vma_node_offset_addr()) is given
* in number of pages, not number of bytes. That means, object sizes and offsets
* must always be page-aligned (as usual).
* If you want to get a valid byte-based user-space address for a given offset,
* please see drm_vma_node_offset_addr().
*
* Additionally to offset management, the vma offset manager also handles access
* management. For every open-file context that is allowed to access a given
* node, you must call drm_vma_node_allow(). Otherwise, an mmap() call on this
* open-file with the offset of the node will fail with -EACCES. To revoke
* access again, use drm_vma_node_revoke(). However, the caller is responsible
* for destroying already existing mappings, if required.
*/
/**
* 文档:VMA 偏移管理器
*
* VMA 管理器负责将驱动相关的任意内存区域映射到线性的用户地址空间。
它为调用者提供偏移量,调用者可以在 drm 设备的 address_space 上使用这些偏移量。
管理器确保各区域不会重叠,大小合适,并避免因虚假的 vm_pgoff 字段导致 mm-core 混乱。
* 驱动不应将此管理器用于 VMEM(虚拟内存)中的对象放置。该管理器仅用于管理
线性用户空间虚拟内存的映射。
*
* 我们使用 drm_mm 作为后端来管理对象分配。但 drm_mm 更适合分配/释放操作,而不是查找。
因此,我们使用红黑树(rb-tree)来加速偏移查找。
*
* 在同一个 address_space 上不能使用多个偏移管理器。
否则,mm-core 将无法正确拆除内存映射,因为虚拟内存将不再是线性的。
*
* 此偏移管理器以页为单位工作。也就是说,所有参数和返回值(除了
drm_vma_node_offset_addr() 之外)都以页数而不是字节数表示。
这意味着对象的大小和偏移必须始终页对齐(如常规要求)。
如果你需要根据偏移获得有效的字节级用户空间地址,请参考 drm_vma_node_offset_addr()。
*
* 除了偏移管理,VMA 偏移管理器还负责访问管理。对于每个被允许访问某节点的打开文件上下文,
都必须调用 drm_vma_node_allow()。否则,使用该节点偏移进行 mmap() 时会返回 -EACCES。
要撤销访问权限,请使用 drm_vma_node_revoke()。
不过,调用者需负责销毁已存在的映射(如有需要)。
*/
虽然解释很长,但drm_vma_offset_manager核心功能可以概括为:管理用户空间 mmap 的虚拟地址分配,防止冲突。内部使用 drm_mm 做区间分配。所以核心是drm_mm。实现也很简单:
struct drm_vma_offset_manager {
rwlock_t vm_lock;
struct drm_mm vm_addr_space_mm;
};
drm_vma_offset_manager管理的是drm_vma_offset_node。每个 GEM 对象内部包含一个该类型节点,用于 mmap 映射,保证用户空间地址分配的唯一性和安全性。
struct drm_vma_offset_node {
rwlock_t vm_lock; // 读写锁,保护该节点的并发访问
struct drm_mm_node vm_node; // 区间分配器节点,记录VMA的起始页号和大小
struct rb_root vm_files; // 红黑树,管理与该VMA节点相关的文件对象
void *driver_private; // 驱动私有数据指针,用于扩展或特定用途
};
3.3 drm_mm / drm_mm_node —— 区间分配器
struct drm_mm {
/* private: */
/* List of all memory nodes that immediately precede a free hole. */
struct list_head hole_stack;
/* head_node.node_list is the list of all memory nodes, ordered
* according to the (increasing) start address of the memory node. */
struct drm_mm_node head_node;
/* Keep an interval_tree for fast lookup of drm_mm_nodes by address. */
struct rb_root_cached interval_tree;
struct rb_root_cached holes_size;
};
-
drm_mm:通用区间分配器,管理地址空间分配。 -
drm_mm_node:每个分配的区间节点。
drm_mm里都是容器类型的成员变量,目的就是管理空闲/已分配的drm_mm_node。用了list来存储,同时为了加速寻找,用了rb-tree。
作用:drm_vma_offset_manager 用 drm_mm 管理 offset 区间,drm_vma_offset_node 内嵌一个 drm_mm_node,实现高效的区间分配和查找。
4. 显存空间的分配与管理
4.1 显存对象的分配
驱动通过 drm_gem_object_init() 分配一个新的 GEM 对象,流程如下:
-
分配
drm_gem_object结构体,初始化成员。 -
初始化
vma_node,为后续 mmap 做准备。 -
将对象挂载到
drm_device的管理链表。
4.2 用户空间 handle 管理
每个进程通过 ioctl 创建 GEM handle,使用了linux的idr组件(参见inux IDR 机制及相关API 介绍)。流程如下:
-
驱动调用
drm_gem_handle_create(),为对象分配一个唯一 handle。 -
handle 映射关系存储在
drm_file->object_idr,实现进程隔离。 -
进程可通过 handle 查找和操作对象,保证安全性。
4.3 显存对象的 mmap 映射
用户空间通过 mmap 映射显存对象,流程如下:
-
驱动调用
drm_gem_dumb_map_offset(),为对象分配 mmap offset。 -
offset 分配由
drm_vma_offset_manager和drm_mm协作完成,保证区间不重叠。 -
用户空间通过 offset 进行 mmap,内核通过
drm_vma_offset_node查找对应对象,实现地址空间映射。
4.4 对象的同步与访问控制(不属于本文的重点)
-
通过
_resv/resv成员,实现 buffer 的访问同步,防止并发冲突。 -
通过
drm_vma_node_allow()/drm_vma_node_revoke()控制进程对对象的访问权限,支持多进程安全共享。
4.5 对象的释放与回收
-
当进程关闭或释放 handle 时,驱动调用
drm_gem_handle_delete(),减少对象引用计数。 -
当所有引用计数归零时,调用
drm_gem_object_free()释放对象,回收显存空间。
5. 总结
GEM 作为 Linux DRM 子系统的核心内存管理机制,通过一系列数据结构实现了显存对象的高效分配、映射、同步和回收。其设计充分考虑了多进程隔离、用户空间映射、跨设备共享等实际需求,具有高度的可扩展性和安全性。
理解drm_gem_object、drm_vma_offset_node和drm_mm_node的功能和层级关系是理解该机制的关键。各厂商的内核驱动实现的核心功能,本质上就是构建这三者间的关联关系。实现的思路就是通过继承自这些node,加入自己的业务数据。
接下来DRM-02:TTM与GEM的关系分析TTM的实现,这是基于GEM扩展的另一个显存管理模块。
技术交流,欢迎加入社区:GPUers。
1028

被折叠的 条评论
为什么被折叠?



