第三章:GEM分析:3.1 GEM设计目标和核心概念

📅 2026/8/12 21:40:32
第三章:GEM分析:3.1 GEM设计目标和核心概念
GEMGraphics Execution Manager是 DRM 中负责GPU 内存对象抽象与管理的核心模块。本文梳理它诞生的背景、设计目标、核心概念与能力边界。1. 背景与需求1.1 GPU内存管理的挑战现代图形应用以及 AI 应用程序需要大量的显存来存储图形相关数据和 AI 计算数据。图形侧主要是帧缓冲区framebuffers、纹理textures、顶点缓冲vertices等AI 侧则包括模型权重weights、激活值activations、梯度gradients、优化器状态optimizer states如 Adam 的一阶/二阶矩、推理时的 KV Cache以及训练/推理输入的张量批次batched tensors等。由于这些数据的高度动态特性高效地管理显存对图形栈和计算栈至关重要并在DRM基础设施中扮演核心角色。具体挑战包括大容量需求GPU需要存储大量的图形数据和训练数据CPU-GPU数据共享CPU和GPU之间需要频繁共享和传输数据多进程安全共享多个用户空间进程需要安全地共享GPU资源同时保证进程隔离1.2 传统方案的问题在GEM出现之前DRM的内存管理存在以下问题功能有限早期DRM只提供framebuffer管理缺乏通用的GPU内存对象抽象驱动碎片化各驱动各自实现内存管理缺乏统一接口代码重复文件描述符限制最初考虑使用文件描述符fd作为对象句柄但存在致命缺陷进程默认限制只能打开1024个左右的文件描述符高fd值会影响X Server的select()处理性能也影响GL客户端应用生命周期管理困难用户空间难以高效管理GPU对象的生命周期AI训练推理的新需求带来的挑战正如drivers/gpu/drm/drm_gem.c注释所述“The goal was to have swap-backed object allocation managed through struct file. However, file descriptors as handles to a struct file have two major failings: Process limits prevent more than 1024 or so being used at a time by default. Inability to allocate high fds will aggravate the X Server’s select() handling.”2. 设计目标基于上述挑战GEM的设计目标明确定义为2.1 统一的内存对象抽象提供通用的GPU内存对象GEM Object表示屏蔽底层硬件差异但允许驱动扩展数据无关性GEM管理抽象的缓冲对象不需要知道单个缓冲区的具体内容2.2 简化的内存管理GEM的设计哲学与TTM完全不同。如Documentation/gpu/drm-mm.rst所述“GEM started as an Intel-sponsored project in reaction to TTM’s complexity. Its design philosophy is completely different: instead of providing a solution to every graphics memory-related problems, GEM identified common code between drivers and created a support library to share it.”关键特性简化的初始化和执行需求相比TTM更轻量级支持标准操作内存分配/释放、映射、命令执行等2.3 安全的多进程访问基于handle的访问控制避免直接暴露内核指针进程隔离每个进程维护独立的handle命名空间引用计数管理自动管理对象生命周期2.4 灵活的扩展性核心只提供共享的公共代码设备特定操作如命令执行、pinning、domain管理留给驱动通过私有ioctl实现驱动可以扩展drm_gem_object结构添加私有数据2.5 GEM 模块提供的核心功能前面几条是设计原则为什么这样设计这一节回答GEM 到底做哪些事——即它作为一个内核模块对外提供的实际功能。可以归纳为七类职责功能做什么代表性接口对象分配与生命周期管理创建/销毁 GPU 内存对象用引用计数自动回收drm_gem_object_init()/drm_gem_object_get/put()/-free()Handle 管理为每个进程维护 handle↔对象映射作为用户态引用对象的凭据drm_gem_handle_create/delete()、drm_gem_object_lookup()CPU 内存映射mmap给对象分配伪地址空间偏移支持用户态 mmap 到 CPU 地址空间drm_gem_mmap()、drm_vma_offset_manager、vma_node跨进程 / 跨设备共享全局名共享FLINK与基于 dma-buf 的 PRIME 导入/导出DRM_IOCTL_GEM_FLINK、drm_gem_prime_import/export()同步与并发控制通过内嵌dma_resv记录读写 fence协调 CPU/GPU、多引擎间访问顺序obj-resv、dma_resv_add_fence()GPU 虚拟地址映射反向索引VM_BIND6.6记录指向本对象的所有 GPU VA 映射支撑用户态显式绑定 GPU 地址驱逐/销毁/校验时按对象枚举映射DRIVER_GEM_GPUVA、drm_gem_gpuva_init()、drm_gem_for_each_gpuvm_bo()、gpuva.list/gpuva.lock底层地址范围分配为上层mmap 偏移、驱动 VRAM/GTT 管理提供通用范围分配器drm_mm、drm_vma_offset_manager一句话概括模块边界GEM 负责对象的身份、引用、共享、映射CPU/GPU的双侧映射与同步的公共骨架而把对象放在哪、如何迁移、如何被 GPU 执行这些设备相关策略留给驱动后者正是第 6 节局限性与 TTM 补位的由来。3. 核心概念与设计3.1 drm_gem_object核心对象抽象drm_gem_object是GEM的核心数据结构定义在include/drm/drm_gem.h关键特性引用计数管理通过kref refcount实现自动生命周期管理使用drm_gem_object_get()获取引用使用drm_gem_object_put()释放引用双重计数机制handle_count独立追踪用户空间handle数量详见下方说明灵活的存储可使用shmem作为后端filp字段也支持驱动私有存储private GEM objectsfilp为NULL可扩展性驱动通过嵌入drm_gem_object创建自己的对象类型为什么需要两个计数refcountvshandle_count这是初学者最易混淆之处计数统计对象归零后果refcountkref内核里所有对该对象的引用用户 handle、mmap、驱动内部引用、dma-buf 导出等对象真正被释放调用-free()handle_count仅用户空间 handle 的数量对象可脱离 handle 命名空间如撤销 FLINK 全局名但只要还有其他引用如已被 dma-buf 导出/mmap对象不会被释放二者解耦的意义一个对象即便所有 handle 都关闭了只要它还被 dma-buf 导出给别的进程/设备使用就不能被过早回收——refcount保证内存安全handle_count只管理用户态可见性。3.2 Handle机制用户空间接口GEM采用32位整数handle作为用户空间对对象的引用而不是文件描述符。这一设计解决了fd的限制问题。实现机制源自drivers/gpu/drm/drm_gem.c/** * This led to a plan of using our own integer IDs (called handles, following * DRM terminology) to mimic fds, and implement the fd syscalls we need as * ioctls. */Handle管理每个进程drm_file维护独立的handle到对象的映射表通过IDR实现Handle创建drm_gem_handle_create()分配handle并建立映射Handle查找drm_gem_object_lookup()根据handle查找对象Handle删除drm_gem_handle_delete()移除映射并释放引用安全性保证进程A的handle 5和进程B的handle 5可以指向不同的对象内核通过drm_file上下文隔离不同进程的handle空间对象访问权限通过drm_vma_node_allow()/revoke()控制3.3 drm_vma_offset_manager地址空间管理为了支持mmap操作GEM需要为每个对象分配一个虚拟地址空间的偏移量。drm_vma_offset_manager负责管理这个伪造的地址空间。设计目标源自drivers/gpu/drm/drm_vma_manager.c/** * 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. */3.4 drm_mm通用范围分配器drm_mm是GEM的底层内存分配器管理连续的地址空间范围。设计特点源自drivers/gpu/drm/drm_mm.c/** * drm_mm provides a simple range allocator. The drivers are free to use the * resource allocator from the linux core if it suits them, the upside of drm_mm * is that its in the DRM core. */3.5 对象共享FLINK 与 PRIME/dma-bufhandle 是进程私有的进程 A 的 handle 5 与进程 B 的 handle 5 互不相干。要让多个进程/设备访问同一个GEM 对象GEM 提供两条共享通道机制原理安全性适用范围FLINK旧DRM_IOCTL_GEM_FLINK给对象起一个全局名每设备一个扁平命名空间别的进程用DRM_IOCTL_GEM_OPEN凭该名换到自己的 handle弱全局名可被猜测/枚举无访问控制同一 DRM 设备内的进程间共享PRIME现代基于dma-bufdrm_gem_prime_export()把 handle 导出为一个 dma-buffd通过 UNIX socketSCM_RIGHTS传给对方对方用drm_gem_prime_import()把 fd 转回自己的 handle强fd 传递受内核权限控制无法凭空猜测跨进程、跨设备、跨驱动共享要点PRIME 已成为主流FLINK 主要为兼容旧用户态保留。共享时底层的drm_gem_object只有一份多方通过各自的 handle 引用它对象的refcount会随每次导入/导出增减呼应 3.1 的双计数。dma-buf 还让 GEM 对象能与非 DRM 子系统V4L2、InfiniBand 等共享同一块内存是 Linux 统一缓冲共享框架的基石。3.6 gpuvaGPU 虚拟地址映射的反向索引6.6 新增前面四个概念都围绕对象的身份、引用、CPU 侧映射与共享。但一个 GEM 对象最终要被 GPU 访问就必须被放进某个 GPU 虚拟地址空间。早期这件事完全由各驱动私有实现如amdgpu_bo_vaGEM 核心并不参与。自Linux 6.6引入drm_gpuvm框架后drm_gem_object新增了一个gpuva字段头文件原注“Fields used by GPUVM to manage mappings pointing to this GEM object”struct{structlist_headlist;// 指向本对象的所有 (对象,VM) 组合structmutexlock;// IMMEDIATE_MODE 下保护 list6.18 引入}gpuva;作用让内核能从一个 GEM 对象反向找到“它被映射到了哪些 GPU 地址空间、各有哪些具体映射”服务于驱逐、销毁、校验等需要按对象枚举映射的场景。它与vma_node对称一个管对象在CPU地址空间的映射mmap一个管在GPU地址空间的映射。这是一个两级链表gpuva.list挂的是drm_gpuvm_bo每 VM 一个再挂具体的drm_gpuva。这一字段对应的是VM_BIND新需求属于较大的演进主题。其框架设计drm_gpuvm/drm_gpuva、Split Merge、VM_BIND 流程详见第八章 gpuva这个字段本身的设计目的、API 与用法详在后面专讲gem_object时具体分析。4. 核心概念关系图handle (32位整数)用户态入口 (ioctl)DRM_IOCTL_GEM_CLOSEDRM_IOCTL_GEM_FLINK (全局名称)驱动私有 ioctl用户空间应用drm_file (进程上下文)object_idr: handle→object 映射prime: dma-buf handle 映射drm_gem_object (核心抽象)refcount: 引用计数handle_count: handle 计数vma_node: 地址空间节点filp: shmem 文件可选funcs: 驱动操作函数drm_vma_offset_manager(mmap 偏移)shmem / 私有存储驱动私有扩展字段(如 amdgpu_bo)drm_mm (底层范围分配器)interval_tree: 区间树holes_size/addr: hole 管理支持对齐、颜色、多种策略物理 / 虚拟地址空间5. GEM的优势5.1 简洁性相比TTM的一刀切方案GEM更轻量级初始化简单不需要复杂的配置适合简单的UMA设备5.2 灵活性只提供共享的公共功能不强加不必要的复杂性驱动可以自由扩展和定制通过函数指针表drm_gem_object_funcs实现多态5.3 标准化统一的用户态API虽然部分操作仍是驱动特定的标准的handle管理和对象生命周期促进了代码共享和复用6. GEM的局限性6.1 功能范围有限不处理视频RAM管理GEM没有VRAM分配和管理能力不支持内存迁移无法在系统内存和VRAM间迁移对象不管理placement策略无法指定内存域memory domains优先级6.2 主要面向UMA设备如Documentation/gpu/drm-mm.rst所述“GEM has simpler initialization and execution requirements than TTM, but has no video RAM management capabilities and is thus limited to UMA devices.”6.3 缺乏复杂的资源管理没有内存预算管理没有内存驱逐eviction策略没有自动的内存迁移和放置优化这些局限性正是引入TTM的原因。现代DRM驱动通常使用GEM作为基础对象抽象和handle管理在需要复杂内存管理时在GEM之上使用TTMAMD等驱动通过amdgpu_bo扩展GEM对象整合TTM功能6.4 边界的演进gpuva 填补了“GEM 核心不参与 GPU 地址空间”这一局限上面 6.1–6.3 描述的是 GEM自身属性上的局限VRAM、迁移、placement。除此之外早期 GEM 还有一条不同性质的边界核心完全不涉及 GPU 虚拟地址空间——对象如何被映射进 GPU 地址空间、哪些映射指向它都由各驱动自行实现。这条局限自 Linux 6.6 起已被部分填补drm_gpuvm框架 drm_gem_object.gpuva字段把“对象 → 指向它的 GPU VA 映射”的反向索引提升为 DRM core 的通用能力见 3.6。于是 GEM 核心从“只管对象、不知 GPU 地址”变为“参与维护 GPU 地址空间中指向各对象的映射关系”。但边界仍需分清以免误解gpuva 只提供映射关系的软件视图反向索引 区间管理不分配 VRAM、不做迁移、不写真正的 GPU 页表——那些仍属 TTM 与驱动。换言之6.1–6.3 那几条局限依然成立。因此准确的表述是gpuva 填补的是“GPU 地址映射追踪”这一空白而非“显存资源管理”。目前 amdgpu 仍用自研的amdgpu_vm/amdgpu_bo_va尚未整体迁到drm_gpuvm。7. 总结GEM作为DRM的图形执行管理器通过以下核心设计实现了其目标drm_gem_object提供统一的GPU内存对象抽象Handle机制解决文件描述符限制实现安全的多进程隔离对象共享FLINK / PRIME以 dma-buf 为基础实现跨进程、跨设备、跨驱动的对象共享同步与并发控制dma_resv内嵌读写 fence协调 CPU/GPU、多引擎间的访问顺序GPU 地址映射反向索引gpuva6.6配合drm_gpuvm框架支撑 VM_BIND让对象持久记录“指向自己的 GPU VA 映射”drm_vma_offset_manager管理mmap虚拟地址空间drm_mm提供灵活高效的底层范围分配器GEM的设计哲学是提供共享的公共代码而不是试图解决所有问题。这使得它简洁、灵活且易于扩展。对于需要更复杂内存管理的场景如独立显卡的VRAM管理驱动可以在GEM基础上整合TTM等更高级的内存管理器。GEM与TTM的关系是互补而非替代GEM提供基础抽象和用户接口TTM提供高级内存管理功能。现代GPU驱动通常同时使用两者充分发挥各自的优势。