难度 : 🟡🔴 进阶到高级
预计学习时间 : 2-2.5小时
前置知识: 前面章节内容、红黑树和区间树基础
📋 概述
SVM范围管理是SVM功能的核心,负责创建、查找、分割、合并和删除虚拟内存范围。这些操作需要在保持数据一致性的同时处理复杂的边界情况。想象一下,管理SVM范围就像管理一本预约日历:
- 📅 添加预约可能与现有预约重叠
- ✂️ 需要分割现有预约来腾出空间
- 🔍 需要快速查找某个时间段的预约
- 🗑️ 取消预约时需要合并相邻的空闲时段
本章将深入这些核心操作的实现细节。
6.1 范围的创建
6.1.1 svm_range_add 函数
这是SVM范围创建的入口函数,处理复杂的重叠情况:
c
int svm_range_add(struct kfd_process *p,
uint64_t start, uint64_t size,
uint32_t nattr,
struct kfd_ioctl_svm_attribute *attrs,
struct list_head *update_list,
struct list_head *insert_list,
struct list_head *remove_list,
struct list_head *remap_list)
功能:在指定地址范围创建SVM范围,处理与现有范围的重叠。
参数详解:
start: 起始地址(页号)size: 大小(页数)nattr: 属性数量attrs: 属性数组update_list: 输出,需要更新的范围insert_list: 输出,需要插入的新范围remove_list: 输出,需要删除的旧范围remap_list: 输出,需要重新映射的范围
6.1.2 创建流程
用户请求创建新范围 [A, B]
↓
检查现有范围的重叠情况
↓
┌─────────────────────────────────┐
│ 情况1: 无重叠 │
│ → 直接创建新范围 │
├─────────────────────────────────┤
│ 情况2: 完全包含现有范围 │
│ → 更新现有范围的属性 │
│ → 分割新范围 │
├─────────────────────────────────┤
│ 情况3: 部分重叠 │
│ → 克隆现有范围 │
│ → 分割克隆 │
│ → 更新分割后的部分 │
├─────────────────────────────────┤
│ 情况4: 有间隙 │
│ → 在间隙中创建新范围 │
└─────────────────────────────────┘
↓
事务性提交(全成功或全失败)
详细示例
场景1:无重叠,直接创建
现有: [无]
请求: [1000-2000]
结果:
insert_list: [1000-2000] (新创建)
update_list: [1000-2000] (需要设置属性)
场景2:完全包含(请求范围是现有范围的子集)
现有: [1000-3000] (属性A)
请求: [1500-2500] (属性B)
步骤:
1. 克隆 [1000-3000] → [1000-3000]'
2. 分割头部: [1000-1499] (属性A)
3. 分割尾部: [2501-3000] (属性A)
4. 中间部分: [1500-2500] (属性B)
结果:
remove_list: [1000-3000] (原始)
insert_list: [1000-1499], [1500-2500], [2501-3000] (三个新范围)
update_list: [1500-2500] (需要更新属性)
场景3:部分重叠
现有: [1000-2000], [3000-4000]
请求: [1500-3500]
步骤:
1. 处理 [1000-2000]:
- 克隆并分割头部: [1000-1499]
- 重叠部分: [1500-2000] (更新)
2. 创建间隙: [2001-2999]
3. 处理 [3000-4000]:
- 克隆并分割: [3000-3500] (更新)
- 尾部: [3501-4000]
结果:
remove_list: [1000-2000], [3000-4000] (原始)
insert_list: [1000-1499], [1500-2000], [2001-2999],
[3000-3500], [3501-4000]
update_list: [1500-2000], [2001-2999], [3000-3500]
6.1.3 代码解析
c
// 主循环:处理所有重叠的范围
node = interval_tree_iter_first(&svms->objects, start, last);
while (node) {
prange = container_of(node, struct svm_range, it_node);
next = interval_tree_iter_next(node, start, last);
// 检查是否属性相同
if (svm_range_is_same_attrs(p, prange, nattr, attrs) &&
prange->mapped_to_gpu) {
// 属性相同,无需操作
}
// 部分重叠:需要分割
else if (node->start < start || node->last > last) {
// 1. 克隆现有范围
struct svm_range *old = prange;
prange = svm_range_clone(old);
// 2. 添加到相应列表
list_add(&old->update_list, remove_list);
list_add(&prange->list, insert_list);
list_add(&prange->update_list, update_list);
// 3. 分割头部(如果需要)
if (node->start < start)
svm_range_split_head(prange, start,
insert_list, remap_list);
// 4. 分割尾部(如果需要)
if (node->last > last)
svm_range_split_tail(prange, last,
insert_list, remap_list);
}
// 完全包含:直接更新
else
list_add(&prange->update_list, update_list);
// 处理间隙
if (node->start > start)
svm_range_split_new(svms, start, node->start - 1,
..., &new_list, update_list);
node = next;
start = next_start;
}
// 处理尾部间隙
if (start <= last)
svm_range_split_new(svms, start, last,
..., &new_list, update_list);
6.1.4 事务性保证
c
// 如果任何操作失败,回滚所有更改
if (r) {
// 释放所有新创建的范围
list_for_each_entry_safe(prange, tmp, insert_list, list)
svm_range_free(prange, false);
list_for_each_entry_safe(prange, tmp, &new_list, list)
svm_range_free(prange, true);
} else {
// 成功,合并新范围到插入列表
list_splice(&new_list, insert_list);
}
为什么需要事务性?
- 避免部分完成导致的不一致状态
- 失败时无需复杂的清理逻辑
- 保证调用者看到的是原子操作
6.1.5 VMA跨越的约束
SVM范围可以跨越多个VMA,但有严格要求。
从 svm_range_is_valid() 函数的实现可以看出,SVM范围允许跨越多个VMA:
c
// 文件: kfd_svm.c
static int
svm_range_is_valid(struct kfd_process *p, uint64_t start, uint64_t size)
{
const unsigned long device_vma = VM_IO | VM_PFNMAP | VM_MIXEDMAP;
struct vm_area_struct *vma;
unsigned long end;
start <<= PAGE_SHIFT;
end = start + (size << PAGE_SHIFT);
do {
vma = vma_lookup(p->mm, start);
if (!vma || (vma->vm_flags & device_vma))
return -EFAULT;
start = min(end, vma->vm_end);
} while (start < end); // 循环检查所有VMA
return svm_range_check_vm(...);
}
VMA必须满足一下要求:
1. VMA必须连续存在
c
vma = vma_lookup(p->mm, start);
if (!vma || ...)
return -EFAULT;
- SVM范围覆盖的整个地址空间必须都有对应的VMA
- 中间不能有空洞(unmapped区域)
- 如果任何部分没有VMA,创建将失败
2. 禁止特殊设备映射
c
const unsigned long device_vma = VM_IO | VM_PFNMAP | VM_MIXEDMAP;
if (vma->vm_flags & device_vma)
return -EFAULT;
不允许的VMA类型:
VM_IO:I/O设备映射(如/dev/mem)VM_PFNMAP:Page Frame Number映射(如framebuffer)VM_MIXEDMAP:混合页面映射
原因:这些特殊映射无法参与页面迁移和MMU通知机制,与SVM的核心机制冲突。
3. 不能与GPU BO映射冲突
c
return svm_range_check_vm(p, start_unchg, (end - 1) >> PAGE_SHIFT, NULL, NULL);
svm_range_check_vm() 确保:
- SVM范围不能覆盖已通过
kfd_ioctl_alloc_memory_of_gpu分配的区域 - 避免与传统GPU内存管理路径冲突
- 返回
-EADDRINUSE表示地址已被占用
4. 尊重VMA的访问权限
在页面错误处理中(svm_range_restore_pages),系统会检查VMA权限:
c
// 文件: kfd_svm.c, 行: 1710-1730
vma = vma_lookup(mm, addr);
if (vma) {
readonly = !(vma->vm_flags & VM_WRITE);
if (!(vma->vm_flags & VM_READ)) {
// 特殊情况:不可读但可写
if (vma->vm_flags & VM_WRITE)
readonly = false;
}
}
含义:
- 跨越的多个VMA可以有不同的访问权限
- 页面迁移时会根据VMA权限设置GPU页表权限
- 只读VMA中的页面会在GPU侧映射为只读
实际场景示例
场景1:跨heap和stack(✅ 允许)
VMA1: [0x1000-0x2000] (heap, RW)
VMA2: [0x3000-0x4000] (stack, RW)
SVM Range: [0x1500-0x3500]
状态: ✅ 允许(连续,都是普通内存)
场景2:跨mmap区域(✅ 允许)
VMA1: [0x1000-0x2000] (anonymous, RW)
VMA2: [0x2000-0x3000] (anonymous, RO)
SVM Range: [0x1000-0x3000]
状态: ✅ 允许(权限会分段处理)
说明: VMA2的只读页面在GPU侧也映射为只读
场景3:包含设备映射(❌ 禁止)
VMA1: [0x1000-0x2000] (normal, RW)
VMA2: [0x2000-0x3000] (device, VM_IO)
SVM Range: [0x1000-0x3000]
状态: ❌ 禁止(包含设备映射)
错误: -EFAULT
场景4:有空洞(❌ 禁止)
VMA1: [0x1000-0x2000] (normal, RW)
[gap: 0x2000-0x3000] ← 未映射区域
VMA2: [0x3000-0x4000] (normal, RW)
SVM Range: [0x1000-0x4000]
状态: ❌ 禁止(中间有未映射区域)
错误: -EFAULT
场景5:与GPU BO冲突(❌ 禁止)
VMA1: [0x1000-0x3000] (normal, RW)
GPU BO: [0x2000-0x2500] (通过kfd_ioctl_alloc_memory_of_gpu分配)
SVM Range: [0x1500-0x2800]
状态: ❌ 禁止(与GPU BO重叠)
错误: -EADDRINUSE
为什么允许跨VMA?
- 灵活性 :用户可能通过多次
mmap()调用创建连续的内存区域 - 简化API:应用程序不需要了解内核内部的VMA边界
- 动态适应:heap和stack会动态增长,导致VMA边界变化
- 统一管理:大块内存可能由多个VMA组成,但希望作为单一SVM范围管理
为什么有这些限制?
-
MMU Notifier依赖:
- SVM使用
mmu_interval_notifier跟踪页面变化 - 设备映射(VM_IO等)不支持标准的页面操作
- SVM使用
-
页面迁移要求:
- 需要能够分配/释放物理页面
- 设备映射的页面无法迁移
-
避免冲突:
- 传统GPU BO和SVM使用不同的内存管理路径
- 同一地址不能同时被两种机制管理
-
硬件限制:
- GPU页表需要可修改的物理页面映射
- 特殊映射可能有固定的物理地址
边界情况处理
svm_range_get_range_boundaries() 函数展示了如何处理VMA边界:
c
static int
svm_range_get_range_boundaries(struct kfd_process *p, int64_t addr,
unsigned long *start, unsigned long *last,
bool *is_heap_stack)
{
struct vm_area_struct *vma;
vma = vma_lookup(p->mm, addr << PAGE_SHIFT);
if (!vma) {
pr_debug("VMA does not exist in address [0x%llx]\n", addr);
return -EFAULT;
}
*is_heap_stack = vma_is_initial_heap(vma) || vma_is_initial_stack(vma);
// 考虑VMA边界和默认粒度
start_limit = max(vma->vm_start >> PAGE_SHIFT,
ALIGN_DOWN(addr, 1UL << p->svms.default_granularity));
end_limit = min(vma->vm_end >> PAGE_SHIFT,
ALIGN(addr + 1, 1UL << p->svms.default_granularity));
// 避免与现有范围重叠
// ...
}
这个函数在页面错误时自动创建范围,它会:
- 限制范围在单个VMA内
- 考虑默认粒度对齐
- 避免与已存在的范围重叠
- 特殊处理heap和stack
最佳实践建议
- 使用大块mmap :尽量通过单次
mmap()创建大区域,减少VMA碎片 - 检查返回值 :调用SVM API后检查错误码,特别是
-EFAULT和-EADDRINUSE - 避免混合映射:不要在SVM区域内混合普通内存和设备映射
- 按粒度对齐 :使用
p->svms.default_granularity对齐地址,减少边界问题 - 分离BO和SVM:传统GPU BO和SVM区域使用不同的地址范围
6.2 范围的查找 (interval tree使用)
区间树原理
区间树是基于红黑树的数据结构,支持高效的区间查询:
普通查找:给定精确地址 → O(log N)
区间查找:给定地址范围 → O(log N + K) (K是结果数量)
数据结构:
c
struct interval_tree_node {
struct rb_node rb; // 红黑树节点
unsigned long start; // 区间起始
unsigned long last; // 区间结束
unsigned long __subtree_last; // 子树最大结束值(内部使用)
};
查找操作
1. 查找包含指定地址的范围
c
struct svm_range *svm_range_from_addr(struct svm_range_list *svms,
unsigned long addr,
struct svm_range **parent)
{
struct interval_tree_node *node;
// 在区间树中查找包含addr的节点
node = interval_tree_iter_first(&svms->objects, addr, addr);
if (!node)
return NULL;
return container_of(node, struct svm_range, it_node);
}
示例:
区间树:
[1000-2000]
[3000-4000]
[5000-6000]
查找地址 3500:
→ interval_tree_iter_first(&svms->objects, 3500, 3500)
→ 返回 [3000-4000]
查找地址 2500:
→ 返回 NULL(无包含此地址的范围)
2. 查找与指定范围重叠的所有范围
c
// 查找所有与 [start, last] 重叠的范围
struct interval_tree_node *node;
node = interval_tree_iter_first(&svms->objects, start, last);
while (node) {
struct svm_range *prange;
prange = container_of(node, struct svm_range, it_node);
// 处理prange...
node = interval_tree_iter_next(node, start, last);
}
示例:
区间树:
[1000-2000]
[2500-3000]
[3500-4000]
查找范围 [1500-3500]:
第一次: 返回 [1000-2000] (与 [1500-3500] 重叠)
第二次: 返回 [2500-3000] (与 [1500-3500] 重叠)
第三次: 返回 [3500-4000] (与 [1500-3500] 重叠)
第四次: 返回 NULL
3. 区间树的维护
c
// 插入
prange->it_node.start = prange->start;
prange->it_node.last = prange->last;
interval_tree_insert(&prange->it_node, &svms->objects);
// 删除
interval_tree_remove(&prange->it_node, &svms->objects);
// 更新(需要先删除再插入)
interval_tree_remove(&prange->it_node, &svms->objects);
prange->start = new_start;
prange->last = new_last;
prange->it_node.start = new_start;
prange->it_node.last = new_last;
interval_tree_insert(&prange->it_node, &svms->objects);
区间树 vs 链表的时间复杂度
操作 区间树 链表
-------------------------------------
精确查找 O(log N) O(N)
区间查找 O(log N + K) O(N)
插入 O(log N) O(1)
删除 O(log N) O(1)
遍历所有 O(N) O(N)
结论:区间树擅长查找,链表擅长遍历。因此SVM同时使用两者!
6.3 范围的分割与合并
分割操作
为什么需要分割?
场景:修改范围的一部分属性
原范围: [1000-3000] (只读)
请求: 将 [1500-2500] 设为可写
解决方案:分割
[1000-1499] (只读)
[1500-2500] (可写) ← 修改这部分
[2501-3000] (只读)
svm_range_split 函数
c
// 文件: kfd_svm.c
/**
* svm_range_split - split a range in 2 ranges
* @prange: the svm range to split
* @start: the remaining range start address in pages
* @last: the remaining range last address in pages
* @new: the result new ranges generated
*
* Two cases only:
* case 1: if start == prange->start
* prange ==> prange[start, last]
* new range [last + 1, prange->last]
*
* case 2: if last == prange->last
* prange ==> prange[start, last]
* new range [prange->start, start - 1]
*
* Return:
* 0 - OK, -ENOMEM - out of memory, -EINVAL - invalid start, last
*/
int svm_range_split(struct svm_range *prange,
uint64_t start, uint64_t last,
struct svm_range **new)
两种分割模式:分割头部和分割尾部。
-
分割头部 :
start == prange->start原范围: [1000-3000]
分割: [1000-2000]
结果:
prange: [1000-2000] (修改原范围)
*new: [2001-3000] (新创建) -
分割尾部 :
last == prange->last原范围: [1000-3000]
分割: [1500-3000]
结果:
prange: [1500-3000] (修改原范围)
*new: [1000-1499] (新创建)
分割步骤
c
int svm_range_split(struct svm_range *prange,
uint64_t start, uint64_t last,
struct svm_range **new)
{
uint64_t old_start = prange->start;
uint64_t old_last = prange->last;
// 1. 参数验证
if (old_start != start && old_last != last)
return -EINVAL; // 必须从头或尾分割
// 2. 创建新范围
if (old_start == start)
*new = svm_range_new(svms, last + 1, old_last, false);
else
*new = svm_range_new(svms, old_start, start - 1, false);
// 3. 调整属性和资源
r = svm_range_split_adjust(*new, prange, start, last);
return r;
}
svm_range_split_adjust 详解
这个函数处理分割后的资源分配:
c
// 文件: kfd_svm.c
int svm_range_split_adjust(struct svm_range *new,
struct svm_range *old,
uint64_t start, uint64_t last)
{
// 1. 调整地址范围
old->start = start;
old->last = last;
old->npages = last - start + 1;
// 2. 分割DMA地址数组
r = svm_range_split_pages(new, old, start, last);
// 3. 分割VRAM节点(如果有)
if (old->actual_loc && old->ttm_res)
r = svm_range_split_nodes(new, old, start, last);
return r;
}
DMA地址数组分割:
原DMA数组 (1000页):
old->dma_addr[0]: [addr0, addr1, ..., addr999]
分割为 [1000-1499] 和 [1500-2999]:
new->dma_addr[0]: [addr0, addr1, ..., addr499] (500页)
old->dma_addr[0]: [addr500, addr501, ..., addr999] (500页)
代码实现:
c
static int svm_range_split_pages(struct svm_range *new,
struct svm_range *old,
uint64_t start, uint64_t last)
{
uint64_t npages = last - start + 1;
int i, r;
for (i = 0; i < MAX_GPU_INSTANCE; i++) {
r = svm_range_split_array(&new->dma_addr[i],
&old->dma_addr[i],
sizeof(*old->dma_addr[i]),
old->start,
npages, new->start, new->npages,
old->actual_loc ? &new->vram_pages : NULL);
if (r)
return r;
}
// 更新VRAM页面计数
if (old->actual_loc)
old->vram_pages -= new->vram_pages;
return 0;
}
合并操作
虽然没有显式的"合并"函数,但SVM通过以下方式实现逻辑合并:
隐式合并:创建时检查相邻范围
c
// 在svm_range_add中
if (svm_range_is_same_attrs(p, prange, nattr, attrs) &&
prange->mapped_to_gpu) {
// 属性相同,无需创建新范围
// 现有范围自然"合并"了新请求
}
例子:
现有: [1000-2000] (可写)
请求: [2001-3000] (可写,相同属性)
结果: 不创建新范围,两者逻辑上是连续的
6.4 范围的删除与清理
删除流程
删除请求
↓
svm_range_unlink() ← 从数据结构移除
↓
svm_range_remove_notifier() ← 移除MMU notifier
↓
svm_range_free() ← 释放资源
svm_range_unlink
c
// 文件: kfd_svm.c
static void svm_range_unlink(struct svm_range *prange)
{
// 1. 从svm_bo的范围列表移除
if (prange->svm_bo) {
spin_lock(&prange->svm_bo->list_lock);
list_del(&prange->svm_bo_list);
spin_unlock(&prange->svm_bo->list_lock);
}
// 2. 从svms的链表移除
list_del(&prange->list);
// 3. 从区间树移除
if (prange->it_node.start != 0 && prange->it_node.last != 0)
interval_tree_remove(&prange->it_node, &prange->svms->objects);
}
svm_range_remove_notifier
c
// 文件: kfd_svm.c
static void svm_range_remove_notifier(struct svm_range *prange)
{
pr_debug("remove notifier svms 0x%p prange 0x%p [0x%lx 0x%lx]\n",
prange->svms, prange,
prange->notifier.interval_tree.start >> PAGE_SHIFT,
prange->notifier.interval_tree.last >> PAGE_SHIFT);
if (prange->notifier.interval_tree.start != 0 &&
prange->notifier.interval_tree.last != 0)
mmu_interval_notifier_remove(&prange->notifier);
}
svm_range_free
c
void svm_range_free(struct svm_range *prange, bool do_unmap)
{
// 1. 取消GPU映射
if (do_unmap && prange->mapped_to_gpu) {
for (i = 0; i < MAX_GPU_INSTANCE; i++) {
if (test_bit(i, prange->bitmap_access))
svm_range_unmap_from_gpu(...);
}
}
// 2. 释放DMA映射
svm_range_dma_unmap(prange);
for (i = 0; i < MAX_GPU_INSTANCE; i++) {
kvfree(prange->dma_addr[i]);
prange->dma_addr[i] = NULL;
}
// 3. 释放VRAM资源
if (prange->svm_bo) {
svm_range_vram_node_free(prange);
}
// 4. 销毁锁
mutex_destroy(&prange->lock);
mutex_destroy(&prange->migrate_mutex);
// 5. 释放结构本身
kfree(prange);
}
6.5 代码示例和流程图
完整示例:创建重叠范围
c
// 场景:创建新范围,与现有范围部分重叠
struct kfd_process *p = ...;
struct svm_range_list *svms = &p->svms;
// 准备输出列表
LIST_HEAD(update_list);
LIST_HEAD(insert_list);
LIST_HEAD(remove_list);
LIST_HEAD(remap_list);
// 现有范围: [1000-2000]
// 请求创建: [1500-3000] with 新属性
mutex_lock(&svms->lock);
r = svm_range_add(p, 1500, 1501, // 1501页
nattr, attrs,
&update_list, &insert_list,
&remove_list, &remap_list);
if (r == 0) {
// 成功!应用更改
// 1. 移除旧范围
list_for_each_entry_safe(prange, tmp, &remove_list, update_list) {
svm_range_unlink(prange);
}
// 2. 插入新范围
list_for_each_entry(prange, &insert_list, list) {
svm_range_add_to_svms(prange);
svm_range_add_notifier_locked(mm, prange);
}
// 3. 更新属性
list_for_each_entry(prange, &update_list, update_list) {
svm_range_apply_attrs(p, prange, nattr, attrs);
}
// 4. 释放旧范围
list_for_each_entry_safe(prange, tmp, &remove_list, update_list) {
svm_range_free(prange, true);
}
}
mutex_unlock(&svms->lock);
流程图:svm_range_add
┌─────────────────────────────────────────┐
│ svm_range_add(p, start, size, ...) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 初始化输出列表 │
│ update_list, insert_list, remove_list │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 查找重叠范围 │
│ interval_tree_iter_first(start, last) │
└─────────────────────────────────────────┘
↓
有重叠?
┌────┴────┐
是 否
↓ ↓
┌──────────┐ ┌──────────────┐
│处理重叠 │ │创建新范围 │
│ ├克隆 │ │svm_range_new │
│ ├分割 │ └──────────────┘
│ └更新 │ ↓
└──────────┘ 加入insert_list
↓ ↓
下一个重叠? ←──────┘
↓ 无更多
┌─────────────────────────────────────────┐
│ 处理间隙:创建新范围填充 │
└─────────────────────────────────────────┘
↓
有错误?
┌────┴────┐
是 否
↓ ↓
┌──────────┐ ┌──────────────┐
│回滚: │ │提交: │
│释放所有 │ │返回输出列表 │
│新创建范围 │ │供调用者应用 │
└──────────┘ └──────────────┘
💡 重点提示
-
事务性 :
svm_range_add是事务性的,要么全成功,要么全失败。 -
克隆而非修改:重叠的范围会被克隆,原范围保持不变直到事务成功。
-
区间树是关键:快速查找重叠范围,避免遍历所有范围。
-
分割的两种模式:只能从头部或尾部分割,不能从中间。
-
资源管理:分割时DMA地址数组和VRAM都需要相应分割。
⚠️ 常见陷阱
❌ 陷阱1:"直接修改现有范围"
- ✅ 正确:克隆后修改,保持原范围直到提交。
❌ 陷阱2:"忘记更新区间树"
- ✅ 正确:范围地址改变时,必须先从区间树删除,修改后再插入。
❌ 陷阱3:"分割后忘记调整VRAM页面计数"
- ✅ 正确:
svm_range_split_pages会更新vram_pages。
❌ 陷阱4:"释放范围前不取消映射"
- ✅ 正确:
svm_range_free的do_unmap参数控制是否取消映射。
📝 实践练习
-
代码追踪:
bash# 追踪范围创建 grep -n "svm_range_add" drivers/gpu/drm/amd/amdkfd/kfd_svm.c # 查看分割实现 grep -A 30 "svm_range_split" drivers/gpu/drm/amd/amdkfd/kfd_svm.c -
思考题:
- 为什么分割只能从头或尾,不能从中间?
- 如果两个范围完全相同属性,为什么不合并成一个?
svm_range_split_array如何处理VRAM页面计数?
-
调试练习:
bash# 启用动态调试 echo 'file kfd_svm.c line 2133 +p' > /sys/kernel/debug/dynamic_debug/control # 触发范围创建并查看日志 dmesg | grep "svm_range_add" -
画图练习 :
画出以下场景的范围变化:
- 现有: [1000-2000], [4000-5000]
- 创建: [1500-4500]
- 画出所有中间步骤
📚 本章小结
- 创建 :
svm_range_add处理复杂重叠,克隆-分割-更新模式 - 查找:区间树提供O(log N)查找,支持精确和范围查询
- 分割 :
svm_range_split从头或尾分割,调整资源分配 - 删除:三步走:unlink → remove_notifier → free
- 事务性:保证操作的原子性,失败时自动回滚
范围管理是SVM的基础,理解这些操作对后续学习至关重要。
➡️ 下一步
掌握了范围管理后,下一章我们将学习内存迁移机制------如何在系统内存和GPU显存之间移动页面。
🔗 导航
- 上一章:05 - 进程与SVM的关系
- 下一章: 审核中...
- 返回目录: AMD ROCm-SVM技术的实现与应用深度分析目录