AMD 正在使用 drm svm框架重构SVM的实现,看来drm svm框架要进入大范围应用了。下面是在kernel社区上由AMD的开发人员提交的POC 验证版本的patches的技术方案实现。这里快速总结了实现,以飨读者。
因是POC版本,设计可能会变动,读者们慎重使用。本文仅用来跟踪前沿驱动技术的迭代发展现状。
概述
SVM range restore 机制用于在 MMU notifier 事件使 GPU 映射失效后,恢复 GPU
页表一致性。它通过双队列工作系统协调三个组件------notifier 回调 、GC worker
和 restore worker------并使用引用计数跟踪 range 生命周期。
核心不变量:
在
begin_restore(暂停队列)和end_restore(恢复队列)之间,GPU shader 不可访问 SVM 映射。restore worker 负责在恢复队列前
重建所有失效的映射。
架构
MMU notifier 事件 (UNMAP / MIGRATE / PROTECTION / ...)
|
v
amdgpu_svm_range_invalidate()
|
+--[清除 GPU PTE(按需)]
+--[解除 DMA 映射]
+--[置 gpu_mapped = false]
+--[将 range 入队到 gc_list 或 restore_work_list]
+--[begin_restore → 暂停队列(仅首次 eviction)]
|
v
+-----+------+
| |
v v
gc_worker restore_worker
(UNMAP) (RESTORE)
| |
| +-- map_attr_ranges(MIGRATE_NONE)
| +-- 成功: end_restore → 恢复队列
| +-- 失败: 重新入队 + 重新调度
|
+-- 清除属性
+-- 移除重叠 range
+-- 重建 range(若 !xnack)
+-- 转发 RESTORE 操作 → restore_worker
关键数据结构
每 Range 的队列状态 (amdgpu_svm_range)
c
struct amdgpu_svm_range {
struct drm_gpusvm_range base;
struct list_head gc_node; /* 挂在 gc_list 或 restore_work_list 上 */
unsigned long pending_start; /* 合并后的 VA 起始(页粒度) */
unsigned long pending_last; /* 合并后的 VA 结束(页粒度) */
uint8_t pending_ops; /* 位掩码: UNMAP | RESTORE */
bool in_queue; /* 引用计数守卫: 在队列中时为 true */
bool gc_queued; /* 在 gc_list 上 */
bool restore_queued; /* 在 restore_work_list 上 */
bool gpu_mapped; /* GPU PTE 是否有效 */
uint64_t pte_flags; /* 上次写入的 PTE 标志 */
uint32_t attr_flags; /* 上次写入的 SVM 属性标志 */
};
每 SVM 的工作状态 (amdgpu_svm)
c
struct amdgpu_svm {
spinlock_t gc_lock; /* 保护两个队列 */
struct list_head gc_list; /* UNMAP 工作项 */
struct list_head restore_work_list; /* RESTORE 工作项 */
struct work_struct gc_work; /* gc_worker */
struct delayed_work restore_work; /* restore_worker(1ms 延迟) */
struct workqueue_struct *gc_wq; /* WQ_UNBOUND | WQ_HIGHPRI */
struct workqueue_struct *restore_wq; /* 有序队列, WQ_HIGHPRI */
atomic_t evicted_ranges; /* >0 表示队列已暂停 */
atomic_t kfd_queues_quiesced; /* quiesce/resume 守卫 */
void (*begin_restore)(struct amdgpu_svm *); /* 暂停回调 */
void (*end_restore)(struct amdgpu_svm *); /* 恢复回调 */
};
详细流程
1. MMU Notifier 回调 (amdgpu_svm_range_invalidate)
在 notifier_lock 下调用(mmu_interval_notifier 的读侧)。
事件分类:
| 事件 | 清除 GPU PTE | 队列操作 | 是否暂停队列? |
|---|---|---|---|
MMU_NOTIFY_UNMAP |
是 | UNMAP | 是(若 !xnack) |
非 UNMAP,!xnack 或 ALWAYS_MAPPED |
否 | RESTORE | 是 |
| 非 UNMAP,xnack,无 ALWAYS_MAPPED | 是(仅 PTE) | RESTORE | 否 |
自发起的 populate(in_populate 置位) |
全部跳过 | --- | --- |
MMU_NOTIFY_RELEASE |
跳过 | --- | --- |
每 range 的操作:
- 通过
amdgpu_vm_update_range()清除 GPU PTE(若CLEAR_PTE) drm_gpusvm_range_unmap_pages()------ 拆除 DMA 映射range_invalidate_gpu_mapping()------ 置gpu_mapped = falsedrm_gpusvm_range_set_unmapped()------ 标记 VMA 已解映射(仅 UNMAP)amdgpu_svm_range_enqueue()------ 加入工作队列
2. 入队逻辑 (amdgpu_svm_range_enqueue)
对同一 range 的多次操作通过 VA 合并进行聚合:
pending_start = min(pending_start, new_start)
pending_last = max(pending_last, new_last)
pending_ops |= new_op
队列优先级: UNMAP > RESTORE。若一个已在 RESTORE 队列中的 range
收到 UNMAP,会通过 list_move_tail 从 restore_work_list 移至 gc_list。
引用计数: 首次入队时取 drm_gpusvm_range_get() 引用,
在所有 pending 操作排空后由 range_put_if_dequeued() 释放。
3. GC Worker (amdgpu_svm_range_gc_worker)
运行在 gc_wq 上(无绑定、高优先级)。
对每个出队的 range:
-
UNMAP 处理 (
amdgpu_svm_range_process_unmap_interval):amdgpu_svm_attr_clear_pages()------ 清除属性条目amdgpu_svm_range_rebuild_locked():- 移除所有重叠的
drm_gpusvm_range对象 - 若
!xnack:为扩展后的范围重建 GPU 映射
(覆盖被部分移除的 range)
- 移除所有重叠的
-
RESTORE 转发 :若同时设置了
RESTORE位:- 若
!xnack且重建成功:queue_delayed_work(restore_wq) - 否则:重新入队为
OP_RESTORE→ 进入 restore_work_list
- 若
4. Restore Worker (amdgpu_svm_range_restore_worker)
运行在 restore_wq 上(有序队列、高优先级、初始延迟 1ms)。
restore_worker():
若 exiting 或 evicted_ranges == 0:
返回
遍历 restore_work_list 中的每个 range:
出队 range
down_write(svm_lock)
map_attr_ranges(start, last, MIGRATE_NONE) ← 关键:不触发迁移
up_write(svm_lock)
若失败:
重新入队 range
need_resched = true
若全部完成且无 pending 工作:
加锁 notifier_lock + gc_lock(双重检查)
若仍无 pending:
atomic_set(evicted_ranges, 0)
end_restore() → kgd2kfd_resume_mm()
返回
重新调度自身(带延迟)
关键设计决策 ------ MIGRATE_NONE:
restore worker 绝对不能触发迁移。否则,当 preferred_loc=VRAM 时会导致活锁:
CPU fault → migrate_to_ram (VRAM→RAM) → MMU_NOTIFY_MIGRATE
→ notifier → 入队 RESTORE → restore_worker
→ map_attr_ranges(MIGRATE_TO_VRAM) ← 错误:会迁移回 VRAM
→ populate_mm → MMU_NOTIFY_MIGRATE → CPU fault → ...
使用 MIGRATE_NONE 后,restore worker 仅重建 GPU PTE,
指向页面当前所在位置(RAM 或 VRAM),不触发任何迁移。
5. Begin/End Restore(队列暂停/恢复)
begin_restore: end_restore:
evicted_ranges++(首次: 0→1) evicted_ranges = 0
kfd_queues_quiesced: 0→1 kfd_queues_quiesced: 1→0
kgd2kfd_quiesce_mm() kgd2kfd_resume_mm()
begin_restore从 notifier 中调用(仅首次 eviction)end_restore从 restore worker 中调用(所有工作排空后)- 两者均使用
atomic_cmpxchg确保恰好执行一次
完成竞态防护: restore worker 在检查队列是否为空并重置 evicted_ranges
时持有 notifier_lock。这防止了在空检查和 end_restore 调用之间,
notifier 入队新工作的竞态。
锁层级
mmu_notifier_lock(中断安全,notifier 路径中最外层)
└── gc_lock(spinlock,保护两个工作列表)
svm_lock(读写信号量,映射/重建时写持有)
└── vm PD lock(drm_exec,用于 GPU PTE 更新)
└── notifier_lock(映射期间的页面有效性检查)
└── gc_lock
notifier 路径 (在 notifier_lock 下):使用 memalloc_noreclaim_save()
避免与内存回收的死锁。
状态机
enqueue(RESTORE)
IDLE ──────────────────────────────► QUEUED_RESTORE
▲ │
│ put_if_dequeued │ 出队 + 映射成功
│ (释放引用) │
│◄─────────────────────────────────────┘
│
│ enqueue(UNMAP)
├──────────────────────────────────► QUEUED_GC
│ │
│ ┌───────────┤
│ │ UNMAP │ +RESTORE
│ │ 完成 │ 转发
│ ▼ ▼
│◄──── put_if_dequeued ── DONE QUEUED_RESTORE ──► ...
range 可以通过 list_move_tail 在 gc_list 和 restore_work_list
之间转换------当一个已入队 RESTORE 的 range 收到 UNMAP 时即会发生。
错误处理
| 失败场景 | 恢复策略 |
|---|---|
map_attr_ranges 返回错误 |
重新入队 range,重新调度 worker |
kgd2kfd_quiesce_mm 返回 -ESRCH |
无 KFD 进程,重置 quiesced 标志 |
drm_gpusvm_range_get_pages 返回 -EAGAIN |
在下一次 amdgpu_svm_range_map 迭代中重试 |
进程退出 (svm->exiting) |
worker 提前退出,work_fini 排空队列 |
与 xnack 的关系
| 模式 | 行为 |
|---|---|
xnack_enabled(重试模式) |
GPU 在硬件中处理缺页;触发 RESTORE 的 notifier 事件更少;NEED_REBUILD 为 false |
!xnack_enabled |
所有失效都需要软件重建;UNMAP 总是附带 RESTORE;每次失效都暂停队列 |