news 2026/5/12 10:19:50

第六章:异步访问的同步:6.1.2 dma_fence spinlock机制深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第六章:异步访问的同步:6.1.2 dma_fence spinlock机制深度解析

本节深度分析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_list
enable_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_seqno=read_hw_seqno(ring);spin_lock(&ring->fence_lock);/* 获取一次 */list_for_each_entry(fence,&ring->pending_fences,link){if(fence->seqno<=completed_seqno)dma_fence_signal_locked(fence);/* 无需再获锁 */}spin_unlock(&ring->fence_lock);}

如果每个 fence 嵌入独立的锁,批量 signal 就需要:

  • 逐个获取/释放锁(性能差,且无法保证原子性)
  • 或者嵌套获锁(死锁风险,lockdep 告警)

3.2 dma_resv 中的 fence 管理

dma_resv(reservation 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)的生命周期必须覆盖所有使用该锁的 fence:

ring 创建 ──────────────────────────────── 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 the
&dma_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 disable
IRQ’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(ret==0)list_add_tail(&cb->node,&fence->cb_list);spin_unlock_irqrestore(fence->lock,flags);

整个 “检查状态 → enable_signaling → 挂 callback” 在同一把锁保护下原子完成,不存在窗口期。

7. 设计总结

设计选择原因
指针而非嵌入支持多 fence 共享锁,批量原子 signal
irqsavesignal 可能在 IRQ 中发生
锁在 fence 外部生命周期由拥有者管理,长于 fence
enable_signaling 持锁调用原子化"启用通知 + 挂 callback"流程

这套机制让 dma_fence 能适应从"单 fence 简单场景"到"数百个 fence 流水线处理"的所有 GPU 同步需求,同时保持 lockdep 友好和 IRQ 安全。

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

抖音无水印视频下载:从技术解析到高效批量管理的完整方案

抖音无水印视频下载&#xff1a;从技术解析到高效批量管理的完整方案 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…

作者头像 李华
网站建设 2026/5/12 10:15:35

ILSVRC2015_VID数据集:从结构解析到实战应用指南

1. ILSVRC2015_VID数据集全景解析 第一次接触ILSVRC2015_VID数据集时&#xff0c;我被它庞大的规模震撼到了——3862个训练视频片段、555个验证片段和937个测试片段&#xff0c;每个片段包含56到458帧不等的图像。这个数据集就像是一个视频目标检测的"百科全书"&…

作者头像 李华
网站建设 2026/5/12 10:13:54

从Hive到Spark-SQL:日期时间函数迁移避坑指南与性能对比

从Hive到Spark-SQL&#xff1a;日期时间函数迁移避坑指南与性能对比 在数据处理领域&#xff0c;Hive和Spark-SQL作为两大主流SQL引擎&#xff0c;各自拥有庞大的用户群体。随着Spark生态的快速发展和性能优势的凸显&#xff0c;越来越多的企业开始将数据处理任务从Hive迁移到S…

作者头像 李华
网站建设 2026/5/12 10:13:52

DeepSeekMoE 路由机制:为什么前 3 层用哈希、后面用学习?

⚙️ 工程深度:L4 生产级 | 📖 预计阅读:18 分钟 一句话理解:语义没成形就让路由器"学习",等于让新兵在没看过地图的情况下指挥作战——先哈希定位,再学习分工。 🎯 本文产出 哈希 / 学习 / 混合三种路由的核心实现(可直接运行,含预期输出) 哈希层数选…

作者头像 李华
网站建设 2026/5/12 10:13:10

第七部分-容器安全与监控——34. 容器监控

34. 容器监控 1. 容器监控概述 容器监控是保障容器化应用稳定运行的关键环节&#xff0c;包括资源监控、性能监控、健康检查和告警等。本节将介绍主流容器监控方案。 ┌──────────────────────────────────────────────────…

作者头像 李华