恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
第三章:GEM分析 3.3.1 drm_vma_offset_manager:偏移窗口与访问控制
首页
资讯中心
/
第三章:GEM分析 3.3.1 drm_vma_offset_manager:偏移窗口与访问控制
第三章:GEM分析 3.3.1 drm_vma_offset_manager:偏移窗口与访问控制
发布时间:2026/10/21 11:33:44
3.3 从用户态的两步调用出发讲了伪偏移如何分配、mmap 如何按偏移找到对象。本文进入细节把这一切背后的组件drm_vma_offset_manager当作主角来进行剖析。全篇可以用二三四概括两个职责偏移分配、访问控制三个结构manager、node、file四组接口窗口、区间、查找、访问控制。此外第 2 节交代它所依赖的drm_mm第 5 节分析查找与对象释放的竞争。1. 职责与边界drm_vma_manager.c组件的定位归纳起来是两件事偏移分配在设备address_space的一段窗口里为每个对象分配互不重叠、按页对齐的区间并能按偏移反查对象。访问控制为每个区间记录哪些drm_file可以映射它。没有被允许的drm_file用这个偏移调用mmap()会得到-EACCES。还有三个细节值得注意只管 mmap 偏移不管内存放置。名字里的 VMA 指的是用户态的线性映射与对象放在 VRAM 还是 GTT 无关驱动不应该用它来管理显存。一个address_space只能对应一个 manager。否则偏移空间不再线性unmap_mapping_range()无法正确撤销映射。DRM 中这个address_space就是设备的dev-anon_inode-i_mapping见 3.1.1manager 就是dev-vma_offset_manager。以页为单位。所有参数和返回值都是页号或页数只有drm_vma_node_offset_addr()返回给用户态的字节偏移。2. vma manager 用到的 drm_mmdrm_vma_offset_manager本身只有一把锁和一个drm_mm区间分配全部交给drm_mm完成。drm_mm是 DRM 的通用范围分配器功能远多于这里用到的部分。理解本文只需要知道下面四点vma manager 中的位置用到的 drm_mm需要知道什么drm_vma_offset_manager_init()/drm_vma_offset_manager_destroy()drm_mm_init()/drm_mm_takedown()drm_mm管理一段[start, start size)的抽象区间单位由使用者决定这里是页。takedown 时若还有节点未删除drm_mm会报告泄漏drm_vma_offset_add()drm_mm_node_allocated()、drm_mm_insert_node()节点结构由调用者提供drm_mm不分配内存。drm_mm_insert_node()不要求对齐、不区分颜色按 best-fit 找空洞成功后填好节点的start与sizedrm_vma_offset_remove()drm_mm_remove_node()删除后 vma manager 把节点清零于是节点是否已分配可以反复判断drm_vma_offset_lookup_locked()直接遍历mm-interval_tree与节点的rbdrm_mm把已分配的节点挂在一棵按start排序的红黑树上。它没有按地址找所属节点的接口vma manager 自己遍历这棵树drm_mm的空洞管理、对齐与颜色、各种插入模式、scan 驱逐等内容vma manager 都用不到见 drm_mm 范围分配器设计与接口 与 drm_mm 实现分析。3. 三个结构vma manager 由三个结构组成按粒度从大到小每个设备一个drm_vma_offset_manager每个对象一个drm_vma_offset_node每个对象上每个被允许的drm_file一个drm_vma_offset_file。structdrm_vma_offset_manager{rwlock_tvm_lock;structdrm_mmvm_addr_space_mm;};structdrm_vma_offset_node{rwlock_tvm_lock;structdrm_mm_nodevm_node;structrb_rootvm_files;void*driver_private;};structdrm_vma_offset_file{structrb_nodevm_rb;structdrm_file*vm_tag;unsignedlongvm_count;};结构成员作用drm_vma_offset_manager每设备一个vm_lock保护drm_mm的读写锁分工见第 5 节vm_addr_space_mm管理整个偏移窗口的drm_mmdrm_vma_offset_node每对象一个即obj-vma_nodevm_lock只保护本节点的vm_filesvm_node本对象在窗口中的区间。start、size以页为单位全为 0 表示尚未分配vm_files允许映射本对象的drm_file集合是一棵以drm_file指针为键的红黑树driver_private留给驱动挂私有数据GEM 核心不使用drm_vma_offset_file每个被允许的drm_file一个vm_tag被允许的drm_filevm_count允许次数见第 4.4 节三层内嵌关系是理解这些接口的关键drm_gem_object内嵌vma_nodestruct drm_vma_offset_nodevma_node又内嵌vm_nodestruct drm_mm_node。所以 mmap 时在drm_mm的树上找到一个drm_mm_node经过两次container_of()就能取回 GEM 对象不需要任何额外的映射表。4. 四组接口4.1 窗口初始化与销毁接口说明drm_vma_offset_manager_init(mgr, page_offset, size)初始化读写锁与drm_mm。GEM 在drm_gem_init()中以DRM_FILE_PAGE_OFFSET_START和DRM_FILE_PAGE_OFFSET_SIZE调用它64 位系统上窗口从 4 GiB 开始大小约 1 TiBdrm_vma_offset_manager_destroy(mgr)调用drm_mm_takedown()。调用前必须删除全部节点。GEM 通过 drmm 托管动作在设备释放时调用它4.2 区间分配、删除与读取接口说明drm_vma_node_reset(node)把节点清零并初始化锁和树节点第一次使用前必须调用。不能对已分配的节点调用否则区间和允许列表都会泄漏drm_vma_offset_add(mgr, node, pages)为节点分配pages页的区间drm_vma_offset_remove(mgr, node)删除节点的区间并把节点清零drm_vma_node_start()/drm_vma_node_size()返回区间起点与大小未分配时返回 0drm_vma_node_offset_addr()返回start PAGE_SHIFT即交给用户态做 mmap 的字节偏移drm_vma_node_unmap(node, mapping)对该区间调用unmap_mapping_range()撤销所有进程中映射到它的页表项有三点值得注意add 和 remove 都是幂等的。对已分配的节点 add 直接返回 0所以 3.3 说同一对象多次取偏移得到同一个值对未分配的节点 remove 什么也不做。add 失败后不需要清理可以重试。pages可以不等于对象大小。注释写明它只限制用户态能映射的长度。GEM 的drm_gem_create_mmap_offset_size()就是为虚拟大小与对象大小不同的情况准备的常规路径用drm_gem_create_mmap_offset()按obj-size分配。drm_vma_node_reset()的一个隐蔽用途。TTM 在ttm_bo_util.c中创建 ghost 对象时用结构体拷贝复制原 BO随后立即对 ghost 的vma_node调用 reset。否则 ghost 会带着原 BO 已分配的drm_mm_node两个对象指向同一个区间。在 GEM 与 TTM 中这组接口的调用者如下动作GEM 通用实现TTMresetdrm_gem_private_object_init()创建 ghost 对象时add首次取偏移时drm_gem_create_mmap_offset()ttm_bo_init_reserved()中对ttm_bo_type_device与ttm_bo_type_sg立即分配removedrm_gem_object_release()中调用drm_gem_free_mmap_offset()BO 释放时unmapshmem helper 在 purge 时ttm_bo_unmap_virtual()BO 搬迁或换出前TTM 只为 device 与 sg 两类 BO 分配区间ttm_bo_type_kernel类型的内核 BO 没有偏移用户态也就无法映射它们原因见 为什么你的内核 BO 永远不该出现在 vma_manager 里。4.3 查找按偏移找节点接口说明drm_vma_offset_lock_lookup(mgr)/drm_vma_offset_unlock_lookup(mgr)对 manager 的vm_lock加、解读锁drm_vma_offset_lookup_locked(mgr, start, pages)找到覆盖[start, start pages)的节点start可以落在节点中间drm_vma_offset_exact_lookup_locked(mgr, start, pages)同上但要求start恰好是节点起点drm_vma_offset_lookup_locked()的算法很直接在interval_tree上找起点不大于start的最大节点再检查它的终点是否覆盖start pages不覆盖就返回NULL。GEM 的 mmap 路径用的是 exact 版本。这意味着通过 DRM 设备 fd 映射 GEM 对象时mmap()的 offset 必须恰好是对象区间的起点长度不能超过区间大小不能只映射对象的后半段。不满足时查找失败drm_gem_mmap()返回-EINVAL。查找接口要求调用者先持读锁是为了在锁内取得对象引用见第 5 节。4.4 访问控制允许列表接口说明drm_vma_node_allow(node, file)把file加入允许列表。已在列表中时vm_count加一drm_vma_node_allow_once(node, file)同上但不计数已在列表中时什么也不做drm_vma_node_revoke(node, file)vm_count减一减到 0 时从列表删除drm_vma_node_is_allowed(node, file)持读锁查询file是否在列表中drm_vma_node_verify_access(node, file)is_allowed的包装返回 0 或-EACCES供 TTM 风格的回调使用为什么要计数。GEM 在drm_gem_handle_create_tail()中调用drm_vma_node_allow()在drm_gem_object_release_handle()中调用drm_vma_node_revoke()每个 handle 各一次。同一个drm_file可能对同一对象持有多个 handle计数保证关闭其中一个 handle 不会让这个drm_file失去映射权限全部关闭后才真正撤销。allow_once不计数只能配一次revoke目前只有 i915 使用。允许列表不一定跟着 handle。任何内核代码都可以为某个drm_file授权。KFD 就是一例KFD 分配的 BO其 GEM handle 建在 KFD 内部的 client file 上而用户态要通过自己打开的 render node 映射它。所以amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu()在创建 BO 后对用户进程的drm_filedrm_priv显式调用drm_vma_node_allow()释放时再revoke。revoke 不撤销已有映射。DOC 注释写明撤销权限后已经建立的映射需要调用者自行处理。所以关闭 handle 之后进程里已有的 mmap 依然有效这与 3.3 第 5 节映射与 handle 的关系一致。分配放在锁外。vma_node_allow()在加写锁之前先kmalloc一个drm_vma_offset_file锁内只做查找和链接用不上就在解锁后释放。这是持锁时不做可能睡眠的分配的典型写法见 持锁与内存分配关键路径的预分配模式。5. 并发查找与释放的竞争两把读写锁分工明确互不嵌套锁写者读者mgr-vm_lockdrm_vma_offset_add()、drm_vma_offset_remove()查找接口经drm_vma_offset_lock_lookup()node-vm_lockallow、allow_once、revokeis_allowed需要细看的是 mmap 查找与对象释放之间的竞争。查找只拿到节点指针并不持有对象引用而对象的refcount可能恰好在此时归零释放路径正在进行。GEM 的drm_gem_object_lookup_at_offset()这样处理drm_vma_offset_lock_lookup(dev-vma_offset_manager);nodedrm_vma_offset_exact_lookup_locked(dev-vma_offset_manager,start,pages);if(likely(node)){objcontainer_of(node,structdrm_gem_object,vma_node);if(!kref_get_unless_zero(obj-refcount))objNULL;}drm_vma_offset_unlock_lookup(dev-vma_offset_manager);这段代码成立依赖两个事实释放路径必须经过drm_gem_object_release()→drm_gem_free_mmap_offset()→drm_vma_offset_remove()而 remove 要拿写锁。只要查找方还持着读锁释放方就停在 remove 之前对象内存不会被回收container_of()得到的指针可以安全访问。kref_get_unless_zero()在计数已为 0 时失败。此时对象虽然还在树上但已进入释放流程查找方把它当作不存在返回-EINVAL。由此也能理解读锁的两条使用限制见drm_vma_offset_lock_lookup()的注释它是自旋型读写锁持有期间处于原子上下文不能睡眠锁内只能调用*_locked查找接口不能调用其他 vma manager 接口否则 add、remove 会在同一把锁上死锁。另外drm_vma_node_unmap()本身不加锁调用者要保证它与drm_vma_offset_remove()不会并发。6. 小结用二三四回顾全篇两个职责在设备address_space的窗口里分配偏移区间记录哪些drm_file可以映射每个区间。二者独立允许列表可以先于区间建立区间删除后也会保留。三个结构每设备一个drm_vma_offset_manager读写锁加drm_mm每对象一个drm_vma_offset_node内嵌drm_mm_node与允许列表每个被允许的drm_file一个带计数的drm_vma_offset_file。drm_gem_object⊃vma_node⊃vm_node的内嵌让查到的drm_mm_node经两次container_of()回到对象。四组接口窗口init、destroy区间reset、add、remove、unmapadd 与 remove 幂等查找持读锁调用GEM 用 exact 版本访问控制allow 与 revoke 按次计数可由 KFD 这样的内核代码独立授权撤销权限不撤销已有映射。3.3.2 将以 amdgpu 为例看这些接口如何与 TTM 的缺页、迁移配合完成一个 BO 从取得偏移到被 CPU 访问、再到搬迁后重新缺页的全过程。