在深入分析drm gpuvm的设计和实现前,我想先给出理解这个领域的一个视角,视角对了,一切设计就是水到渠成。视角如下: Linux 内核 GPU 内存管理子系统(TTM、GEM、GPUVM)与经典 CPU 内存管理(MM)子系统之间的结构和功能具有高度相似性。我们认为,GPU 内存栈不仅仅是受 CPU MM 启发------它是在不同硬件约束下对相同基本原则的重新推导。这种类比并非偶然,而是架构上的必然,它通过共享的术语、相同的算法模式以及趋同的设计轨迹深深嵌入内核的 DRM 子系统中。
1. 论点
Linux GPU 内存管理子系统(TTM/GEM/GPUVM)构成了经典 CPU 虚拟内存概念的领域特定重新实现------包括虚拟地址空间、按需分页、基于 LRU 的驱逐和交换------以适应离散加速器内存层次结构的约束。
这一论点由以下证据支持:
- 直接的词汇借用(
swap_storage、TTM_TT_FLAG_SWAPPED、ttm_tt_swapin()) - 同构的数据结构设计(
drm_gpuvm↔mm_struct,drm_gpuva↔vm_area_struct) - 相同的算法策略(LRU 驱逐扫描、shrinker 集成、故障驱动的填充)
- 趋同的演化(GPU 故障处理通过 SVM/HMM 向 CPU 风格的按需分页发展)
2. 论证
2.1 地址空间管理
| CPU MM | GPU MM(DRM) | 结构角色 |
|---|---|---|
mm_struct |
struct drm_gpuvm |
每上下文的虚拟地址空间容器 |
vm_area_struct |
struct drm_gpuva |
映射到后备对象的连续 VA 区域 |
| VMA 红黑树 | GPUVM 区间红黑树 | 用于快速查找的空间索引 |
mmap() / munmap() |
drm_gpuvm_sm_map() / drm_gpuvm_sm_unmap() |
面向用户的 VA 空间变更 |
| 部分解映射时的 VMA 分割/合并 | drm_gpuva_op_remap(分割) |
维护 VA 空间一致性 |
drm_gpuvm 文档指出:
"DRM GPU VA 管理器使用 maple_tree 结构来跟踪 GPU 的虚拟地址空间......每个 GPU 虚拟地址空间应该有一个管理器实例。"
这在功能上与 mm_struct 管理进程的虚拟地址空间完全相同。drm_gpuvm 中的 kernel_alloc_node 直接对应于进程 VA 空间中的内核保留地址范围。
2.2 后备存储和放置
| CPU MM | GPU MM(TTM) | 结构角色 |
|---|---|---|
| 物理 RAM(zones) | VRAM(TTM_PL_VRAM) |
快速、有限的主存储 |
| 交换设备 | 系统内存(TTM_PL_SYSTEM) |
较慢、较大的溢出存储 |
struct page |
struct ttm_resource |
物理分配跟踪单元 |
| 页帧分配(buddy) | gpu_buddy_alloc_blocks() |
二的幂次方物理分配器 |
| NUMA 节点亲和性 | ttm_place.mem_type |
放置偏好层次结构 |
TTM 放置系统(包含有序 struct ttm_place 数组的 struct ttm_placement)类似于 NUMA 内存策略------表达内存应该物理驻留位置的偏好层次。
2.3 交换类比
这是最明确的类比,TTM 直接采用了 CPU MM 的术语:
c
struct ttm_tt {
struct page **pages;
#define TTM_TT_FLAG_SWAPPED BIT(0) // ← CPU 交换术语
struct file *swap_storage; // ← shmem 后备,类似交换
...
};
TTM 中驱逐到系统内存的路径在结构上与页面换出完全相同:
| CPU 换出 | GPU 驱逐(TTM) |
|---|---|
| 通过 LRU 扫描选择受害者 | 通过 ttm_resource_manager.lru[] 选择 BO |
| 将页面内容写入交换设备 | 将 BO 内容移动到系统内存 / shmem(swap_storage) |
| 用交换条目替换 PTE | 更新 ttm_resource 放置;设置 TTM_TT_FLAG_SWAPPED |
| 释放物理页帧 | 释放 VRAM 分配 |
换入/故障恢复路径:
| CPU 换入 | GPU 重新验证 |
|---|---|
页面故障触发 do_swap_page() |
命令提交触发 drm_gpuvm_validate() |
| 从交换读取,分配页帧 | 调用 ttm_bo_validate() → 分配 VRAM,复制回来 |
| 安装指向新帧的 PTE | 更新 GPU 页表条目 |
| 清除交换条目 | 清除 TTM_TT_FLAG_SWAPPED;调用 ttm_tt_swapin() |
GEM VRAM 文档明确指出:
"如果 VRAM 中没有更多空间,不活跃的 GEM 对象可以移动到系统内存。"
这句话是以下 CPU 等价表述的 GPU 版本:"如果没有更多物理 RAM,不活跃的页面可以移动到交换空间。"
2.4 基于 LRU 的驱逐和回收
两个子系统都使用 LRU(最近最少使用)作为主要的驱逐策略:
CPU MM:
- 每个内存区的活跃/不活跃 LRU 列表
kswapd守护进程执行后台回收- slab 缓存的 shrinker 回调
lru_gen(多代 LRU)用于改进老化
GPU MM(TTM/GEM):
ttm_resource_manager.lru[TTM_MAX_BO_PRIORITY]--- 每种内存类型的优先级感知 LRU- 带有
drm_gem_lru_scan()的drm_gem_lru--- GEM 对象的 shrinker 集成 ttm_pool_type.shrinker_list--- TTM 页面池注册为内核 shrinkerdrm_mm_scan_init()/drm_mm_scan_add_block()--- 用于连续驱逐的 LRU 扫描
DRM MM 文档描述了驱逐扫描模式:
"使用
drm_mm_scan_add_block()添加驱逐候选者,直到找到合适的空洞或没有更多可驱逐的对象。"
这在算法上与 CPU MM 的 shrink_inactive_list() 扫描候选者直到释放足够内存完全相同。
2.5 按需分页和故障处理
| CPU MM | GPU MM | 结构角色 |
|---|---|---|
handle_mm_fault() |
GEM 故障处理器(vm_operations_struct.fault) |
按需页面/BO 填充 |
| 延迟分配(首次访问时分配) | 首次使用时的 ttm_tt_populate() |
推迟物理分配 |
FAULT_FLAG_WRITE → CoW |
写时锁定 / 访问时迁移 | 访问类型特定处理 |
GEM 文档指出:
"驱动程序负责通过为每个页面调用
shmem_read_mapping_page_gfp()来实际分配物理页面。注意,他们可以选择在初始化 GEM 对象时分配页面,或者推迟分配直到需要内存时(例如当发生页面故障时)。"
这是标准的按需分页,移植到了 GPU 领域。
2.6 Madvise 和可清除性
| CPU MM | GPU MM | 结构角色 |
|---|---|---|
madvise(MADV_DONTNEED) |
DRM_GEM_OBJECT_PURGEABLE / shmem.madv |
提示:内存可以被回收 |
madvise(MADV_WILLNEED) |
预取操作(DRM_GPUVA_OP_PREFETCH) |
提示:内存即将被需要 |
| 页面标记为干净 → 无需写回即可释放 | ttm_backup_flags.purge --- 无需备份即可释放 |
优化:跳过写回 |
2.7 休眠作为完全换出
TTM 甚至通过将系统休眠视为完全换出来处理:
c
int ttm_device_prepare_hibernation(struct ttm_device *bdev);
// "将 GTT BO 移动到 shmem 以进行休眠"
这是 CPU MM 在挂起到磁盘期间将所有活跃页面写入交换的 GPU 等价操作。
2.8 趋同:GPU SVM 和 HMM
这种类比不是静态的------它正在趋同。现代 GPU 驱动程序(AMD KFD SVM、Intel Xe SVM)现在实现了真正的共享虚拟内存,其中:
- GPU 和 CPU 共享相同的虚拟地址空间
- GPU 页面故障像 CPU 页面故障一样处理
- HMM(
hmm_range_fault())桥接了这两个世界 - 设备私有页面(
MEMORY_DEVICE_PRIVATE)在 CPU 页表中表现为类似交换的条目
这种趋同验证了论点:GPU 内存子系统一直在解决与 CPU MM 相同的问题,而两者现在正通过 HMM 真正合并。
3. 架构映射
这里总结下整个架构中元素的对应关系。
| CPU MM | GPU MM(DRM/TTM) | 结构角色 |
|---|---|---|
mm_struct |
drm_gpuvm |
每进程虚拟地址空间容器 |
vm_area_struct |
drm_gpuva |
连续 VA 区域描述符 |
struct page / folio |
ttm_resource / drm_gem_object |
物理分配跟踪单元 |
| 物理 RAM | VRAM(TTM_PL_VRAM) |
快速本地存储 |
| 交换空间 | 系统内存(TTM_PL_SYSTEM) |
溢出存储 |
| 页表(PGD→PTE) | GPU 页表(驱动程序特定) | 虚拟→物理地址转换 |
| Buddy 分配器 | drm_buddy / drm_mm 范围分配器 |
物理内存分配 |
LRU 列表 + kswapd |
ttm_resource_manager.lru[] + shrinker |
驱逐候选者排序与回收 |
do_swap_page() |
ttm_tt_swapin() |
换入/重新填充路径 |
| PTE 中的交换条目 | TTM_TT_FLAG_SWAPPED |
标记内容已换出 |
madvise(MADV_DONTNEED) |
DRM_GEM_OBJECT_PURGEABLE |
可回收提示 |
handle_mm_fault() |
ttm_tt_populate() / GEM 故障处理器 |
按需分配/填充 |
migrate_pages() |
ttm_bo_validate()(在内存间移动) |
跨存储层迁移 |
mmu_notifier |
drm_gpuvm_bo_evict() 回调 |
映射失效通知 |
/proc/pid/maps |
debugfs GPU VA 转储 | 地址空间调试接口 |
| OOM killer | 驱逐失败 → -ENOMEM |
资源耗尽处理 |
4. 为什么这种类比在架构上是必然的
这种趋同并非巧合。两个子系统解决的是相同的抽象问题:
给定一个虚拟地址空间大于其快速本地内存的处理器,使用间接寻址(页表)、延迟分配(按需分页)和容量管理(驱逐/交换)在竞争的消费者之间复用有限的物理存储。
唯一的区别是:
- 粒度:CPU MM 以页面粒度(4KB--2MB)操作;GPU MM 通常以缓冲对象粒度(KB--GB)操作。
- 一致性模型:CPU 具有硬件缓存一致性;GPU 需要显式刷新/无效化或域转换。
- 故障延迟容忍度:CPU 页面故障会阻塞单个线程;GPU "故障"(驱逐+重新验证)在提交时批量处理(尽管现代 GPU 现在支持真正的页面故障)。
- 复用单位:CPU 在进程之间复用;GPU 在缓冲对象之间复用(尽管 GPUVM 现在提供每进程的 GPU VA 空间)。
5. 文档来源
5.1 主要内核文档
-
DRM 内存管理 --- 权威参考。TTM、GEM、GPUVM、DRM MM、Buddy 分配器都在这里有文档。包含
swap_storage、LRU、shrinker 和驱逐 API。 -
DRM GPUVM --- 记录了带有驱逐跟踪、分割/合并和验证的 GPU 虚拟地址空间管理器。
5.2 树内源代码(自文档化)
| 文件 | 相关类比 |
|---|---|
drivers/gpu/drm/ttm/ttm_tt.c |
ttm_tt_swapin()、ttm_tt_swapout()、swap_storage |
drivers/gpu/drm/ttm/ttm_bo.c |
LRU 驱逐、ttm_bo_validate() |
drivers/gpu/drm/ttm/ttm_pool.c |
带 shrinker 的页面池(对应 slab shrinker) |
drivers/gpu/drm/drm_gpuvm.c |
GPU VA 空间管理、驱逐列表 |
drivers/gpu/drm/drm_gem.c |
drm_gem_lru_scan() --- GEM 对象的 shrinker |
include/drm/ttm/ttm_tt.h |
TTM_TT_FLAG_SWAPPED、struct ttm_tt |
5.3 会议演讲和文章
- XDC(X.Org 开发者大会) --- Christian König 关于 TTM 重构的演讲明确讨论了内存层次结构和驱逐模型。
- LWN.net --- 关于 DRM 内存管理、GPUVM(Danilo Krummrich 的系列文章,2023年)和 VM_BIND 的文章使用 CPU MM 开发者熟悉的术语讨论 GPU VA 管理。
- "GEM - 图形执行管理器"(LWN,2008年) --- 建立 GEM 围绕 shmem 后备对象的设计理念的基础性文章。
5.4 HMM 桥梁
HMM(异构内存管理)子系统是这种类比的最终证据,因为它字面上桥接了两者:
- 设备私有页面在 CPU 页表中表现为类似交换的条目
hmm_range_fault()为设备内存镜像handle_mm_fault()migrate_vma_*()将migrate_pages()扩展到设备内存
6. 结论
Linux 中的 GPU 内存管理子系统不仅仅是与 CPU 内存管理类似------它是对物理稀缺下虚拟内存管理这一根本问题的趋同重新推导。证据如下:
- 词汇层面 :TTM 使用 CPU MM 术语(
swap、swapin、populate、evict、LRU、shrinker)。 - 结构层面 :
drm_gpuvm/drm_gpuva在角色和实现上镜像mm_struct/vm_area_struct。 - 算法层面:基于 LRU 的驱逐扫描、按需填充和 shrinker 集成遵循相同的模式。
- 演化层面:两个子系统正通过 HMM/SVM 积极趋同,GPU 驱动程序现在直接参与 CPU MM 的页面故障和迁移基础设施。
对于内核开发者来说,理解 CPU MM 为理解 GPU 内存管理提供了直接的概念框架------反之亦然。DRM 子系统的内存管理最好被理解为不是一种新颖的设计,而是应用于不同类型处理器的经典虚拟内存理论。
实际上,这也给理解TTM的设计提供了一个视角,可以从这个视角再去理解下第四章的TTM分析。