AMD 正在使用 drm svm框架重构SVM的实现,看来drm svm框架要进入大范围应用了。下面是在kernel社区上由AMD的开发人员提交的POC 验证版本的patches的技术方案实现。这里快速总结了实现,以飨读者。
因是POC版本,设计可能会变动,读者们慎重使用。本文仅用来跟踪前沿驱动技术的迭代发展现状。
1. 整体架构
用户空间 (UAPI) 内核空间 (attr 管理) 内核空间 (SVM 核心)
┌───────────────────┐ ┌──────────────────────┐ ┌──────────────────┐
│drm_amdgpu_gem_svm │ ioctl │ amdgpu_svm_attr_tree │ │ amdgpu_svm │
│ start_addr │──SET/GET─────────▶│ lock (mutex) │◄────────────── │ attr_tree │
│ size │ │ tree (rb_root) │ │ gpusvm │
│ nattr │ │ range_list │ │ adev, vm ... │
│ attrs_ptr ───────┤ └─────┬────────────────┘ └──────────────────┘
│ │ │ 包含 N 个 │
│ ┌────────────────┤ ▼ │ 包含 N 个
│ │drm_amdgpu_svm_ │ ┌──────────────────────┐ ┌──────┴───────────┐
│ │ attribute │ 转换为 │amdgpu_svm_attr_range │ │amdgpu_svm_range │
│ │ type (u32) │────────────────▶ │ it_node (区间树) │ 查询 attrs │ base (gpusvm) │
│ │ value (u32) │ │ list (链表) │◄────────────── │ pte_flags │
│ └────────────────┘ │ attrs ──────────────┤ │ attr_flags │
│ │ preferred_loc │ │ gpu_mapped ... │
│ │ prefetch_loc │ └──────────────────┘
│ │ flags │
│ │ granularity │
│ │ access (enum) │
│ └──────────────────────┘
2. 各结构体解析
2.1 amdgpu_svm_attrs --- 属性值集合(Value Object)
c
struct amdgpu_svm_attrs {
int32_t preferred_loc; // 首选内存位置: GPU ID / SYSMEM(0) / UNDEFINED(0xffffffff)
int32_t prefetch_loc; // 预取位置
uint32_t flags; // 位标志组合 (COHERENT, GPU_RO, GPU_EXEC 等)
uint32_t granularity; // 映射粒度
enum amdgpu_svm_attr_access access; // NONE / ENABLE / IN_PLACE
};
纯数据容器,没有指针 ,可以直接拷贝。它把用户空间的多个离散
drm_amdgpu_svm_attribute(type/value 对)聚合成一个完整的属性快照。
2.2 amdgpu_svm_attr_range --- 区间-属性映射节点
c
struct amdgpu_svm_attr_range {
struct interval_tree_node it_node; // ← 嵌入区间树,key=[start, last] 页号
struct list_head list; // ← 链入 attr_tree->range_list 有序链表
struct amdgpu_svm_attrs attrs; // ← 嵌入属性值
};
双索引结构:同一个节点同时存在于两个数据结构中:
| 索引方式 | 数据结构 | 用途 |
|---|---|---|
it_node |
区间红黑树 (rb_root_cached) |
O(log n) 按地址范围查询 |
list |
有序链表 (range_list) |
O(n) 顺序遍历、合并相邻同属性区间 |
这是内核中常见的"同一对象多索引"模式(类似 VMA 同时在 mm->mm_rb 和
mm->mmap 中)。
2.3 amdgpu_svm_attr_tree --- 属性管理器(容器)
c
struct amdgpu_svm_attr_tree {
struct mutex lock; // 保护下面两个数据结构
struct rb_root_cached tree; // ← attr_range 的区间红黑树根
struct list_head range_list; // ← attr_range 的有序链表头
struct amdgpu_svm *svm; // ← 回指所属 SVM 实例
};
每个 SVM 实例一个 attr_tree,管理该进程(fd)所有地址范围的属性。
2.4 amdgpu_svm_attr_change_trigger --- 变更分类枚举
c
enum amdgpu_svm_attr_change_trigger {
ACCESS_CHANGE = (1U << 0), // access 级别变了 → 可能需要 unmap/remap
PTE_FLAG_CHANGE = (1U << 1), // GPU PTE flags 变了 → 需要重建 PTE
MAPPING_FLAG_CHANGE = (1U << 2), // 映射策略变了 → 可能影响缓存一致性
LOCATION_CHANGE = (1U << 3), // 首选位置变了 → 可能触发迁移
GRANULARITY_CHANGE = (1U << 4), // 粒度变了 → 需要拆分/合并 range
ATTR_ONLY = (1U << 5), // 只更新属性,不影响 GPU 映射
};
位掩码设计,允许一次 set_attr 操作同时触发多种变更。
3. 属性标志分类
amdgpu_svm_attrs.flags 中的标志位按作用范围分为两组:
PTE 级标志(影响 GPU 页表条目)
c
#define AMDGPU_SVM_PTE_FLAG_MASK \
(AMDGPU_SVM_FLAG_COHERENT | AMDGPU_SVM_FLAG_EXT_COHERENT | \
AMDGPU_SVM_FLAG_GPU_RO | AMDGPU_SVM_FLAG_GPU_EXEC)
| 标志 | 值 | 含义 |
|---|---|---|
COHERENT |
0x02 | GPU-CPU 缓存一致性 |
EXT_COHERENT |
0x80 | 跨节点扩展一致性 |
GPU_RO |
0x08 | GPU 只读访问 |
GPU_EXEC |
0x10 | GPU 可执行 |
映射级标志(影响映射策略)
c
#define AMDGPU_SVM_MAPPING_FLAG_MASK \
(AMDGPU_SVM_FLAG_HOST_ACCESS | AMDGPU_SVM_FLAG_HIVE_LOCAL | \
AMDGPU_SVM_FLAG_GPU_READ_MOSTLY | AMDGPU_SVM_FLAG_GPU_ALWAYS_MAPPED)
| 标志 | 值 | 含义 |
|---|---|---|
HOST_ACCESS |
0x01 | 允许 CPU 访问设备内存 |
HIVE_LOCAL |
0x04 | 限制在 XGMI hive 内 |
GPU_READ_MOSTLY |
0x20 | 读多写少优化 |
GPU_ALWAYS_MAPPED |
0x40 | 始终保持 GPU 映射 |
4. 数据流
用户调用 ioctl(AMDGPU_SVM_OP_SET_ATTR)
│
▼
drm_amdgpu_svm_attribute[] (type/value 对数组)
│
▼ amdgpu_svm_attr_set()
│
├─▶ 解析为 amdgpu_svm_attrs (聚合所有 type/value)
│
├─▶ 在 attr_tree 中插入/拆分/合并 amdgpu_svm_attr_range
│ (区间树 + 链表同步维护)
│
├─▶ 计算 trigger 位掩码 (哪些属性变了)
│
└─▶ 通知 svm_range 层:amdgpu_svm_range_apply_attr_change()
│
▼
根据 trigger 决定:重建 PTE / 迁移 VRAM / 销毁重建 range
5. 关键设计:属性层与映射层解耦
attr_range 和 svm_range 是独立的区间覆盖,粒度不同:
地址空间: 0 4K 8K 12K 16K
attr_range: |←── preferred=GPU, flags=COHERENT ──────────→| (一个大区间)
svm_range: |← range1 →|← range2 →| |← range3 →| (按 fault 按需创建)
(已映射) (已映射) (无fault) (已映射)
attr_range:由用户set_attr显式创建,覆盖用户声明的整个地址范围svm_range:由 GPU page fault 按需创建,只覆盖实际被 GPU 访问的页- 当
svm_range需要知道某个页的属性时,调用
amdgpu_svm_attr_lookup_page_locked()查询attr_tree
这种解耦的好处:
| 场景 | 优势 |
|---|---|
| 用户声明 1GB 范围的属性 | attr_tree 只存一个节点(O(1) 空间) |
| 只有 4KB 被 GPU 访问 | svm_range 只创建一个 4KB 范围 |
| 用户修改属性 | 只更新 attr_range,通知 svm_range 按需调整 |
| GPU fault 新地址 | svm_range 查询 attr_tree 获取属性 |
6. 内存中的对象层次
amdgpu_device (GPU 设备)
└── amdgpu_vm (每个进程一个)
└── amdgpu_svm (每个 fd/vm 一个)
├── drm_gpusvm gpusvm
│ └── drm_gpusvm_notifier[]
│ └── drm_gpusvm_range[] ← amdgpu_svm_range.base
│
└── amdgpu_svm_attr_tree *attr_tree
├── rb_root_cached tree ─────────┐
└── list_head range_list ─────────┤
▼
amdgpu_svm_attr_range
├── it_node (in tree)
├── list (in range_list)
└── attrs (属性值)
7. UAPI 到内核属性的转换
用户空间通过 drm_amdgpu_svm_attribute 数组传递离散的 type/value 对:
c
// 用户空间
struct drm_amdgpu_svm_attribute attrs[] = {
{ .type = AMDGPU_SVM_ATTR_PREFERRED_LOC, .value = gpu_id },
{ .type = AMDGPU_SVM_ATTR_SET_FLAGS, .value = AMDGPU_SVM_FLAG_COHERENT },
{ .type = AMDGPU_SVM_ATTR_ACCESS, .value = gpu_id },
};
内核在 amdgpu_svm_attr_set() 中将其聚合为一个 amdgpu_svm_attrs:
| UAPI type | attrs 字段 |
|---|---|
ATTR_PREFERRED_LOC |
preferred_loc = value |
ATTR_PREFETCH_LOC |
prefetch_loc = value |
ATTR_ACCESS |
access = ENABLE |
ATTR_ACCESS_IN_PLACE |
access = IN_PLACE |
ATTR_NO_ACCESS |
access = NONE |
ATTR_SET_FLAGS |
`flags |
ATTR_CLR_FLAGS |
flags &= ~value |
ATTR_GRANULARITY |
granularity = value |