当前位置: 首页 > news >正文

第六章:异步访问的同步:6.3.1 dma_resv_usage 层级机制详解

1. 概述

dma_resv(DMA reservation object)是 Linux 内核中管理 GPU buffer 同步的核心机制。每个dma_resv对象维护一组dma_fence,用于追踪对该 buffer 的各种操作。

enum dma_resv_usage定义了 fence 的用途级别,控制"谁能看到这个 fence"。这套层级机制是理解 TTM 内存管理、GPU 命令提交、隐式同步的关键。


2. 四个 Usage 级别

enumdma_resv_usage{DMA_RESV_USAGE_KERNEL,// 0 — 内核内存管理DMA_RESV_USAGE_WRITE,// 1 — 隐式写同步DMA_RESV_USAGE_READ,// 2 — 隐式读同步DMA_RESV_USAGE_BOOKKEEP,// 3 — 无隐式同步(仅记账)};

2.1 KERNEL — 内核内存管理操作

级别值: 0(最低/最严格) 语义: 内核内部的内存管理 DMA 操作 场景: BO 搬迁(move)、内存清零(clear)、页表更新

谁应该等待 KERNEL fence?
所有人。任何访问该 buffer 的代码都必须先等待 KERNEL fence 完成。
唯一的例外是 buffer 已被 pin 住(位置锁定)的情况。

amdgpu 实例

// TTM 搬迁时添加 KERNEL fencedma_resv_add_fence(bo->base.resv,fence,DMA_RESV_USAGE_KERNEL);// VM 页表更新等待 KERNEL fencedma_resv_wait_timeout(bo->tbo.base.resv,DMA_RESV_USAGE_KERNEL,...);

2.2 WRITE — 隐式写同步

级别值: 1 语义: 用户空间命令提交的写操作 场景: GPU 渲染写入 render target、compute shader 写入 buffer

amdgpu 实例(命令提交amdgpu_cs.c):

// gang leader 的 fence 作为 WRITE 添加dma_resv_add_fence(gobj->resv,p->fence,DMA_RESV_USAGE_WRITE);

2.3 READ — 隐式读同步

级别值: 2 语义: 用户空间命令提交的读操作 场景: GPU 采样纹理、读取 uniform buffer

amdgpu 实例(命令提交amdgpu_cs.c):

// gang 非 leader 的 fence 作为 READ 添加dma_resv_add_fence(gobj->resv,&p->jobs[i]->base.s_fence->finished,DMA_RESV_USAGE_READ);

2.4 BOOKKEEP — 无隐式同步

级别值: 3(最高/最宽松) 语义: 不参与隐式同步,仅内部记账 场景: 抢占 fence、页表更新、TLB flush、eviction fence

BOOKKEEP fence不会被隐式同步查询(READ、WRITE 级别)看到。
只有显式使用DMA_RESV_USAGE_BOOKKEEP级别查询时才会被返回。

KFD eviction fence 实例amdgpu_amdkfd_gpuvm.c):

dma_resv_add_fence(vm->root.bo->tbo.base.resv,&vm->process_info->eviction_fence->base,DMA_RESV_USAGE_BOOKKEEP);

3. 层级包含规则

3.1 核心规则

查询某个级别的 fence 时,该级别及所有更低级别的 fence 都会被返回。

这是由数值大小决定的:

KERNEL(0)<WRITE(1)<READ(2)<BOOKKEEP(3)\text{KERNEL}(0) < \text{WRITE}(1) < \text{READ}(2) < \text{BOOKKEEP}(3)KERNEL(0)<WRITE(1)<READ(2)<BOOKKEEP(3)

查询 KERNEL → 返回 KERNEL 查询 WRITE → 返回 KERNEL + WRITE 查询 READ → 返回 KERNEL + WRITE + READ 查询 BOOKKEEP → 返回 KERNEL + WRITE + READ + BOOKKEEP(全部)

3.2 实现原理

fence 列表中每个条目存储的是(fence_ptr | usage),usage 编码在指针的低 2 位。

迭代器的过滤逻辑(dma-resv.c):

staticvoiddma_resv_iter_walk_unlocked(structdma_resv_iter*cursor){do{dma_resv_list_entry(cursor->fences,cursor->index++,cursor->obj,&cursor->fence,&cursor->fence_usage);// ...if(!dma_fence_is_signaled(cursor->fence)&&cursor->usage>=cursor->fence_usage)// ← 关键过滤条件break;}while(true);}

过滤条件cursor->usage >= cursor->fence_usage的含义:

  • 查询级别(cursor->usage)大于等于fence 的实际级别(fence_usage)时,该 fence 被返回
  • 例:查询 READ(2),fence 级别为 WRITE(1) → 2 ≥ 1 → ✅ 返回
  • 例:查询 WRITE(1),fence 级别为 READ(2) → 1 ≥ 2 → ❌ 跳过

3.3 可见性矩阵

查询级别 ↓ / Fence 级别 →KERNELWRITEREADBOOKKEEP
KERNEL
WRITE
READ
BOOKKEEP

4. Fence 升降级规则

4.1 可以升级(提升可见性)

// 原来是 BOOKKEEP,再次添加为 READ → 升级为 READdma_resv_add_fence(resv,fence,DMA_RESV_USAGE_BOOKKEEP);// fence 级别 = 3dma_resv_add_fence(resv,fence,DMA_RESV_USAGE_READ);// fence 级别 → 2

升级意味着:数值变小 → 被更多查询可见 → 参与更多同步。

4.2 不能降级(降低可见性)

// 原来是 WRITE,再次添加为 READ → 不变,仍为 WRITEdma_resv_add_fence(resv,fence,DMA_RESV_USAGE_WRITE);// fence 级别 = 1dma_resv_add_fence(resv,fence,DMA_RESV_USAGE_READ);// fence 级别仍然 = 1

实现(dma_resv_add_fence):

for(i=0;i<count;++i){dma_resv_list_entry(fobj,i,obj,&old,&old_usage);if((old->context==fence->context&&old_usage>=usage&&dma_fence_is_later_or_same(fence,old))||dma_fence_is_signaled(old)){dma_resv_list_set(fobj,i,fence,usage);// 替换return;}}

替换条件中old_usage >= usage确保:只有当旧 fence 的 usage 值 ≥ 新 usage 值时
(即旧 fence 可见性 ≤ 新 fence 可见性),才执行替换。


5. 主要 API

5.1 添加 Fence

// 预留 slot(不可失败的后续 add 需要预先分配)intdma_resv_reserve_fences(structdma_resv*obj,unsignedintnum_fences);// 添加 fence(必须先 reserve,且持有 resv lock)voiddma_resv_add_fence(structdma_resv*obj,structdma_fence*fence,enumdma_resv_usageusage);

5.2 等待 Fence

// 等待指定级别及以下的所有 fencelongdma_resv_wait_timeout(structdma_resv*obj,enumdma_resv_usageusage,bool intr,unsignedlongtimeout);// 返回值: >0 成功, 0 超时, <0 错误

5.3 测试 Fence 状态

// 检查指定级别及以下的所有 fence 是否都已 signaledbooldma_resv_test_signaled(structdma_resv*obj,enumdma_resv_usageusage);

5.4 遍历 Fence

// 需要持锁遍历structdma_resv_itercursor;structdma_fence*fence;dma_resv_for_each_fence(&cursor,obj,DMA_RESV_USAGE_READ,fence){// fence 的 usage <= READ 的都会被遍历到}// 无锁遍历(RCU 保护,可能 restart)dma_resv_for_each_fence_unlocked(&cursor,fence){if(dma_resv_iter_is_restarted(&cursor))// 处理 restart}

5.5 获取所有 Fence

// 获取指定级别及以下的所有 fence(返回数组,调用者负责释放)intdma_resv_get_fences(structdma_resv*obj,enumdma_resv_usageusage,unsignedint*num_fences,structdma_fence***fences);

5.6 辅助函数

// 隐式同步用:写操作需要等读+写,读操作只需等写staticinlineenumdma_resv_usagedma_resv_usage_rw(bool write){returnwrite?DMA_RESV_USAGE_READ:DMA_RESV_USAGE_WRITE;}

这个看似"反直觉"的映射逻辑:

  • 新写操作(write=true)→ 返回READ(2) → 等待 KERNEL + WRITE + READ → 等所有读写完成
  • 新读操作(write=false)→ 返回WRITE(1) → 等待 KERNEL + WRITE → 只等写完成

6. TTM 如何使用 Usage 层级

6.1 TTM 搬迁时添加 KERNEL fence

// ttm_bo.c — ttm_bo_handle_move_memdma_resv_add_fence(bo->base.resv,fence,DMA_RESV_USAGE_KERNEL);

搬迁产生的 DMA 操作是内核内部行为,必须被所有后续操作等待。

6.2 TTM eviction 等待所有 fence

// ttm_bo.c — ttm_bo_wait_ctxintttm_bo_wait_ctx(structttm_buffer_object*bo,structttm_operation_ctx*ctx){ret=dma_resv_wait_timeout(bo->base.resv,DMA_RESV_USAGE_BOOKKEEP,ctx->interruptible,15*HZ);}

TTM 在驱逐 BO 前用BOOKKEEP级别等待 →等待所有 fence(包括 BOOKKEEP)
这确保了:

  • 所有 GPU 命令已完成(WRITE/READ fence)
  • 所有内核搬迁已完成(KERNEL fence)
  • 所有内部记账 fence 已完成(BOOKKEEP fence)

6.3 DISCARDABLE BO 的驱逐流程

ttm_bo_evict(bo) // num_placement = 0 (DISCARDABLE) → ttm_bo_wait_ctx(bo) // 等 BOOKKEEP 级(全部 fence) → dma_resv_wait_timeout(..., DMA_RESV_USAGE_BOOKKEEP, ...) → 遍历所有 fence,等待每一个 → ttm_bo_pipeline_gutting(bo) // 丢弃 BO 内容

7. 实际应用场景

7.1 GPU 命令提交(隐式同步)

// amdgpu_cs.c — 提交完成后在每个引用的 BO 上添加 fence// 所有 gang member 的 fence → READ(其他人能并行读)dma_resv_add_fence(gobj->resv,&p->jobs[i]->base.s_fence->finished,DMA_RESV_USAGE_READ);// gang leader 的 fence → WRITE(后续读写都要等它)dma_resv_add_fence(gobj->resv,p->fence,DMA_RESV_USAGE_WRITE);

后续对同一 BO 的操作会按需等待:

  • 另一个写操作提交时 → 查询 READ → 等待所有读+写 fence
  • 另一个读操作提交时 → 查询 WRITE → 只等写 fence

7.2 DMA-BUF 导出/隐式同步

// dma-buf.c — 用户空间 DMA_BUF_IOCTL_SYNCusage=(arg.flags&DMA_BUF_SYNC_WRITE)?DMA_RESV_USAGE_WRITE:DMA_RESV_USAGE_READ;dma_resv_wait_timeout(dmabuf->resv,dma_resv_usage_rw(write),...);

跨设备共享 buffer 时,隐式同步依赖 WRITE/READ fence。
BOOKKEEP fence 不参与 → 不会阻塞跨设备共享。

7.3 SVM Eviction Fence

dma_resv_reserve_fences(bo->tbo.base.resv,1);dma_resv_add_fence(bo->tbo.base.resv,&evict_fence->base,DMA_RESV_USAGE_BOOKKEEP);

选择 BOOKKEEP 的原因

  1. 语义正确:eviction fence 是内部管理用途,不代表 GPU 读写操作
  2. 不干扰隐式同步:READ/WRITE 级别的查询看不到这个 fence
  3. TTM 驱逐时可见ttm_bo_wait_ctx使用 BOOKKEEP 查询 → 能等到我们的 fence

7.4 Xe 驱动中的 Page Table 更新

// xe_vm.c — 页表更新 fence 使用 BOOKKEEPdma_resv_add_fence(xe_vm_resv(vm),&fence->base,DMA_RESV_USAGE_BOOKKEEP);

页表更新是内部操作,不参与 buffer 隐式同步。


8. 如何选择正确的 Usage 级别

我的 fence 代表什么操作? │ ├─ 内核内存管理 DMA(搬迁/清零/拷贝) │ → DMA_RESV_USAGE_KERNEL │ ├─ GPU 命令提交的写操作 │ → DMA_RESV_USAGE_WRITE │ ├─ GPU 命令提交的读操作 │ → DMA_RESV_USAGE_READ │ └─ 内部管理/不参与隐式同步 (抢占、页表更新、eviction fence、TLB flush) → DMA_RESV_USAGE_BOOKKEEP

等待时应该用什么级别?

我需要等什么? │ ├─ 只等内核搬迁完成 │ → dma_resv_wait_timeout(resv, DMA_RESV_USAGE_KERNEL, ...) │ ├─ 等之前的写操作完成(我要读) │ → dma_resv_wait_timeout(resv, DMA_RESV_USAGE_WRITE, ...) │ ├─ 等之前的所有读写完成(我要写) │ → dma_resv_wait_timeout(resv, DMA_RESV_USAGE_READ, ...) │ └─ 等所有操作完成(驱逐/释放 BO) → dma_resv_wait_timeout(resv, DMA_RESV_USAGE_BOOKKEEP, ...)

9. amdgpu_bo_fence 封装说明

amdgpu_bo_fence是 amdgpu 对dma_resv_add_fence的封装:

voidamdgpu_bo_fence(structamdgpu_bo*bo,structdma_fence*fence,bool shared){structdma_resv*resv=bo->tbo.base.resv;intr;r=dma_resv_reserve_fences(resv,1);if(r){dma_fence_wait(fence,false);// OOM 时退化为同步等待return;}dma_resv_add_fence(resv,fence,shared?DMA_RESV_USAGE_READ:DMA_RESV_USAGE_WRITE);}
  • shared=trueDMA_RESV_USAGE_READ
  • shared=falseDMA_RESV_USAGE_WRITE

注意amdgpu_bo_fence只支持 READ 和 WRITE 两个级别。
如果需要 KERNEL 或 BOOKKEEP,必须直接调用dma_resv_add_fence


10. 总结

级别数值语义典型场景被谁等待
KERNEL0内核内存管理BO move/clear所有操作
WRITE1隐式写同步GPU 写命令后续读写
READ2隐式读同步GPU 读命令后续写操作
BOOKKEEP3无隐式同步eviction fence, PT update仅 BOOKKEEP 查询

核心设计思想

  • 数值越小,可见范围越广,同步约束越强
  • 数值越大,可见范围越窄,越不影响其他操作
  • TTM eviction 和 BO 释放用 BOOKKEEP 查询 → 保证等待一切
  • 隐式同步(dma-buf 共享)只看 KERNEL/WRITE/READ → BOOKKEEP 完全透明
  • 选择 BOOKKEEP 给内部管理 fence 是最安全的做法:不干扰外部同步,只在需要时(驱逐/释放)被等待
http://www.jsqmd.com/news/616632/

相关文章:

  • 【LeetCode 53】最大子数组和(Maximum Subarray)题解
  • Youtu-Parsing开源文档解析模型详解:像素级定位+RAG就绪JSON/Markdown输出
  • Ostrakon-VL-8B入门:Anaconda创建独立Python环境避免依赖冲突
  • YOLOv12官版镜像实战:手把手教你验证COCO数据集,小白也能轻松上手
  • OpenClaw配置文件详解:对接百川2-13B-4bits量化模型的最佳实践
  • Qwen3-ASR-0.6B部署案例:广电媒体素材库语音元数据自动打标系统
  • 手把手教你用Phi-4-mini-reasoning搭建智能解题助手:从部署到实战
  • OpenClaw配置备份:千问3.5-9B模型切换无忧方案
  • SecGPT-14B效果展示:对Splunk SPL查询语句进行安全语义解释与优化建议
  • SiameseAOE模型效果深度评测:多领域文本抽取能力对比
  • LeetCode 207|课程表(Course Schedule)题解 – 拓扑排序判环法
  • Qwen3.5-2B部署教程:WSL2环境下Windows用户一键运行图文模型
  • VSCode下载与配置Starry Night Art Gallery开发环境
  • C++易搞混知识: 指针、引用与取地址运算符对比分析
  • 专家答辩:视频不再是监控:基于三维空间智能体的空间计算系统构建与应用
  • Qwen3-Embedding-4B新手指南:可视化界面,轻松玩转文本向量化
  • OpenClaw技能市场指南:为千问3.5-9B寻找合适的功能扩展
  • LeetCode 210 课程表 II | 拓扑排序详解(C语言实现)
  • Swoole 5.0适配踩坑实录,深度解析协程生命周期变更、内存管理新规与RPC协议不兼容问题
  • OpenClaw+Qwen3-14B内容工厂:自动生成技术博客与SEO优化
  • VibeVoice实时语音合成实战:25种音色一键切换,打造多语言语音助手
  • nanobot超轻量级AI助手部署实测:快速体验Qwen3-4B模型的智能回复
  • [具身智能-314]:大语言模型处理文本的全过程
  • 镜像视界VS 专家 :空间计算系统最刁钻10问 + 答案
  • 一键部署实时口罩检测-通用:基于Gradio的交互式Web界面快速上手
  • Lychee-Rerank安全加固指南:防止注入攻击与数据泄露
  • Fish-speech-1.5多语言支持实战:13种语言的语音合成技巧
  • 2026年12VDC通讯设备电磁开关/家电用电磁开关多家厂家对比分析 - 品牌宣传支持者
  • 镜像视界数字孪生空间系统:二轮追问反杀清单
  • 5分钟玩转像素语言·跨维传送门:腾讯混元引擎翻译工具实测