DRM-01:GEM显存管理

1. 引言:显存空间管理

在现代图形系统中,显存(GPU memory)管理是驱动开发的核心问题之一。显存空间不仅需要高效分配和回收,还要支持用户空间映射、多进程并发访问、跨设备共享等复杂需求。

GEM(Graphics Execution Manager)作为 DRM 的一部分,专门负责图形内存对象的统一管理。本文将围绕“如何管理一块显存”这一问题,深入分析 GEM 的核心数据结构、它们之间的关系,并通过原理解析和代码示例,帮助读者理解其设计思想和实际用法。

2. 问题分析:显存空间管理的需求

假设我们有一块显存空间,我们要如何完成下列功能:

  1. 分配和释放显存对象:支持动态分配、回收显存 buffer。

  2. 用户空间映射:支持 mmap,将显存映射到用户空间。

  3. 多进程访问隔离:每个进程只能访问自己拥有的 buffer。

  4. 跨进程/设备共享:支持 buffer 在不同进程或设备间共享。

  5. 同步与访问控制:保证并发访问的正确性和安全性。

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 对象,流程如下:

      1. 分配 drm_gem_object 结构体,初始化成员。

      2. 初始化 vma_node,为后续 mmap 做准备。

      3. 将对象挂载到 drm_device 的管理链表。

      4.2  用户空间 handle 管理

      每个进程通过 ioctl 创建 GEM handle,使用了linux的idr组件(参见inux IDR 机制及相关API 介绍)。流程如下:

      1. 驱动调用 drm_gem_handle_create(),为对象分配一个唯一 handle。

      2. handle 映射关系存储在 drm_file->object_idr,实现进程隔离。

      3. 进程可通过 handle 查找和操作对象,保证安全性。

      4.3  显存对象的 mmap 映射

      用户空间通过 mmap 映射显存对象,流程如下:

      1. 驱动调用 drm_gem_dumb_map_offset(),为对象分配 mmap offset。

      2. offset 分配由 drm_vma_offset_manager 和 drm_mm 协作完成,保证区间不重叠。

      3. 用户空间通过 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

      评论
      成就一亿技术人!
      拼手气红包6.0元
      还能输入1000个字符
       
      红包 添加红包
      表情包 插入表情
       条评论被折叠 查看
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包

      打赏作者

      DeeplyMind

      你的鼓励将是我创作的最大动力

      ¥1 ¥2 ¥4 ¥6 ¥10 ¥20
      扫码支付:¥1
      获取中
      扫码支付

      您的余额不足,请更换扫码支付或充值

      打赏作者

      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值