Linux 内核学习(18) --- linux dma_buf 机制 目录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 Exporter4.2 Importer4.3 用户空间5. 四种地址不要混淆6. sg_table 与 scatter-gather7. Exporter创建与导出 DMA-BUF7.1 dma_buf_ops 的职责8. Importer设备 DMA 访问流程8.1 获取与 attach8.2 Map、提交与完成8.3 DMA 方向8.4 正确的释放顺序9. 引用计数与生命周期10. CPU 访问与缓存一致性10.1 内核空间访问10.2 用户空间 mmap11. 缓存一致性与执行同步是两件事12. 隐式同步与显式同步12.1 隐式同步12.2 显式同步13. DMA-Heap从用户空间分配 DMA-BUF14. 图像 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_tabledma_fence和dma_resv用于协调异步硬件访问CPU 访问前后需要执行相应的缓存一致性协议。一个典型多媒体链路可以表示为Camera / ISP - Video Codec - GPU - Display \___________ 同一个 DMA-BUF ___________/ 控制面fd、尺寸、格式、stride、offset、modifier、fence 数据面由 exporter 管理的实际 backing storageAndroid 图形栈中Gralloc/Allocator、SurfaceFlinger、GPU 和显示驱动经常通过包含 DMA-BUF fd 的 native handle 共享图形缓冲区。具体由哪个进程分配、使用几个 fd以及元数据怎样传递取决于 Android 和厂商实现因此不应把某一种 Android 流程当成 DMA-BUF 本身的定义。2. DMA-BUF 不负责什么理解边界比记忆接口更重要DMA-BUF 本身不是通用内存分配器。backing storage 由 exporter 管理DMA-Heap、DRM GEM、V4L2 或厂商分配器可以提供 DMA-BUF。fd 不是物理地址。fd 是进程文件描述符表中的句柄内核通过它找到struct file和struct dma_buf。DMA-BUF 不保证物理连续。缓冲区可能由离散页、CMA 连续内存、设备本地内存或其他存储组成。DMA-BUF 不描述像素格式。宽高、stride、plane、offset、format 和 modifier 必须由上层 API 一起传递。共享同一缓冲区不等于可以无序并发访问。调用方仍需要处理硬件执行顺序、CPU 缓存一致性和业务层互斥。“零拷贝”不保证没有任何复制。exporter 可能因设备可达性、内存迁移或 CPU 访问而移动或复制 backing storage。3. 三个核心原语当前 DMA-BUF 文档把体系拆成三个相互配合的原语。3.1 dma_buf共享缓冲区对象struct dma_buf表示共享缓冲区及其生命周期。用户空间看到的是 fd内核驱动看到的是struct dma_buf *。几个重要概念字段是size缓冲区字节数在该 DMA-BUF 生命周期中保持不变file用于 fd 表示和引用计数的struct fileopsexporter 提供的回调attachments已经附着的设备关系privexporter 的私有缓冲区对象resv与缓冲区关联的struct dma_resv。3.2 dma_fence异步操作完成信号struct dma_fence表示一个异步 DMA 操作的完成点例如GPU 渲染完成视频解码完成显示控制器不再读取某个 bufferDMA engine 拷贝完成。fence 从未完成状态变为 signaled 状态。它表示“操作完成”不负责缓存刷新也不是内存锁。3.3 dma_resv与缓冲区关联的 fence 容器struct dma_resv管理与一个资源关联的一组 fence并为隐式同步提供基础。当前接口用 usage 区分读、写等访问语义。粗略理解依赖关系后续读通常需要等待先前写完成 后续写通常需要等待先前读和写完成具体驱动必须遵循enum dma_resv_usage和所属子系统的同步规则不能只凭“一个共享 fence、一个独占 fence”的旧模型实现新代码。4. 参与者exporter、importer 与用户空间4.1 ExporterExporter 是拥有并管理backing storage的驱动或子系统主要负责分配、回收或迁移实际存储实现struct dma_buf_ops检查导入设备能否访问该存储为每个 attachment 建立合适的 DMA 映射配合 CPU 访问完成缓存一致性处理在最后一个引用释放时销毁 exporter 私有对象。exporter描述内存对象的所有者并不等同于数据流中的“生产者”。一个 importer 也可能通过设备向 buffer 写入数据。4.2 ImporterImporter 是希望让某个设备访问 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 的 ioctlUnix domain socket 的SCM_RIGHTSBinder/native handleWayland、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-gatherstruct 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-BUFExporter 先创建自己的私有 buffer 对象然后用 DMA-BUF 包装它。简化骨架如下#includelinux/dma-buf.h#includelinux/err.h#includelinux/fcntl.hstaticintmy_export_buffer(structmy_buffer*buffer){DEFINE_DMA_BUF_EXPORT_INFO(exp_info);structdma_buf*dmabuf;intfd;exp_info.opsmy_dma_buf_ops;exp_info.sizebuffer-size;exp_info.flagsO_RDWR;exp_info.privbuffer;dmabufdma_buf_export(exp_info);if(IS_ERR(dmabuf))returnPTR_ERR(dmabuf);fddma_buf_fd(dmabuf,O_CLOEXEC);if(fd0){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()通常需要完成以下工作确保 backing storage 已分配且满足设备约束必要时固定或迁移存储为当前 attachment 构造独立的sg_table通过 DMA API 映射到attachment-dev的 DMA 地址空间在失败时按逆序释放已经取得的资源。每个 attachment 应获得自己可释放的映射对象。Exporter 不能随意把同一份已 DMA-map 的sg_table返回给不同设备。8. Importer设备 DMA 访问流程8.1 获取与 attachImporter 从用户传入的 fd 取得引用structdma_buf*dmabuf;structdma_buf_attachment*attach;dmabufdma_buf_get(fd);if(IS_ERR(dmabuf))returnPTR_ERR(dmabuf);attachdma_buf_attach(dmabuf,dev);if(IS_ERR(attach)){intretPTR_ERR(attach);dma_buf_put(dmabuf);returnret;}dma_buf_get()使用底层struct file的引用计数增加一份内核引用。这个引用计数包含 fd 和其他内核持有者不等于“消费者数量”。Attach 阶段 exporter 可以检查设备 DMA mask最大 segment 大小和边界backing storage 是否能被该设备访问是否需要移动到 system memorypeer-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)这段代码只展示资源顺序不是完整的异步驱动实现。真实驱动不能在提交硬件后立即执行 unmapmapping 和 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#includelinux/dma-buf.h#includelinux/errno.h#includelinux/iosys-map.hstaticintprocess_buffer_on_cpu(structdma_buf*dmabuf){structiosys_mapmap{};intret;retdma_buf_begin_cpu_access(dmabuf,DMA_BIDIRECTIONAL);if(ret)returnret;retdma_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包围#includeerrno.h#includestdint.h#includesys/ioctl.h#includesys/mman.h#includelinux/dma-buf.hstaticintdmabuf_sync(intfd,uint64_tflags){structdma_buf_syncsync{.flagsflags};intret;do{retioctl(fd,DMA_BUF_IOCTL_SYNC,sync);}while(ret0(errnoEINTR||errnoEAGAIN));returnret;}staticintwrite_dmabuf(intfd,size_tsize){void*addr;intret-1;addrmmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(addrMAP_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;ret0;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 已经 signaledCPU 在 non-coherent 平台上仍可能需要 invalidate cache 才能看到设备写入。反过来即使做了 cache flush也不表示仍在运行的 GPU 已停止访问内存。CPU 访问的一般顺序是等待先前设备 fence - begin CPU access / SYNC_START - CPU 读写 - end CPU access / SYNC_END - 提交后续设备任务并建立 fence12. 隐式同步与显式同步12.1 隐式同步隐式同步把 fence 关联到 DMA-BUF 的dma_resv。支持该模型的驱动在提交任务时根据读写方向取得并等待所需依赖 fence创建代表本次异步任务的 fence以正确 usage 加入 reservation object在硬件真正完成后 signal fence。用户空间可以对 DMA-BUF fd 使用poll()/epoll()查询隐式 fencePOLLIN/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_fileDMA_BUF_IOCTL_IMPORT_SYNC_FILE把一个sync_filefence 加入 DMA-BUF 的隐式同步状态。这两个 ioctl 可以桥接显式同步和隐式同步但“导出依赖→提交任务→导入完成 fence”不是原子操作。多个线程或上下文同时操作时用户空间还需要额外锁定或协议保证这组步骤之间没有竞态。13. DMA-Heap从用户空间分配 DMA-BUFDMA-Heap 为用户空间提供分配 DMA-BUF 的统一 UAPI。每个/dev/dma_heap/name节点表示一种分配策略或内存池。例如上游内核常见的systemheap 提供 cacheable、虚拟连续的 buffer配置 CMA 后还可能出现连续内存 heap。实际节点由内核配置和平台决定。简化的用户空间分配代码#includefcntl.h#includestdint.h#includesys/ioctl.h#includeunistd.h#includelinux/dma-heap.hstaticintalloc_from_system_heap(uint64_tsize){structdma_heap_allocation_datadata{.lensize,.fd_flagsO_RDWR|O_CLOEXEC,.heap_flags0,};intheap_fd;intret;heap_fdopen(/dev/dma_heap/system,O_RDWR|O_CLOEXEC);if(heap_fd0)return-1;retioctl(heap_fd,DMA_HEAP_IOCTL_ALLOC,data);close(heap_fd);if(ret0)return-1;returndata.fd;}DMA-Heap 只解决“从哪个 heap 分配并取得 DMA-BUF fd”。它不提供图像宽高、格式、stride也不替调用方完成设备任务同步。14. 图像 buffer 还需要布局元数据一个 fd 和一个 size 不足以解释图像内容。跨 DRM、V4L2、EGL、Wayland 或 Vulkan 交换图像时通常还需要width、heightpixel formatplane 数量每个 plane 的 fd、offset 和 strideformat modifiercolor 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/fdDMA-BUF 的 fdinfo 通常包含size、引用相关计数和exp_name等字段具体内容随内核和 exporter 变化。启用 debugfs 的开发系统还可能提供cat/sys/kernel/debug/dma_buf/bufinfodebugfs 不应作为稳定生产 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-BUFdma_buf_get()失败必须正常返回确保异常、进程退出和设备 reset 路径最终都能释放引用并 signal 或终止相关 fence。18. 原文中需要纠正的几个结论原表述更准确的理解DMA-BUF 在物理内存分配后导出backing storage 由 exporter 管理可以提前分配也可能延迟分配、迁移或位于设备内存dma_buf绑定文件描述符dma_buf关联struct filefd 是每个进程 fd 表中的句柄可以有多个引用计数表示消费者数量引用计数统计文件和内核引用不等于 attachment 或业务消费者数量attachment 是消费者对象attachment 是 DMA-BUF 与某个struct device的关系scatterlist 是按页的连续物理块map 后的sg_table是面向具体设备的 DMA 段描述可能受 IOMMU 和 DMA API 合并影响unmap 通知 DMA 结束驱动必须先确认硬件结束unmap 只释放 mappingbegin/end 负责所有同步begin/end 主要解决 CPU 可见性与缓存一致性硬件执行顺序仍需 fence 或子系统同步机制kmap/kmap_atomic是当前通用访问接口当前 DMA-BUF 整体内核映射使用vmap/vunmap和iosys_map旧接口不应直接套用19. 建议的验证实验实验一验证 fd 传递与引用生命周期使用 DMA-Heap 分配一块 DMA-BUF进程 A 通过SCM_RIGHTS发送给进程 BA 分配并设置 DMA-BUF 名称A 通过mmap DMA_BUF_IOCTL_SYNC写入固定模式A 用 Unix domain socket 发送 fdB 映射并校验内容A 关闭自己的 fdB 再次读取分别观察两个进程的/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 设备的平台上分别记录设备任务 fence 何时 signaledCPU 的 begin/end access 何时执行数据校验何时进行。正确顺序应是先等待设备完成再开始 CPU access。不要在生产设备上故意省略缓存同步来“证明会出错”因为 coherent 平台可能掩盖问题non-coherent 平台上的结果也可能是非确定的数据损坏。20. 小结DMA-BUF 的主线可以归纳为exporter 管理存储并导出对象用户空间传递 fdimporter 为具体设备建立 attachment 和 DMA mappingfence/reservation 管理异步访问顺序begin/end 协议管理 CPU 缓存一致性最后所有资源按相反顺序释放。排查问题时应始终分别回答四个问题谁持有 buffer 的引用生命周期是否正确当前地址属于 CPU 虚拟、物理还是设备 DMA/IOVA 空间上一次异步读写是否已经通过 fence 确认完成CPU 与设备之间是否完成了正确方向的缓存同步把这四点分开DMA-BUF 中大多数“偶尔花屏”“某平台正常、另一平台错误”“buffer 无法释放”和 IOMMU fault 问题都会更容易定位。参考资料Linux 内核文档Buffer Sharing and SynchronizationDMA-BUFLinux 内核源码include/linux/dma-buf.hLinux 内核文档DMA APILinux 内核文档DMA API HOWTOLinux 内核 UAPIDMA-BUF HeapsLinux 内核 UAPIExchanging pixel buffersLinux 内核文档Sync File APILinux 内核文档/proc fdinfo 中的 DMA Buffer 信息