
一次操作往往依赖多个异步结果同时就绪一次提交等待多块 BO 的写完成、一个drm_syncobj收集多条 in-fence、dma_resv把多个 fence 合成一个对外句柄。这些场景需要把「一组 fence」包装成「一个 fence」让上层用统一的dma_fence接口去等待。dma_fence_array就是这个聚合原语它本身是一个dma_fence内部持有一组子 fence当子 fence全部或任一就绪时它自己随之 signal。本篇我们关注这个功能的数据结构、创建接口、signal 传播机制。1. 为什么需要聚合dma_fence的消费接口dma_fence_wait、dma_fence_add_callback、挂入dma_resv都以单个fence 为单位。当一个逻辑操作依赖 N 个 fence 时若不聚合调用方就得自己维护「还差几个未完成」的计数、自己串联回调重复实现同一套逻辑。dma_fence_array把这套逻辑收进一个标准dma_fence子类对上它是一个普通 fence可被wait/add_callback/ 挂入dma_resv对内它跟踪 N 个子 fence 的完成情况按「全部就绪」或「任一就绪」的策略决定自己何时 signal。典型使用者包括dma_resv_get_singleton()N 个 fence 合成一个、drm_syncobj、i915/xe 的提交路径以及经dma_fence_unwrap_merge()合并多个容器的场景。2. 数据结构structdma_fence_array{structdma_fencebase;// 自身也是一个 dma_fencespinlock_tlock;// base.lock 指向它unsignednum_fences;// 子 fence 总数atomic_tnum_pending;// 仍未 signal 的子 fence 计数structdma_fence**fences;// 子 fence 指针数组所有权归 arraystructirq_workwork;// signal 收尾放到 irq_work 中执行structdma_fence_array_cbcallbacks[]__counted_by(num_fences);};几个要点base让它天然是一个dma_fencebase.lock指向内嵌的lock。num_pending是聚合的核心状态每个子 fence signal 时原子递减减到 0 触发自身 signal。它的初值由signal_on_any决定见 §3。callbacks[]是柔性数组与num_fences对齐——为每个子 fence 预留一个回调结构避免在 signaling 路径再分配内存。fences数组的所有权移交给 arrayrelease 时对每个子 fencedma_fence_put()再kfree(fences)。dma_fence_array_cb把回调与所属 array 关联起来structdma_fence_array_cb{structdma_fence_cbcb;structdma_fence_array*array;};类型判定与转换staticinlinebooldma_fence_is_array(structdma_fence*fence);// ops dma_fence_array_opsstaticinlinestructdma_fence_array*to_dma_fence_array(structdma_fence*fence);3. 创建接口structdma_fence_array*dma_fence_array_alloc(intnum_fences);voiddma_fence_array_init(structdma_fence_array*array,intnum_fences,structdma_fence**fences,u64 context,unsignedseqno,bool signal_on_any);structdma_fence_array*dma_fence_array_create(intnum_fences,structdma_fence**fences,u64 context,unsignedseqno,bool signal_on_any);dma_fence_array_create是常用入口它内部先dma_fence_array_alloc()一次kzalloc用struct_size()把柔性数组callbacks[num_fences]一并算进去再调用dma_fence_array_init()。分配失败返回NULL。调用方需自行分配fences数组并填入要聚合的子 fence该数组的所有权被 array 接管无需自己释放。signal_on_any决定聚合语义体现在num_pending的初值上signal_on_anynum_pending初值语义falsenum_fences全部子 fence signal 后array 才 signalANDtrue1任一子 fence signal 后array 即 signalORcontext/seqno是 array 作为一个dma_fence自身的 context 与序号context/seqno 语义见 6.1.1。聚合 fence 通常不属于任何硬件 timeline调用方一般用dma_fence_context_alloc()分配一个专用 context 传入。OR 语义下的生命周期陷阱signal_on_anytrue时 array 在第一个子 fence signal 时即对外 signal但 §4.1 的enable_signaling已为每个子 fence 挂了回调并各持一份 array 引用这些回调不会在提前 signal 时被摘除。因此 array 虽已 signal要等所有子 fence 都 signal、逐个释放引用后才真正被回收。使用方不应假设「array 一 signal子 fence 与 array 就能马上释放」。allocinit的两段式拆分对应了「预分配 关键路径不失败」的模式dma_fence_array_init()不做任何分配专供在reclaim 路径或 dma_fence signaling 上下文中初始化一个预分配好的 array这类上下文禁止会触发回收的分配机理见附录 B.6。init的两条断言值得注意WARN_ON(!num_fences||!fences);// 不允许空聚合...while(num_fences--)WARN_ON(dma_fence_is_container(fences[num_fences]));// 子 fence 不能是容器第二条是本篇的关键约束§6 详述。4. signal 传播机制array 自己何时 signal取决于子 fence 的完成事件如何汇总。整条链路围绕num_pending展开。4.1 enable_signaling为每个子 fence 挂回调当有消费者首次要等待这个 array 时框架调用dma_fence_array_ops.enable_signalingenable_signaling的通用机制见 6.1.3。它遍历所有子 fence为每个挂上dma_fence_array_cb_func回调for(i0;iarray-num_fences;i){cb[i].arrayarray;dma_fence_get(array-base);// 每挂一个回调给 array 自身加一次引用if(dma_fence_add_callback(array-fences[i],cb[i].cb,dma_fence_array_cb_func)){/* add_callback 返回非 0该子 fence 已 signal */dma_fence_array_set_pending_error(array,array-fences[i]-error);dma_fence_put(array-base);if(atomic_dec_and_test(array-num_pending)){dma_fence_array_clear_pending_error(array);returnfalse;// 全部已就绪无需异步等待}}}要点额外引用array 可能在所有回调完成前就 signal因此每挂一个回调都给array-base加一次引用防止过早释放。引用在回调触发或 array signal 时释放。已 signal 的子 fencedma_fence_add_callback对已 signal 的 fence 返回非 0此时直接递减num_pending若递减到 0说明所有子 fence 已就绪返回false表示无需启用异步信号。4.2 回调与收尾num_pending 归零后 signal每个子 fence signal 时触发dma_fence_array_cb_funcstaticvoiddma_fence_array_cb_func(structdma_fence*f,structdma_fence_cb*cb){structdma_fence_array*array...;dma_fence_array_set_pending_error(array,f-error);if(atomic_dec_and_test(array-num_pending))irq_work_queue(array-work);// 最后一个调度收尾elsedma_fence_put(array-base);// 非最后一个释放本回调持有的引用}减到 0 的那个回调不直接 signal而是irq_work_queue把收尾推到 irq_work 上下文执行staticvoidirq_dma_fence_array_work(structirq_work*wrk){dma_fence_array_clear_pending_error(array);dma_fence_signal(array-base);// 在这里真正 signaldma_fence_put(array-base);// 释放最后一个引用}用 irq_work 的原因子 fence 的回调可能在持有其 spinlock 的上下文中被调用直接在此调用dma_fence_signal()会去拿 array 自己的锁、并触发 array 的下游回调容易造成锁序问题或深层递归。irq_work 把 signal 收尾切换到一个干净的上下文规避这类风险。未归零归零子 fence signaldma_fence_array_cb_funcatomic_dec_and_test(num_pending)dma_fence_put(base)释放该回调引用irq_work_queue(work)irq_dma_fence_array_workdma_fence_signal(base)唤醒 array 的下游等待者4.3 signaled 快路径dma_fence_is_signaled()会先走dma_fence_array_ops.signaled做无锁快查num_pendingatomic_read_acquire(array-num_pending);if(test_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,array-base.flags)){/* 已启用异步信号直接看 num_pending 是否 0 */returnnum_pending0;}/* 尚未启用主动轮询子 fence */for(i0;iarray-num_fences;i)if(dma_fence_is_signaled(array-fences[i])!--num_pending)returntrue;returnfalse;先用atomic_read_acquire读num_pending再判ENABLE_SIGNAL位acquire语义与enable_signaling里的atomic_dec_and_test配对避免与并发的使能过程发生撕裂读。5. 错误传播array 的base.error汇总子 fence 的错误。实现用一个占位值PENDING_ERROR (1)表示「尚未决定」init时把base.error置为PENDING_ERROR任一子 fence 带错误时dma_fence_array_set_pending_error()用cmpxchg把第一个报告的错误写入仅在仍为PENDING_ERROR时从而只保留首个错误signal 收尾时dma_fence_array_clear_pending_error()把仍是占位值的error归零。效果array 对外报告其子 fence 中首个出现的错误若都成功则error 0。6. 关键约束容器不能嵌套init结尾的断言禁止子 fence 本身是容器dma_fence_array或dma_fence_chainwhile(num_fences--)WARN_ON(dma_fence_is_container(fences[num_fences]));原因是递归深度如果 array 里再嵌 array对聚合 fence 的遍历、signal、release 等操作就会沿嵌套层层递归在深层嵌套下可能耗尽内核栈。正确做法是由调用方先把嵌套结构**摊平flatten**成一层扁平数组再构造 array。内核提供了现成的摊平合并入口dma_fence_unwrap_merge()fencedma_fence_unwrap_merge(fence_a,fence_b,...);它深度展开每个参数中的dma_fence_array/dma_fence_chain去重后合并成一个扁平的dma_fence_array。amdgpu 在 VM 更新后合并vm-last_update与bo_va-last_pt_update时正是这样用的fencedma_fence_unwrap_merge(vm-last_update,bo_va-last_pt_update);if(!fence){/* OOM 兜底退化为逐个同步等待 */dma_fence_wait(vm-last_update,false);dma_fence_wait(bo_va-last_pt_update,false);fencedma_fence_get_stub();}dma_resv_get_singleton()见 6.3.2在需要把多个 fence 合成一个时底层也是构造一个扁平的dma_fence_array。7. 遍历接口按是否深入容器提供两套迭代浅层遍历——只遍历一个 array 的直接子 fence若head不是 array则只迭代它自身一个structdma_fence*dma_fence_array_first(structdma_fence*head);structdma_fence*dma_fence_array_next(structdma_fence*head,unsignedintindex);dma_fence_array_for_each(fence,index,head){/* fence 是 head 的第 index 个子 fence */}深度遍历——递归展开dma_fence_array与dma_fence_chain把所有底层 fence 逐个交出定义在dma-fence-unwrap.hdma_fence_unwrap_for_each(fence,cursor,head){/* fence 是 head 中所有容器展开后的每一个叶子 fence */}辅助判断dma_fence_match_context()检查一个 fence或 array 中全部子 fence是否都来自同一 context。8. 小结dma_fence_array用「一个 fence 代表一组 fence」的方式把多依赖等待收敛成标准的dma_fence语义数据结构内嵌base使其本身是 fencenum_pending跟踪未就绪计数柔性callbacks[]为每个子 fence 预置回调。聚合语义signal_on_any决定「全部就绪」AND还是「任一就绪」OR映射为num_pending的初值。signal 传播enable_signaling为每个子 fence 挂回调回调递减num_pending归零后经 irq_work 完成自身 signal——用 irq_work 规避锁序与递归风险。两段式创建allocinit支持在 reclaim / signaling 上下文用预分配对象初始化对应附录 B.6 的预分配模式。不可嵌套子 fence 不能是容器需先用dma_fence_unwrap_merge()摊平避免递归耗尽内核栈。下一篇6.1.5转向另一个组合原语dma_fence_chain——它以 timeline 语义把 fence 串成链是drm_syncobjtimeline 的底层支撑。