1. 问题背景
1.1 STALE _mapcount 问题
在 VRAM 超量分配(overcommit)场景下,当 GPU VRAM 被占满时,TTM 内存管理器需要驱逐(evict)旧的 BO 来为新的分配腾出空间。
问题 :对于 SVM(Shared Virtual Memory)的 BO,其 VRAM 背后关联着 ZONE_DEVICE 页面(struct page),这些页面通过 dev_pagemap 机制映射到用户进程的页表中。如果 TTM 直接释放 VRAM 资源而不先将这些页面迁移回系统内存,被释放的 VRAM PFN 会被新的 BO 复用,导致新分配的 struct page 发现 _mapcount 不是预期的 -1(STALE 状态),引发内核告警:
zone_device_page_init: STALE _mapcount=0 on pfn=0x3ffffdc00 (expected -1)
1.2 根因
普通 BO 的驱逐(VRAM → GTT)只是移动 TTM 管理的内存资源,不涉及 struct page 状态。但 SVM BO 的 VRAM 与 ZONE_DEVICE 页面绑定------页面的 _mapcount、PTE 映射等状态都挂在这些 PFN 上。TTM 不了解 ZONE_DEVICE 页面的存在,直接丢弃 VRAM 资源会留下悬空的页面状态。
2. 解决方案概述
2.1 核心思想
在 SVM BO 的 dma_resv(reservation object)上附加一个 eviction fence (驱逐围栏)。利用 dma_fence 的 enable_signaling 机制,在 TTM 真正驱逐前,先将 ZONE_DEVICE 页面安全迁移回系统内存,然后才允许 TTM 丢弃空的 VRAM 资源。
2.2 两个关键配合
| 机制 | 作用 |
|---|---|
| Eviction Fence | 挡在 TTM 驱逐路径上,触发迁移工作并延迟驱逐直到迁移完成 |
| DISCARDABLE 标志 | 告诉 TTM:驱逐时不要搬到 GTT,直接丢弃 VRAM 资源(因为数据已经由 fence worker 迁移到了系统内存) |
3. 原理详解
3.1 dma_fence 的 enable_signaling 机制
dma_fence 框架提供了一个关键回调 enable_signaling:
-
当某个消费者(如 TTM)首次需要等待 一个 fence 时,框架调用
enable_signaling -
这个回调的设计意图是:告诉 fence 的生产者"有人在等你了,请安排 signal"
-
我们利用这个时机调度迁移工作
TTM 要驱逐 BO
→ 发现 BO 的 dma_resv 上有一个未 signaled 的 fence
→ 调用 dma_fence_enable_sw_signaling()
→ 触发我们的 enable_signaling 回调
→ 回调中调度异步工作项(work item)
→ TTM 阻塞等待 fence signal
3.2 DISCARDABLE 的含义
普通 BO 被 TTM 驱逐时,TTM 会将数据从 VRAM 搬到 GTT(系统内存中的 GPU 可访问区域)。但 SVM BO 不需要这个行为:
- SVM 页面通过 HMM/drm_pagemap 机制管理,数据迁移由 eviction worker 通过 SDMA 完成
- 迁移完成后 VRAM 的数据可直接丢弃
- 设置 DISCARDABLE 后,TTM 驱逐时直接
num_placement = 0,丢弃 VRAM 资源即可
3.3 Eviction Worker 的迁移原理
Worker 使用 hmm_range_fault(dev_private_owner=NULL) 来触发页面迁移:
dev_private_owner 参数的含义:
hmm_range_fault遍历虚拟地址范围内的每个页面- 遇到 device-private 页面时,比较
dev_private_owner与page->pgmap->owner - 若相同:认为是"自己"的页面,直接返回 PFN(正常 GPU fault 路径使用这个)
- 若不同(包括 NULL ):认为是"外部"的页面,触发
migrate_to_ram回调
设置 dev_private_owner = NULL 确保所有 device-private 页面都会被迁移回 RAM。
迁移通过 dev_pagemap 框架的 migrate_to_ram 回调完成:
hmm_range_fault(owner=NULL)
→ 遇到 device-private page
→ 调用 page->pgmap->ops->migrate_to_ram()
→ drm_pagemap 框架处理迁移流程
→ 最终调用 copy_to_ram 回调(SDMA 将 VRAM 数据拷贝到系统内存)
→ 页面状态更新:PTE 指向系统内存,ZONE_DEVICE 页面被正确释放
3.4 临时 MMU Interval Notifier
hmm_range_fault() API 要求调用者提供一个 mmu_interval_notifier,用于:
- 序列号机制 :
mmu_interval_read_begin()返回的序列号用于检测 hmm_range_fault 执行期间是否有并发的页表修改 - 失效通知:当虚拟地址范围内的页表发生变化时,通知注册者
Eviction worker 注册一个临时的 notifier,仅在迁移期间存在。其 invalidate 回调是空操作(返回 true),因为这是一次性迁移,不需要跟踪后续失效事件。迁移完成后立即移除。
4. 完整驱逐流程
4.1 阶段一:BO 创建时的准备
BO 分配时完成以下准备:
- 创建 eviction fence,记录进程地址空间(
mm)和虚拟地址范围[start, end) - 创建 VRAM BO,设置
DISCARDABLE标志 - 将 fence 附加到 BO 的
dma_resv上
此时 fence 处于 unsignaled 状态,静静地挂在 BO 上等待。
4.2 阶段二:TTM 驱逐触发
当 TTM 需要腾出 VRAM 时:
- TTM 选中某个 SVM BO 进行驱逐
- 发现
DISCARDABLE标志,设置num_placement = 0(丢弃模式) - 等待 BO 的
dma_resv上的 fences - 触发 eviction fence 的
enable_signaling enable_signaling调度 eviction worker- TTM 阻塞等待 fence signal
4.3 阶段三:Worker 执行迁移
Worker 在异步工作线程中执行:
- 检查进程是否还存活(
mmget_not_zero),若已退出则跳过迁移 - 注册临时
mmu_interval_notifier - 调用
hmm_range_fault(owner=NULL)迁移所有 device-private 页面 - 清理临时 notifier,释放 mm 引用
- Signal fence --- 通知 TTM 可以继续
4.4 阶段四:TTM 完成驱逐
- 收到 fence signal
- 此时所有 ZONE_DEVICE 页面已安全迁移回 RAM
- 丢弃空的 VRAM 资源(
num_placement = 0) - VRAM 空间归还给 buddy allocator,可供新分配使用
5. 生命周期管理
5.1 Fence 引用计数
Fence 的引用来自三方:
| 持有者 | 获取时机 | 释放时机 |
|---|---|---|
svm_bo |
创建时 | 所有页面迁移回 RAM 后,框架回调释放 |
BO 的 dma_resv |
附加到 resv 时 | TTM 驱逐完成后 resv 清理 |
| Worker | enable_signaling 时获取引用 |
Worker 完成后释放引用 |
Fence 引用归零后:释放 mm 引用(mmdrop),RCU 延迟释放 fence 内存。
6. 错误处理原则
核心原则:无论成功还是失败,必须 signal fence。
如果不 signal,TTM 会永远等待,导致整个 GPU 内存管理挂起。
| 场景 | 处理 |
|---|---|
| 进程已退出 | 跳过迁移,直接 signal |
| 内存分配失败 | 记录告警,直接 signal |
hmm_range_fault 返回 -EBUSY |
重试(并发修改导致) |
hmm_range_fault 超时或其他错误 |
记录告警,直接 signal |
7. 时序图
TTM Fence Worker drm_pagemap
│ │ │ │
│ evict BO │ │ │
│──wait on fence──────> │ │ │
│ │ enable_signaling │ │
│ │─────────────────────>│ │
│ │ schedule_work │ │
│ (blocked) │ │ │
│ │ │ mmget_not_zero │
│ │ │ notifier_insert │
│ │ │ │
│ │ │ hmm_range_fault ───>│
│ │ │ (owner=NULL) │
│ │ │ │ migrate_to_ram
│ │ │ │ SDMA: VRAM→RAM
│ │ │ │ 页面状态更新
│ │ │<─────────────────────│
│ │ │ │
│ │ │ notifier_remove │
│ │ │ mmput │
│ │ signal │ │
│ │<─────────────────────│ │
│ │ fence_put │ │
│ fence signaled │ │ │
│<──────────────────────│ │ │
│ │ │ │
│ discard VRAM resource│ │ │
│ (VRAM 已空, 安全丢弃) │ │ │
▼ ▼ ▼ ▼