☰
第三章:GEM分析 3.1.1 从文件视角理解 drm_gem_object(导读)
2026/10/20 11:14:19 网站建设 项目流程

本文是第三章的导读:承接 3.1 GEM 概述,在进入 3.2 drm_gem_object 的字段详解之前,用 Linux 文件的直觉把设备、drm_file、gem bo 对象、handle、mmap 与回调表串成一条线。

阅读提示:本文只建立直觉,涉及的机制都在后续小节展开。遇到暂不理解的术语可先跳过,读完后续内容再回来看这篇。

初学者看drm_gem_object常被它的一堆字段和回调劝退。但如果你用过 Linux 的open()/mmap(),其实已经有了理解它所需的直觉:把一块 GPU(drm_device)看作一个大文件。第二章已经讲过,这在 VFS 层并不只是比喻,DRM 就是这样实现的。

1. drm_device 就是"一个大文件"

用户态访问 GPU,要先打开一个设备节点。本专栏走 render node/dev/dri/renderD128(见 2.1 ),同一块 GPU 通常还有 primary 节点/dev/dri/card0。节点有多个,"文件"却只有一个。2.2 讲过,drm_open()会执行:

filp->f_mapping=dev->anon_inode->i_mapping;

同一设备所有节点、所有 open 实例的f_mapping都指向设备的同一个address_space。从 mmap 的角度看,它们打开的是同一个文件。也正因如此,对象迁移或设备拔出时,内核可以在这一个address_space上撤销所有进程对该对象的 CPU 映射。

这个文件的偏移空间中,有一段窗口由设备的dev->vma_offset_manager管理,专门分配给对象:在 64 位系统上从 4 GiB 处开始,大小约 1 TiB(见 3.3 §1)。

2. drm_file 就是对这个文件的一次 open()

每次open()设备节点,内核创建一个drm_file,它与 VFS 的struct file(open file description)一一对应,挂在filp->private_data上。和普通文件一样(见 2.1.3 §1):

  • 隔离的边界是open 实例,不是进程:同一进程 open 两次,得到两个独立的drm_file;
  • dup()、fork()产生的 fd 共享同一个drm_file;libdrm_amdgpu 的设备去重也会让同一进程内的多个组件共享一个drm_file(见 2.2 §9)。

drm_file不"拥有"对象,它只持有一张句柄表object_idr,把 per-file 的 32 位 handle 映射到共享的drm_gem_object。两个drm_file里的 handle 1 可以指向不同的对象。

3. drm_gem_object 就是文件里的一段区间

需要 CPU 映射的对象,会在设备的偏移空间里分配一段按页对齐、互不重叠的区间,记录在对象的vma_node中。这个偏移不对应任何物理地址,只是"大文件"里的一个位置,因此称为伪偏移(fake offset)。

于是:一个drm_gem_object≈ 设备这个大文件里的一段区间;handle 是某个 open 实例给它起的名字。

区间是对象的属性,一个对象只有一段;handle 是 open 实例的属性,同一个对象可以在多个drm_file中各有 handle。图中的 objB 就被两个drm_file同时引用。

4. mmap:按偏移找到对象,再交给对象自己

一次 mmap 的完整步骤见下图:①② 在用户态,③④⑤ 由设备唯一的 file 级.mmap入口drm_gem_mmap()完成。

结合文件类比,有两点值得强调:

  • 偏移不需要保密,权限靠 handle(④)。建 handle 时drm_vma_node_allow()把该drm_file加入对象的允许列表,其他drm_file即使拿到偏移值也会被拒绝。handle 决定谁能看见对象,偏移决定它在文件里的位置,mmap 两者都要。
  • 入口只有一个,做法由对象决定(⑤)。未实现funcs->mmap时,由 core 按默认方式设置 VMA,缺页交给vm_ops->fault。

5. 为什么是 funcs:file 接口细化到每个对象

第 4 节的步骤 ⑤ 揭示了drm_gem_object_funcs的本质。对象的后备存储千差万别:shmem 页(drm_gem_shmem_object)、物理连续内存(drm_gem_dma_object)、VRAM 与 GTT(ttm_buffer_object),但 core 只有一套流程。解决办法是给每个对象挂一张函数指针表funcs,让同一段 core 代码对不同对象表现出不同行为,相当于 C++ 的虚函数表,是对象级的多态。

其中一部分回调,正是把 file 级操作下放到对象:

回调对应的 file 级操作
mmap/vm_ops设备 fd 的mmap()(fops->mmap)
open/close在某个drm_file中建立、释放一个 handle
exportPRIME_HANDLE_TO_FD:对象被包进 dma-buf 自己的匿名struct file
status/rss读取/proc/<pid>/fdinfo/<fd>(fops->show_fdinfo,见 2.1.3 §6);amdgpu 用自己的统计实现,不经过这两个回调

其余回调由内核内部事件触发,不对应 file 级入口:free在refcount归零时调用,pin/get_sg_table由 dma-buf 导入方触发,vmap由需要 CPU 地址的内核代码触发,evict由驱动的回收路径触发。完整分组见 3.2.1 drm_gem_object_funcs:回调的分组、功能与调用时机。

6. 类比的边界

"对象 = 文件里的一段区间"在偏移空间这一层成立,但 GEM 对象不是磁盘文件里的静态字节:

维度磁盘文件的一段内容GEM 对象
内容落点固定在文件里后备页会迁移、被驱逐、缺页时按需填入:偏移稳定,物理落点动态
归属属于这个文件可被多个drm_file引用,经 dma-buf 还可跨进程、跨设备共享
寿命随文件由refcount决定,独立于任何drm_file

另有四点要摆正:

  1. "大文件"是drm_device,不是某个设备节点,也不是某个drm_file。各节点只是它的入口,drm_file只是对它的一次 open。
  2. 有两种"文件"。设备这个大文件提供偏移空间,用来定位对象;对象的后备存储可能是另一个文件,shmem 对象的obj->filp就是一个真实的 shmem 文件,页从那里来。前者回答"在哪里找到它",后者回答"内容存在哪里"。
  3. handle 与偏移是两层名字。handle 是 per-file 的,ioctl 用它指称对象;偏移是全设备的,mmap 用它定位对象。两者经 mmap 的权限检查连在一起(第 4 节的步骤 ④)。
  4. 类比只覆盖身份与 CPU 视图。按 3.1 §4 的划分,对象还有 GPU 视图(gpuva,见 3.4)与设备间视图(dma_buf/import_attach,见 3.5),这两类访问不经过设备的偏移空间,不在本类比的适用范围内。

7. 小结

把drm_device看作一个大文件:

  • 它的偏移空间被切成"每对象一段",每段就是一个drm_gem_object;
  • drm_file是对它的一次open(),用 per-file 的 handle 引用这些共享对象;
  • 设备唯一的.mmap入口按偏移找到对象、确认本 open 实例持有 handle,再交给对象的funcs完成映射,这就是对象级多态。

有了这个模型,再读 3.2 drm_gem_object 的字段详解,vma_node(它在文件里的位置)、handle_count(它有几个名字)、refcount(它的寿命)、funcs(它的虚函数表)、filp(shmem 后备存储)就都各归其位了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询