最好的学习从源码开始,本章学习linux\Documentation\driver-api\dma-buf.rst。
dma-buf(Buffer Sharing and Synchronization)即缓冲共享与同步。
dma-buf 子系统提供了跨多个设备驱动程序和子系统共享硬件 (DMA) 访问缓冲区的框架,并且用于同步异步硬件访问。
例如,它被 DRM 子系统广泛用于在进程之间、同一进程内的上下文之间、库 API 之间交换缓冲区,以及与其他子系统(如 V4L2)交换缓冲区。
本文档描述了内核子系统如何使用和与 dma-buf 提供的三个主要机制进行交互:
- dma-buf:表示一个 `sg_table` 并暴露给用户空间作为文件描述符,以便在进程、子系统、设备等之间传递。
- dma-fence:提供一种机制来信号异步硬件操作何时完成。
- dma-resv:管理特定 dma-buf 的一组 dma-fences,允许隐式(内核顺序)同步工作,以保持一致访问的错觉。
#### 用户空间 API 原则和使用
有关如何设计您的子系统的 API 以使用 dma-buf 的更多详细信息,请参阅 Documentation/userspace-api/dma-buf-alloc-exchange.rst。
#### 共享 DMA 缓冲区
本文档为设备驱动程序编写者提供了 dma-buf 缓冲区共享 API 的指南,说明如何导出和使用共享缓冲区。任何希望参与 DMA 缓冲区共享的设备驱动程序都可以作为缓冲区的“导出者”或“用户”或“导入者”。
假设驱动程序 A 想要使用由驱动程序 B 创建的缓冲区,那么我们称 B 为导出者,A 为缓冲区用户/导入者。
The exporter导出者:
- 在 `struct dma_buf_ops <dma_buf_ops>` 中实现和管理缓冲区的操作。
- 允许其他用户通过使用 dma_buf 共享 API 来共享缓冲区。
- 管理缓冲区分配的细节,封装在一个 `struct dma_buf <dma_buf>` 中。
- 决定实际存储此分配的后备存储。
- 处理 scatterlist 的迁移 - 对于所有(共享)用户。
The buffer-user缓冲区用户:
- 是该缓冲区的众多共享用户之一。
- 不需要担心缓冲区是如何分配的,或者在哪里分配。
- 需要一种机制来获取组成此缓冲区的 scatterlist 的访问权限,并将其映射到自己的地址空间中,以便可以访问同一内存区域。此接口由 `struct dma_buf_attachment <dma_buf_attachment>` 提供。
任何 dma-buf 缓冲区共享框架的导出者或用户都必须在其各自的 Kconfig 中选择 `DMA_SHARED_BUFFER`。
#### 用户空间接口注意事项
大多数情况下,DMA 缓冲区文件描述符对用户空间来说只是一个不透明的对象,因此暴露的通用接口非常简单。但是,有几点需要注意:
自内核 3.12 起,dma-buf FD 支持 llseek 系统调用,但仅支持 offset=0 和 whence=SEEK_END|SEEK_SET。SEEK_SET 支持通常的大小发现模式 size = SEEK_END(0); SEEK_SET(0)。其他所有 llseek 操作将报告 -EINVAL。
如果 dma-buf FDs 不支持 llseek,则内核将在所有情况下报告 -ESPIPE。用户空间可以使用此方法检测是否支持使用 llseek 发现 dma-buf 大小。
为了避免在 exec 时发生 fd 泄漏,必须在文件描述符上设置 FD_CLOEXEC 标志。这不仅是一个资源泄漏问题,而且还是潜在的安全漏洞。它可能会使新执行的应用程序通过泄露的 fd 访问不应被其访问的缓冲区。
通过单独的 fcntl() 调用来设置此标志,而不是在创建 fd 时原子地设置,这在多线程应用程序中本质上是竞争条件。当库代码打开/创建文件描述符时,问题会更加严重,因为应用程序可能甚至不知道这些 fd。
为避免此问题,用户空间必须有一种方式请求在创建 dma-buf fd 时设置 O_CLOEXEC 标志。因此,导出驱动程序提供的任何创建 dmabuf fd 的 API 必须提供一种方式,让用户空间控制传递给 dma_buf_fd() 的 O_CLOEXEC 标志。
内存映射 DMA 缓冲区的内容也是支持的。。
DMA 缓冲区 FD 也可以被轮询。
DMA 缓冲区 FD 还支持一些 dma-buf 特定的 ioctl。
#### 基本操作和设备 DMA 访问
.. kernel-doc:: drivers/dma-buf/dma-buf.c
:doc: dma buf device access
#### CPU 对 DMA 缓冲区对象的访问
.. kernel-doc:: drivers/dma-buf/dma-buf.c
:doc: cpu access
#### Implicit Fence Poll Support隐式栅栏轮询支持
.. kernel-doc:: drivers/dma-buf/dma-buf.c
:doc: implicit fence polling
#### DMA-BUF 统计
.. kernel-doc:: drivers/dma-buf/dma-buf-sysfs-stats.c
:doc: overview
#### DMA 缓冲区 ioctls
.. kernel-doc:: include/uapi/linux/dma-buf.h
#### DMA-BUF 锁定约定
.. kernel-doc:: drivers/dma-buf/dma-buf.c
:doc: locking convention
#### 内核函数和结构参考
.. kernel-doc:: drivers/dma-buf/dma-buf.c
:export:
.. kernel-doc:: include/linux/dma-buf.h
:internal:
#### 预留对象
.. kernel-doc:: drivers/dma-buf/dma-resv.c
:doc: Reservation Object Overview
.. kernel-doc:: drivers/dma-buf/dma-resv.c
:export:
.. kernel-doc:: include/linux/dma-resv.h
:internal:
#### DMA 栅栏
.. kernel-doc:: drivers/dma-buf/dma-fence.c
:doc: DMA fences overview
#### DMA 栅栏跨驱动器合同
.. kernel-doc:: drivers/dma-buf/dma-fence.c
:doc: fence cross-driver contract
#### DMA 栅栏信号注释
.. kernel-doc:: drivers/dma-buf/dma-fence.c
:doc: fence signalling annotation
#### DMA 栅栏截止时间提示
.. kernel-doc:: drivers/dma-buf/dma-fence.c
:doc: deadline hints
#### DMA 栅栏函数参考
.. kernel-doc:: drivers/dma-buf/dma-fence.c
:export:
.. kernel-doc:: include/linux/dma-fence.h
:internal:
#### DMA 栅栏数组
.. kernel-doc:: drivers/dma-buf/dma-fence-array.c
:export:
.. kernel-doc:: include/linux/dma-fence-array.h
:internal:
#### DMA 栅栏链
.. kernel-doc:: drivers/dma-buf/dma-fence-chain.c
:export:
.. kernel-doc:: include/linux/dma-fence-chain.h
:internal:
#### DMA 栅栏解开
.. kernel-doc:: include/linux/dma-fence-unwrap.h
:internal:
#### DMA 栅栏同步文件
.. kernel-doc:: drivers/dma-buf/sync_file.c
:export:
.. kernel-doc:: include/linux/sync_file.h
:internal:
#### DMA 栅栏同步文件 uABI
.. kernel-doc:: include/uapi/linux/sync_file.h
:internal:
#### Indefinite DMA Fences无限期 DMA 栅栏
在某些时候,提议了具有不确定完成时间的 struct dma_fence。示例包括:
- Future fences未来栅栏,在 HWC1 中用于指示显示不再使用缓冲区的时间,并在使缓冲区可见的屏幕更新时创建。此栅栏完成的时间完全受用户空间控制。
- Proxy fences代理栅栏,提议用于处理尚未设置栅栏的 &drm_syncobj。用于异步延迟命令提交。
- Userspace fences or gpu futexes用户空间栅栏或 GPU futexes,在命令缓冲区内由用户空间使用的细粒度锁定,用于跨引擎或与 CPU 同步,然后作为 DMA 栅栏导入现有 winsys 协议。
- Long-running compute command buffers长运行计算命令缓冲区,仍使用传统批处理结束 DMA 栅栏进行内存管理,而不是上下文抢占 DMA 栅栏,后者在计算作业重新调度时重新附加。
所有这些方案的共同点是用户空间控制这些栅栏的依赖关系并控制它们何时触发。即使包含回退超时以防止恶意用户空间,混合不定期栅栏和正常内核 DMA 栅栏也不可行:
只有内核知道所有 DMA 栅栏依赖关系,用户空间不知道由于内存管理或调度决策注入的依赖关系。
只有用户空间知道不定期栅栏中的所有依赖关系及其确切完成时间,内核没有可见性。
此外,内核必须能够因内存管理需求而暂停用户空间命令提交,这意味着我们必须支持不定期栅栏依赖于 DMA 栅栏。如果内核也像 DMA 栅栏一样支持内核中的不定期栅栏(如上述任何提案),则存在死锁的可能性。
这意味着内核可能会意外地通过用户空间不知情的内存管理依赖关系创建死锁,从而随机挂起工作负载,直到超时。从用户空间的角度来看,这些工作负载并不包含死锁。在这种混合栅栏架构中,没有单一实体知道所有依赖关系。因此,从内核内部防止此类死锁是不可能的。
唯一避免依赖循环的方法是不允许内核中出现不定期栅栏。这意味着:
- 没有未来栅栏、代理栅栏或用户空间栅栏作为 DMA 栅栏导入,无论是否有超时。
- 没有 DMA 栅栏来信号批处理结束,其中用户空间允许使用用户空间栅栏或长运行计算工作负载。这也意味着在这些情况下没有共享缓冲区的隐式栅栏。
#### 可恢复硬件页错误的影响
现代硬件支持可恢复的页错误,这对 DMA 栅栏有很多影响。
首先,待处理的页错误显然会阻塞加速器上运行的工作,并且通常需要内存分配来解决错误。但是,不允许内存分配阻止 DMA 栅栏的完成,这意味着任何使用可恢复页错误的工作负载不能使用 DMA 栅栏进行同步。必须使用用户空间控制的同步栅栏。
对于 GPU,这构成了一个问题,因为当前 Linux 上的桌面合成协议依赖于 DMA 栅栏,这意味着如果没有基于用户空间栅栏的全新用户空间堆栈,它们无法从可恢复页错误中受益。具体来说,这意味着不可能进行隐式同步。例外情况是页错误仅用作迁移提示,而不会按需填充内存请求。目前,这意味着 GPU 上的可恢复页错误仅限于纯计算工作负载。
此外,GPU 通常在 3D 渲染和计算方面共享资源,如计算单元或命令提交引擎。如果同时有待处理的带有 DMA 栅栏的 3D 作业和使用可恢复页错误的计算工作负载,可能会发生死锁:
- - 3D 工作负载可能需要等待计算作业完成并释放硬件资源。
- - 计算工作负载可能因页错误而卡住,因为内存分配正在等待 3D 工作负载的 DMA 栅栏完成。
有几种选项可以防止这个问题,其中一个驱动程序需要确保:
- - 即使页错误待处理且尚未修复,计算工作负载始终可以被抢占。并非所有硬件都支持这一点。
- - DMA 栅栏工作负载和需要页错误处理的工作负载具有独立的硬件资源,以保证向前进展。这可以通过专用引擎和最小计算单元预留来实现。
- - 通过仅在 DMA 栅栏工作负载处于飞行状态时保留硬件资源来进一步完善预留方法。这必须覆盖从 DMA 栅栏对其他线程可见到通过 dma_fence_signal() 完成栅栏的时刻。
- - 作为最后的手段,如果硬件没有有用的预留机制,必须在切换需要 DMA 栅栏或需要页错误处理的作业时从 GPU 中刷新所有工作负载:这意味着所有 DMA 栅栏必须在插入计划队列前完成。反之亦然,在 DMA 栅栏在系统中可见之前,必须抢占所有计算工作负载以确保所有待处理的 GPU 页错误被刷新。
- - 只有一个相当理论性的选项是在分配内存以修复硬件页错误时解开这些依赖关系,要么通过单独的内存块,要么通过运行时跟踪所有 DMA 栅栏的完整依赖图。这会对内核产生很大影响,因为在 CPU 侧解析页面本身可能涉及页错误。限制处理硬件页错误的影响到特定驱动程序更为可行和稳健。
注意,运行在独立硬件上的工作负载(如复制引擎或其他 GPU)没有任何影响。这使得我们可以在内核内部继续使用 DMA 栅栏,即使是为了修复硬件页错误,例如,使用复制引擎清除或复制所需内存以解决页错误。
在某些方面,这个页错误问题是 `Infinite DMA Fences` 讨论的一个特殊情况:来自计算工作负载的无限栅栏可以依赖于 DMA 栅栏,但反过来不行。甚至页错误问题也不是新的,因为用户空间中的某些其他 CPU 线程可能会遇到导致用户空间栅栏挂起的页错误 - 支持 GPU 上的页错误并没有引入任何根本性的新内容。