☰
Linux 内核学习(18) --- linux dma_buf 机制
2026/10/26 10:06:46 网站建设 项目流程

目录

  • Linux DMA-BUF:跨设备缓冲区共享与同步
    • 1. DMA-BUF 解决什么问题
    • 2. DMA-BUF 不负责什么
    • 3. 三个核心原语
      • 3.1 dma_buf:共享缓冲区对象
      • 3.2 dma_fence:异步操作完成信号
      • 3.3 dma_resv:与缓冲区关联的 fence 容器
    • 4. 参与者:exporter、importer 与用户空间
      • 4.1 Exporter
      • 4.2 Importer
      • 4.3 用户空间
    • 5. 四种地址不要混淆
    • 6. sg_table 与 scatter-gather
    • 7. Exporter:创建与导出 DMA-BUF
      • 7.1 dma_buf_ops 的职责
    • 8. Importer:设备 DMA 访问流程
      • 8.1 获取与 attach
      • 8.2 Map、提交与完成
      • 8.3 DMA 方向
      • 8.4 正确的释放顺序
    • 9. 引用计数与生命周期
    • 10. CPU 访问与缓存一致性
      • 10.1 内核空间访问
      • 10.2 用户空间 mmap
    • 11. 缓存一致性与执行同步是两件事
    • 12. 隐式同步与显式同步
      • 12.1 隐式同步
      • 12.2 显式同步
    • 13. DMA-Heap:从用户空间分配 DMA-BUF
    • 14. 图像 buffer 还需要布局元数据
    • 15. 动态映射与内存迁移
    • 16. 锁与错误处理
    • 17. 调试、统计与安全
      • 17.1 查看 fd 与 DMA-BUF 信息
      • 17.2 常见安全检查
    • 18. 原文中需要纠正的几个结论
    • 19. 建议的验证实验
      • 实验一:验证 fd 传递与引用生命周期
      • 实验二:记录 importer 的资源状态
      • 实验三:区分 fence 等待与缓存同步
    • 20. 小结
    • 参考资料

Linux DMA-BUF:跨设备缓冲区共享与同步

本文面向 Linux 设备驱动开发者,接口语义以当前上游内核文档为参考。DMA-BUF 是内核内部 API,版本之间会演进;实际开发必须以目标内核的include/linux/dma-buf.h、include/linux/dma-resv.h和对应驱动实现为准。

1. DMA-BUF 解决什么问题

摄像头、视频编解码器、GPU、NPU 和显示控制器经常需要依次访问同一块图像或张量数据。如果每经过一个设备都复制一次数据,会增加内存带宽、延迟和功耗。

DMA-BUF 提供了一套跨驱动、跨子系统、跨进程共享缓冲区的框架:

  • 内核使用struct dma_buf表示一个共享缓冲区对象;
  • 用户空间通常使用普通文件描述符(fd)持有和传递这个对象;
  • 导入驱动通过struct dma_buf_attachment建立缓冲区与设备的关系;
  • 设备访问时,导入驱动取得针对该设备完成 DMA 映射的struct sg_table;
  • dma_fence和dma_resv用于协调异步硬件访问;
  • CPU 访问前后需要执行相应的缓存一致性协议。

一个典型多媒体链路可以表示为:

Camera / ISP -> Video Codec -> GPU -> Display \___________ 同一个 DMA-BUF ___________/ 控制面:fd、尺寸、格式、stride、offset、modifier、fence 数据面:由 exporter 管理的实际 backing storage

Android 图形栈中,Gralloc/Allocator、SurfaceFlinger、GPU 和显示驱动经常通过包含 DMA-BUF fd 的 native handle 共享图形缓冲区。具体由哪个进程分配、使用几个 fd,以及元数据怎样传递取决于 Android 和厂商实现,因此不应把某一种 Android 流程当成 DMA-BUF 本身的定义。

2. DMA-BUF 不负责什么

理解边界比记忆接口更重要:

  1. DMA-BUF 本身不是通用内存分配器。backing storage 由 exporter 管理;DMA-Heap、DRM GEM、V4L2 或厂商分配器可以提供 DMA-BUF。
  2. fd 不是物理地址。fd 是进程文件描述符表中的句柄,内核通过它找到struct file和struct dma_buf。
  3. DMA-BUF 不保证物理连续。缓冲区可能由离散页、CMA 连续内存、设备本地内存或其他存储组成。
  4. DMA-BUF 不描述像素格式。宽高、stride、plane、offset、format 和 modifier 必须由上层 API 一起传递。
  5. 共享同一缓冲区不等于可以无序并发访问。调用方仍需要处理硬件执行顺序、CPU 缓存一致性和业务层互斥。
  6. “零拷贝”不保证没有任何复制。exporter 可能因设备可达性、内存迁移或 CPU 访问而移动或复制 backing storage。

3. 三个核心原语

当前 DMA-BUF 文档把体系拆成三个相互配合的原语。

3.1 dma_buf:共享缓冲区对象

struct dma_buf表示共享缓冲区及其生命周期。用户空间看到的是 fd,内核驱动看到的是struct dma_buf *。

几个重要概念字段是:

  • size:缓冲区字节数,在该 DMA-BUF 生命周期中保持不变;
  • file:用于 fd 表示和引用计数的struct file;
  • ops:exporter 提供的回调;
  • attachments:已经附着的设备关系;
  • priv:exporter 的私有缓冲区对象;
  • resv:与缓冲区关联的struct dma_resv。

3.2 dma_fence:异步操作完成信号

struct dma_fence表示一个异步 DMA 操作的完成点,例如:

  • GPU 渲染完成;
  • 视频解码完成;
  • 显示控制器不再读取某个 buffer;
  • DMA engine 拷贝完成。

fence 从未完成状态变为 signaled 状态。它表示“操作完成”,不负责缓存刷新,也不是内存锁。

3.3 dma_resv:与缓冲区关联的 fence 容器

struct dma_resv管理与一个资源关联的一组 fence,并为隐式同步提供基础。当前接口用 usage 区分读、写等访问语义。

粗略理解依赖关系:

后续读:通常需要等待先前写完成 后续写:通常需要等待先前读和写完成

具体驱动必须遵循enum dma_resv_usage和所属子系统的同步规则,不能只凭“一个共享 fence、一个独占 fence”的旧模型实现新代码。

4. 参与者:exporter、importer 与用户空间

4.1 Exporter

Exporter 是拥有并管理backing storage的驱动或子系统,主要负责:

  • 分配、回收或迁移实际存储;
  • 实现struct dma_buf_ops;
  • 检查导入设备能否访问该存储;
  • 为每个 attachment 建立合适的 DMA 映射;
  • 配合 CPU 访问完成缓存一致性处理;
  • 在最后一个引用释放时销毁 exporter 私有对象。

exporter描述内存对象的所有者,并不等同于数据流中的“生产者”。一个 importer 也可能通过设备向 buffer 写入数据。

4.2 Importer

Importer 是希望让某个设备访问 DMA-BUF 的驱动,主要负责:

  • 从 fd 获得struct dma_buf引用;
  • 将自己的struct deviceattach 到缓冲区;
  • 按正确方向取得设备 DMA 映射;
  • 等待必要 fence 后向硬件提交任务;
  • 在任务完成后 signal 自己的 fence;
  • 按相反顺序解除 mapping、attachment 和引用。

struct dma_buf_attachment表示“一个 DMA-BUF 与一个设备之间的附着关系”,不是一个用户进程,也不等同于 fd。

4.3 用户空间

用户空间通常把 DMA-BUF fd 当成 opaque handle,通过以下方式传递:

  • 某个设备 UAPI 的 ioctl;
  • Unix domain socket 的SCM_RIGHTS;
  • Binder/native handle;
  • Wayland、DRM、V4L2、EGL、Vulkan 等上层协议或 API。

不同进程的 fd 数字没有全局意义。把整数7通过普通消息告诉另一个进程,并不能让对方访问同一个对象;必须通过能传递struct file引用的机制发送 fd。

5. 四种地址不要混淆

共享 DMA 缓冲区时会同时出现多种地址空间:

地址使用者含义
用户虚拟地址用户进程mmap()返回的进程地址
内核虚拟地址CPU 内核代码dma_buf_vmap()得到的映射
物理地址/页内存管理与 exporterbacking storage 的物理组成
DMA 地址或 IOVA具体设备设备发起 DMA 时使用的地址

存在 IOMMU 时,设备使用的 IOVA 与 CPU 物理地址可能完全不同。即使物理页离散,IOMMU 也可能提供适合设备访问的地址视图。

dma_buf_map_attachment()返回的sg_table已针对attachment->dev完成 DMA 映射。驱动给硬件编程时应使用 DMA API 提供的 DMA 地址和长度,不能把page_to_phys()、sg_phys()或 CPU 虚拟地址直接当成设备地址。

同一个 DMA-BUF 对不同设备可能得到不同 DMA 地址,因此不能把设备 A 的sg_table缓存后交给设备 B 使用。

6. sg_table 与 scatter-gather

struct sg_table管理 scatterlist,描述缓冲区的若干内存段。它并不简单等于“按页排列的物理连续块列表”。需要区分:

  • exporter 的 backing store 可能由多段物理页组成;
  • DMA API 可以合并相邻段;
  • orig_nents与 DMA 映射后的nents可能不同;
  • DMA 地址受设备 DMA mask、IOMMU、segment size 和边界约束影响;
  • 映射结果只在对应 attachment 和 mapping 生命周期内有效。

遍历已映射的 scatterlist 时,应采用目标内核为 DMA 映射提供的遍历宏,并读取 DMA address/length 字段。具体宏和语义应依据目标内核版本核对。

7. Exporter:创建与导出 DMA-BUF

Exporter 先创建自己的私有 buffer 对象,然后用 DMA-BUF 包装它。简化骨架如下:

#include<linux/dma-buf.h>#include<linux/err.h>#include<linux/fcntl.h>staticintmy_export_buffer(structmy_buffer*buffer){DEFINE_DMA_BUF_EXPORT_INFO(exp_info);structdma_buf*dmabuf;intfd;exp_info.ops=&my_dma_buf_ops;exp_info.size=buffer->size;exp_info.flags=O_RDWR;exp_info.priv=buffer;dmabuf=dma_buf_export(&exp_info);if(IS_ERR(dmabuf))returnPTR_ERR(dmabuf);fd=dma_buf_fd(dmabuf,O_CLOEXEC);if(fd<0){dma_buf_put(dmabuf);returnfd;}/* 成功后,fd 持有这份引用;不要再对同一引用调用 dma_buf_put()。 */returnfd;}

DEFINE_DMA_BUF_EXPORT_INFO()会初始化结构并设置 exporter/module 信息。dma_buf_export()创建struct dma_buf和关联的匿名文件对象,但是否已经分配实际 backing storage 由 exporter 决定。有些 exporter 可以延迟到 attach 或 map 时再选择内存位置。

dma_buf_fd()把 DMA-BUF 安装到当前进程的 fd 表。创建 fd 时必须原子设置O_CLOEXEC,避免多线程进程在exec竞态中把敏感 buffer fd 泄漏给新程序。

7.1 dma_buf_ops 的职责

当前内核中需要重点理解的回调包括:

回调必选性主要职责
attach/detach可选检查设备约束并管理每个 attachment 的私有状态
map_dma_buf/unmap_dma_buf必选创建和释放面向导入设备的 DMA 映射
release必选最后一个 DMA-BUF 引用消失后释放 exporter 私有对象
begin_cpu_access/end_cpu_access可选准备和结束 CPU 访问,处理缓存或存储迁移
mmap可选支持用户空间映射
vmap/vunmap可选支持整个 buffer 的内核虚拟映射
pin/unpin动态映射相关控制 backing store 是否允许迁移

Exporter的map_dma_buf()通常需要完成以下工作:

  1. 确保 backing storage 已分配且满足设备约束;
  2. 必要时固定或迁移存储;
  3. 为当前 attachment 构造独立的sg_table;
  4. 通过 DMA API 映射到attachment->dev的 DMA 地址空间;
  5. 在失败时按逆序释放已经取得的资源。

每个 attachment 应获得自己可释放的映射对象。Exporter 不能随意把同一份已 DMA-map 的sg_table返回给不同设备。

8. Importer:设备 DMA 访问流程

8.1 获取与 attach

Importer 从用户传入的 fd 取得引用:

structdma_buf*dmabuf;structdma_buf_attachment*attach;dmabuf=dma_buf_get(fd);if(IS_ERR(dmabuf))returnPTR_ERR(dmabuf);attach=dma_buf_attach(dmabuf,dev);if(IS_ERR(attach)){intret=PTR_ERR(attach);dma_buf_put(dmabuf);returnret;}

dma_buf_get()使用底层struct file的引用计数增加一份内核引用。这个引用计数包含 fd 和其他内核持有者,不等于“消费者数量”。

Attach 阶段 exporter 可以检查:

  • 设备 DMA mask;
  • 最大 segment 大小和边界;
  • backing storage 是否能被该设备访问;
  • 是否需要移动到 system memory;
  • peer-to-peer 访问能力。

因此dma_buf_attach()可以失败,importer 必须处理错误。

8.2 Map、提交与完成

当前内核同时提供普通 helper 和带_unlocked后缀的变体。按照上游 locking convention,普通map/unmaphelper 要求 importer 已经持有 DMA-BUF 的 reservation 锁;_unlocked变体要求调用时未持有该锁,并由 helper 处理需要的加锁。不能只按后缀的字面含义推断锁条件。未自行管理 reservation 锁的 importer 可采用下面的简化流程:

dir = DMA_FROM_DEVICE sgt = dma_buf_map_attachment_unlocked(attach, dir) if sgt 返回错误: dma_buf_detach(dmabuf, attach) dma_buf_put(dmabuf) 返回错误 等待本次访问依赖的 fence 使用 sgt 中面向 attach->dev 的 DMA 地址配置硬件 创建并发布代表本次任务的 dma_fence 提交硬件任务 硬件中断或完成 worker: 确认设备不再访问该映射 signal fence dma_buf_unmap_attachment_unlocked(attach, sgt, dir) dma_buf_detach(dmabuf, attach) dma_buf_put(dmabuf)

这段代码只展示资源顺序,不是完整的异步驱动实现。真实驱动不能在提交硬件后立即执行 unmap;mapping 和 attachment 必须至少存活到硬件任务完成。

8.3 DMA 方向

DMA 方向从内存与设备之间的数据流理解:

方向含义示例
DMA_TO_DEVICE内存中的数据被设备读取显示扫描、编码器读取输入
DMA_FROM_DEVICE设备向内存写入数据摄像头采集、解码器写输出
DMA_BIDIRECTIONAL设备可能读写确实无法使用更精确方向时

方向必须与 map、硬件访问和 unmap 保持一致。错误方向在 DMA-coherent 平台上可能暂时看不出问题,但在 non-coherent 平台上可能产生陈旧数据或额外同步开销。

8.4 正确的释放顺序

确认硬件访问完成 -> signal/处理 fence -> dma_buf_unmap_attachment_unlocked() -> dma_buf_detach() -> dma_buf_put()

dma_buf_unmap_attachment()的含义是释放 DMA mapping,并不负责等待硬件完成,也不等价于“通知 exporter DMA 已完成”。硬件完成应由驱动中断、完成队列和 fence 等机制确认。

9. 引用计数与生命周期

DMA-BUF 的生命期主要由关联struct file的引用计数管理:

  • dma_buf_get(fd)获得一份引用,必须由dma_buf_put()配对;
  • get_dma_buf(dmabuf)增加已有对象的额外内核引用,也必须配对dma_buf_put();
  • dup()、fork()、SCM_RIGHTS和 Binder fd 传递都可能增加或共享文件引用;
  • 用户空间close(fd)只释放该 fd 持有的引用;
  • 所有 fd 与内核引用都释放后,才会调用 exporter 的release()。

fd、attachment 和 mapping 是三个不同生命周期:

fd / dma_buf reference:保证 dma_buf 对象存在 attachment:保证设备与 buffer 的关系存在 mapping:保证当前设备 DMA 地址映射有效,通常也约束 backing store 迁移

Importer 从 fd 调用dma_buf_get()后,用户进程关闭原 fd 不会立刻使 importer 手中的指针失效,因为 importer 已有独立引用。

10. CPU 访问与缓存一致性

CPU 访问 DMA-BUF 有两种常见情况:内核通过vmap访问,或用户空间通过mmap访问。二者都必须遵循 begin/end 协议。

10.1 内核空间访问

当前接口使用struct iosys_map表示映射,因为 backing storage 可能是普通系统内存,也可能是 I/O memory:

#include<linux/dma-buf.h>#include<linux/errno.h>#include<linux/iosys-map.h>staticintprocess_buffer_on_cpu(structdma_buf*dmabuf){structiosys_mapmap={};intret;ret=dma_buf_begin_cpu_access(dmabuf,DMA_BIDIRECTIONAL);if(ret)returnret;ret=dma_buf_vmap_unlocked(dmabuf,&map);if(ret)gotoout_end_access;/* 使用 iosys_map helper 访问内容,不要假定 map 一定是普通 void *。 */dma_buf_vunmap_unlocked(dmabuf,&map);out_end_access:if(dma_buf_end_cpu_access(dmabuf,DMA_BIDIRECTIONAL)&&!ret)ret=-EIO;returnret;}

需要注意:

  • begin_cpu_access()可以失败;
  • vmap()也可以因 exporter 不支持或虚拟地址空间不足而失败;
  • begin_cpu_access()成功后,即使后续处理失败也必须调用end_cpu_access();
  • vmap/vunmap与begin/end_cpu_access解决不同问题,不能互相替代;
  • 大 buffer 的长期vmap会消耗内核虚拟地址空间。

旧文中的dma_buf_kmap()、dma_buf_kmap_atomic()已不适合作为当前通用接口讲解。高端内存和局部页访问应根据目标内核提供的现代内存映射 API 重新设计。

10.2 用户空间 mmap

如果 exporter 支持mmap,用户空间可以对 DMA-BUF fd 调用普通mmap()。每一次 CPU 读写都应由DMA_BUF_IOCTL_SYNC包围:

#include<errno.h>#include<stdint.h>#include<sys/ioctl.h>#include<sys/mman.h>#include<linux/dma-buf.h>staticintdmabuf_sync(intfd,uint64_tflags){structdma_buf_syncsync={.flags=flags};intret;do{ret=ioctl(fd,DMA_BUF_IOCTL_SYNC,&sync);}while(ret<0&&(errno==EINTR||errno==EAGAIN));returnret;}staticintwrite_dmabuf(intfd,size_tsize){void*addr;intret=-1;addr=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addr==MAP_FAILED)return-1;if(dmabuf_sync(fd,DMA_BUF_SYNC_START|DMA_BUF_SYNC_WRITE)<0)gotoout_unmap;/* 在 [addr, addr + size) 范围内写入数据。 */if(dmabuf_sync(fd,DMA_BUF_SYNC_END|DMA_BUF_SYNC_WRITE)<0)gotoout_unmap;ret=0;out_unmap:munmap(addr,size);returnret;}

生产代码必须保证 START 成功后,即使 CPU 处理失败也尽力执行匹配的 END;上面为突出主线省略了该状态管理。

DMA_BUF_IOCTL_SYNC只提供 CPU 映射所需的缓存一致性机会,并不阻止其他进程或设备同时访问。CPU 开始访问前必须先等待 GPU/其他设备的相关工作完成,END 之后才能提交依赖 CPU 写入结果的新设备任务。

11. 缓存一致性与执行同步是两件事

这是 DMA-BUF 最容易混淆的部分:

执行同步:某个设备是否已经完成读写? 工具:dma_fence、dma_resv、sync_file、poll、子系统同步对象 缓存一致性:CPU 与设备看到的数据是否一致? 工具:begin/end_cpu_access、DMA_BUF_IOCTL_SYNC、DMA API

即使 fence 已经 signaled,CPU 在 non-coherent 平台上仍可能需要 invalidate cache 才能看到设备写入。反过来,即使做了 cache flush,也不表示仍在运行的 GPU 已停止访问内存。

CPU 访问的一般顺序是:

等待先前设备 fence -> begin CPU access / SYNC_START -> CPU 读写 -> end CPU access / SYNC_END -> 提交后续设备任务并建立 fence

12. 隐式同步与显式同步

12.1 隐式同步

隐式同步把 fence 关联到 DMA-BUF 的dma_resv。支持该模型的驱动在提交任务时:

  1. 根据读写方向取得并等待所需依赖 fence;
  2. 创建代表本次异步任务的 fence;
  3. 以正确 usage 加入 reservation object;
  4. 在硬件真正完成后 signal fence。

用户空间可以对 DMA-BUF fd 使用poll()/epoll()查询隐式 fence:

  • POLLIN/EPOLLIN用于判断读取所需的先前写是否完成;
  • POLLOUT/EPOLLOUT用于判断写入所需的先前读写是否完成。

poll()就绪只表示相应 fence 完成;CPU 访问前仍需执行DMA_BUF_IOCTL_SYNC。

12.2 显式同步

显式同步把dma_fence包装为独立的sync_filefd,由用户空间明确随任务传递。它常用于 Vulkan 等显式同步 API,也用于跨子系统交换完成点。

DMA-BUF UAPI 提供:

  • DMA_BUF_IOCTL_EXPORT_SYNC_FILE:把 DMA-BUF 当前相关 fence 的快照导出为sync_file;
  • DMA_BUF_IOCTL_IMPORT_SYNC_FILE:把一个sync_filefence 加入 DMA-BUF 的隐式同步状态。

这两个 ioctl 可以桥接显式同步和隐式同步,但“导出依赖→提交任务→导入完成 fence”不是原子操作。多个线程或上下文同时操作时,用户空间还需要额外锁定或协议保证这组步骤之间没有竞态。

13. DMA-Heap:从用户空间分配 DMA-BUF

DMA-Heap 为用户空间提供分配 DMA-BUF 的统一 UAPI。每个/dev/dma_heap/<name>节点表示一种分配策略或内存池。例如上游内核常见的systemheap 提供 cacheable、虚拟连续的 buffer;配置 CMA 后还可能出现连续内存 heap。实际节点由内核配置和平台决定。

简化的用户空间分配代码:

#include<fcntl.h>#include<stdint.h>#include<sys/ioctl.h>#include<unistd.h>#include<linux/dma-heap.h>staticintalloc_from_system_heap(uint64_tsize){structdma_heap_allocation_datadata={.len=size,.fd_flags=O_RDWR|O_CLOEXEC,.heap_flags=0,};intheap_fd;intret;heap_fd=open("/dev/dma_heap/system",O_RDWR|O_CLOEXEC);if(heap_fd<0)return-1;ret=ioctl(heap_fd,DMA_HEAP_IOCTL_ALLOC,&data);close(heap_fd);if(ret<0)return-1;returndata.fd;}

DMA-Heap 只解决“从哪个 heap 分配并取得 DMA-BUF fd”。它不提供图像宽高、格式、stride,也不替调用方完成设备任务同步。

14. 图像 buffer 还需要布局元数据

一个 fd 和一个 size 不足以解释图像内容。跨 DRM、V4L2、EGL、Wayland 或 Vulkan 交换图像时,通常还需要:

  • width、height;
  • pixel format;
  • plane 数量;
  • 每个 plane 的 fd、offset 和 stride;
  • format modifier;
  • color space、range、transfer function 等颜色信息;
  • 与读写完成相关的 fence。

多 plane 图像可能所有 plane 共用一个 DMA-BUF,也可能每个 plane 使用独立 fd。Importer 必须验证 offset、stride 和尺寸计算不会越过 buffer 边界;不能仅信任用户空间元数据。

Modifier 描述线性、tiling、压缩等实际内存布局。两个设备都认识同一个四字符格式,不代表它们一定能以相同 modifier 访问该 buffer。

15. 动态映射与内存迁移

简单 importer 通常在 attachment 或 mapping 存活期间固定 backing storage。对于 GPU VRAM 等需要迁移的内存,这会限制内存管理器回收和移动资源。

动态 importer 可以使用dma_buf_dynamic_attach()和 importer attach ops,在 exporter 准备移动 backing storage 时接收 mapping 失效通知,然后销毁旧 mapping,并在下一次设备访问前重新 map。

动态路径需要严格遵守:

  • dma_resv锁规则;
  • fence 等待与发布规则;
  • mapping 失效回调的时限;
  • pin/unpin的适用范围;
  • peer-to-peer memory 是否有struct page。

这不是普通 importer 的必选优化。除非驱动确实需要可迁移内存并理解所有同步约束,否则先实现静态、生命周期清晰的 attachment/mapping 更容易保证正确性。

16. 锁与错误处理

DMA-BUF、DMA reservation 和各子系统锁会形成跨驱动锁依赖。实现时必须阅读目标内核文档中的 DMA-BUF locking convention。

关键原则:

  • 不要在 core 已持有dma_resv锁调用的 exporter callback 中再次获取同一锁;
  • 普通map/unmap、vmap/vunmaphelper 要求 importer 已持有 reservation 锁;对应的_unlockedhelper 要求调用时未持锁,并由 helper 处理需要的加锁;
  • attach、map、begin_cpu_access、vmap和用户态 ioctl 都可能失败;
  • dma_buf_get()、dma_buf_attach()和dma_buf_map_attachment()返回错误指针时使用IS_ERR()/PTR_ERR();
  • 清理路径必须按获取资源的逆序执行;
  • 不能在原子上下文调用可能 sleep 的回调;map_dma_buf()可能因分配或迁移而睡眠。

推荐把 importer 状态明确拆成:

EMPTY -> GOT_DMABUF -> ATTACHED -> MAPPED -> IN_FLIGHT ^ | | | | +----------+------------+----------+-----------+ 每次错误都按逆序回退

不要用一个布尔变量同时表示“已经 attach”“已经 map”和“硬件已经完成”。

17. 调试、统计与安全

17.1 查看 fd 与 DMA-BUF 信息

对于持有 DMA-BUF fd 的进程,可以先检查:

ls-l/proc/<pid>/fdcat/proc/<pid>/fdinfo/<fd>

DMA-BUF 的 fdinfo 通常包含size、引用相关计数和exp_name等字段,具体内容随内核和 exporter 变化。

启用 debugfs 的开发系统还可能提供:

cat/sys/kernel/debug/dma_buf/bufinfo

debugfs 不应作为稳定生产 ABI,也不应仅为采集数据就在生产环境放宽挂载权限。某些内核在启用相应配置时还提供 DMA-BUF sysfs statistics;路径和字段应以目标内核的 ABI 文档为准。

Exporter 和用户空间可以为 DMA-BUF 设置有意义的名称,便于定位泄漏和统计,但名称只用于调试,不能作为权限或身份判断依据。

17.2 常见安全检查

  • 创建或接收 fd 时使用O_CLOEXEC/MSG_CMSG_CLOEXEC;
  • 把 DMA-BUF fd 当作访问内存的能力句柄,只传给确实需要的进程;
  • 分配给用户空间或其他安全域前清除旧内容,防止信息泄漏;
  • 验证 buffer size、plane offset、stride、长度加法和整数溢出;
  • 验证设备 DMA mask、IOMMU domain 和访问权限;
  • 不要信任用户提供的 fd 一定是 DMA-BUF,dma_buf_get()失败必须正常返回;
  • 确保异常、进程退出和设备 reset 路径最终都能释放引用并 signal 或终止相关 fence。

18. 原文中需要纠正的几个结论

原表述更准确的理解
DMA-BUF 在物理内存分配后导出backing storage 由 exporter 管理,可以提前分配,也可能延迟分配、迁移或位于设备内存
dma_buf绑定文件描述符dma_buf关联struct file;fd 是每个进程 fd 表中的句柄,可以有多个
引用计数表示消费者数量引用计数统计文件和内核引用,不等于 attachment 或业务消费者数量
attachment 是消费者对象attachment 是 DMA-BUF 与某个struct device的关系
scatterlist 是按页的连续物理块map 后的sg_table是面向具体设备的 DMA 段描述,可能受 IOMMU 和 DMA API 合并影响
unmap 通知 DMA 结束驱动必须先确认硬件结束;unmap 只释放 mapping
begin/end 负责所有同步begin/end 主要解决 CPU 可见性与缓存一致性,硬件执行顺序仍需 fence 或子系统同步机制
kmap/kmap_atomic是当前通用访问接口当前 DMA-BUF 整体内核映射使用vmap/vunmap和iosys_map;旧接口不应直接套用

19. 建议的验证实验

实验一:验证 fd 传递与引用生命周期

使用 DMA-Heap 分配一块 DMA-BUF,进程 A 通过SCM_RIGHTS发送给进程 B:

  1. A 分配并设置 DMA-BUF 名称;
  2. A 通过mmap + DMA_BUF_IOCTL_SYNC写入固定模式;
  3. A 用 Unix domain socket 发送 fd;
  4. B 映射并校验内容;
  5. A 关闭自己的 fd,B 再次读取;
  6. 分别观察两个进程的/proc/<pid>/fdinfo/<fd>。

预期结果:B 收到的 fd 数字不一定与 A 相同;A 关闭 fd 后,只要 B 仍持有引用,buffer 就不会释放。

实验二:记录 importer 的资源状态

在一个可控的测试驱动中为get -> attach -> map -> submit -> complete -> unmap -> detach -> put每一步记录 tracepoint 或动态调试日志,并注入 attach 失败、map 失败和设备超时。

检查每个失败点是否都按逆序清理,重点确认:

  • map 失败时没有调用 unmap;
  • 已 map 但未提交时仍会 unmap;
  • 已提交任务必须等硬件停止访问后才能 unmap;
  • 任意路径最终都恰好执行一次 detach 和 put。

实验三:区分 fence 等待与缓存同步

在具有真实 non-coherent DMA 设备的平台上,分别记录:

  1. 设备任务 fence 何时 signaled;
  2. CPU 的 begin/end access 何时执行;
  3. 数据校验何时进行。

正确顺序应是先等待设备完成,再开始 CPU access。不要在生产设备上故意省略缓存同步来“证明会出错”,因为 coherent 平台可能掩盖问题,non-coherent 平台上的结果也可能是非确定的数据损坏。

20. 小结

DMA-BUF 的主线可以归纳为:exporter 管理存储并导出对象,用户空间传递 fd,importer 为具体设备建立 attachment 和 DMA mapping,fence/reservation 管理异步访问顺序,begin/end 协议管理 CPU 缓存一致性,最后所有资源按相反顺序释放。

排查问题时,应始终分别回答四个问题:

  1. 谁持有 buffer 的引用,生命周期是否正确?
  2. 当前地址属于 CPU 虚拟、物理还是设备 DMA/IOVA 空间?
  3. 上一次异步读写是否已经通过 fence 确认完成?
  4. CPU 与设备之间是否完成了正确方向的缓存同步?

把这四点分开,DMA-BUF 中大多数“偶尔花屏”“某平台正常、另一平台错误”“buffer 无法释放”和 IOMMU fault 问题都会更容易定位。

参考资料

  • Linux 内核文档:Buffer Sharing and Synchronization(DMA-BUF)
  • Linux 内核源码:include/linux/dma-buf.h
  • Linux 内核文档:DMA API
  • Linux 内核文档:DMA API HOWTO
  • Linux 内核 UAPI:DMA-BUF Heaps
  • Linux 内核 UAPI:Exchanging pixel buffers
  • Linux 内核文档:Sync File API
  • Linux 内核文档:/proc fdinfo 中的 DMA Buffer 信息

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

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

立即咨询