news 2026/9/8 18:54:38

8.4 依赖与同步点:in-fence 收集与 out-fence 生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
8.4 依赖与同步点:in-fence 收集与 out-fence 生成

8.2 把用户递交的 chunk 翻译成了作业骨架,8.3 又为其引用的所有 BO 完成了加锁与驻留。此刻 job 已经「有形有料」,但还不能贸然交给调度器——因为一次提交往往并非孤立执行:它可能必须等待其他提交先完成(例如上一帧的渲染结果、另一个队列写入的纹理),也可能被别人等待(后续提交要读它的输出)。本节聚焦第 ③ 阶段中依赖类 chunk 的解析与第 ⑦ 阶段amdgpu_cs_sync_rings,讲清内核如何收集本次提交的入方向依赖(in-fence)、又如何为其准备出方向信号(out-fence)

1. 两个方向、两种同步

围绕一次提交的同步关系可以拆成两个方向:

  • in-fence(前向依赖):本次提交的 job 开始执行前必须等待的一组 fence。只有它们全部 signal,job 才被允许上 ring。
  • out-fence(完成信号):本次提交完成时对外发出的 fence,供后续提交或 CPU 侧等待。

而 in-fence 又按「谁来指定」分为两类,这正对应第六章讲过的两种同步策略:

类别来源对应章节
显式依赖用户在 chunk 中明确列出要等待的 fence / syncobj6.4 显式同步
隐式依赖从本次引用的 BO 的dma_resv自动读取已挂载的 fence6.3 隐式同步 dma_resv

内核用 parser 上的两个字段分别承接这两个方向:

  • p->syncamdgpu_sync容器):汇集所有 in-fence,无论显式还是隐式,最终统一压入 job 作为调度依赖。
  • p->post_deps:登记那些需要用本次 out-fence 去「点亮」的 syncobj,待提交成功后再逐一 signal。

out-fence 分发

in-fence 收集 → p->sync

显式:DEPENDENCIES chunk
(ctx+ring+seq)

p->sync

显式:SYNCOBJ_IN / TIMELINE_WAIT

隐式:各 BO 的 dma_resv

压入 job 作为调度依赖
amdgpu_sync_push_to_job

job 完成 → finished fence

ctx_add_fence → seq → cs->out.handle

signal p->post_deps 中的 syncobj

其中「显式依赖的解析」与「out-fence 目标的登记」发生在第 ③ 阶段的pass2;「隐式依赖的收集与依赖入队」发生在第 ⑦ 阶段的sync_rings;「out-fence 的真正生成与分发」发生在第 ⑧ 阶段的submit。下面按此顺序展开。

2. 显式 in-fence:pass2 解析依赖类 chunk(③)

回顾 8.2:pass2遍历所有 chunk,IB 分支已在 8.2 讲过;本节接手它的依赖类分支。这些分支的共同归宿都是把解析出的 fence 通过amdgpu_sync_fence(&p->sync, fence, ...)塞进p->sync

2.1 fence-based 依赖:DEPENDENCIES

amdgpu_cs_p2_dependencies处理AMDGPU_CHUNK_ID_DEPENDENCIES。用户以「(ctx_id, ip_type, ip_instance, ring, handle)」五元组指明「我要等某个上下文某条 ring 上序号为 handle 的那次提交」:

ctx=amdgpu_ctx_get(fpriv,deps[i].ctx_id);amdgpu_ctx_get_entity(ctx,deps[i].ip_type,deps[i].ip_instance,deps[i].ring,&entity);fence=amdgpu_ctx_get_fence(ctx,entity,deps[i].handle);/* 按 seq 取出目标 fence */...if(chunk->chunk_id==AMDGPU_CHUNK_ID_SCHEDULED_DEPENDENCIES){/* 取 scheduled 而非 finished,允许流水线重叠 */s_fence=to_drm_sched_fence(fence);fence=dma_fence_get(&s_fence->scheduled);...}r=amdgpu_sync_fence(&p->sync,fence,GFP_KERNEL);

这里的handle正是被依赖的那次提交返回给用户的 seq(见第 5 节 out-fence 的cs->out.handle)——依赖链就此闭合。值得注意的是SCHEDULED_DEPENDENCIES变体:它取目标 fence 的scheduled(已被调度器排上但未必执行完)而非finished。这是一种流水线优化——后继作业只需等前驱「排上队」即可开始准备,无需苦等其彻底完成,前提是二者在同一条 ring 上天然保序。

2.2 syncobj 依赖:SYNCOBJ_IN 与 TIMELINE_WAIT

两者共用辅助函数amdgpu_syncobj_lookup_and_add,区别只在于「取哪个点的 fence」:

r=drm_syncobj_find_fence(p->filp,handle,point,flags,&fence);...r=amdgpu_sync_fence(&p->sync,fence,GFP_KERNEL);
  • amdgpu_cs_p2_syncobj_inSYNCOBJ_IN):二元 syncobj,固定point = 0——取该 syncobj 当前持有的 fence。
  • amdgpu_cs_p2_syncobj_timeline_waitSYNCOBJ_TIMELINE_WAIT):timeline syncobj,按用户给定的point取对应时间点的 fence。

关于 syncobj 与 timeline 的内核实现,可回看 6.4.2 drm_syncobj。

综上,pass2 中所有显式 in-fence 的解析可归纳为一张表——终点都是p->sync

chunk 类型处理函数依赖来源
DEPENDENCIES/SCHEDULED_DEPENDENCIESp2_dependencies另一提交的 fence(按 ctx+ring+seq 取,后者取 scheduled)
SYNCOBJ_INp2_syncobj_in二元 syncobj 当前 fence
SYNCOBJ_TIMELINE_WAITp2_syncobj_timeline_waittimeline syncobj 指定 point 的 fence

3. 显式 out-fence 目标:登记待 signal 的 syncobj(③)

对称地,用户也可要求「本次提交完成后去点亮某些 syncobj」。这类 out chunk 在 pass2 里只做登记,把目标 syncobj 存进p->post_deps,真正 signal 留到第 ⑧ 阶段:

staticintamdgpu_cs_p2_syncobj_out(structamdgpu_cs_parser*p,...){...p->post_deps=kmalloc_array(num_deps,sizeof(*p->post_deps),GFP_KERNEL);for(i=0;i<num_deps;++i){p->post_deps[i].syncobj=drm_syncobj_find(p->filp,deps[i].handle);p->post_deps[i].chain=NULL;/* 二元:无需 chain */p->post_deps[i].point=0;p->num_post_deps++;}}
  • amdgpu_cs_p2_syncobj_outSYNCOBJ_OUT):二元 syncobj,chain = NULLpoint = 0
  • amdgpu_cs_p2_syncobj_timeline_signalSYNCOBJ_TIMELINE_SIGNAL):timeline syncobj,若point != 0则预先dma_fence_chain_alloc()备好一个 chain 节点(timeline 语义要求把新 fence 以「点」的形式串上时间线,参见 6.1.5)。

可跳过的实现细节:第 2、3 节的多个p2_*函数形态高度相似,本质都是「遍历数组 → 查出 fence/syncobj → 存入p->syncp->post_deps」。若只关心主干,记住「显式 in-fence 汇入p->sync,显式 out 目标登记进p->post_deps」即可,各 chunk 子类型的差异可在需要时再回看。

4. 隐式 in-fence 与依赖入队:sync_rings(⑦)

BO 加锁完成后(8.3),第 ⑦ 阶段amdgpu_cs_sync_rings补齐隐式依赖,并把p->sync里累积的全部 fence 正式压入 job。它依次做三件事:

4.1 等待本 entity 的上一次提交

r=amdgpu_ctx_wait_prev_fence(p->ctx,p->entities[p->gang_leader_idx]);

这一步保证同一调度实体上的提交按序推进,兼作对 ctx fence 环的流控——避免尚有在途提交时就覆盖其记录。

4.2 从每个 BO 的 dma_resv 收集隐式依赖

drm_exec_for_each_locked_object(&p->exec,index,obj){structdma_resv*resv=gem_to_amdgpu_bo(obj)->tbo.base.resv;sync_mode=amdgpu_bo_explicit_sync(bo)?AMDGPU_SYNC_EXPLICIT:AMDGPU_SYNC_NE_OWNER;r=amdgpu_sync_resv(p->adev,&p->sync,resv,sync_mode,&fpriv->vm);}

遍历 8.3 锁定的每个 BO,把其dma_resv上已有的 fence 纳入p->syncsync_mode是关键:

  • AMDGPU_SYNC_NE_OWNER(默认):只同步不属于本 VM的 fence(Not-Equal-OWNER)。同一进程在同一 VM 上对某 BO 的先前写入,本就由 ring 顺序保证可见,无需再插入依赖——借此避免自我串行化,是重要的性能优化。
  • AMDGPU_SYNC_EXPLICIT:当 BO 被标记为「显式同步」时,隐式路径不再自动为它建立依赖,一切交由用户显式指定。

dma_resv上 fence 的读/写用途分层(DMA_RESV_USAGE_*)决定了哪些 fence 需要被等待,细节见 6.3.1 dma_resv_usage。

4.3 把依赖压入 job,并为同 ring 依赖插入流水线同步

for(i=0;i<p->gang_size;++i)amdgpu_sync_push_to_job(&p->sync,p->jobs[i]);/* 全部 in-fence → 各 job 的调度依赖 */sched=p->gang_leader->base.entity->rq->sched;while((fence=amdgpu_sync_get_fence(&p->sync))){s_fence=to_drm_sched_fence(fence);if(!s_fence||s_fence->sched!=sched){/* 非同一调度器 ring:跳过 */dma_fence_put(fence);continue;}/* 同一 ring 上的依赖:额外挂到 gang_leader->explicit_sync */amdgpu_sync_fence(&p->gang_leader->explicit_sync,fence,GFP_KERNEL);}

amdgpu_sync_push_to_jobp->sync中的每个 fence 登记为 job 的调度依赖——调度器要等这些 fence 全部 signal 才会真正发射该 job。随后那段while循环单独处理「依赖恰好落在与本提交同一条 ring 上」的情况:把它们额外收进gang_leader->explicit_sync,以便在执行前插入一次流水线同步(pipeline sync),确保上一作业的缓存刷新、结果对下一作业可见——同一 ring 上先后执行的两个作业之间,硬件并不天然保证这种可见性。

5. out-fence 的生成与分发(⑧ 的依赖部分)

依赖备齐后,真正的 out-fence 在第 ⑧ 阶段amdgpu_cs_submit中诞生。submit 的完整临界区(drm_sched_job_armdma_resv回写、userptr 失效重试等)是 8.5 的主题,这里只截取与「out-fence」直接相关的三步:

p->fence=dma_fence_get(&leader->base.s_fence->finished);/* ① out-fence = 领队的 finished fence */...seq=amdgpu_ctx_add_fence(p->ctx,p->entities[p->gang_leader_idx],p->fence);/* ② 登记进 ctx,得 seq */amdgpu_cs_post_dependencies(p);/* ③ 用 out-fence 点亮 post_deps 中的 syncobj */...cs->out.handle=seq;/* seq 返回用户态 */

三步含义:

  1. out-fence 的本体是 gang 领队 job 的调度器finishedfence——它在整个 gang 执行完毕时 signal。
  2. amdgpu_ctx_add_fence把该 fence 登记进 ctx 的 fence 环并返回一个序号seqseqcs->out.handle回传用户态。日后用户可凭此 seq 通过amdgpu_cs_wait_ioctl等待,或经amdgpu_cs_fence_to_handle_ioctl转成 sync_file fd——这也正是第 2.1 节里DEPENDENCIES依赖所引用的那个handle
  3. amdgpu_cs_post_dependencies遍历第 3 节登记的p->post_deps,用刚诞生的p->fence去点亮每个目标 syncobj:
if(post_deps[i].chain&&post_deps[i].point)drm_syncobj_add_point(syncobj,chain,p->fence,point);/* timeline:串上时间点 */elsedrm_syncobj_replace_fence(syncobj,p->fence);/* 二元:替换当前 fence */

至此,入方向(等待谁)与出方向(谁来等我)双双闭合:本次提交的 job 已挂好全部依赖,其完成信号也已既能被 seq 查询、又能点亮用户指定的 syncobj。

6. 形象小结:一次「排班与交接」

若把每次提交看作车间里的一道工序,本阶段做的正是排班与交接

环节阶段动作
列出「我要等哪些前道工序」——点名指定③ pass2解析DEPENDENCIES/SYNCOBJ_IN/TIMELINE_WAITp->sync
登记「我完工后要通知谁」③ pass2SYNCOBJ_OUT/TIMELINE_SIGNALp->post_deps
自动补上「我碰过的料还有谁在用」⑦ sync_rings从各 BOdma_resv收集隐式依赖 →p->sync
把等待清单钉到工单上⑦ sync_ringspush_to_job+ 同 ring 流水线同步
发出完工凭据、通知下游⑧ submit领队 finished fence →seq/cs->out.handle+ 点亮 post_deps

设计上的两条主线值得记住:其一,所有 in-fence(显式 + 隐式)汇于一处p->sync,再统一压入 job,让调度器只面对一份干净的依赖清单;其二,out-fence 一体两面——对内是可按 seq 查询的 ctx fence,对外是可点亮 syncobj 的信号源,二者同为领队的finishedfence。

可跳过的实现细节:只关心主干的读者记住三句话即可——(1) 显式依赖来自 chunk、隐式依赖来自 BO 的dma_resv,两者都进p->sync并压给 job;(2)NE_OWNER模式跳过本 VM 自己的 fence,避免自我串行;(3) out-fence 就是领队 job 的 finished fence,既登记为 seq 返回用户、又用来点亮 out-syncobj。

依赖与信号均已就位,job 只差最后一步——被真正「武装」并推入调度器。下一节 8.5 将进入第 ⑧ 阶段amdgpu_cs_submit,剖析drm_sched_job_armnotifier_lock保护下的dma_resvfence 回写、userptr 失效导致的-EAGAIN重试,以及最终的drm_sched_entity_push_job

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

CodeGraph 安装指南:5 分钟让 AI 编程 Agent 少调 88% 工具

CodeGraph 安装指南&#xff1a;5 分钟让 AI 编程 Agent 少调 88% 工具 【免费下载链接】codegraph Pre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — few…

作者头像 李华
网站建设 2026/9/8 18:54:11

3步装好Video2X:免费视频超分辨率与帧率插值完全指南

3步装好Video2X&#xff1a;免费视频超分辨率与帧率插值完全指南 【免费下载链接】video2x A machine learning-based video super resolution and frame interpolation framework. Est. Hack the Valley II, 2018. 项目地址: https://gitcode.com/GitHub_Trending/vi/video2…

作者头像 李华
网站建设 2026/9/8 18:50:08

自制PCB从入门到实战:电路板制作环境搭建与蚀刻焊接完整指南

1. 环境搭建的整体思路&#xff1a;从"买现成"到"自己动手"的分水岭刚开始玩电路板自制的时候&#xff0c;很多人都会经历一个纠结期&#xff1a;到底是直接买现成的开发板、模块&#xff0c;还是从零开始自己搭一套环境&#xff1f;我个人的看法是&#x…

作者头像 李华
网站建设 2026/9/8 18:49:41

GEC6818传感器驱动实战:DHT11温湿度与MQ-2烟雾检测ko模块开发

简介&#xff1a;面向基于GEC6818开发板做毕业设计的电子、嵌入式方向学生&#xff0c;这套驱动资源覆盖温湿度、红外、超声波、步进电机、继电器、光敏、烟雾火焰、ADC等常用外设模块&#xff0c;基本满足智能家居、环境监测类项目的底层驱动需求。压缩包共含116个文件&#x…

作者头像 李华