本节深度分析dma_fence的spinlock的实现和应用。1. 为什么是指针而不是嵌入变量struct dma_fence中的 lock 成员定义为spinlock_t*lock;而非spinlock_tlock;这是一个经过深思熟虑的设计决策。核心原因是多个 fence 需要共享同一把锁以实现原子性操作。2. 锁的职责这把 spinlock 保护的是 fence 的状态转换临界区具体包括操作锁的作用dma_fence_signal()持锁设置 SIGNALED_BIT遍历 cb_list 触发回调dma_fence_add_callback()持锁将 callback 挂入 cb_listenable_signaling()在锁持有状态下被调用dma_fence_remove_callback()持锁从 cb_list 摘除 callback这些操作之间存在竞态CPU0: dma_fence_add_callback() CPU1: dma_fence_signal() spin_lock_irqsave(lock) spin_lock_irqsave(lock) 检查 SIGNALED_BIT 设置 SIGNALED_BIT 挂入 cb_list 遍历 cb_list触发回调 spin_unlock_irqrestore(lock) spin_unlock_irqrestore(lock)锁确保了这两个操作互斥要么 callback 先挂上signal 遍历时能触发它要么 signal 先完成add_callback 发现已 signaled 直接返回。3. 共享锁的典型场景3.1 GPU Ring 上的有序 fence一个 GPU ring 产生的 fence 具有严格的时间顺序seqno 递增。当 GPU 完成到某个 seqno 时interrupt handler 需要原子地signal 所有 该 seqno 的 fence/* 所有 fence 共享 ring-fence_lock */dma_fence_init(job1-fence,ops,ring-fence_lock,ring-ctx,1);dma_fence_init(job2-fence,ops,ring-fence_lock,ring-ctx,2);dma_fence_init(job3-fence,ops,ring-fence_lock,ring-ctx,3);/* IRQ handler — 一次获锁批量 signal */voidgpu_irq_handler(structgpu_ring*ring){u64 completed_seqnoread_hw_seqno(ring);spin_lock(ring-fence_lock);/* 获取一次 */list_for_each_entry(fence,ring-pending_fences,link){if(fence-seqnocompleted_seqno)dma_fence_signal_locked(fence);/* 无需再获锁 */}spin_unlock(ring-fence_lock);}如果每个 fence 嵌入独立的锁批量 signal 就需要逐个获取/释放锁性能差且无法保证原子性或者嵌套获锁死锁风险lockdep 告警3.2 dma_resv 中的 fence 管理dma_resvreservation object管理一组 fence。当 TTM 需要等待所有 fence 完成时共享锁让操作更高效/* drm_sched_fence 共享 scheduler 的锁 */dma_fence_init(sched_fence-scheduled,sched_ops,scheduler-lock,ctx,seqno);3.3 独占锁的简单场景并非所有场景都需要共享。对于一个对象只有一个 fence的情况锁嵌入在拥有者对象中即可/* amdgpu_svm_bo每个 bo 只有一个 eviction fence */structamdgpu_svm_bo{structdma_fenceeviction_fence;spinlock_tlock;/* 仅此一个 fence 使用 */};dma_fence_init(svm_bo-eviction_fence,ops,svm_bo-lock,ctx,1);这等效于嵌入变量但 API 统一为指针形式。4. 锁与生命周期4.1 锁必须比 fence 活得久dma_fence_signal()和dma_fence_release()都会访问 lock。fence 可能在 signal 后被立即 put 掉refcount→0但如果此时另一个 CPU 正在dma_fence_add_callback()中持有锁lock 必须仍然有效。因此锁的拥有者ring、scheduler、svm_bo的生命周期必须覆盖所有使用该锁的 fencering 创建 ──────────────────────────────── ring 销毁 │ │ ├── fence1 创建 ─── signal ─── release │ ├── fence2 创建 ────────── signal ── rel │ └── fence3 创建 ─────────────── signal ────┘4.2 signal 后的 lock 访问限制根据dma-fence.h中的文档All data not stored directly in the dma-fence object, such as thedma_fence.lock … MUST NOT be accessed after the fence has been signalled这意味着一旦 fence signaled驱动可以释放锁的拥有者。但实际上这受 RCU grace period 保护——fence 的 release 使用kfree_rcu确保所有并发的 lock 访问完成后才真正释放内存。5. irqsave 语义fence 的锁使用spin_lock_irqsave/spin_unlock_irqrestore/* dma_fence_signal 内部 */spin_lock_irqsave(fence-lock,flags);// ... signal 逻辑 ...spin_unlock_irqrestore(fence-lock,flags);原因是 fence signal 可能发生在进程上下文worker thread 主动 signal中断上下文GPU interrupt handler 中 signal如果不禁 IRQ进程上下文持锁时 IRQ 到来handler 尝试获取同一把锁 →死锁。irqsave确保持锁期间本 CPU 中断关闭。这也是enable_signaling文档中强调的This is called with irq’s disabled, so only spinlocks which disableIRQ’s can be used in the code outside of this callback.6. enable_signaling 与锁的交互enable_signaling在锁持有状态下被调用这保证了/* dma_fence_add_callback 内部简化逻辑 */spin_lock_irqsave(fence-lock,flags);if(test_bit(SIGNALED_BIT,fence-flags)){// 已 signal直接返回ret-ENOENT;}elseif(!test_and_set_bit(ENABLE_SIGNAL_BIT,fence-flags)){// 首次需要软件通知调用 enable_signalingif(!fence-ops-enable_signaling(fence)){// 返回 false fence 已完成或无法启用dma_fence_signal_locked(fence);ret-ENOENT;}}if(ret0)list_add_tail(cb-node,fence-cb_list);spin_unlock_irqrestore(fence-lock,flags);整个 “检查状态 → enable_signaling → 挂 callback” 在同一把锁保护下原子完成不存在窗口期。7. 设计总结设计选择原因指针而非嵌入支持多 fence 共享锁批量原子 signalirqsavesignal 可能在 IRQ 中发生锁在 fence 外部生命周期由拥有者管理长于 fenceenable_signaling 持锁调用原子化启用通知 挂 callback流程这套机制让 dma_fence 能适应从单 fence 简单场景到数百个 fence 流水线处理的所有 GPU 同步需求同时保持 lockdep 友好和 IRQ 安全。