news 2026/4/29 20:49:31

AMDGPU SVM 属性设置流程:从用户态 ioctl 到 attr_set_ctx 的完整信息收集

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AMDGPU SVM 属性设置流程:从用户态 ioctl 到 attr_set_ctx 的完整信息收集

AMD 正在使用 drm svm框架重构SVM的实现,看来drm svm框架要进入大范围应用了。下面是在kernel社区上由AMD的开发人员提交的POC 验证版本的patches的技术方案实现。这里快速总结了实现,以飨读者。

因是POC版本,设计可能会变动,读者们慎重使用。本文仅用来跟踪前沿驱动技术的迭代发展现状。


1. 概述

本文档描述 AMDGPU SVM(Shared Virtual Memory)子系统中,用户态通过 DRM ioctl 设置 SVM 属性的完整内核态处理流程。重点分析从 ioctl 入口到属性变更上下文struct attr_set_ctx收集完毕的全过程。

核心设计思想:attr 层作为「属性管理器」,负责维护虚拟地址空间上的属性区间树(interval tree),并在属性变更时收集差异信息,封装为attr_set_ctx传递给 range 层执行实际的 GPU 映射/迁移操作。

2. 数据结构定义

2.1 UAPI 层(用户态/内核态接口)

/* include/uapi/drm/amdgpu_drm.h */structdrm_amdgpu_gem_svm{__u64 start_addr;/* 目标虚拟地址(页对齐) */__u64 size;/* 区间大小(字节,页对齐) */__u32 operation;/* 操作码:AMDGPU_SVM_OP_SET_ATTR (0) / GET_ATTR (1) */__u32 nattr;/* 属性数组长度 */__u64 attrs_ptr;/* 指向用户态 drm_amdgpu_svm_attribute[] 的指针 */};structdrm_amdgpu_svm_attribute{__u32 type;/* 属性类型 */__u32 value;/* 属性值 */};

属性类型枚举

类型常量含义
AMDGPU_SVM_ATTR_PREFERRED_LOC0首选内存位置
AMDGPU_SVM_ATTR_PREFETCH_LOC1预取目标位置
AMDGPU_SVM_ATTR_ACCESS2启用 GPU 访问
AMDGPU_SVM_ATTR_ACCESS_IN_PLACE3启用 GPU 原地访问
AMDGPU_SVM_ATTR_NO_ACCESS4禁用 GPU 访问
AMDGPU_SVM_ATTR_SET_FLAGS5按位设置标志
AMDGPU_SVM_ATTR_CLR_FLAGS6按位清除标志
AMDGPU_SVM_ATTR_GRANULARITY7页粒度

标志位定义

标志分类
AMDGPU_SVM_FLAG_HOST_ACCESS0x01MAPPING
AMDGPU_SVM_FLAG_COHERENT0x02PTE
AMDGPU_SVM_FLAG_HIVE_LOCAL0x04MAPPING
AMDGPU_SVM_FLAG_GPU_RO0x08PTE
AMDGPU_SVM_FLAG_GPU_EXEC0x10PTE
AMDGPU_SVM_FLAG_GPU_READ_MOSTLY0x20MAPPING
AMDGPU_SVM_FLAG_GPU_ALWAYS_MAPPED0x40MAPPING
AMDGPU_SVM_FLAG_EXT_COHERENT0x80PTE

标志位被分为两组掩码,对应不同的触发类型:

/* amdgpu_svm_attr.h */#defineAMDGPU_SVM_PTE_FLAG_MASK\(AMDGPU_SVM_FLAG_COHERENT|AMDGPU_SVM_FLAG_EXT_COHERENT|\AMDGPU_SVM_FLAG_GPU_RO|AMDGPU_SVM_FLAG_GPU_EXEC)#defineAMDGPU_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)

2.2 内核态属性结构

/* amdgpu_svm_attr.h */enumamdgpu_svm_attr_access{AMDGPU_SVM_ACCESS_NONE=0,/* GPU 无访问权限 */AMDGPU_SVM_ACCESS_ENABLE=1,/* GPU 可访问(允许迁移) */AMDGPU_SVM_ACCESS_IN_PLACE=2,/* GPU 原地访问(不迁移) */};structamdgpu_svm_attrs{int32_tpreferred_loc;/* 首选内存位置 */int32_tprefetch_loc;/* 预取目标位置 */uint32_tflags;/* 标志位集合 */uint32_tgranularity;/* 页粒度 */enumamdgpu_svm_attr_accessaccess;/* 访问模式 */};

2.3 属性区间树

structamdgpu_svm_attr_range{structinterval_tree_nodeit_node;/* 区间树节点 [start_page, last_page] */structlist_headlist;/* 链表节点(按地址有序) */structamdgpu_svm_attrsattrs;/* 该区间的属性值 */};structamdgpu_svm_attr_tree{structmutexlock;/* 保护区间树的互斥锁 */structrb_root_cachedtree;/* 红黑树根(区间树底层实现) */structlist_headrange_list;/* 所有 attr_range 的有序链表 */structamdgpu_svm*svm;/* 回指 SVM 上下文 */};

2.4 变更上下文(attr 层与 range 层的桥梁)

/* amdgpu_svm_attr.c - 文件作用域,仅在 attr 层内部使用 */structattr_set_ctx{unsignedlongstart;/* 受影响的起始页 */unsignedlonglast;/* 受影响的结束页 */uint32_ttrigger;/* 变更触发位掩码 */structamdgpu_svm_attrsprev_attrs;/* 变更前属性 */structamdgpu_svm_attrsnew_attrs;/* 变更后属性 */};

触发位定义

enumamdgpu_svm_attr_change_trigger{AMDGPU_SVM_ATTR_TRIGGER_ACCESS_CHANGE=(1U<<0),/* 访问权限变更 */AMDGPU_SVM_ATTR_TRIGGER_PTE_FLAG_CHANGE=(1U<<1),/* PTE 级标志变更 */AMDGPU_SVM_ATTR_TRIGGER_MAPPING_FLAG_CHANGE=(1U<<2),/* 映射策略标志变更 */AMDGPU_SVM_ATTR_TRIGGER_LOCATION_CHANGE=(1U<<3),/* 预取位置变更 */AMDGPU_SVM_ATTR_TRIGGER_GRANULARITY_CHANGE=(1U<<4),/* 页粒度变更 */AMDGPU_SVM_ATTR_TRIGGER_ATTR_ONLY=(1U<<5),/* 仅属性记录变更,无需 range 层操作 */};

3. 完整调用链

用户态: ioctl(fd, DRM_IOCTL_AMDGPU_GEM_SVM, &args) │ ▼ ┌─────────────────────────────────────────────────────┐ │ DRM 子系统分发 │ │ amdgpu_drv.c: amdgpu_ioctls_kms[] 注册表 │ │ DRM_IOCTL_DEF_DRV(AMDGPU_GEM_SVM, │ │ amdgpu_gem_svm_ioctl, │ │ DRM_AUTH|DRM_RENDER_ALLOW) │ └─────────────────────┬───────────────────────────────┘ │ DRM 框架自动 copy_from_user │ 将 drm_amdgpu_gem_svm 复制到内核栈 ▼ amdgpu_gem_svm_ioctl() [amdgpu_svm.c] │ ├── ① 参数校验 ├── ② amdgpu_svm_copy_attrs() → 从用户态复制属性数组 └── ③ switch(operation) │ SET_ATTR │ ▼ amdgpu_svm_set_attr() [amdgpu_svm.c] │ ├── amdgpu_svm_range_sync_work() → 刷新 range 工作队列 └── amdgpu_svm_attr_set() → 进入 attr 层 │ ├── ④ 逐属性校验 ├── ⑤ VMA 范围校验 ├── ⑥ 初始化默认属性 └── ⑦ amdgpu_svm_attr_set_range() → 按段迭代 ┌────────┴────────┐ 命中已有区间 落入空洞 │ │ ▼ ▼ amdgpu_svm_attr_ amdgpu_svm_attr_ set_existing() set_hole() │ │ └────────┬─────────┘ ▼ attr_set_ctx 收集完毕 ▼ amdgpu_svm_attr_apply_change() ▼ amdgpu_svm_range_apply_attr_change() (range 层消费)

4. 各阶段详细分析

4.1 阶段一:DRM ioctl 入口与参数复制

函数amdgpu_gem_svm_ioctl()

DRM 框架在分发 ioctl 之前,已经根据DRM_IOWR宏的定义,将drm_amdgpu_gem_svm结构体从用户空间copy_from_user到内核栈上的data指针。ioctl handler 直接将data强转为struct drm_amdgpu_gem_svm *

校验逻辑

1. SVM 是否已启用(vm->svm != NULL) → -EOPNOTSUPP 2. start_addr 和 size 是否页对齐 → -EINVAL 3. start_addr 和 size 是否非零 → -EINVAL

4.2 阶段二:用户态属性数组复制

函数amdgpu_svm_copy_attrs()

staticintamdgpu_svm_copy_attrs(conststructdrm_amdgpu_gem_svm*args,structdrm_amdgpu_svm_attribute**attrs,size_t*size){if(!args->nattr||args->nattr>AMDGPU_SVM_MAX_ATTRS)/* 上限 64 */return-EINVAL;if(!args->attrs_ptr)return-EINVAL;*size=args->nattr*sizeof(**attrs);*attrs=memdup_user(u64_to_user_ptr(args->attrs_ptr),*size);returnPTR_ERR_OR_ZERO(*attrs);}

关键点:

  • 属性数量上限为64AMDGPU_SVM_MAX_ATTRS),防止用户态传入过大数组导致内存耗尽。
  • memdup_user()原子地完成内存分配 +copy_from_user,返回内核堆上的副本。
  • u64_to_user_ptr()__u64安全转换为用户态指针(处理 32/64 位兼容性)。

至此,内核拥有用户请求的完整副本:目标地址范围+属性数组

4.3 阶段三:中间分发层

函数amdgpu_svm_set_attr()

staticintamdgpu_svm_set_attr(structamdgpu_vm*vm,...){structamdgpu_svm*svm=vm->svm;amdgpu_svm_range_sync_work(svm);/* 刷新 range 工作队列 */returnamdgpu_svm_attr_set(svm->attr_tree,start,size,nattr,attrs);}
  • amdgpu_svm_range_sync_work()在进入 attr 层之前刷新 range 层的异步工作队列,减少后续操作因 mmap 锁竞争而失败的概率。
  • vmsvm->attr_tree的转换完成了从 DRM/VM 抽象到 SVM 子系统内部结构的过渡。

4.4 阶段四:属性校验

函数amdgpu_svm_attr_set()amdgpu_svm_attr_set_validate()

逐条遍历用户传入的属性数组,按类型执行合法性检查:

属性类型校验规则
PREFERRED_LOC接受SYSMEMUNDEFINED,GPU ID > 0 隐式接受(单 GPU 架构)
PREFETCH_LOC接受SYSMEM和 GPU ID > 0,拒绝UNDEFINED
ACCESS/ACCESS_IN_PLACE/NO_ACCESSvalue 不能为 0 或UNDEFINED
SET_FLAGS/CLR_FLAGSvalue 不能包含AMDGPU_SVM_VALID_FLAG_MASK之外的位
GRANULARITY无限制(由attr_apply时截断到 0x3f)

任何一条属性校验失败,整个 ioctl 立即返回-EINVAL

4.5 阶段五:VMA 范围校验

函数amdgpu_svm_attr_validate_range_vma()

在持有mmap_read_lock的情况下,遍历目标页范围[start_page, last_page]对应的所有 VMA,确保:

  • 每一页都被有效 VMA 覆盖(不存在空洞)。
  • VMA 不带有VM_IO | VM_PFNMAP | VM_MIXEDMAP标志(排除设备映射区域)。

不满足条件则返回-EFAULT。这保证了后续操作不会作用在不合法的虚拟地址范围上。

4.6 阶段六:默认属性初始化

函数attr_set_default()

staticvoidattr_set_default(structamdgpu_svm*svm,structamdgpu_svm_attrs*attrs){attrs->preferred_loc=AMDGPU_SVM_LOCATION_UNDEFINED;attrs->prefetch_loc=AMDGPU_SVM_LOCATION_UNDEFINED;attrs->granularity=svm->default_granularity;attrs->flags=AMDGPU_SVM_FLAG_HOST_ACCESS|AMDGPU_SVM_FLAG_COHERENT;attrs->access=svm->xnack_enabled?AMDGPU_SVM_ACCESS_ENABLE:AMDGPU_SVM_ACCESS_NONE;}

默认属性用于 interval tree 中不存在属性区间(空洞)的地址段。这意味着 attr 层采用稀疏存储策略:只有与默认值不同的区间才会分配amdgpu_svm_attr_range节点。

4.7 阶段七:按段迭代 ——amdgpu_svm_attr_set_range()

这是信息收集的主循环。用户请求的地址范围[start_page, last_page]可能跨越多个已有属性区间和空洞,因此需要逐段处理。

用户请求范围: [========================================] 区间树现状: [range_A] [range_B] [range_C] ↑ ↑ ↑ ↑ 空洞 空洞 空洞 空洞

主循环以cursor指针从start_page开始,每次处理一个段:

while(cursor<=last){mutex_lock(&attr_tree->lock);node=interval_tree_iter_first(&attr_tree->tree,cursor,cursor);if(node){/* 命中已有区间 → amdgpu_svm_attr_set_existing() */seg_last=min(last,attr_last_page(range));}else{/* 落入空洞 → amdgpu_svm_attr_set_hole() */next=interval_tree_iter_first(...,cursor+1,ULONG_MAX);seg_last=min(last,attr_start_page(next_range)-1);}/* 填充 attr_set_ctx ... */mutex_unlock(&attr_tree->lock);/* 持 svm_lock 消费 change ... */cursor=seg_last+1;}

锁序:每段处理中,先持attr_tree->lock(mutex)操作区间树,释放后再持svm->svm_lock(rwsem write)传递给 range 层。两把锁不嵌套。

4.8 路径 A:空洞处理 ——amdgpu_svm_attr_set_hole()

cursor所在位置没有任何属性区间时,该段地址继承默认属性。

输入: default_attrs + 用户属性数组 处理: 1. new_attrs = default_attrs 2. amdgpu_svm_attr_apply(&new_attrs, nattr, attrs) // 叠加用户请求 3. if (new_attrs == default_attrs) → return 0 // 无变化,跳过 4. 分配新的 amdgpu_svm_attr_range,插入区间树 5. trigger = attr_change_ctx_trigger(default_attrs, &new_attrs) 6. 填充 attr_set_ctx{start, last, trigger, default_attrs, new_attrs}

优化:如果叠加后属性仍等于默认值,则不分配节点、不产生 change,保持稀疏存储。

4.9 路径 B:已有区间处理 ——amdgpu_svm_attr_set_existing()

cursor命中已有的amdgpu_svm_attr_range时,流程更为复杂。

4.9.1 属性叠加
old_attrs=range->attrs;new_attrs=old_attrs;amdgpu_svm_attr_apply(&new_attrs,nattr,attrs);

amdgpu_svm_attr_apply()遍历用户属性数组,按类型修改new_attrs

操作语义
PREFERRED_LOCnew.preferred_loc = value
PREFETCH_LOCnew.prefetch_loc = value
ACCESSnew.access = ENABLE
ACCESS_IN_PLACEnew.access = IN_PLACE
NO_ACCESSnew.access = NONE
SET_FLAGS`new.flags
CLR_FLAGSnew.flags &= ~value
GRANULARITYnew.granularity = min(value, 0x3f)

注意:用户可以在一次 ioctl 中传入多条属性,它们按数组顺序依次叠加。后出现的同类型属性覆盖先前的。

4.9.2 无变化快速路径
if(attr_same_attrs(range,nattr,attrs)){if(!force_trigger)return0;// 完全无变化,跳过// force_trigger 场景: 见 4.9.3}
4.9.3 Force Trigger 机制

两种情况下,即使属性值未变也必须产生触发:

  1. xnack 关闭时的 ACCESS 设置:attr 层不知道 range 层的gpu_mapped状态,range 层必须重新检查并按需建立映射。
  2. PREFETCH_LOC 重复设置:预取是一次性命令(one-shot),不是持久状态。即使prefetch_loc值相同,页可能已被驱逐回 RAM,必须重新触发迁移。
force_trigger=(!attr_tree->svm->xnack_enabled&&attr_has_access(nattr,attrs))||attr_has_prefetch_loc(nattr,attrs);
4.9.4 触发类型计算
staticuint32_tattr_change_ctx_trigger(conststructamdgpu_svm_attrs*prev,conststructamdgpu_svm_attrs*new){uint32_ttrigger=0;uint32_tchanged_flags=prev->flags^new->flags;if(prev->access!=new->access)trigger|=TRIGGER_ACCESS_CHANGE;if(changed_flags&AMDGPU_SVM_PTE_FLAG_MASK)trigger|=TRIGGER_PTE_FLAG_CHANGE;// COHERENT/EXT_COHERENT/GPU_RO/GPU_EXECif(changed_flags&AMDGPU_SVM_MAPPING_FLAG_MASK)trigger|=TRIGGER_MAPPING_FLAG_CHANGE;// HOST_ACCESS/HIVE_LOCAL/READ_MOSTLY/ALWAYS_MAPPEDif(prev->prefetch_loc!=new->prefetch_loc)trigger|=TRIGGER_LOCATION_CHANGE;if(prev->granularity!=new->granularity)trigger|=TRIGGER_GRANULARITY_CHANGE;if(!trigger)trigger=TRIGGER_ATTR_ONLY;// 属性值没变或变化不影响硬件returntrigger;}

trigger 位掩码的设计使得 range 层可以精确知道需要执行哪些操作:

  • ACCESS_CHANGE→ 重新评估 GPU 映射/解映射
  • PTE_FLAG_CHANGE→ 需要更新 GPU 页表项的标志位
  • MAPPING_FLAG_CHANGE→ 需要重新评估映射策略
  • LOCATION_CHANGE→ 触发数据迁移
  • GRANULARITY_CHANGE→ 影响后续 range 分割粒度
  • ATTR_ONLY→ 仅属性变更,range 层无需操作
4.9.5 区间树分裂

当用户请求范围不完整覆盖已有区间时,需要将其分裂:

已有区间: [----------- range -----------] 用户范围: [=======] 分裂后: [left] [updated] [ right ]
if(start>range_start)left=attr_alloc_range(range_start,start-1,&old_attrs);// 保留旧属性if(last<range_last)right=attr_alloc_range(last+1,range_last,&old_attrs);// 保留旧属性attr_remove_range_locked(attr_tree,range,false);// 从树中移除原区间(不释放)if(left)attr_insert_range_locked(attr_tree,left);attr_set_interval(range,start,last);// 复用原节点,调整区间range->attrs=new_attrs;// 更新为新属性attr_insert_range_locked(attr_tree,range);if(right)attr_insert_range_locked(attr_tree,right);

注意:中间段复用原range对象(避免额外分配),两端新分配节点继承旧属性。

4.9.6 打包 attr_set_ctx

无论走哪条路径,最终都调用:

amdgpu_svm_attr_change_ctx_set(change,start,last,trigger,&prev_attrs,&new_attrs);

将本段的变更信息完整封装到栈上的attr_set_ctx变量中。

5. attr_set_ctx 消费

主循环中,每段attr_set_ctx收集完毕后,释放attr_tree->lock,随即消费:

mutex_unlock(&attr_tree->lock);down_write(&svm->svm_lock);ret=amdgpu_svm_attr_apply_change(svm,&change);up_write(&svm->svm_lock);

amdgpu_svm_attr_apply_change()是消费端的入口:

staticintamdgpu_svm_attr_apply_change(structamdgpu_svm*svm,conststructattr_set_ctx*change){if(!change->trigger||change->trigger==AMDGPU_SVM_ATTR_TRIGGER_ATTR_ONLY)return0;/* 无硬件影响,直接跳过 */returnamdgpu_svm_range_apply_attr_change(svm,change->start,change->last,change->trigger,&change->prev_attrs,&change->new_attrs);}

过滤条件:trigger == 0(不可能,但防御性编程)和ATTR_ONLY(属性记录变了但不影响硬件)直接跳过,不进入 range 层。

若 range 层返回-EAGAIN,主循环将标记need_retry,在整个范围处理完后由上层amdgpu_svm_attr_set()的 retry 循环重试。

6. 关键设计要点

  1. 稀疏存储:attr 层只为与默认值不同的地址段创建attr_range节点,空洞隐式继承默认属性。

  2. 段式处理:用户请求范围被区间树自然地分割为多个段,每段独立收集attr_set_ctx、独立消费。这意味着一次 ioctl 可能产生多个到 range 层的调用。

  3. 锁分离attr_tree->lock(mutex, attr 层)和svm->svm_lock(rwsem, range 层)不嵌套,每段处理先释放 attr 锁再获取 range 锁,减少锁持有时间。

  4. Force Trigger:attr 层引入force_trigger机制弥补自身信息不足——它不知道 range 层的gpu_mapped状态,也无法判断页面是否已被驱逐,因此在特定场景下即使属性值未变也生成触发。

  5. 重试机制:range 层可能因无法获取 mmap 锁而返回-EAGAIN,上层通过flush + cond_resched + goto retry实现退让重试。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/11 6:43:57

使用Node.js构建SDMatte任务调度与管理后台

使用Node.js构建SDMatte任务调度与管理后台 1. 快速了解SDMatte任务管理系统 SDMatte是一种基于AI的智能抠图技术&#xff0c;能够自动分离图像中的前景和背景。在实际应用中&#xff0c;我们需要一个可靠的后台系统来管理这些抠图任务。本文将带你从零开始&#xff0c;用Nod…

作者头像 李华
网站建设 2026/4/11 6:42:58

暗黑破坏神2单机完美体验:PlugY插件全方位使用指南

暗黑破坏神2单机完美体验&#xff1a;PlugY插件全方位使用指南 PlugY插件是专为暗黑破坏神2单机玩家设计的终极增强工具&#xff0c;彻底改变了传统单机游戏体验。这款暗黑2插件通过智能存档管理和功能扩展&#xff0c;让单机模式拥有接近战网的完整体验&#xff0c;为玩家带来…

作者头像 李华
网站建设 2026/4/11 6:40:07

Open Interpreter法务文书:合同生成脚本部署案例

Open Interpreter法务文书&#xff1a;合同生成脚本部署案例 1. 引言 想象一下&#xff0c;你是一名法务人员或创业者&#xff0c;每天需要处理大量格式相似但细节各异的合同文书。从保密协议到采购合同&#xff0c;每一份都需要根据具体情况进行条款调整和内容填充。传统方式…

作者头像 李华
网站建设 2026/4/11 6:33:09

黑丝空姐-造相Z-Turbo开源协作:Git代码管理与模型版本控制实践

黑丝空姐-造相Z-Turbo开源协作&#xff1a;Git代码管理与模型版本控制实践 最近和团队一起折腾一个基于“黑丝空姐-造相Z-Turbo”的图像生成项目&#xff0c;过程挺有意思的。我们几个人&#xff0c;有的写推理脚本&#xff0c;有的搞前端界面&#xff0c;还有的专门调模型参数…

作者头像 李华