news 2026/8/28 6:50:40

6.1.5 dma_fence_chain — timeline 语义的底层支撑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
6.1.5 dma_fence_chain — timeline 语义的底层支撑

6.1.4 的dma_fence_array把 N 个 fence无序聚合成一个,表达「一组工作同时就绪」。另一类需求是有序的:一条 timeline 上有一串单调递增的进度点(point),point N 完成即蕴含所有更早的 point 已完成。dma_fence_chain就是承载这种语义的组合原语——它把 fence 串成一条单向链,每个节点代表 timeline 上的一个 point,是drm_syncobjtimeline(对应 Vulkan timeline semaphore)的底层支撑。

本篇讲解 chain 的数据结构、创建与串接规则、timeline 的 point 查找、signal 传播,以及它与drm_syncobjtimeline 的对接。


1. 为什么需要 timeline chain

用户态的drm_syncobj有两种模式:

  • binary(二值):syncobj 内部只持有单个 fence,signal 一次、wait 一次,语义等价于一个 fence 句柄。

  • timeline(时间线):syncobj 关联一个单调递增的 64 位计数器,用户按point 值提交 signal、按point 值等待。语义遵循 Vulkan timeline semaphore:

    • 单调递增:point 值只增不减;
    • 蕴含性:point N 完成 ⟹ 所有 point < N 均已完成;
    • wait-before-signal:允许先对某个尚未提交的 point 挂等待,之后该 point 的 fence 才被 signal。

要用dma_fence表达这条 timeline,需要一个结构满足三点:

  • 既是标准 fence(可wait/add_callback/ 挂入dma_resv);
  • 又能表达「某个 point 的完成蕴含更早 point 的完成」;
  • 还能在 point 的实际工作 fence 尚未就绪前先建立这个 point。
    dma_fence_chain用「链式串接 + 复用 context + 遍历判定」实现了这三点。

2. 数据结构

/* include/linux/dma-fence-chain.h */structdma_fence_chain{structdma_fencebase;// 自身也是一个 dma_fence,base.seqno 即该节点的 point 值structdma_fence__rcu*prev;// 指向链中前一个节点(RCU 保护,支持无锁遍历与 GC)u64 prev_seqno;// GC 前的前驱 seqno,用于按 point 定位节点覆盖的区间structdma_fence*fence;// 本节点封装的实际工作 fence(该 point 的完成条件)union{structdma_fence_cbcb;// 未完成时:挂到某个子 fence 上的回调structirq_workwork;// 收尾时:irq_work,规避锁序反转};spinlock_tlock;// base.lock 指向它};

关键点:

  • 每个节点 = timeline 上一个 pointbase.seqno是该 point 的值;fence是让这个 point「达成」的实际工作 fence;prev指向前一个(更小的)point。
  • base内嵌使 chain 本身是一个标准dma_fence,对上完全复用 6.1 讲的生命周期与接口。
  • prev用 RCU 保护,遍历时无需持锁,同时支持在遍历中原子回收已 signal 的节点(§6)。
  • cbwork共用 union:signal 传播的两个阶段(挂回调 / irq_work 收尾)不会同时发生,复用同一块内存(与 6.1.4 array 的设计同理)。

两个类型辅助函数:

// fence 是 chain 则转成 dma_fence_chain*,否则 NULLstructdma_fence_chain*to_dma_fence_chain(structdma_fence*fence);// fence 是 chain 则返回其封装的 fence,否则返回自身("剥壳")structdma_fence*dma_fence_chain_contained(structdma_fence*fence);

3. 创建与串接

3.1 两段式:alloc + init

dma_fence_array一致,chain 的分配与初始化分离:

// 分配(宏,便于按调用点单独统计 alloc_tag)——GFP_KERNELstructdma_fence_chain*dma_fence_chain_alloc(void);// 未发布前释放(无需 RCU 宽限期)voiddma_fence_chain_free(structdma_fence_chain*chain);// 初始化并串接voiddma_fence_chain_init(structdma_fence_chain*chain,structdma_fence*prev,// 前一个节点(可为普通 fence 或 chain)structdma_fence*fence,// 本 point 的工作 fenceuint64_tseqno);// 本 point 的值

分离的意义在于:signal 路径上追加 timeline point 时,可以先在允许睡眠的上下文alloc,再在不可失败的临界区init(预分配模式,参见附录 B.6)。init自身不做分配,因此不会失败。

3.2 context 复用规则:一条 timeline 一个 context

init的核心是决定新节点的(context, seqno)

structdma_fence_chain*prev_chain=to_dma_fence_chain(prev);if(prev_chain&&__dma_fence_is_later(prev,seqno,prev->seqno)){// 前驱是 chain,且新 point 值更靠后 → 复用同一条 timeline 的 contextcontext=prev->context;chain->prev_seqno=prev->seqno;}else{// 否则开一条新 timelinecontext=dma_fence_context_alloc(1);if(prev_chain)seqno=max(prev->seqno,seqno);// 保证 seqno 始终有效递增}dma_fence_init64(&chain->base,&dma_fence_chain_ops,&chain->lock,context,seqno);

这段逻辑正是 timeline 语义的落点:同一条 timeline 上的所有 point 共享同一个contextseqno取 point 值并单调递增(呼应 6.1.1「context = timeline」)。注意这里用dma_fence_init64——point 值是完整 64 位,置位DMA_FENCE_FLAG_SEQNO64_BIT走 64 位比较(6.1.1 §3.1)。prev_seqno记录 GC 前的前驱 seqno,标明本节点覆盖的 point 区间为(prev_seqno, base.seqno],供按 point 查找使用。

3.3 约束:封装的 fence 不能是 chain

// 容器只能通过 prev 串接,不能把 chain 塞进 fence 字段WARN_ON(dma_fence_is_chain(fence));

串接方向唯一:链的延伸只走prev。若要把嵌套的依赖并入 point,需由调用方先摊平(例如合成dma_fence_array)再作为fence传入。这与 6.1.4 array「容器不能嵌套」的约束一脉相承,目的同样是避免遍历/释放时的深度递归。

3.4 链的形态

prev

prev

prev

node point=3
base.seqno=3
fence=job3

node point=2
base.seqno=2
fence=job2

node point=1
base.seqno=1
fence=job1

NULL(链头)

新 point 从链尾接入:prev指向当前最新节点,base.seqno取新的 point 值。对上层而言,「timeline 当前的完成句柄」就是链尾那个 chain fence。


4. timeline 语义:单调蕴含与 point 查找

4.1 完成的蕴含性

chain 判定自身是否 signal,靠遍历整条链:

staticbooldma_fence_chain_signaled(structdma_fence*fence){dma_fence_chain_for_each(fence,fence){structdma_fence*f=dma_fence_chain_contained(fence);if(!dma_fence_is_signaled(f)){dma_fence_put(fence);returnfalse;}}returntrue;}

一个节点 signaled,当且仅当它自己的fence与其所有前驱节点的fence全部signaled。由此得到 timeline 的蕴含性:point N 完成 ⟹ point 1…N-1 全部完成

4.2 按 point 查找节点

等待「timeline 到达 point N」时,需要先定位「将 signal point N」的那个节点:

intdma_fence_chain_find_seqno(structdma_fence**pfence,uint64_tseqno){structdma_fence_chain*chain=to_dma_fence_chain(*pfence);if(!seqno)return0;if(!chain||chain->base.seqno<seqno)return-EINVAL;// 不是 chain,或 timeline 尚未推进到 seqnodma_fence_chain_for_each(*pfence,&chain->base){if((*pfence)->context!=chain->base.context||to_dma_fence_chain(*pfence)->prev_seqno<seqno)break;// 停在覆盖区间 (prev_seqno, base.seqno] 含 seqno 的节点}dma_fence_put(&chain->base);return0;}

利用prev_seqno,沿链下行直到某节点的覆盖区间包含seqno*pfence即指向该节点。随后对它dma_fence_wait就等价于「等待 timeline 到达 point N」。seqno尚未提交(timeline 未推进到 N)时返回-EINVAL,这正是 wait-before-signal 需要处理的边界。

4.3 遍历接口

// 浅遍历:沿 prev 逐个访问 chain 节点,并顺带回收已 signaled 的节点#definedma_fence_chain_for_each(iter,head)...structdma_fence*dma_fence_chain_walk(structdma_fence*fence);

dma_fence_chain_for_each遍历链上的每个节点;若要深入到每个节点封装的fence(可能又是 array 等容器),使用 6.1.4 提到的dma_fence_unwrap_for_each


5. signal 传播机制

chain 不主动轮询子 fence,而是按需挂回调,逐段推进:

staticbooldma_fence_chain_enable_signaling(structdma_fence*fence){structdma_fence_chain*head=to_dma_fence_chain(fence);dma_fence_get(&head->base);dma_fence_chain_for_each(fence,&head->base){structdma_fence*f=dma_fence_chain_contained(fence);dma_fence_get(f);if(!dma_fence_add_callback(f,&head->cb,dma_fence_chain_cb)){dma_fence_put(fence);returntrue;// 挂在第一个未 signal 的子 fence 上,等它回调}dma_fence_put(f);// 该子 fence 已 signal,继续看下一个}dma_fence_put(&head->base);returnfalse;// 整条链都已 signal}
  • 按需推进:从链头(head)沿prev遍历,跳过已 signal 的子 fence,把回调head->cb挂到第一个未 signal的子 fence 上,返回true
  • 回调 → irq_work 收尾:子 fence signal 时触发dma_fence_chain_cb,它把工作转交irq_workirq_work里重新enable_signaling尝试挂到下一个未完成的子 fence,若已无未完成者则dma_fence_signal(&chain->base)点亮自身。
chain.baseirq_workdma_fence_chain_cb子 fencechain.baseirq_workdma_fence_chain_cb子 fencealt[还有未完成子 fence][全部已完成]子 fence signal,触发回调init_irq_work + irq_work_queuerearm enable_signaling(挂到下一个未完成子 fence)挂新回调,等待dma_fence_signal(chain.base)

irq_work而非在回调里直接推进,是为了规避锁序反转与递归(与 6.1.4 array、附录 B.6 中「完成信号临界区不就地做重活」的取向一致)。head->cbhead->work复用同一 union,两阶段互斥,不会同时使用。


6. 垃圾回收与防栈溢出

timeline 会不断追加 point,链可能很长。两处机制避免长链带来的资源与栈问题:

遍历时回收(walk 中 GC)dma_fence_chain_walk下行时,若前驱节点已 signal,就用cmpxchgprev原子地改指向「前驱的前驱」,摘除已完成节点:

tmp=unrcu_pointer(cmpxchg(&chain->prev,RCU_INITIALIZER(prev),RCU_INITIALIZER(replacement)));

已 signal 的中间节点因此被逐步短路回收,长链自动收缩。prev_seqno保留原始区间信息,使 GC 后按 point 查找仍然正确。

释放时手动解链(release):直接靠dma_fence_put递归释放整条链,会在长链上耗尽内核栈。dma_fence_chain_release因此在只持有最后一个引用(kref_read(&prev->refcount) == 1)时,手动沿prev逐节点解链,把递归展平为循环:

while((prev=rcu_dereference_protected(chain->prev,true))){if(kref_read(&prev->refcount)>1)break;// 还有别的持有者,交给它释放prev_chain=to_dma_fence_chain(prev);if(!prev_chain)break;chain->prev=prev_chain->prev;// 摘链RCU_INIT_POINTER(prev_chain->prev,NULL);dma_fence_put(prev);}

7. 与 drm_syncobj timeline 的对接

dma_fence_chain的直接使用者是drm_syncobj的 timeline 模式:

syncobj 操作chain 侧动作
timeline signal(point, fence)alloc一个 chain 节点,init(prev=当前链尾, fence, seqno=point),链尾更新为新节点
timeline wait(point)dma_fence_chain_find_seqno()定位到覆盖 point 的节点,再dma_fence_wait
binary syncobj内部直接是单个 fence,不经过 chain

wait-before-signal由此成立:等待方可以对尚未提交的 point 先行find_seqno(此刻返回-EINVAL,由 syncobj 层挂起为「未来 fence」等待),待 signal 侧追加该 point 的 chain 节点后再兑现。命令提交路径中的SYNCOBJ_TIMELINE_WAIT/SYNCOBJ_TIMELINE_SIGNAL(参见 8.4)正是按 point 走这套接口。drm_syncobj的完整内核实现见 6.4.2。


8. 与 dma_fence_array 的对比

维度dma_fence_arraydma_fence_chain
组合形态N 个子 fence无序聚合成一个节点有序串成单向链
完成语义AND(全部)/ OR(任一)单调蕴含:point N 完成 ⟹ 更早 point 完成
context/seqno自身独立 context一条 timeline 共享 context,seqno = point 值(64 位)
典型场景一次等待多块 BO / 多条 in-fenceVulkan timeline semaphore、drm_syncobjtimeline
定位能力find_seqno按 point 定位节点
共同约束封装的子 fence 不能是容器,需先摊平同左(WARN_ON(dma_fence_is_chain(fence))

两者是互补的组合原语:array 表达「并列的一组」,chain 表达「递进的一串」。


9. 小结

dma_fence_chain以链式结构把 fence 组织成一条 timeline:

  • 数据结构:内嵌base使其本身是 fence;prev串接前驱(RCU 保护);fence封装本 point 的工作;prev_seqno标明 point 覆盖区间。
  • timeline 语义:同一 timeline 共享 context,seqno 取 64 位 point 值单调递增;signaled遍历全链,保证「后完成蕴含先完成」。
  • point 查找dma_fence_chain_find_seqno定位将 signal 指定 point 的节点,支撑按 point 等待与 wait-before-signal。
  • signal 传播enable_signaling逐段挂回调,回调经 irq_work 推进到下一段,规避锁序反转与递归。
  • 抗长链:walk 时 GC 已完成节点,release 时手动解链防栈溢出。
  • 上层对接:是drm_syncobjtimeline 与 Vulkan timeline semaphore 的底层支撑。

至此 6.1 的组合原语(6.1.4 array、6.1.5 chain)讲完。下一节,我们从面向对象的设计模式角度总结下dma_fence的家族。

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

ReAct模式解析:大模型如何通过思考与行动协同完成复杂任务

大模型本身是“回答问题”的高手&#xff0c;但在“完成复杂任务”这件事上却经常捉襟见肘。你问它“北京今天适合带伞吗”&#xff0c;它能给出一个听起来合理但可能是编造的回答&#xff1b;你让它帮你对比三份方案并给出结论&#xff0c;它往往只在文字层面打转&#xff0c;…

作者头像 李华
网站建设 2026/8/28 6:44:42

从FLOPs到内存流量:HarDNet如何优化神经网络访存效率

1. 从“算力瓶颈”到“访存瓶颈”的范式转移如果你在2018年前后开始接触深度学习模型部署&#xff0c;尤其是尝试在嵌入式设备或移动端跑一个像样的视觉模型&#xff0c;那你大概率经历过一段“内存焦虑”的时期。那时候&#xff0c;模型设计的焦点几乎完全集中在“计算量”&am…

作者头像 李华
网站建设 2026/8/28 6:44:10

Python实现混合搜索引擎:关键词检索与向量语义检索实战

1. 背景&#xff1a;为什么还需要一种“新类型”的搜索引擎先看一个我们都很熟悉的场景&#xff1a;在传统的搜索引擎里输入“如何用Python做文本去重”&#xff0c;返回的结果往往是关键词匹配的页面集合&#xff0c;用户需要自己打开三到五个网页&#xff0c;把碎片化的答案拼…

作者头像 李华
网站建设 2026/8/28 6:43:57

控制系统Matlab仿真:数学模型建立与Simulink实现全解析

1. 项目概述&#xff1a;从理论到实践的桥梁搞控制系统&#xff0c;尤其是自动控制、机器人或者机电一体化方向的工程师和学生&#xff0c;估计都绕不开一个环节&#xff1a;仿真。而一提到仿真&#xff0c;Matlab/Simulink几乎是我们的“第二工作台”。但不知道你有没有过这样…

作者头像 李华
网站建设 2026/8/28 6:43:43

AI Agent文档层设计:DocuQueue如何构建可检索的RAG异步管道

AI Agent 能调模型、能写代码、能操作工具&#xff0c;但一遇到企业内部 PDF、Word、Excel 组成的长文档&#xff0c;问题就出来了&#xff1a;上下文窗口放不下&#xff0c;文件格式又杂&#xff0c;文档还在不断更新。这个问题不能靠 prompt 修补&#xff0c;需要一个专门的文…

作者头像 李华
网站建设 2026/8/28 6:43:00

AI对冲基金濒临崩盘:自动化决策如何用四道闸门防失控

AI 进入金融交易的时间并不算短&#xff0c;但真正让人后背发凉的&#xff0c;是它开始自己下决定之后的那一瞬间。最近&#xff0c;一只名为 Situational Awareness 的 AI 对冲基金被曝险些崩盘&#xff0c;并且正在遭受 SEC 调查。标题里的几个词放到一起&#xff0c;几乎戳中…

作者头像 李华