6.1.6 dma_fence 家族:继承、组合模式在内核中的应用
2026/8/29 5:53:17 网站建设 项目流程

6.1.1 到 6.1.5 逐个拆解了 dma_fence 家族的成员:base fence 的生命周期、context/seqno 的排序身份、spinlock 的并发保护、enable_signaling 的按需使能,以及两个组合原语 array 与 chain。本篇换一个视角,把它们作为一个整体重新审视——dma_fence_arraydma_fence_chain都「内嵌一个dma_fence且又持有一组dma_fence」,从面向对象设计的设计模式视角总结下。

用一句话概括本篇要论证的结论:

dma_fence 家族是「组合模式(Composite)」在内核 C 语言中的一次典范实现:用结构体内嵌 +ops虚表实现子类型多态,让「一组 fence」对上层伪装成「一个 fence」。

下面从机制层、模式层、约束层三层展开。


1. 家族全景:谁是谁

先把成员和它们的角色列清楚:

成员本质角色详见
struct dma_fence抽象基类(接口 + 生命周期骨架)抽象 fence6.1
驱动 hw fence内嵌dma_fence,对应一次硬件完成叶子(Leaf)6.1
dma_fence_stub已 signal 的空 fence(占位/兜底)特殊叶子6.1
dma_fence_array内嵌base+ 持有 N 个子 fence组合(Composite)·扁平6.1.4
dma_fence_chain内嵌base+ 持有 prev 链 + 1 个工作 fence组合(Composite)·线性6.1.5

它们共享同一个基类,构成一棵「部分—整体」树:叶子是真正对应硬件完成的 fence,组合节点内部聚合其它 fence,而对上层消费者,两者都只是一个struct dma_fence *

N 个子 fence

prev + 工作 fence

«abstract»

dma_fence

+context / seqno

+flags / refcount

+ops : dma_fence_ops

+lock : spinlock_t

驱动 hw fence (Leaf)

stub (Leaf)

dma_fence_array (Composite·扁平)

dma_fence_chain (Composite·线性)

注意 array 与 chain 的聚合边(o-->)又指回dma_fence自身——组合节点持有的仍是抽象 fence,可以是叶子,也可以(在受限条件下)是另一个组合。这种「基类聚合基类」的自引用,正是组合模式树形结构的来源。


2. 机制层:C 语言如何实现「继承」

家族没有 C++ 的class,它用内核惯用的三件套实现子类型多态

面向对象概念C 语言实现家族中的体现
继承 / is-a结构体把基类作为第一个成员内嵌struct dma_fence_array { struct dma_fence base; ... }
向上转型(upcast)取内嵌成员地址&array->base当作struct dma_fence *传出
向下转型(downcast)container_ofto_dma_fence_array()/to_dma_fence_chain()
虚函数 / 动态分发函数指针表fence->ops->{enable_signaling, signaled, wait, release}
运行时类型识别(RTTI)比较ops指针dma_fence_is_array()/dma_fence_is_chain()

base放在首成员,使struct dma_fence *struct dma_fence_array *在地址上重合,向上转型零开销;ops指针再把同名调用分发到各自实现。这与struct kobjectstruct inodesk_buff的做法一脉相承——是 Linux 内核里「面向对象 C」的标准范式。

lock字段是指针spinlock_t *)这一点也在此得到解释:基类不内嵌锁实例,而由子类提供自己的lock并让base.lock指过去(array 的base.lock指向array->lock,chain 同理)。基类只约定「有一把锁」,锁的归属交给子类——这正是继承中「基类定接口、子类定实现」的体现(详见 6.1.2)。


3. 模式层:核心是组合模式(Composite)

继承只是手段,家族真正要表达的意图是组合模式。GoF 对组合模式的定义是:

将对象组合成树形结构以表示「部分—整体」层次,使客户端对「单个对象」和「组合对象」的使用方式一致。

逐条对应到 dma_fence 家族:

  • 统一接口(Component)dma_fence+dma_fence_ops。消费接口dma_fence_wait()/dma_fence_add_callback()/ 挂入dma_resv全部只认struct dma_fence *
  • 叶子(Leaf):驱动 hw fence,对应一次真实硬件完成,没有子节点。
  • 组合(Composite)dma_fence_arraydma_fence_chain,内部聚合子 fence,自身完成与否取决于子节点。
  • 一致性(关键收益):消费者无需知道手里的 fence 是叶子还是组合。等待一块 BO 的写完成,和等待「N 块 BO 全部写完成」的聚合 fence,写法完全一样。

这份「一致性」就是家族全部价值所在:上层(dma_resvdrm_syncobj、命令提交)永远只面对单个dma_fence接口,而「一个还是一组」「无序还是有序」的复杂度被封装进组合节点内部。

除 Composite 外,家族还叠加了三个配套模式,各司其职:

设计模式在家族中的落点作用
Composite(组合)array / chain 聚合子 fence让一组 fence 伪装成一个 fence(主模式)
Strategy(策略)/ 虚表dma_fence_ops四个钩子同一动作路由到 leaf/array/chain 各自实现
Template Method(模板方法)dma_fence_signal()/dma_fence_wait_timeout()核心定骨架,可变步骤下沉到ops
Observer(观察者)add_callback注册、signal时遍历cb_list通知完成事件的一对多异步通知(详见 6.2)

可以说:Composite 是目的,Strategy(ops) 是手段,Template Method 定骨架,Observer 管通知


4. 两种组合拓扑:array 扁平 vs chain 线性

同样是 Composite,array 与 chain 是这棵组合树的两种不同形态,这正是家族最有意思的地方:

维度dma_fence_array(6.1.4)dma_fence_chain(6.1.5)
拓扑扁平、宽:一层,N 个兄弟线性、深:单向链,prev递归
聚合语义AND(全就绪)/ OR(任一),靠num_pending有序 timeline:单调递增 + 蕴含性
context/seqno自身独立 context一条 timeline 共享 context,seqno = point 值
signal 传播子回调递减num_pending,归零经 irq_work 冒泡沿链逐段 enable、经 irq_work 推进
定位能力find_seqno按 point 定位节点
类比数据结构集合 / 计数栅栏链表 / 版本号轴
一句话表达「并列的一组」表达「递进的一串」

两者互补:需要「多个结果同时(无序)就绪」用 array,需要「有序进度点、后者蕴含前者」用 chain。它们共同覆盖了组合模式在同步语义上的两个正交维度——无序并列聚合有序串接


5. 虚表对照:同一接口,三套实现

把 Strategy 落到实处,看四个核心ops钩子在叶子与两个组合上的不同实现,最能体现「多态」:

ops钩子叶子(hw fence)dma_fence_arraydma_fence_chain
get_timeline_name驱动 timeline 名"unbound""unbound"
enable_signaling开硬件完成中断每个子 fence 挂回调为链上首个未完成节点挂回调
signaled查硬件状态num_pending <= 0遍历全链判定
releasedma_fence_free(kfree_rcu)put 所有子 fence + kfree 数组手动解链防栈溢出

同一个dma_fence_wait()调用,落到不同ops上就有截然不同的行为——这就是虚表分发的意义。上层代码写一次,家族里每种 fence 各自「翻译」成自己的完成判定方式。


6. 模式带来的约束

家族里几条看似琐碎的实现约束,其实都是「组合树」这一模式在内核无栈递归约束下的直接后果。理解了模式,这些规矩就不再是需要死记的特例:

  • array 禁止嵌套容器WARN_ON(dma_fence_is_container(...)))+dma_fence_unwrap_merge()摊平:array 选择做扁平组合,用「构造前 flatten 成一层」替代「运行时递归遍历」,避免组合树过深、在 signal/遍历/release 时耗尽内核栈。
  • chain 的release手动解链:chain 是深链形态的组合,靠析构递归dma_fence_put(prev)会栈溢出,于是把递归展平为循环。与 array「禁止嵌套」是同一问题(递归深度)的两种解法——array 从源头压平,chain 在析构时展平。
  • num_pending+ irq_work(array)/ 逐段 irq_work(chain):组合节点要等所有/下一个子节点完成再向上游 signal,是组合树里「完成事件向根冒泡」的实现;用 irq_work 把 signal 收尾切到干净上下文,规避在子 fence 锁内触发下游回调造成的锁序反转与递归。
  • cb/work共用 union:组合节点在「等待阶段(挂回调)」与「收尾阶段(irq_work)」不会同时发生,复用同一块内存——是组合节点两阶段生命周期的体现。
  • 两段式alloc+init:把「可能失败的分配」与「不可失败的初始化」分离,支持在 reclaim / signaling 上下文用预分配对象构造组合节点(附录 B.6 的预分配模式)。
  • 两套遍历接口dma_fence_array_for_each(浅层,只展开一层)与dma_fence_unwrap_for_each(深度,递归展开所有容器交出叶子)——正是组合模式经典的树遍历,按「是否深入子树」分成两档。

7. 家族如何协同出场

这些成员并非各自孤立,它们在上层同步设施里经常一起出现:

dma_fence 家族

上层同步设施

get_singleton 合成一个

binary 模式

timeline 模式

dma_resv 隐式同步
(6.3)

drm_syncobj 显式同步
(6.4.2)

dma_fence_array
扁平 AND/OR

dma_fence_chain
线性 timeline

驱动 hw fence (叶子)

  • dma_resv_get_singleton()把一块 BO 上的多个 fence 合成一个对外句柄,底层构造扁平的 array(6.3.2)。
  • drm_syncobj的 binary 模式直接持单个 fence(叶子),timeline 模式则用chain表达单调递增的 point(6.4.2)。
  • 无论哪条路径,最终的叶子都是驱动的 hw fence——组合节点只是把它们按不同拓扑组织起来。

8. 小结

从继承视角回看,dma_fence 家族是一套结构清晰的面向对象设计:

  • 机制层:结构体内嵌base+container_of转型 +ops虚表,实现 C 语言的子类型多态;lock用指针交由子类提供,是「基类定接口、子类定实现」的缩影。
  • 模式层:核心是组合模式(Composite)——让「一组 fence」对上层伪装成「一个 fence」;配套 Strategy(ops 分发)、Template Method(核心定骨架)、Observer(signal 通知回调)。
  • 两种拓扑:array 是扁平的 AND/OR 组合,chain 是线性有序的 timeline 组合,正交互补。
  • 约束即模式:不可嵌套 / 手动解链 / unwrap 遍历 / irq_work 冒泡,都是「组合树」在内核无栈递归约束下的工程化取舍,而非零散特例。

dma_fence定义了「一次异步完成」的抽象;array 与 chain 用组合模式把这个抽象递归地组织成树——并列成组、有序成链。上层只需面对一个dma_fence *,家族在内部消化了「一个还是一组、无序还是有序」的全部复杂度。

至此 6.1 全章(基类、context/seqno、spinlock、enable_signaling、array、chain 及本总结篇)完结。接下来6.2回到 fence 的消费侧,深入阻塞等待与异步回调两种同步策略。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询