Linux dma-buf核心函数实现分析
1. 前言
Linux dma-buf 框架原理、实现与应用 一文中,介绍了dma-buf的框架和应用场景,这里再啰嗦一句:
DMA-BUF 允许一个驱动(Exporter,导出者)将自己管理的物理内存缓冲区以文件描述符(fd)的形式导出,其他驱动(Importer,导入者)通过 fd 获取该缓冲区的访问权,实现跨设备的高效数据共享。其核心思想是:
-
以文件描述符为统一接口,屏蔽底层物理内存的分配与管理细节。
-
通过引用计数、同步机制(如 fence)、附件(attachment)等,保证多方安全、同步地访问同一块物理内存。
-
支持 mmap、vmap、CPU/GPU 访问、同步等多种操作。
2. 导出与导入的完整流程示例
2.1 导出(Exporter)流程
-
驱动分配物理缓冲区
例如 GPU 驱动分配一块显存或系统内存。最常见的就是分配一个drm_gem_object。
-
填充 dma_buf_export_info
-
设置
priv
指向私有缓冲区对象。 -
设置
ops
为导出者实现的操作函数表。 -
设置
size
、owner
、exp_name
等。
-
-
调用 dma_buf_export
根据dma_buf_export_info,返回
struct dma_buf *dmabuf
。 -
调用 dma_buf_fd
获得 fd,返回给用户空间。
-
用户空间传递 fd 给其他驱动/进程
2.2 导入(Importer)流程
-
用户空间将 fd 传递给导入驱动(如通过 IOCTL)
-
导入驱动调用 dma_buf_get
通过dma_buf_get(fd) 获取
struct dma_buf *
,增加引用计数。 -
调用 dma_buf_attach
在 dma_buf 上创建 attachment,加入 attachments 链表。
-
调用 dma_buf_map_attachment
获取物理页表(sg_table),进行 DMA 映射。
-
数据传输/处理
-
完成后 detach、put
调用 dma_buf_detach、dma_buf_put,释放引用。
2.3 代码示例(伪代码)
导出端:
struct my_buffer *buf = my_buffer_alloc(...);DEFINE_DMA_BUF_EXPORT_INFO(exp_info);
exp_info.priv = buf;
exp_info.ops = &my_dma_buf_ops;
exp_info.size = buf->size;
exp_info.owner = THIS_MODULE;
exp_info.exp_name = "my_exporter";struct dma_buf *dmabuf = dma_buf_export(&exp_info);int fd = dma_buf_fd(dmabuf, O_CLOEXEC);
return fd; // 返回给用户空间
导入端:
int fd = ...; // 用户空间传入struct dma_buf *dmabuf = dma_buf_get(fd);
struct dma_buf_attachment *attach = dma_buf_attach(dmabuf, my_device);
struct sg_table *sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
// 进行 DMA 操作
dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);dma_buf_detach(dmabuf, attach);
dma_buf_put(dmabuf);
3. 导出端核心函数分析
核心函数入口dma_buf_export。关键调用路径和函数如下:
dma_buf_exportdma_buf_getfilealloc_anon_inodealloc_file_pseudo
struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
{struct dma_buf *dmabuf;struct dma_resv *resv = exp_info->resv;struct file *file;size_t alloc_size = sizeof(struct dma_buf);int ret;...//1. 生成filefile = dma_buf_getfile(exp_info->size, exp_info->flags);//2. 创建dmabufdmabuf = kzalloc(alloc_size, GFP_KERNEL);//3. 把传入的参数设置给dmabufdmabuf->priv = exp_info->priv;dmabuf->ops = exp_info->ops;dmabuf->size = exp_info->size;dmabuf->exp_name = exp_info->exp_name;dmabuf->owner = exp_info->owner;spin_lock_init(&dmabuf->name_lock);init_waitqueue_head(&dmabuf->poll);dmabuf->cb_in.poll = dmabuf->cb_out.poll = &dmabuf->poll;dmabuf->cb_in.active = dmabuf->cb_out.active = 0;INIT_LIST_HEAD(&dmabuf->attachments);if (!resv) {dmabuf->resv = (struct dma_resv *)&dmabuf[1];dma_resv_init(dmabuf->resv);} else dmabuf->resv = resv;ret = dma_buf_stats_setup(dmabuf, file);//4. 设置双向关联file->private_data = dmabuf;dmabuf->file = file;__dma_buf_list_add(dmabuf);return dmabuf;
}
有个dma_buf(这时已经是一个buf[存储在dmabuf的priv里]和file结合体了),就可以用dma_buf_fd来关联fd了。
int dma_buf_fd(struct dma_buf *dmabuf, int flags)
{int fd;if (!dmabuf || !dmabuf->file)return -EINVAL;fd = get_unused_fd_flags(flags);fd_install(fd, dmabuf->file);return fd;
}
4. 导入端核心函数分析
importer侧做的关键动作就是拿到fd后找到对应的dmabuf。这个动作的实现是dma_buf_get。
struct dma_buf *dma_buf_get(int fd)
{struct file *file;file = fget(fd);if (!file)return ERR_PTR(-EBADF);if (!is_dma_buf_file(file)) {fput(file);return ERR_PTR(-EINVAL);}//exporter时已经把dmabuf放到了file->private_datareturn file->private_data;
}
拿到dmabuf就可以继续访问dmabuf里的私有数据priv,可能就是一个drm_gem_object了。
5.设计哲学与安全性分析
5.1 统一接口与抽象
- 以 fd 为统一抽象,简化用户空间与内核、驱动间的交互。
- 通过 file_operations 实现多态,支持多种导出者/导入者。
5.2 引用计数与生命周期管理
- 通过 file 的引用计数,自动管理 dma_buf 生命周期。
- 导入者/导出者无需关心底层释放时机,防止悬挂指针。
5.3 同步与一致性
- 通过 dma_resv、fence 等机制,保证多方访问的一致性与同步。
- 支持隐式同步(poll、fence)与显式同步(ioctl、sync_file)。
5.4 安全性与隔离
- 只暴露 fd,用户空间无法直接访问物理地址。
- 通过权限、引用计数、锁等机制,防止资源泄漏与竞争。
DMA-BUF 框架通过 dma_buf_export
、dma_buf_fd
、dma_buf_getfile
等核心函数,实现了跨设备、跨驱动、跨进程的高效缓冲区共享。其设计充分利用了 Linux 文件系统、引用计数、同步等基础设施,既保证了高性能,又兼顾了安全性和易用性。
Everything is a file in Linux。好好理解这句话,建议读者去按下面的顺序去学习:
1. 理解linux里的file结构体以及与其相关的结构体;
2. 理解anon_inode,参见相关博文;
3. 理解本文dma_buf;
接下来理解DRM框架里的prime机制,该机制就是在dma-buf的机制上实现的。后续出一个专门介绍DRM prime机制是如何使用dma-buf的文章。