难度: 🟡 进阶 预计学习时间: 1-1.5小时 前置知识: 前面章节内容、Linux进程管理基础
📋 概述
在AMDGPU驱动中,每个使用GPU计算的进程都有一个kfd_process结构。SVM功能是以进程为单位组织的——每个进程都有独立的SVM范围列表。本章将深入探讨进程如何管理SVM,以及在进程生命周期的各个阶段SVM如何初始化、使用和清理。
想象一下进程就像一个图书馆:
- 📖 kfd_process 是图书馆本身
- 📚 svm_range_list 是图书管理系统
- 📘 每个 svm_range 是一本书
- 🔑 进程拥有所有书籍的管理权
5.1 kfd_process 中的 SVM 管理
kfd_process 结构体
// 文件: kfd_priv.h
struct kfd_process {
// === 进程身份 ===
struct hlist_node kfd_processes; // 全局进程哈希表节点
void *mm; // mm_struct指针(不持有引用)
struct task_struct *lead_thread; // 主线程
uint32_t pasid; // Process Address Space ID
u16 context_id; // 上下文ID
// === 引用计数 ===
struct kref ref; // 引用计数
struct work_struct release_work; // 释放工作
// === 同步保护 ===
struct mutex mutex; // 进程互斥锁
// === GPU设备 ===
struct kfd_process_device *pdds[MAX_GPU_INSTANCE]; // 每GPU的设备数据
uint32_t n_pdds; // 设备数量
// === 队列管理 ===
struct process_queue_manager pqm; // 队列管理器
// === SVM管理 ★ 重点 ===
struct svm_range_list svms; // SVM范围列表
bool xnack_enabled; // XNACK是否启用
// === MMU通知 ===
struct mmu_notifier mmu_notifier; // MMU通知器
// === 事件管理 ===
struct mutex event_mutex;
struct idr event_idr;
struct kfd_signal_page *signal_page;
// === 内存驱逐 ===
void *kgd_process_info; // GPU驱动信息
struct dma_fence __rcu *ef; // 驱逐fence
struct delayed_work eviction_work;
struct delayed_work restore_work;
// === 调试支持 ===
bool debug_trap_enabled;
struct kfd_process *debugger_process;
// === 其他 ===
bool is_32bit_user_mode;
struct kobject *kobj;
// …更多字段
};
SVM在进程中的位置
kfd_process (进程)
↓
├─ pdds[0..7] (每GPU设备数据)
│ ├─ GPU 0相关数据
│ ├─ GPU 1相关数据
│ └─ …
│
├─ pqm (队列管理)
│ ├─ 计算队列
│ └─ SDMA队列
│
├─ svms ★ (SVM管理)
│ ├─ objects (区间树:快速查找)
│ ├─ list (链表:顺序遍历)
│ ├─ restore_work (恢复工作)
│ └─ deferred_list (延迟处理)
│
└─ mmu_notifier (MMU通知)
└─ 监听CPU页表变化
进程与SVM的关系
一对一关系:
一个进程 ←→ 一个svm_range_list
←→ 多个svm_range
示例:
// 获取进程的SVM管理结构
struct kfd_process *p = ...;
struct svm_range_list *svms = &p->svms;
// 查找包含某地址的SVM范围
struct svm_range *prange;
prange = svm_range_from_addr(svms, addr, NULL);
// 遍历进程的所有SVM范围
list_for_each_entry(prange, &svms->list, list) {
pr_debug("Range [0x%lx-0x%lx]\\n", prange->start, prange->last);
}
5.2 进程创建时的SVM初始化
进程创建流程
用户打开 /dev/kfd
↓
kfd_open()
↓
kfd_create_process(current)
↓
create_process(thread, true)
↓
┌─────────────────────────┐
│1. 分配kfd_process │
│2. 初始化基本字段 │
│3. 初始化队列管理器 │
│4. 初始化apertures │
│5. ★svm_range_list_init()│ ← SVM初始化
│6. 注册MMU notifier │
│7. 添加到全局哈希表 │
└─────────────────────────┘
svm_range_list_init 详解
// 文件: kfd_svm.c
int svm_range_list_init(struct kfd_process *p)
{
struct svm_range_list *svms = &p->svms;
int i;
// 1. 初始化区间树(红黑树)
svms->objects = RB_ROOT_CACHED; // 空的红黑树
// 2. 初始化互斥锁
mutex_init(&svms->lock);
// 3. 初始化链表
INIT_LIST_HEAD(&svms->list);
// 4. 初始化状态计数器
atomic_set(&svms->evicted_ranges, 0); // 驱逐计数
atomic_set(&svms->drain_pagefaults, 0); // 排空标志
// 5. 初始化工作队列
INIT_DELAYED_WORK(&svms->restore_work, svm_range_restore_work);
INIT_WORK(&svms->deferred_list_work, svm_range_deferred_list_work);
// 6. 初始化延迟列表
INIT_LIST_HEAD(&svms->deferred_range_list);
spin_lock_init(&svms->deferred_list_lock);
// 7. 初始化CRIU列表
INIT_LIST_HEAD(&svms->criu_svm_metadata_list);
// 8. 检查哪些GPU支持SVM
for (i = 0; i < p->n_pdds; i++) {
if (KFD_IS_SVM_API_SUPPORTED(p->pdds[i]->dev->adev)) {
bitmap_set(svms->bitmap_supported, i, 1);
}
}
// 9. 设置默认粒度
svms->default_granularity = min_t(u8, amdgpu_svm_default_granularity, 0x1B);
pr_debug("Default SVM Granularity: %d\\n", svms->default_granularity);
return 0;
}
初始化后的状态:
p->svms:
├─ objects: 空的区间树
├─ list: 空链表
├─ lock: 已初始化
├─ evicted_ranges: 0
├─ drain_pagefaults: 0
├─ restore_work: 已初始化(未调度)
├─ deferred_list_work: 已初始化(未调度)
├─ bitmap_supported: [1][1][0][0][0][0][0][0]
│ (假设GPU 0和1支持SVM)
└─ default_granularity: 9 (512页 = 2MB)
GPU支持检查
// KFD_IS_SVM_API_SUPPORTED 宏定义
#define KFD_IS_SVM_API_SUPPORTED(adev) \\
((adev)->kfd.pgmap.type != 0 || (adev)->apu_prefer_gtt)
检查条件:
示例:
// 4个GPU系统
for (i = 0; i < p->n_pdds; i++) {
struct amdgpu_device *adev = p->pdds[i]->dev->adev;
if (adev->kfd.pgmap.type != 0) {
// dGPU,VRAM已注册
bitmap_set(svms->bitmap_supported, i, 1);
pr_debug("GPU %d: dGPU with VRAM, SVM supported\\n", i);
} else if (adev->apu_prefer_gtt) {
// APU,使用系统内存
bitmap_set(svms->bitmap_supported, i, 1);
pr_debug("GPU %d: APU with GTT, SVM supported\\n", i);
} else {
pr_debug("GPU %d: SVM not supported\\n", i);
}
}
XNACK支持检查
// 在create_process()中
process->xnack_enabled = kfd_process_xnack_mode(process, false);
XNACK (Exception No ACK) 是GPU页面重试机制:
XNACK启用:
GPU访问地址 → 缺页 → 发送中断 → GPU暂停 → 驱动处理 → GPU重试 → 成功
XNACK禁用:
GPU访问地址 → 缺页 → GPU挂起(无法恢复)
检查方式:
bool kfd_process_xnack_mode(struct kfd_process *p, bool supported)
{
// 检查所有GPU是否支持XNACK
for (i = 0; i < p->n_pdds; i++) {
if (!p->pdds[i]->dev->kfd->device_info.needs_iommu_device)
return false; // 不支持
}
return true;
}
5.3 进程销毁时的清理
进程销毁流程
进程退出
↓
kfd_process_wq_release()
↓
kfd_process_destroy_delayed()
↓
┌──────────────────────────┐
│1. 取消所有队列 │
│2. 释放事件资源 │
│3. ★ svm_range_list_fini()│ ← SVM清理
│4. 释放apertures │
│5. 销毁pdds │
│6. 从哈希表移除 │
│7. 释放kfd_process │
└──────────────────────────┘
svm_range_list_fini 详解
// 文件: kfd_svm.c
void svm_range_list_fini(struct kfd_process *p)
{
struct svm_range *prange;
struct svm_range *next;
pr_debug("process pid %d svms 0x%p\\n",
p->lead_thread->pid, &p->svms);
// 1. 取消恢复工作
cancel_delayed_work_sync(&p->svms.restore_work);
// 2. 确保延迟工作完成
flush_work(&p->svms.deferred_list_work);
// 3. 停止新的缺页处理
atomic_set(&p->svms.drain_pagefaults, 1);
svm_range_drain_retry_fault(&p->svms);
// 4. 遍历并清理所有范围
list_for_each_entry_safe(prange, next, &p->svms.list, list) {
// 从列表和区间树移除
svm_range_unlink(prange);
// 移除MMU notifier
svm_range_remove_notifier(prange);
// 释放范围(包括DMA映射、VRAM等)
svm_range_free(prange, true);
}
// 5. 销毁互斥锁
mutex_destroy(&p->svms.lock);
pr_debug("process pid %d svms 0x%p done\\n",
p->lead_thread->pid, &p->svms);
}
清理步骤详解:
步骤1-2:停止异步工作
// 取消延迟恢复工作
cancel_delayed_work_sync(&p->svms.restore_work);
// 如果工作正在运行,等待完成
// 刷新延迟列表工作
flush_work(&p->svms.deferred_list_work);
// 确保所有pending的工作都执行完
为什么重要?
- 避免在清理时还有异步操作在执行
- 防止use-after-free
步骤3:排空缺页异常
atomic_set(&p->svms.drain_pagefaults, 1);
svm_range_drain_retry_fault(&p->svms);
作用:
设置drain_pagefaults标志 → GPU缺页处理函数检查标志
↓
如果为1,直接返回,不处理
↓
等待所有进行中的缺页处理完成
实现:
void svm_range_drain_retry_fault(struct svm_range_list *svms)
{
struct task_struct *task;
// 等待所有缺页处理任务完成
while ((task = svms->faulting_task)) {
// 等待任务退出缺页处理
wait_event_interruptible(svms->wait_drain,
svms->faulting_task != task);
}
}
步骤4:清理所有范围
list_for_each_entry_safe(prange, next, &p->svms.list, list) {
// a. 从数据结构移除
svm_range_unlink(prange);
// b. 移除MMU notifier
svm_range_remove_notifier(prange);
// c. 释放资源
svm_range_free(prange, true);
}
svm_range_free 做什么?
void svm_range_free(struct svm_range *prange, bool do_unmap)
{
// 1. 取消GPU映射
if (do_unmap && prange->mapped_to_gpu) {
svm_range_unmap_from_gpu(...);
}
// 2. 释放DMA映射
for (i = 0; i < MAX_GPU_INSTANCE; i++) {
if (prange->dma_addr[i]) {
svm_range_dma_unmap(prange, i);
kvfree(prange->dma_addr[i]);
}
}
// 3. 释放VRAM资源
if (prange->svm_bo) {
svm_range_vram_node_free(prange);
}
// 4. 释放范围结构本身
kfree(prange);
}
清理过程可视化
进程销毁前:
p->svms
├─ range1 [0x1000-0x2000]
│ ├─ DMA映射 GPU0: [addr0, addr1, …]
│ ├─ GPU页表映射 ✓
│ └─ MMU notifier ✓
├─ range2 [0x3000-0x5000]
│ ├─ svm_bo (VRAM)
│ ├─ GPU页表映射 ✓
│ └─ MMU notifier ✓
└─ range3 [0x6000-0x7000]
清理步骤:
1. drain_pagefaults = 1 → 停止新缺页
2. 清理range1:
– 移除MMU notifier
– 取消GPU映射
– 释放DMA映射
– 释放结构
3. 清理range2:
– 移除MMU notifier
– 取消GPU映射
– 释放VRAM (svm_bo)
– 释放结构
4. 清理range3: …
清理后:
p->svms
├─ objects: 空树
└─ list: 空链表
5.4 多GPU场景下的管理
进程的多GPU视图
kfd_process
├─ pdds[0] → GPU 0
│ ├─ 队列
│ ├─ aperture
│ └─ VM管理
├─ pdds[1] → GPU 1
│ ├─ 队列
│ ├─ aperture
│ └─ VM管理
├─ pdds[2] → GPU 2
└─ …
svms (共享)
├─ range1: GPU 0和1可访问
├─ range2: 仅GPU 0可访问
└─ range3: 所有GPU可访问
多GPU的SVM策略
1. 访问位图管理
struct svm_range *prange = ...;
// 设置GPU 0和2可以访问
set_bit(0, prange->bitmap_access);
set_bit(2, prange->bitmap_access);
// GPU 1尝试访问 → 缺页
if (!test_bit(1, prange->bitmap_access)) {
// 决定策略:
// 选项1: 迁移到GPU 1的VRAM
// 选项2: 保持在原位,建立P2P映射
// 选项3: 保持在系统内存,GPU 1通过PCIe访问
}
2. AIP (Access In Place) 支持
AIP允许GPU无需迁移即可访问其他位置的内存:
range在GPU 0的VRAM:
prange->actual_loc = 0;
prange->svm_bo->node = GPU 0;
GPU 1想访问:
选项1: 迁移到GPU 1 (慢,但后续快)
选项2: AIP – 通过XGMI直接访问GPU 0的VRAM (无需迁移)
if (supports_xgmi(GPU0, GPU1)) {
// 使用AIP
set_bit(1, prange->bitmap_aip);
// 建立P2P映射,无需迁移
}
XGMI互连示例:
GPU 0 ←─XGMI─→ GPU 1
│ │
VRAM VRAM
│ │
[range数据] (可直接访问)
↑
AIP映射
3. 多GPU缺页处理
// GPU 1访问range(actual_loc = 0,在GPU 0)
int svm_range_restore_pages(...)
{
// 1. 检查访问位图
if (!test_bit(gpu_id, prange->bitmap_access)) {
// 2. 决定策略
if (can_use_aip(prange, gpu_id)) {
// 使用AIP,无需迁移
set_bit(gpu_id, prange->bitmap_aip);
// 建立P2P映射
} else if (should_migrate(prange, gpu_id)) {
// 迁移到目标GPU
svm_migrate_to_gpu(prange, gpu_id);
prange->actual_loc = gpu_id;
} else {
// 保持在当前位置,通过PCIe访问
}
}
// 3. 建立GPU页表映射
svm_range_map_to_gpu(prange, gpu_id, ...);
set_bit(gpu_id, prange->bitmap_access);
}
迁移策略示例
// 自适应迁移策略
bool should_migrate_to_gpu(struct svm_range *prange, uint32_t gpu_id)
{
// 1. 已经在目标GPU?
if (prange->actual_loc == gpu_id)
return false;
// 2. 检查访问模式
if (prange->prefetch_loc == gpu_id)
return true; // 用户预取提示
// 3. 检查访问频率
if (access_frequency[gpu_id] > threshold)
return true;
// 4. 检查AIP是否可用
if (test_bit(gpu_id, prange->bitmap_aip))
return false; // 已有AIP,无需迁移
// 5. 默认:根据粒度和大小决定
if (prange->npages < (1 << prange->granularity))
return true; // 小范围,迁移成本低
return false;
}
💡 重点提示
进程级管理:SVM是以进程为单位管理的,每个进程有独立的svms。
生命周期管理:SVM初始化在进程创建时,清理在进程销毁时,确保资源不泄漏。
异步操作排空:进程销毁前必须确保所有异步操作完成,包括工作队列和缺页处理。
多GPU共享:一个进程的SVM范围可以被多个GPU共享,通过位图管理访问权限。
XNACK是关键:没有XNACK,GPU缺页会导致挂起,SVM无法工作。
⚠️ 常见误区
❌ 误区1:“每个GPU有独立的SVM管理”
- ✅ 正确理解:一个进程只有一个svms,被所有GPU共享。
❌ 误区2:“进程退出时内核会自动清理SVM”
- ✅ 正确理解:需要显式调用svm_range_list_fini清理资源。
❌ 误区3:“bitmap_supported一直不变”
- ✅ 正确理解:GPU热插拔或runtime变化时可能改变。
❌ 误区4:“清理顺序无关紧要”
- ✅ 正确理解:必须先排空异步操作,再清理数据结构,顺序错误会崩溃。
📝 实践练习
代码追踪:
# 查找进程创建
grep -n "create_process" drivers/gpu/drm/amd/amdkfd/kfd_process.c
# 查找SVM初始化
grep -A 20 "svm_range_list_init" drivers/gpu/drm/amd/amdkfd/kfd_svm.c
# 查找SVM清理
grep -A 30 "svm_range_list_fini" drivers/gpu/drm/amd/amdkfd/kfd_svm.c
思考题:
- 为什么要在清理前设置drain_pagefaults标志?
- 如果不调用cancel_delayed_work_sync会发生什么?
- 多个GPU访问同一个range时,如何避免重复迁移?
调试练习:
# 查看进程的SVM信息(需要debugfs)
cat /sys/kernel/debug/dri/0/kfd/proc/<pid>/svm
# 查看GPU支持情况
dmesg | grep "SVM.*supported"
画图练习: 画出进程创建和销毁时SVM状态的变化时序图。
📚 本章小结
- kfd_process: 每个使用GPU计算的进程都有一个,包含svms字段
- 初始化: svm_range_list_init在进程创建时调用,初始化数据结构和工作队列
- 清理: svm_range_list_fini在进程销毁时调用,排空异步操作并释放所有资源
- 多GPU: 一个进程的SVM范围可被多GPU共享,通过位图管理访问权限
- XNACK: GPU页面重试机制,是SVM正常工作的必要条件
理解进程与SVM的关系是掌握SVM生命周期管理的关键。
➡️ 下一步
完成了数据结构篇,我们已经理解了SVM的"骨架"。接下来进入核心功能篇,我们将学习SVM的核心操作:范围管理、页面迁移、GPU映射和缺页处理。
🔗 导航
- 上一章:04 – SVM核心数据结构详解
- 下一章: 审核中…
- 返回目录: AMD ROCm-SVM技术的实现与应用深度分析目录
网硕互联帮助中心






评论前必须登录!
注册