第六章:异步访问的同步:6.1.1 dma_fence Context 机制深度解析

张开发
2026/4/13 10:25:07 15 分钟阅读

分享文章

第六章:异步访问的同步:6.1.1 dma_fence Context 机制深度解析
1. 概述dma_fence是 Linux 内核中用于 DMA 操作GPU 渲染、视频编解码、显示等的跨驱动同步原语。每个dma_fence由两个关键标识组成context— 标识 fence 所属的执行上下文timelineseqno— 该 fence 在其 context 内的序列号二者构成了 DMA fence 同步体系的核心排序逻辑。structdma_fence{spinlock_t*lock;conststructdma_fence_ops*ops;u64 context;/* 执行上下文由 dma_fence_context_alloc() 分配 */u64 seqno;/* 上下文内的序列号 */unsignedlongflags;structkrefrefcount;interror;/* ... */};2. fence_context 的分配机制2.1 全局原子计数器内核维护一个全局 64 位原子计数器每次dma_fence_context_alloc(N)调用原子地递增 N返回分配区间的起始值/* drivers/dma-buf/dma-fence.c */staticatomic64_tdma_fence_context_counterATOMIC64_INIT(1);u64dma_fence_context_alloc(unsignednum){WARN_ON(!num);returnatomic64_fetch_add(num,dma_fence_context_counter);}dma_fence_context_alloc(1)— 分配单个唯一 contextdma_fence_context_alloc(N)—批量分配 N 个连续的 context返回首个值由于使用atomic64_fetch_add该操作天然线程安全无需额外锁保护。64 位空间在实际使用中不可能耗尽。2.2 设计哲学context timeline一个context代表一条执行 timeline。同一 timeline 上的 fence 之间存在全序关系seqno 更大的 fence隐含seqno 更小的 fence 已经完成或即将完成。核心不变量同一 context 下fence 按 seqno 严格有序。3. fence_context 的三个关键作用3.1 排序判定 —dma_fence_is_later()/* include/linux/dma-fence.h */staticinlinebooldma_fence_is_later(structdma_fence*f1,structdma_fence*f2){if(WARN_ON(f1-context!f2-context))returnfalse;return__dma_fence_is_later(f1,f1-seqno,f2-seqno);}仅对同一 context的 fence 有意义不同 context 的 fence 无法比较先后尝试比较会触发WARN_ON比较结果用于决定哪个 fence 涵盖另一个3.2dma_resv中的 fence 去重/替换dma_resvreservation object是内核管理 buffer 共享同步的核心数据结构。当向dma_resv添加 fence 时/* drivers/dma-buf/dma-resv.c */voiddma_resv_add_fence(structdma_resv*obj,structdma_fence*fence,enumdma_resv_usageusage){for(i0;icount;i){dma_resv_list_entry(fobj,i,obj,old,old_usage);if((old-contextfence-contextold_usageusagedma_fence_is_later_or_same(fence,old))||dma_fence_is_signaled(old)){/* 替换旧 fence */dma_resv_list_set(fobj,i,fence,usage);dma_fence_put(old);return;}}/* 否则追加到列表末尾 */}关键逻辑如果新 fence 与已有 fence 属于同一 context且 seqno 更大dma_fence_is_later_or_same则直接替换旧 fence而不是保留两个。这就是为什么context的正确使用至关重要——它直接决定了 fence 是否会被意外替换。3.3dma_resv_replace_fences()— 按 context 批量替换voiddma_resv_replace_fences(structdma_resv*obj,uint64_tcontext,structdma_fence*replacement,enumdma_resv_usageusage){for(i0;listilist-num_fences;i){dma_resv_list_entry(list,i,obj,old,NULL);if(old-context!context)continue;dma_resv_list_set(list,i,dma_fence_get(replacement),usage);dma_fence_put(old);}}此函数按context值查找并替换所有匹配的 fence。这在 GPU 抢占preemption等场景中使用用页表更新 fence 替换抢占 fence表示资源已不可访问。4. 正确使用 fence_context 的原则4.1 原则一独立硬件队列 独立 context如果两个执行序列没有隐含的先后关系它们必须使用不同的context。正确示例 — GPU 硬件 ring/* amdgpu_device.c: 一次性为所有 ring 分配 context */adev-fence_contextdma_fence_context_alloc(AMDGPU_MAX_RINGS);/* amdgpu_fence.c: 每个 ring 用 base ring_idx 作为 context */dma_fence_init(fence,amdgpu_fence_ops,ring-fence_drv.lock,adev-fence_contextring-idx,seq);GPU 的每个硬件 ringGFX ring、SDMA ring、Compute ring 等是独立执行通道ring 内部 fence 有序ring 之间 fence 无序。因此每个 ring 需要独立的 context。4.2 原则二独立生命周期对象 独立 context如果 fence 绑定的资源对象如 BO各自独立、互不影响每个对象需要独立的 context。正确示例 — KFD SVM per-BO eviction fence/* kfd_svm.c: 每个 svm_bo 分配独立 context */svm_bo-eviction_fenceamdgpu_amdkfd_fence_create(dma_fence_context_alloc(1),mm,svm_bo,p-context_id);每个 SVM BO 的驱逐eviction是独立事件。BO_A 的驱逐完成与否与 BO_B 无关。独立 context 确保dma_resv_add_fence()不会将不同 BO 的 eviction fence 相互替换。4.3 原则三共享 context 仅用于同一 timeline 上的顺序事件只有当一系列 fence 确实表示同一条执行流的顺序进展时才应共享 context。正确场景同一个 ring 上连续提交的 job fence — job_1(seq1) → job_2(seq2) → job_3(seq3)seq3 信号化意味着 1 和 2 也已完成。错误场景不同 BO 的 eviction fence 共享 context — BO_A 的 eviction(seq1) 和 BO_B 的 eviction(seq2)没有因果关系seq2 完成不意味着 seq1 完成。5. 经典错误案例共享 context 导致 fence 失效5.1 问题场景假设所有 SVM eviction fence 共享同一个 context/* 错误实现 */staticu64 shared_fence_context;/* 全局共享 */staticatomic_tshared_seqATOMIC_INIT(0);/* 每个 BO 的 eviction fence 使用相同 context递增 seqno */dma_fence_init(fence-base,ops,lock,shared_fence_context,atomic_inc_return(shared_seq));考虑以下时序时间线 t1: 创建 BO_A 的 eviction fence (context42, seqno1) → dma_resv_add_fence(bo_a-resv, fence_a, BOOKKEEP) t2: 创建 BO_B 的 eviction fence (context42, seqno2) → dma_resv_add_fence(bo_b-resv, fence_b, BOOKKEEP) [这一步没问题因为 BO_A 和 BO_B 的 resv 不同] t3: 某种原因需要将 fence_b 也添加到 BO_A 的 resv → dma_resv_add_fence(bo_a-resv, fence_b, BOOKKEEP)灾难发生在 t3dma_resv_add_fence()发现fence_a和fence_b拥有相同context42且fence_b的seqno2 seqno1于是直接替换 fence_a 为 fence_b。结果BO_A 的dma_resv中不再持有自己的 eviction fence而是持有 BO_B 的 fence。当 TTM 尝试 evict BO_A 时它等待的是 BO_B 的 fence——这完全错误。5.2 更隐蔽的问题dma_fence_is_later()的假设失效即使 fence 没有被替换dma_fence_later()也会产生错误判断staticinlinestructdma_fence*dma_fence_later(structdma_fence*f1,structdma_fence*f2){if(WARN_ON(f1-context!f2-context))returnNULL;if(dma_fence_is_later(f1,f2))returndma_fence_is_signaled(f1)?NULL:f1;elsereturndma_fence_is_signaled(f2)?NULL:f2;}当 context 相同时框架认为 seqno2 的 fence 隐含 seqno1 已处理可能在等待 fence_b 时认为 fence_a无需额外等待。但实际上二者是完全独立的操作。5.3 正确修复为每个 BO 分配独立 context/* 正确实现 */dma_fence_init(fence-base,ops,lock,dma_fence_context_alloc(1),/* 独立 context */1);/* seqno 无关紧要从 1 开始即可 */不同 context 的 fence 在dma_resv中永远不会相互替换各自独立管理。6. AMDGPU 中的应用场景全景6.1 GPU 硬件 Ring Fence场景context 分配seqno 策略所有硬件 Ringdma_fence_context_alloc(AMDGPU_MAX_RINGS)批量分配base ring-idx为每个 ring 分配唯一 context单个 Ring 的 fence共享该 ring 的 contextring-fence_drv.sync_seq递增/* amdgpu_device.c */adev-fence_contextdma_fence_context_alloc(AMDGPU_MAX_RINGS);/* amdgpu_fence.c — 同一 ring 上的 fence 共享 contextseqno 递增 */seqring-fence_drv.sync_seq;dma_fence_init(fence,amdgpu_fence_ops,ring-fence_drv.lock,adev-fence_contextring-idx,seq);为什么正确同一 ring 的 job 按提交顺序执行后提交的 job 完成意味着先提交的也已完成。这正是 timeline 语义。6.2 KFD SVM per-BO Eviction Fence场景context 分配seqno 策略每个 SVM BO 的 eviction fencedma_fence_context_alloc(1)每个独立分配atomic_inc_return(fence_seq)/* kfd_svm.c */svm_bo-eviction_fenceamdgpu_amdkfd_fence_create(dma_fence_context_alloc(1),mm,svm_bo,p-context_id);为什么正确每个 BO 的驱逐是独立事件完成时间无关。独立 context 防止dma_resv中 fence 被错误替换。6.3 KFD per-Process Eviction Fence场景context 分配seqno 策略进程级 eviction fencedma_fence_context_alloc(1)在 init_kfd_vm 时分配每次 restore 后创建新 fence进程级 eviction fence 附加到该进程的所有BO 上。当驱逐完成后restore 操作创建新的 fence新 seqno替换掉所有 BO 上的旧 fence。这里同一 process 的所有 fence 共享 context 是正确的——它们确实代表同一进程页表状态的 timeline。6.4 VM TLB Flush Fence/* amdgpu_vm.c */vm-tlb_fence_contextdma_fence_context_alloc(1);每个 VM 的 TLB flush 操作构成一条 timeline后续 flush 隐含前面的 flush 也已生效。共享 context 正确。6.5 User Queue Fence/* amdgpu_userq_fence.c */fence_drv-contextdma_fence_context_alloc(1);每个 user queue 是独立执行通道各自独立 context。7. 决策流程图选择 fence context 分配策略时按以下流程判断fence_A 和 fence_B 是否在同一条执行序列上 │ ├─ 是fence_A signal ⟹ fence_B 之前的工作一定完成 │ │ │ ├─ 是 → 共享 context用递增 seqno 区分 │ │ 例同一 ring 上的 job fence │ │ │ └─ 否 → 独立 context │ 看似在同一序列但实际无因果关系 │ └─ 否fence_A 和 fence_B 保护的资源是否完全独立 │ ├─ 是 → 独立 context │ 例不同 BO 的 eviction fence │ └─ 否 → 需要 dma_fence_array 或其他组合机制 而非简单的 context 共享8. 常见陷阱总结陷阱后果正确做法所有独立对象的 fence 共享同一 contextdma_resv中 fence 被后来者替换丢失同步点每个独立对象dma_fence_context_alloc(1)dma_fence_context_alloc(1)的结果用static变量缓存多实例或多设备时 context 冲突不缓存或仅在 timeline 语义正确时缓存共享 context 的 fence 被添加到不同 BO 的dma_resv某 BO 的 fence 被另一 BO 的 fence 替换独立 context seqno1初始化 context 时存在 race condition多线程/多设备初始化时 context 值不确定dma_fence_context_alloc()本身是原子操作在正确时机调用即可enable_signaling永远返回trueschedule_work 失败时 fence 永远不会 signal导致等待者卡死检查返回值失败时返回false本文涉及的context和seqno是很重要的两个概念大家一定要理解其设计目的和用法。

更多文章