1. 共享的一般性问题
dma-buf 框架是 Linux 内核为解决“跨设备零拷贝缓冲区共享”而设计的通用机制。这里强调一下该机制是内核级通用缓冲区共享框架(底层基础设施),从其实现文件的目录上drivers/dma-buf也可以看出这一点。dma-buf提供统一的共享接口,各种外设的驱动、各子系统都可以使用。
既然是共享,就涉及共享功能的通用问题:
| 序号 | 问题 | 答案 |
|---|---|---|
| 1 | 共享的内容是什么? | buffer |
| 2 | 共享方是谁? | exporter |
| 3 | 使用方是谁? | importer |
| 4 | 如何共享? | dma-buf |
| 5 | 怎么控制同步访问? | dma-fence |
2. dma-buf 的核心原理
2.1 角色划分
该节回答了共享方和使用方的问题。
-
Exporter(导出者):负责分配和管理物理缓冲区的驱动(如 GPU、VPU、分配器等)。
-
Importer(导入者):需要访问该缓冲区的其他驱动(如显示控制器、ISP、DMA 控制器等)。
2.2 共享机制
该节回答了如何共享。
dma-buf机制建立在 anon_inode机制之上,是 anon_inode技术的一个重要应用。我们来回顾下anon_inode导出内核对象的步骤,并说明该步骤对应的dma-buf中的结构体或对象。
1. 自定义内核对象:这里是 struct dma_buf,描述一个可共享的物理缓冲区;
2. 定义 file_operations : 对应的是dma_buf_fops;
static const struct file_operations dma_buf_fops = {
.release = dma_buf_file_release,
.mmap = dma_buf_mmap_internal,
.llseek = dma_buf_llseek,
.poll = dma_buf_poll,
.unlocked_ioctl = dma_buf_ioctl,
.compat_ioctl = compat_ptr_ioctl,
.show_fdinfo = dma_buf_show_fdinfo,
};
3. 获取fd:核心逻辑是如下:
//全局变量
static struct vfsmount *dma_buf_mnt;
//1. 获取匿名的inode
struct inode *inode = alloc_anon_inode(dma_buf_mnt->mnt_sb);
//2. 打开inode,得到file
struct file *file = alloc_file_pseudo(
inode, dma_buf_mnt, "dmabuf",
flags, &dma_buf_fops);
//3. 创建dma_buf,设置dma_buf的内部成员
struct dma_buf *dmabuf = kzalloc(alloc_size, GFP_KERNEL);
...
//4. dma_buf与file做双向关联
file->private_data = dmabuf;
dmabuf->file = file;
//5. 获取未用的fd,然后关联到file
fd = get_unused_fd_flags(flags);
fd_install(fd, file);
整个链路从用户态可见的fd开始:
-
用户空间通过 fd 调用特定ioctl来获取buffer;
-
fd 在内核中对应 struct file;
-
file->private_data 指向 struct dma_buf;
-
dma_buf 结构体内部再通过 priv 指针指向实际的 buffer object(显存对象、内存块等)。
用户空间 fd
│
▼
struct file
│
▼
struct dma_buf (file->private_data 指向)
│
▼
buffer object (bo) (dma_buf->priv指向)
获取到的fd就可以交给用户态了,用户态程序通过fd传递机制共享给其他进程,就实现了共享。先用这个anon_inode机制去理解dma-buf机制,是比较好的一个学习路线,抓住了核心,不会被庞杂的代码消灭了学习的热情。
2.3 同步机制
该节回答了如何同步访问。第六章我们会专门讲解同步机制。
3. bo的共享流程
dma-buf 框架为 exporter 和 importer 提供标准化的缓冲区共享、映射、同步等接口。下面的两个流程中涉及函数和结构体都是实际的实现。我并不打算详细讲这些函数和结构体,一个是这些命名都见名知意;二个是在理解上面anon_inode机制后,这些可看可不看。
3.1 Exporter(导出者)流程
-
场景:某个驱动(exporter)创建了底层 buffer,想把它导出为 dma-buf 供其他驱动/用户空间使用。
Exporter driver has BO (private)
│
├─ prepare export info: struct dma_buf_export_info {
│ .priv = pointer to BO,
│ .ops = &dma_buf_ops (callbacks),
│ .resv = optional reservation object or NULL
│ }
│
└─ dmabuf = dma_buf_export(&exp_info)
├-- create anon file (alloc_file_pseudo)
├-- allocate struct dma_buf
├-- if !resv: allocate internal dma_resv and dma_resv_init(dmabuf->resv)
└-- file->private_data = dmabuf; dmabuf->file = file
▼
User space gets fd: fd = dma_buf_fd(dmabuf)
3.2 Importer(导入者)流程
importer流程要复杂些,但不要紧,可以先记住主流程,等理解了5.2.3 dma-buf的实现后再来看细节。
-
场景:用户空间拿到一个 dma-buf 的 fd,Importer(设备驱动)想要把它附着到自己的设备并为 DMA 做准备。
User space fd
│
▼
kernel: dma_buf_get(fd) // fget -> file -> file->private_data -> struct dma_buf *
│
▼
struct dma_buf *dmabuf = file->private_data
(Attach phase) [Importer MUST NOT hold dmabuf->resv here]
│
├─> dma_buf_attach(dmabuf, dev) // wrapper -> dma_buf_dynamic_attach()
│ ├-- may call dmabuf->ops->attach(dmabuf, attach) (EXPORTER callback)
│ └-- list_add(&attach->node, &dmabuf->attachments) // protected by dma_resv_lock around list op
│
▼
(Prepare mapping / pin) [Importer MUST hold dmabuf->resv for map/pin]
│
├─ hold dma-resv: dma_resv_lock(dmabuf->resv, NULL)
│
├─ optional: dma_buf_pin(attach) // calls exporter ops->pin() while resv locked
│
├─ sg_table = dma_buf_map_attachment(attach, direction)
│ └-- __map_dma_buf -> dmabuf->ops->map_dma_buf(attach, direction)
│ (exporter callback invoked WITH dmabuf->resv locked)
│
└─ release dma-resv: dma_resv_unlock(dmabuf->resv)
▼
(Use DMA) --> device performs DMA using returned sg_table
│
(Teardown mapping) [Importer MUST hold dmabuf->resv]
│
├─ hold dma-resv
├─ dma_buf_unmap_attachment(attach, sg_table, direction)
│ └-- exporter ops->unmap_dma_buf (called WITH resv locked)
├─ if pinned: dma_buf_unpin(attach) // exporter ops->unpin() WITH resv locked
└─ release dma-resv
▼
(Detach) [Importer MUST NOT hold dmabuf->resv]
│
└─ dma_buf_detach(dmabuf, attach) // calls exporter ops->detach() (invoked with unlocked resv)
│
▼
dma_buf_put(dmabuf) // fput(file)
//中文版
导入者驱动
│
├─ 接收文件描述符(来自用户空间或其他驱动)
│
├─ 调用 dma_buf_get(fd)
│ └─ 获取 struct dma_buf 指针
│
├─ 调用 dma_buf_attach()
│ └─ 创建 dma_buf_attachment
│ └─ 通知导出者有新设备附加(调用 ops->attach)
│
├─ 调用 dma_buf_map_attachment()
│ └─ 获取 sg_table(scatter-gather 表)
│ └─ 导出者返回适合此设备的物理地址映射
│
└─ 设备使用 sg_table 进行 DMA 访问
4. 关键特性与应用
4.1 基于文件描述符的共享
-
安全性:fd 不能被猜测,必须显式传递(通过 UNIX socket 的 SCM_RIGHTS)
-
引用计数:内核自动管理 fd 的生命周期
-
跨进程:fd 可以传递给其他进程,实现安全的跨进程共享
4.2 Scatter-Gather 支持
-
非连续内存:支持物理上不连续的内存(通过
sg_table) -
IOMMU 支持:可利用 IOMMU 将分散的物理页映射为连续的设备地址
-
灵活性:不同设备可能得到不同的映射
4.3 同步机制
-
dma-fence:异步操作完成的信号机制
-
隐式同步:使用dma_resv自动处理设备间的依赖
正是因为是底层机制,dma-buf 被广泛应用于多个子系统:
-
DRM:GPU 缓冲区共享
-
V4L2(Video for Linux 2):视频帧缓冲共享
-
媒体子系统:ISP、编解码器等设备间的缓冲区共享
-
DMA 引擎:通用 DMA 控制器的缓冲区共享
-
Android ION/DMA Heaps:用户空间缓冲区分配器
下一节看下dma_buf的关键结构体和函数接口的使用。
422

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



