[TOC]
Linux 内核 dma-buf 核心实现(drivers/dma-buf/dma-buf.c)全面解析
[drivers/dma-buf/dma-buf.c] [dma-buf 共享缓冲区框架核心] [跨子系统/设备共享 DMA 缓冲区 + 同步与 CPU 访问支持的核心对象与文件接口]
介绍
dma-buf 子系统提供一种跨多个驱动/子系统共享同一块底层缓冲区的内核框架,并配套处理异步硬件访问的同步(基于 dma-resv/dma-fence)以及CPU 访问一致性(begin/end CPU access、mmap、poll/notify 等)。官方文档把其定位为:共享用于硬件(DMA)访问的缓冲区,并同步异步硬件访问。drivers/dma-buf/dma-buf.c 则是**dma-buf 核心对象(struct dma_buf)与其“文件描述符语义”**的主要实现:全局跟踪、fd 导出、attachment 管理、map/unmap、CPU access、poll、ioctl 等。
历史与背景
这项技术是为了解决什么特定问题而诞生的?
在图形、多媒体、相机、编解码等场景中,常见“一个子系统分配 buffer,另一个子系统/设备使用同一 buffer 做 DMA”。若各驱动各自定义共享方式,会导致:
- 缓冲区传递机制碎片化(每对驱动一套接口)
- 同步机制不统一(谁负责等待/信号?)
- 缓冲区生命周期难以管理(引用计数/关闭 fd/释放顺序)
dma-buf 用统一的文件描述符(fd)载体来传递 buffer,并用 dma-resv/dma-fence 体系做隐式同步,降低跨子系统互操作成本。
发展里程碑或版本迭代(从代码/文档可直接看到的能力点)
不在这里强行给出“某版本引入某特性”的时间线(那需要进一步查 git log 才严谨),但从当前主线实现可确认这些关键能力已经稳定存在:
- 全局 dma-buf 列表与迭代接口:
dmabuf_list+dma_buf_iter_begin()/dma_buf_iter_next(),并强调 list mutex 不保护 refcount,需要file_ref_get()方式安全拿引用。) - 明确的锁定约定:文档化说明“哪些 API 调用方必须/必须不持有 reservation lock”,将 deadlock 风险前置到接口契约层。)
- map_attachment 路径集成 pin 语义:
dma_buf_map_attachment()里might_sleep()、dma_resv_assert_held(),并在需要时走ops->pin(),对导出者错误行为做WARN。) - CPU 访问路径显式等待隐式 fence:在
dma_buf_begin_cpu_access()相关逻辑里,会等待隐式渲染 fences(通过dma_resv_wait_timeout)。) - 官方文档持续更新(发布时间为近一周),说明仍是活跃维护的主流框架。
社区活跃度与主流应用情况
dma-buf 是 Linux 图形/多媒体栈里的核心基础设施,DRM、V4L2 等广泛依赖它进行 buffer 交换与同步(官方文档也明确点到这些典型使用)。
核心原理与设计
核心工作原理是什么?
可以按“对象模型 + 文件语义 + 同步模型 + 映射模型”来理解:
- 对象模型:struct dma_buf 作为共享对象
- 导出者(exporter)实现
struct dma_buf_ops,决定底层内存如何分配、如何 map 成 sg_table、如何处理 CPU access 等。 - 导入者(importer/user)通过 attachment 把该 dma-buf 绑定到某个设备,再把它映射成该设备可 DMA 的形式。
- 文件语义:dma-buf 作为 fd 传递
- dma-buf 通过匿名 inode/伪文件系统的 file 对象呈现为 fd,从而在进程/子系统之间传递。代码中可以看到创建 pseudo file 的路径(
alloc_file_pseudo),并设置 inode size 等信息。) - 代码维护全局 dmabuf 列表,并提供迭代:迭代时需要安全地“提升引用计数以防销毁”。)
- 同步模型:dma-resv / dma-fence 隐式同步
- dma-buf 的
resv(reservation object)聚合 fences:读/写访问顺序由 fences 表达。 - CPU 访问路径会等待隐式 fences,确保 CPU 读写看到一致数据(至少在指定范围/方向语义内)。)
- 映射模型:attachment + map/unmap
dma_buf_map_attachment()要求调用方持有 reservation lock(代码里dma_resv_assert_held),并可能 sleep。)- 它通过 exporter 的
ops->map_dma_buf()返回sg_table,供设备 DMA 使用;并在需要时结合pin语义提升“底层存储不可迁移/可访问性”约束。)
- 锁定约定(非常关键)
源码中专门列出导入者约定:例如dma_buf_map_attachment()/vmap()这类需要持有 reservation lock,而dma_buf_attach()/export()/fd/get/put等则要求不持有。这个约定是排查死锁的第一优先级依据。)
主要优势体现在哪些方面?
- 跨子系统统一共享模型:fd 传递 + 统一 attachment/map 语义,降低耦合(官方文档描述的核心目标)。
- 统一的隐式同步基础设施:围绕
dma-resv/dma-fence组织同步,CPU 访问路径也能通过等待 fences 保证一致性。) - 明确的调用方锁约束:把“谁持锁调用什么”写进契约,降低系统集成时的不可控并发问题。)
劣势、局限性或不适用性
- 复杂度高:必须正确处理 exporter/importer 角色、reservation lock、fence 语义、CPU access begin/end、mmap/ioctl/poll 等一整套机制;错误通常表现为隐式同步失效或死锁。)
- 不是“通用共享内存”接口:它面向 DMA buffer 共享与同步;若只是 CPU 侧共享数据结构,可能更适合其他 IPC/共享内存机制(取决于需求)。这一点需要你基于目标场景做判断。
- 关键路径可能 sleep:例如
dma_buf_map_attachment()明确might_sleep(),因此调用上下文要满足可睡眠条件。)
使用场景
首选场景(举例)
- DRM ↔ V4L2 / 编解码器 / ISP / 显示:图形与多媒体流水线中,一个组件产出帧缓冲,另一个组件消费;dma-buf 用 fd 传递 buffer,fence 做隐式同步,是典型路径(官方文档点名 DRM、V4L2 等互通)。
- 跨设备共享同一底层存储:例如 GPU 渲染结果直接给显示/视频编码设备 DMA 使用,通过 attachment/map 取 sg_table。
不推荐使用的场景(原因)
- 不涉及 DMA 的纯 CPU 数据共享:dma-buf 引入的同步/映射语义与实现复杂度可能是负担。
- 无法接受隐式同步模型的系统:如果你需要显式控制每一步同步、或系统中同步策略与 dma-resv 机制冲突,使用 dma-buf 可能会增加调试难度(需要你对 fence/resv 语义非常熟)。
对比分析
这里选 3 类“相似但定位不同”的技术路线做对比(你学习时容易混淆的点):
1) dma-buf vs “普通共享内存/文件(memfd/shm/tmpfs)”
实现方式
- dma-buf:面向 DMA 缓冲区共享,核心是 exporter ops + attachment + sg_table + fence/resv。
- memfd/shm:面向 CPU 地址空间共享,不提供统一的设备 DMA 映射与隐式同步语义。
性能开销
- dma-buf:在 map/unmap、同步等待、poll/notify 上有额外机制成本,但换来跨设备共享与正确同步。)
资源占用
- dma-buf:底层存储由 exporter 决定(可能是 pages、carveout、IOMMU 映射等),并伴随 resv/fence 元数据。
隔离级别
- dma-buf:以 fd 为句柄,权限由 fd 传递控制;设备访问能力通过 attachment 绑定设备。
启动速度
- dma-buf:建立共享关系通常要 export→fd→get→attach→map,多步骤;但这是为了明确控制共享与同步。
2) dma-buf vs “每设备独立分配 + 自定义传递”
- dma-buf 的主要优势就是避免 N×M 的私有接口组合;并统一同步。
- 代价是所有参与者都要遵循锁定约定与同步模型,否则系统性问题更隐蔽。)
3) dma-buf vs dma-heap(补充理解边界)
- dma-heap 更像“面向用户态申请 dma-buf 的分配入口/内存池策略”,而 dma-buf.c 是“共享对象与语义核心”。两者经常一起出现,但职责不同。你在继续读源码时会很快感受到这一点(
drivers/dma-buf/dma-heap.c是另一条线)。
总结
关键特性
dma_buf作为可通过 fd 传递的共享 DMA 缓冲区对象,并支持跨设备 attachment、映射为 sg_table。- 以
dma-resv/dma-fence为核心的隐式同步,CPU 访问路径会等待相关 fences。) - 明确的锁定约定,是正确使用与排障的第一要点。)
- 核心实现还包含全局列表迭代、poll 支持、命名与统计等工程性能力。
dma-heap 与 dma-buf 的职责关系:分配入口/内存池策略 vs 共享对象/语义核心
1) dma-buf 是“共享对象与语义核心”
dma-buf.c 这类代码主要在做两件事:
- 定义共享对象
struct dma_buf的生命周期与规则
例如:引用计数、release 路径、attachments 管理、fence/同步语义、统计与 debug 导出等。 - 提供跨驱动共享的通用接口
例如:导出(export)、导入(attach/map/detach)、文件描述符关联、dentry/vfs 相关回调等。
也就是说:dma-buf 不关心“这块内存来自哪个池子、怎么分配出来的”,它关心的是:
一旦你给我一个 buffer,我怎么把它变成一个可共享、可同步、可回收的‘共享对象’。
2) dma-heap 更像“面向用户态申请 dma-buf 的分配入口/内存池策略”
dma-heap 的定位更偏“上游入口 + 策略”:
- 它提供给用户态一个标准入口(通常是
/dev/dma_heap/<heap>这样的设备节点)
用户态通过 ioctl 之类请求“给我分配一个 dma-buf”。 - 它把“分配策略”抽象成不同 heap
例如:system heap、cma heap、carveout heap(具体有哪些取决于内核实现/配置)。 - 它最终会创建一个 dma-buf(或者包装成 dma-buf 导出的对象)返回给用户态。
也就是说:dma-heap 的职责是:
决定从哪里分配、用什么策略分配、给用户态一个可用的 dma-buf fd。
3) 两者如何“经常一起出现”
典型链路(概念上)是:
- 用户态:打开某个 heap 设备节点,请求分配
dma-heap:选择对应 heap 的分配器,分配物理/页面/SG 列表等底层内存dma-heap:把分配结果“导出”为一个dma-buf对象(绑定 ops,形成共享语义)- 返回给用户态:一个 dma-buf 的 fd
- 后续共享:别的驱动/子系统通过 dma-buf attach/map 等使用同一份底层内存
这里的分工就是:
- dma-heap:分配与池化策略(入口、选择、分配)
- dma-buf:共享与生命周期语义(对象、同步、引用、回收)
DMA-BUF 初始化与生命周期管理:dma_buf_init / dmabuffs_dname / dma_buf_release / dma_buf_file_release
先说明适用边界(与你要求的 STM32H750 单核无 MMU 视角直接相关):
这段代码属于 Linux 内核 dma-buf 子系统 + VFS(挂载、dentry、文件释放)+ sysfs/debugfs。它依赖内核的挂载体系、dentry 生命周期、模块引用计数、以及通常与虚拟内存映射相关的约束(例如vmapping_counter)。因此在 STM32H750(ARMv7-M、无 MMU、裸机/RTOS) 场景中无法直接运行;但其中的“资源生命周期一致性”“引用计数与不变量检查”“并发保护(spinlock)”思想仍可用于你在嵌入式驱动/中间件里设计资源管理。
dma_buf_init: 在子系统初始化阶段建立 dma-buf 的 sysfs/debugfs 与伪文件系统挂载
此函数是dma-buf 子系统的启动入口,通过 subsys_initcall 在内核子系统初始化阶段执行。它的核心动作是:
- 初始化统计 sysfs 节点:
dma_buf_init_sysfs_statistics()负责把 dma-buf 的统计信息以 sysfs 形式对外发布(失败即直接退出,避免半初始化状态)。 - 挂载 dma-buf 专用伪文件系统:
kern_mount(&dma_buf_fs_type)创建并挂载一个仅供内核使用的 mount,用于承载 dma-buf 的匿名文件/dentry 管理(这是后续 dentry 回调能够生效的前提)。 - 初始化 debugfs:
dma_buf_init_debugfs()用于调试信息导出;此处不影响核心功能但方便定位问题。
该函数体现的关键技巧是:把一个核心资源(dma_buf_mnt 挂载点)作为全局根对象建立起来,后续所有 dma-buf 文件对象的 dentry 生命周期都依赖它。
dmabuffs_dname: 为 dma-buf dentry 动态生成可读的路径名称
此函数是 dentry 的 .d_dname 回调,用于按需生成 dentry 的显示名称。其核心机制是:
- 从
dentry->d_fsdata取回struct dma_buf:驱动/子系统把私有对象指针挂在 dentry 上,VFS 回调时再取回,这是典型的“对象挂接”模式。 - 用
spin_lock保护 name 读取:dmabuf->name可能被并发更新或释放,因此通过dmabuf->name_lock在读取期间保持一致性。 - 使用
dynamic_dname输出格式化名称:返回值不是静态缓冲区,而是通过dynamic_dname在 VFS 需要时格式化输出(避免长期保存字符串导致额外生命周期管理复杂度)。
dma_buf_release: dentry 释放时销毁 dma-buf 对象并回收所有关联资源
此函数是 dentry 的 .d_release 回调,是 dma-buf 对象最终销毁路径 的关键部分。它的核心原则是:在释放前强制验证不变量,防止“仍在使用的对象被释放”。
关键点(只讲关键技巧,不讲“代码怎么跑”的基础):
不变量检查(BUG_ON)
BUG_ON(dmabuf->vmapping_counter);:要求不存在仍在进行的 vmap 映射计数,否则释放会导致地址空间/引用悬挂。BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);:要求不存在仍活跃的 fence 回调状态,否则说明异步完成路径存在引用不平衡或状态机错误。
先拆统计与驱动资源,再拆通用资源
dma_buf_stats_teardown(dmabuf):撤销统计信息,避免 sysfs/debugfs 仍引用已释放对象。dmabuf->ops->release(dmabuf):调用导出方提供的 release,释放与底层 exporter 相关的资源。
处理内嵌 resv 对象的析构
- 若
dmabuf->resv指向&dmabuf[1](即紧随主结构体的内嵌对象),则需要dma_resv_fini()做对象析构。
- 若
确保附件链表为空并维护模块引用计数
WARN_ON(!list_empty(&dmabuf->attachments));:释放时不应仍有 attachment。module_put(dmabuf->owner);:归还 exporter 模块引用,保证模块可卸载性与引用一致性。
最终释放内存:释放 name 字符串与 dmabuf 本体。
dma_buf_file_release: dma-buf 文件关闭路径中的全局列表摘除
此函数是 file 的释放路径(通常由 file_operations->release 触发),它的核心动作很集中:
is_dma_buf_file(file):确认该 file 确实是 dma-buf 文件,避免误删。__dma_buf_list_del(file->private_data):从 dma-buf 的全局跟踪链表中移除该对象(属于“框架侧 bookkeeping”)。
1 | /** |
drivers/dma-buf/dma-fence.c DMA Fence(DMA 栅栏)同步机制
[功能概述] 为异步 DMA 硬件操作提供内核内的统一同步原语(可跨驱动、可跨进程边界通过用户态载体传递),并与 dma-buf/dma-resv 形成显式/隐式同步体系。
介绍
dma-fence 用 struct dma_fence 表示,是 Linux 内核中面向异步硬件工作(GPU 渲染、视频编解码、显示等)的同步原语:通过 dma_fence_init() 初始化,通过 dma_fence_signal()(或带时间戳的变体)标记完成;同一 context 上的 fence 具有全序关系,并可用于显式 fencing(sync_file)、子系统封装(如 DRM syncobj),以及隐式 fencing(挂在 dma_buf 的 dma_resv 上)。
历史与背景
这项技术为了解决什么问题而诞生
核心问题是:共享 buffer 的多设备/多驱动异步访问需要统一的“完成信号”语义,否则容易出现数据竞争、重复写、读到未完成结果等问题。dma-buf 文档明确把 dma-fence 定义为“指示异步硬件操作何时完成”的机制,并与 dma-resv 一起实现隐式同步。
重要里程碑或迭代点(从源码注释与近期讨论归纳)
- 2012 年左右进入主线生态:源码头部版权与作者信息显示该机制至少在 2012 年已形成并由多方维护。
- 跨驱动契约(cross-driver contract)与 lockdep 注解体系:源码文档强调“跨驱动一致规则”、超时/恢复、以及用
dma_fence_begin_signalling()/dma_fence_end_signalling()标注可能影响dma_fence_signal()路径的临界区,以便 lockdep 检查死锁风险。 - 显式同步用户态载体 sync_file 成熟:sync_file 文档把它定义为 fences 的载体,用于驱动与用户态之间传递同步点,从而实现跨进程边界的显式 fencing。
- 新增“deadline hint”提示机制(优化调度/等待体验):源码中专门描述了可通过
dma_fence_set_deadline(以及用户态 ioctl 间接路径)提供“紧迫性提示”。 - 2025 年关于 dma_fence 模块卸载/RCU 保护的持续改进:LWN 转发的补丁讨论指出:fence 可能泄漏到外部驱动并在 signaled 后仍延迟释放,导致发行者模块卸载后
dma_fence_ops不可用而崩溃,补丁集尝试用 RCU 保护 ops 并允许 fence 更“自包含”。
社区活跃度与主流应用情况
- 主流应用:dma-buf 文档点名 DRM 大量使用 dma-buf 交换 buffer,并与 V4L2 等子系统交互;其中同步体系的三大原语就包含 dma-fence。
- 活跃度:内核官方文档页面近期仍在更新(docs.kernel.org 页面显示近期发布/抓取),并且 2025 年仍有围绕 dma_fence 的设计问题在 dri-devel 等社区持续讨论。
核心原理与设计
核心工作原理
- 对象模型:
struct dma_fence表示一个同步点,具有“未完成/已完成”状态;初始化用dma_fence_init(),完成用dma_fence_signal()(或dma_fence_signal_timestamp()记录完成时间戳)。 - context + seqno 的全序语义:通过
dma_fence_context_alloc()分配 context,同一 context 上 fence 全序。源码用一个全局atomic64计数器分配 context。 - 等待与唤醒路径:
dma_fence_wait_timeout()会进行可睡眠检查,并在等待前调用dma_fence_enable_sw_signaling()触发“尽快完成”的软件信号支持(内部会调用dma_fence_ops.enable_signaling)。 - 回调机制:fence 支持注册 callback;源码也明确警告取消 callback 风险高(容易引入死锁/竞态),通常仅用于硬件卡死恢复等场景。
- 跨驱动契约约束:源码文档强调“必须在合理时间内完成”“需要 hang recovery”“等待场景下的锁/内存分配限制”等,目的是避免跨子系统组合时产生不可控死锁与回收路径问题。
主要优势
- 跨驱动同步契约统一:把“异步硬件完成”抽象成跨驱动可理解的对象,降低多子系统协作成本。
- 显式/隐式同步都能承载:既能通过
sync_file走显式 fencing(跨进程传递),也能通过dma_resv走隐式 fencing(buffer 附带同步点)。 - 调试与可观测性:源码导出 tracepoint(如
dma_fence_emit/dma_fence_signaled等),便于跟踪同步行为。
已知劣势、局限性与不适用点
- 设计上强依赖“发行者必须可靠完成/恢复”:如果硬件/驱动无法保证 fence 在合理时间完成,或缺失 hang recovery,系统整体等待链条会放大风险。
- 回调取消与复杂锁层级容易出错:源码对取消 callback 的风险做了强提示;此外还需要遵循锁注解与 dma_resv 锁层级规则。
- 模块卸载边界问题:共享 buffer 会让 fence “流入外部驱动”,signaled 后仍可能延迟释放,导致发行者模块卸载带来 ops 指针失效风险;社区仍在通过 RCU 等手段改进。
使用场景
首选场景(举例)
- 图形/显示管线:DRM 在进程/上下文之间交换 buffer,需要用 fence 表达“GPU 渲染完成/scanout 完成”等同步点。
- 媒体处理:与 V4L2 等协作时,在 producer/consumer 间传递 buffer,并用 fence 保证读写顺序。
- 用户态显式同步(Vulkan/OpenGL/媒体栈互操作):用
sync_file把 fence 作为 fd 传递,实现跨进程显式 fencing。
不推荐使用的场景
- 纯驱动内部、无跨设备共享需求的简单同步:优先用 completion/waitqueue 等更直接的内核同步原语(更少契约约束、更少与 dma_resv/用户态交互复杂度)。
- 无法提供可靠超时与恢复路径的硬件任务:因为跨驱动等待链条会把“不可完成 fence”的风险放大到系统层面。
对比分析
| 维度 | dma_fence | completion / waitqueue(典型内核同步) | sync_file |
|---|---|---|---|
| 定位 | 异步 DMA 同步点(跨驱动契约) | 通用内核同步(多用于同一子系统/驱动内部) | fence 的用户态载体(fd 传递) |
| 实现方式 | struct dma_fence + ops + callback + wait |
waitqueue/completion 结构 + 唤醒 | file 对象包装一个/多个 fence,并提供导入导出流程 |
| 性能开销 | 创建轻量,但启用 signalling/等待会触发 ops 与回调管理;强调可观测性与契约约束 | 通常更直接、更少跨子系统约束 | 增加 file/fd 管理与跨进程传递成本 |
| 资源占用 | fence 对象 + 回调链 + 可能的追踪/注解;共享场景下生命周期更复杂 | 一般更少对象间外溢 | 额外的 file 结构与 fd 生命周期 |
| 隔离级别 | 可跨驱动、可跨进程边界(借助载体) | 通常局限在内核内部调用关系 | 面向用户态边界的显式同步 |
| “启动速度”(可理解为创建/投入使用的时延) | dma_fence_init() 初始化即可发布;但要遵循发布时机与 lockdep 注解规则 |
通常更快更直接 | 需要创建 file、安装 fd 等步骤 |
补充:drm_syncobj 属于 DRM 子系统提供的显式 fencing 原语,相比 sync_file,其特点是允许底层 fence 被更新(源码在 overview 中点到这一差异)。
总结
关键特性
struct dma_fence作为异步 DMA 完成信号的统一抽象;context 保证同一执行序列全序。- 支持等待/回调;等待前可触发软件 signalling;取消回调需极谨慎。
- 面向跨驱动组合的严格契约:超时与恢复、锁与内存分配上下文约束、lockdep 注解。
- 与 dma-buf/dma-resv 形成显式(sync_file)与隐式同步体系。
dma_fence_init_stub: 初始化并立即完成一个全局 stub fence,用于提供可用的默认同步对象
在内核子系统初始化阶段,构造一个全局的“stub fence”,并把它立即 signal(完成)。这样做的目的不是表示真实的异步 DMA 工作,而是提供一个始终已完成、且具备合法 dma_fence 语义的默认对象,供框架在某些“需要 fence 但当前没有真实 fence”或“需要一个占位且永不阻塞”的路径中使用。
这段代码背后的关键点是 dma_fence 的跨驱动契约:
- fence 必须可等待、可 signal、可识别(driver/timeline 名称)
- fence 必须遵循统一的锁与回调规则,以便跨设备、跨子系统进行同步
stub fence 的存在可以让框架在缺省情况下仍保持这些规则成立。
核心原理概述
提供最小可用的
dma_fence_ops
通过dma_fence_stub_ops提供.get_driver_name与.get_timeline_name,确保该 fence 在调试/追踪时具备可识别的名称。初始化 fence 的核心字段与锁
dma_fence_init()会把 fence 与 ops、lock、context、seqno 绑定,建立 fence 的基础身份与并发保护策略。打开 signal 能力位并立即 signal
DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT允许 fence 走 signal 路径;随后立刻dma_fence_signal(),使该 fence 永远处于“已完成”状态。在 subsys_initcall 阶段完成初始化
通过subsys_initcall(dma_fence_init_stub)保证该 stub 在大量子系统使用 fence 之前就已就绪,避免早期路径拿到未初始化对象。
1 | /* |
__dma_fence_init / dma_fence_init: 初始化 dma_fence 的身份、并发保护与回调容器
这段代码的核心作用是:把一个已分配的 struct dma_fence 对象初始化成“可被框架安全使用的同步原语实例”。它不负责 signal、不负责等待,只负责建立最关键的三类基础设施:
- 生命周期基础:初始化引用计数
refcount,让 fence 能被跨子系统安全持有与释放。 - 并发一致性基础:绑定
lock,规定后续所有状态转换(signal、回调链处理等)必须在该锁保护下完成。 - 语义比较基础:设置
context与seqno,让 fence 之间可以通过dma_fence_later()这类比较函数建立有序关系(同一 context 内要求完全有序)。
核心原理概述
严格的前置条件检查(BUG_ON)
lock必须存在:否则后续 signal/wait/callback 的并发一致性不可保证。ops必须提供get_driver_name/get_timeline_name:这是跨驱动契约的一部分,用于诊断、跟踪与一致的接口语义。缺失会导致框架在 debug/trace/输出路径上出现不可接受的不确定性。
引用计数初始化(kref_init)
- fence 的引用计数是其“跨模块共享”的基本机制。
- 后续任何 attach/等待/回调注册都可能持有 fence,必须依赖 refcount 保证对象不被提前释放。
回调链表容器初始化(cb_list)
INIT_LIST_HEAD(&fence->cb_list)建立回调链表头。- 这使得
dma_fence_add_callback()能在 fence 未 signal 时挂回调;signal 时可遍历并执行。
并发保护锁绑定(fence->lock)
- 该指针定义 fence 的“互斥域”。
- signal 与 add_callback 的竞态处理依赖这把锁的语义:必须是 irqsafe,以便在可能的中断上下文中也能安全使用。
context/seqno 初始化(有序语义基础)
context表示执行上下文(例如某个硬件队列/时间线)。seqno在同一 context 内单调递增,用于比较“哪个 fence 更晚”。- 这为
dma_fence_later()提供数学意义上的偏序关系:同一 context 可全序比较,不同 context 不保证可比。
flags 与 error 初始化
flags保存 fence 的状态标志(例如是否允许 enable_signaling 等)。error = 0表示初始无错误,后续可以通过 signal 带错误完成语义。
trace 钩子
trace_dma_fence_init(fence)用于追踪初始化事件,便于定位生命周期与竞态问题。
1 | /* |
dma_fence_signal_timestamp_locked / dma_fence_signal_timestamp / dma_fence_signal_locked / dma_fence_signal: 以原子方式将 fence 从“未完成”转换为“已完成”,并执行回调链
这组函数实现的是 dma_fence 最核心的状态转换:signal(完成)。它们的共同语义是:
- 将 fence 的状态从“未 signaled”变为“已 signaled”(只允许一次)
- 设置完成时间戳(timestamp 版本)
- 解除所有
dma_fence_wait()的阻塞条件 - 执行所有通过
dma_fence_add_callback()注册的回调 - 并且用锁与原子位操作保证:signal 与回调注册之间不会出现丢回调、重复回调或释放后访问
这段代码最关键的技巧有三个:
- 一次性状态转换的原子保证:
test_and_set_bit(SIGNALED_BIT)保证只有第一个调用者能成功 signal,后续调用直接失败返回。 - 回调链“先摘链后执行”:用
list_replace把fence->cb_list原子地换成一个临时链表cb_list,然后在锁保护外(或锁内的后半段)安全遍历执行,避免回调执行过程中破坏 fence 内部链表结构或造成死锁/递归。 - 锁的两层封装:
*_locked版本要求调用者已持锁,非 locked版本负责spin_lock_irqsave/restore,保证在中断上下文与普通上下文都成立。
在 STM32H750(单核、无 MMU)视角下需要强调:
- 单核不意味着没有并发:中断、抢占、软中断/工作队列都能产生并发交错,因此
spin_lock_irqsave与原子位仍然必要。 irqsave的意义不是“多核同步”,而是“禁止本 CPU 中断导致的重入竞态”。
1 | /* |








