AMDGPU SVM Range Restore 机制

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,!xnackALWAYS_MAPPED RESTORE
非 UNMAP,xnack,无 ALWAYS_MAPPED 是(仅 PTE) RESTORE
自发起的 populate(in_populate 置位) 全部跳过 --- ---
MMU_NOTIFY_RELEASE 跳过 --- ---

每 range 的操作:

  1. 通过 amdgpu_vm_update_range() 清除 GPU PTE(若 CLEAR_PTE
  2. drm_gpusvm_range_unmap_pages() ------ 拆除 DMA 映射
  3. range_invalidate_gpu_mapping() ------ 置 gpu_mapped = false
  4. drm_gpusvm_range_set_unmapped() ------ 标记 VMA 已解映射(仅 UNMAP)
  5. 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_tailrestore_work_list 移至 gc_list

引用计数: 首次入队时取 drm_gpusvm_range_get() 引用,

在所有 pending 操作排空后由 range_put_if_dequeued() 释放。

3. GC Worker (amdgpu_svm_range_gc_worker)

运行在 gc_wq 上(无绑定、高优先级)。

对每个出队的 range:

  1. UNMAP 处理 (amdgpu_svm_range_process_unmap_interval):

    • amdgpu_svm_attr_clear_pages() ------ 清除属性条目
    • amdgpu_svm_range_rebuild_locked()
      • 移除所有重叠的 drm_gpusvm_range 对象
      • !xnack:为扩展后的范围重建 GPU 映射
        (覆盖被部分移除的 range)
  2. 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_tailgc_listrestore_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;每次失效都暂停队列
相关推荐
DeeplyMind5 小时前
AMDGPU SVM 属性设置流程:从用户态 ioctl 到 attr_set_ctx 的完整信息收集
amdgpu svm·drm_gpusvm
DeeplyMind13 天前
linux中的HMM vs drm_pagemap 对比分析
hmm·drm_gpusvm·drm_pagemap·dev_pagemap·hmm_range
DeeplyMind17 天前
AMDGPU 基于DRM SVM框架的新SVM功能实现 :attr_range 与 svm_range 的对应关系分析
drm_gpusvm·drm_pagemap
DeeplyMind18 天前
AMDGPU 基于DRM SVM框架的新SVM功能实现 :属性子系统结构体关系解析
amdgpu svm·drm_gpusvm
DeeplyMind23 天前
drm_gpusvm_pages — svm range物理页面映射状态管理者的实现详细分析
drm_gpusvm·gpusvm_pages
DeeplyMind2 个月前
11 - SVM的高级特性:多GPU支持
svm·amdgpu·rocm·kfd
DeeplyMind2 个月前
09 - SVM缺页处理机制
svm·amdgpu·rocm·kfd·rocr
DeeplyMind2 个月前
07 - SVM内存迁移机制
svm·amdgpu·rocm·kfd·rocr
DeeplyMind2 个月前
06 - SVM范围管理
svm·amdgpu·rocm·kfd