1. 前言
Linux dma-buf 框架原理、实现与应用 一文中,介绍了dma-buf的框架和应用场景,这里再啰嗦一句:
DMA-BUF 允许一个驱动(Exporter,导出者)将自己管理的物理内存缓冲区以文件描述符(fd)的形式导出,其他驱动(Importer,导入者)通过 fd 获取该缓冲区的访问权,实现跨设备的高效数据共享。其核心思想是:
-
以文件描述符为统一接口,屏蔽底层物理内存的分配与管理细节。
-
通过引用计数、同步机制(如 dma_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_export
dma_buf_getfile
alloc_anon_inode
alloc_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. 生成file
file = dma_buf_getfile(exp_info->size, exp_info->flags);
//2. 创建dmabuf
dmabuf = kzalloc(alloc_size, GFP_KERNEL);
//3. 把传入的参数设置给dmabuf
dmabuf->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_data
return file->private_data;
}
拿到dmabuf就可以继续访问dmabuf里的私有数据priv,可能就是一个drm_gem_object了。
5.设计哲学与安全性分析
5.1 统一接口与抽象
- 以 fd 为统一抽象,简化用户空间与内核、驱动间的交互。
- 通过 file_operations 实现多态,支持多种导出者/导入者。
5.2 引用计数与生命周期管理
- 通过 file 的引用计数,自动管理 dma_buf 生命周期。
- 导入者/导出者无需关心底层释放时机,防止悬挂指针。
5.3 同步与一致性
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的机制上实现的。
906

被折叠的 条评论
为什么被折叠?



