ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

第三章:GEM分析:drm_gem_object_funcs:GEM 对象回调表的设计初衷与调用时机

2026/8/14 16:13:12 拓冰建站 浏览量
第三章:GEM分析:drm_gem_object_funcs:GEM 对象回调表的设计初衷与调用时机

drm_gem_object回答"对象是什么",而drm_gem_object_funcs(对象里的funcs指针)回答"对这个对象做各种操作时,具体该怎么做"。本文分析这张回调表为什么这样设计,以及每个回调由谁、在什么时机、持什么锁调用。本节算是对gem_object动态视角的一个从回调函数角度的补充。

1. 设计初衷:为什么是一张函数指针表

GEM 的核心哲学是"只提供驱动间共享的公共代码,不试图解决所有问题"。这条哲学落到代码上,就必须解决一个矛盾:

DRM core 想用统一的流程处理所有 GEM 对象(handle 创建、mmap、dma-buf 导出、销毁……),但每个驱动的后端存储千差万别(shmem 页、VRAM、TTM 管理的 BO、CMA 连续内存……),具体每一步该怎么做只有驱动自己知道。

drm_gem_object_funcs就是这个矛盾的解法——把"流程骨架"留在 core,把"每一步的具体实现"通过函数指针下放给驱动。它体现了几个明确的设计意图:

设计意图含义
机制与策略分离core 定义"何时该做某件事"(机制),驱动通过回调决定"具体怎么做"(策略)
面向对象的多态每个对象携带自己的funcs,同一段 core 代码对不同驱动的对象表现出不同行为——等价于 C++ 的虚函数表(vtable)
可选即降级绝大多数回调是 optional;不实现某回调,就等于声明"本对象不支持该能力",core 会走默认路径或返回不支持
每对象粒度funcs挂在对象而非驱动上,因此同一驱动可为不同类型的对象(如 shmem BO vs 私有 BO)挂不同的回调表
稳定的 ABI 边界core 与驱动只通过这张表交互,新增回调不破坏既有驱动(老驱动该字段为 NULL 即可)

历史注记:早期这些操作分散在drm_driver的全局操作集里(如gem_free_object等),是"每驱动一份"。后来收敛为每对象drm_gem_object_funcs,粒度更细、也更符合"对象自带行为"的直觉。

2. 回调全景

观测 / 调试

print_info

status

rss

内存管理

evict

CPU 映射

mmap

vm_ops

dma-buf 共享 / PRIME

export

pin / unpin

get_sg_table

vmap / vunmap

生命周期

free(必需)

open

close

drm_gem_object.funcs

下表汇总"谁调用、何时调用、锁上下文"(obj->resv指 GEM 的 dma_resv 预留锁):

回调必需主要调用者(core 函数)触发时机锁上下文
freedrm_gem_object_free()refcount归零、对象最终销毁无(已无人引用)
opendrm_gem_handle_create_tail()每次为对象新建一个 handle(含 dma-buf 导入生成 handle)无特殊要求
closedrm_gem_object_release_handle()每次释放一个 handleGEM_CLOSE、进程退出、handle_delete无特殊要求
exportdrm_gem_prime_export()PRIME_HANDLE_TO_FD,把对象导出为 dma-buf无(默认实现已足够,少有覆盖)
pindrm_gem_map_attach()dma-buf被导入方 attach时,钉住后端存储内部持obj->resv
unpindrm_gem_map_detach()dma-buf detach 时,解钉内部持obj->resv
get_sg_table○†drm_gem_map_dma_buf()导入方dma_buf_map_attachment()需要 sg 表时由 dma-buf 框架保证
vmapdrm_gem_dmabuf_vmap()/drm_gem_vmap()需要把 BO 映射到内核虚拟地址(如导入方 vmap)调用时已持obj->resv
vunmapdrm_gem_dmabuf_vunmap()释放上面的内核映射调用时已持obj->resv
mmapdrm_gem_mmap_obj()用户态 mmap 该对象(含 PRIME mmap)无特殊要求
evictdrm_gem_object_evict()内存紧张、需把对象后端换出调用时已持obj->resv
print_infodrm_gem_print_info()debugfs 打印对象信息
statusdrm_show_memory_stats()fdinfo 统计内存(resident/purgeable 等)table_lock
rssdrm_show_memory_stats()fdinfo 统计物理常驻大小table_lock
vm_ops○‡缺页时由 mm 子系统回调用户态访问 mmap 区域触发缺页mm/vma 锁

get_sg_table在"用 core 默认的drm_gem_map_dma_buf作为 dma-buf 导出实现"时是必需的;驱动若自己实现map_dma_buf则不需要。
vm_opsmmap二选一:若实现了mmap回调,则由它自行设置vma->vm_ops,此时表中的vm_ops字段不被使用。

3. 分组详解

3.1 生命周期:free / open / close

这三者对应对象"从被引用到被释放"的关键节点。理解它们必须先分清 [gem设计目标]讲的双计数refcount(内核总引用)与handle_count(用户态 handle 数)。

  • free(唯一必需回调)refcount归零时由drm_gem_object_free()调用,是对象的析构器。驱动在这里释放后端存储(页/VRAM/TTM BO)、调用drm_gem_object_release()清理 core 部分,然后kfree自己的对象。因为此刻已无人引用,无需加锁。
  • open每新建一个 handle就调一次(drm_gem_handle_create_tail)。注意它绑定的是handle,不是对象——同一对象被同一/不同进程多次 open,会多次触发。典型用途:amdgpu 在此为"进程(drm_file)× BO"建立 per-VM 的记账(如把 BO 关联到该文件的 VM)。
  • close每释放一个 handle调一次(GEM_CLOSEdrm_release进程退出遍历、drm_gem_handle_delete)。与open对称,用于拆掉 open 时建立的 per-file 关系。
驱动回调DRM core用户态驱动回调DRM core用户态... 使用对象 ...alt[refcount 归零]创建 / 导入 → 得到 handlefuncs->>open(obj, file) (每个 handle 一次)GEM_CLOSE / 进程退出funcs->>close(obj, file) (每个 handle 一次)handle_count--, refcount--funcs->>free(obj) (仅一次,析构)

常见误区:把free当成"handle 关闭就触发"。实际上只要对象还被 mmap、dma-buf 导出或驱动内部引用,close之后对象不会free——真正触发free的是refcount归零。

3.2 dma-buf 共享:export / pin / unpin / get_sg_table / vmap / vunmap

这一组服务于PRIME/dma-buf 跨进程、跨设备共享它们的调用方往往是导入端或 dma-buf 框架,而非本驱动自己。

  • export:把对象包成dma_buf。多数驱动不覆盖它,直接用 core 的drm_gem_prime_export()(内部会装配一套标准dma_buf_ops,其回调再转回下面这些函数)。仅当驱动需要自定义 dma-buf 行为时才实现。
  • pin/unpin:当导入方 attach到这个 dma-buf 时(drm_gem_map_attach),core 会先dma_resv_lock再调pin,把后端存储钉在一个可被外部 DMA 访问的位置(例如从可迁移的 VRAM 钉到稳定处),防止共享期间被迁移/换出。detach 时对称调unpin这正是"共享会抑制迁移"的根源
  • get_sg_table:导入方真正映射时(drm_gem_map_dma_buf),core 调它拿到描述后端物理页的 scatter-gather 表,再dma_map_sgtable给导入设备。注意:core 的释放路径用dma_unmap_sg + sg_free_table,因此该回调不能用于指向驱动私有内存区间的 sg 表。
  • vmap/vunmap:把 BO 映射到内核地址空间(返回iosys_map)。用于导入方或内核内部需要 CPU 直接读写 BO 的场景。关键约束:core 调用时已持obj->resv,回调内不得再去拿这把锁。
导出端驱动回调DRM core (导出端)dma-buf 框架导入方 (另一驱动/进程)导出端驱动回调DRM core (导出端)dma-buf 框架导入方 (另一驱动/进程)opt[需要内核 CPU 访问]dma_buf_attach()drm_gem_map_attach()dma_resv_lock(obj->>resv)funcs->>pin(obj) (钉住,抑制迁移)dma_buf_map_attachment()drm_gem_map_dma_buf()funcs->>get_sg_table(obj)dma_map_sgtable() → 返回 sgtdma_buf_vmap() → drm_gem_dmabuf_vmap()funcs->>vmap(obj, &map) (已持 resv)

3.3 CPU 映射:mmap / vm_ops

  • mmap:用户态对该对象mmap()时,drm_gem_mmap_obj()会调它,由驱动完成 VMA 的建立(页缓存属性、vma->vm_ops等)。若实现了它,就由它负责设置vm_ops
  • vm_ops:不实现mmap回调时的默认路径所用的 VMA 操作集,其中的fault处理缺页——用户首次访问某页时按需把后端页填入。二者互斥:有mmap就不用表里的vm_ops

这组回调让"给对象分配 mmap 伪偏移"与"真正建立 CPU 映射"衔接起来。

3.4 内存管理:evict

  • evict:内存紧张时,drm_gem_object_evict()调它把对象后端换出(如从 VRAM 移到系统内存或释放可重建的页)。调用时已持obj->resv。它体现了一个边界:GEM core不自己做迁移策略,只在合适时机"通知驱动去 evict",具体搬移仍由驱动(通常借 TTM)完成——与第 6 节"迁移属 TTM"一致。

与 gpuva 的联动:一个对象被 evict 后,指向它的所有 GPU VA 映射都要标记失效。驱动通常在 evict 路径里遍历gpuva.list逐条drm_gpuva_invalidate()

3.5 观测与调试:print_info / status / rss

这三者不影响功能,只服务于可观测性

  • print_info:debugfs 打印对象的驱动私有信息,用drm_printf_indent()输出。
  • status:返回对象状态(如 resident/purgeable),决定它被计入 fdinfo 的哪类统计;在table_lock下调用,允许与状态变更竞争(“无害”)。
  • rss:返回对象在物理内存中的常驻大小(Resident Set Size)。

statusrss都由drm_show_memory_stats()调用,支撑/proc/<pid>/fdinfo的 GPU 内存统计(gputop 等工具依赖它)。

4. 锁上下文速记(最易出错处)

实现回调时最常见的 bug 是锁误用。归纳如下:

回调进入时是否已持obj->resv实现注意
vmap/vunmap内部不要dma_resv_lock
evict同上;且此路径可能在内存回收上下文,避免大块分配
pin/unpin是(由drm_gem_map_*加持)
free否(已无引用)可自由清理
open/close/mmap/print_info按需自行加锁

5. 与 amdgpu 的对照

amdgpu 通过amdgpu_bo(内嵌ttm_buffer_object→ 内嵌drm_gem_object)实现这张表,把 GEM 的对象语义与 TTM 的迁移能力缝合:

  • free→ 释放amdgpu_bo、退还 TTM 资源;
  • open/close→ 维护"drm_file × BO"在该进程 VM 中的关系;
  • pin/unpin/get_sg_table→ 桥接到 TTM 的 pin 与 sg 表;
  • evict→ 交给 TTM 的迁移逻辑;
  • mmap/vm_ops→ TTM 的缺页与页属性处理。

可见这张表正是 core 与"驱动 + TTM"之间的契约边界:core 决定时机,驱动(借 TTM)落实动作。

6. 小结

  • drm_gem_object_funcs是 GEM"机制与策略分离"哲学的直接产物——一张每对象的虚函数表,让统一的 core 流程对接千差万别的驱动后端。
  • 唯一必需的是free;其余皆 optional,不实现即代表不支持该能力
  • 记住三条主线:生命周期(free/open/close,注意双计数)、dma-buf 共享(export/pin/unpin/get_sg_table/vmap/vunmap,注意 pin 抑制迁移、vmap 已持 resv)、CPU 映射(mmap/vm_ops 互斥)。
  • 锁是最大的坑:vmap/vunmap/evict/pin/unpin进入时 core已持obj->resv,回调内不得重复加锁。
  • 这张表定义了 core 与驱动(及 TTM)之间清晰的契约:core 管"何时",驱动管"如何"