简介
在 Linux 内核中,DMA-BUF(DMA Buffer Sharing)是一个非常核心的子系统,主要目的是在不同硬件驱动之间高效共享内存缓冲区,同时避免用户空间与内核空间之间昂贵的数据拷贝,也就是常说的零拷贝(zero-copy)。
工作原理
DMA-BUF 的核心思想,是把内存缓冲区抽象成一个文件描述符(fd)。Linux 奉行“一切皆文件”,通过 fd 就能方便地在不同进程与驱动之间传递访问权限。
DMA-BUF 的参与者主要有两类角色:
- Exporter(导出者):负责分配物理内存并创建 DMA-BUF 对象的驱动程序。它需要实现底层的内存分配、映射与销毁逻辑,并对外提供一个 fd。
- Importer(导入者):接收这个 fd 并希望访问该内存的驱动程序。它会通过 fd 找到对应的 DMA-BUF 对象,再把这块内存映射到自己硬件的 IOMMU/MMU 中,让本设备的 DMA 控制器能够访问。
核心机制
为了保证多个硬件高效、安全地协同工作,DMA-BUF 引入了两个关键机制。
dma-fence
如果 GPU 正在写缓冲区,Display Controller 绝不能同时读取,否则画面会撕裂。dma-fence 就是用来解决这类硬件同步问题的。
- 它是一个轻量级同步信号。Exporter 或 Importer 完成一次硬件操作后,会触发(signal)这个 fence,通知排队的下一个硬件:“我已经用完,你可以开始访问了。”
- 这套机制是异步的,硬件驱动不需要阻塞 CPU,而是让硬件流水线自己等待信号。
dma-resv
- dma-resv(Reservation Object)是一个容器,用来管理与这块缓冲区相关的所有独占锁(Exclusive)和共享锁(Shared)的 fence。
- 它保证了读写互斥,比如多个硬件可以同时读(共享锁),但同一时刻只能有一个硬件写(独占锁)。
用户空间如何传递 fd
在用户空间,跨进程传递 DMA-BUF fd 最标准、最常用的方式是通过 Unix Domain Socket(本地套接字),并配合 sendmsg 与 recvmsg 接口,加上 SCM_RIGHTS 控制消息。
大致流程:
- 进程 A(发送方,比如 Camera 服务进程)拥有一块内存的 DMA-BUF fd,假设值是 3。
- 进程 A 创建一条 Unix Domain Socket 连接到进程 B。
- 进程 A 构造
msghdr 结构体,把 fd=3 放入辅助数据(Ancillary Data / Control Message)中,类型置为 SCM_RIGHTS,然后调用 sendmsg()。
- 内核介入:内核识别到
SCM_RIGHTS 后,会在进程 A 的文件描述符表中找到对应的底层 struct file(也就是真正的 DMA-BUF 对象),递增其引用计数,并将对象指针暂存起来。
- 进程 B(接收方,比如 UI 合成器进程)调用
recvmsg()。
- 内核再次介入:内核在进程 B 的文件描述符表中找一个空闲位置(假设是 7),把暂存的
struct file 指针填进去,最后让 recvmsg() 返回数字 7。
- 最终结果:进程 B 拿到 fd=7。虽然数字不同,但它和进程 A 的 fd=3 指向内核中同一个 DMA-BUF 内存对象。
应用场景
多媒体流水线(Camera -> GPU -> Display)
- V4L2 驱动(摄像头)把一帧图像捕获到内存,并导出为 DMA-BUF fd。
- GPU 驱动导入这个 fd,直接在这块内存上做滤镜或 3D 渲染。
- DRM/KMS 驱动导入同一个 fd,把渲染好的数据直接推送到屏幕。
- 全程零拷贝,CPU 只负责传递 fd 和调度,不参与数据搬运。
Wayland / Android SurfaceFlinger(窗口系统)
- 客户端应用(如 Chrome 或游戏)渲染完一帧画面后,把这块内存的 DMA-BUF fd 通过 IPC 发送给合成器(Compositor)。
- 合成器直接合成并显示,实现跨进程的高效内存共享。
相关阅读
|