DMA-BUF缓冲区共享和同步【ChatGPT】

发布时间 2023-12-10 14:42:07作者: 摩斯电码

DMA-BUF缓冲区共享和同步

DMA-BUF子系统提供了一个框架,用于在多个设备驱动程序和子系统之间共享硬件(DMA)访问的缓冲区,并用于同步异步硬件访问。

例如,drm的“prime”多GPU支持就使用了这个框架,但当然不仅限于GPU的使用情况。

这个框架的三个主要组件是:(1)dma-buf,代表一个sg_table,并以文件描述符的形式暴露给用户空间,以允许在设备之间传递;(2)fence,提供了一种机制,用于在一个设备完成访问时发出信号;(3)reservation,管理与缓冲区关联的共享或独占fence。

共享DMA缓冲区

本文档作为设备驱动程序编写者的指南,介绍了dma-buf缓冲区共享API是什么,以及如何将其用于导出和使用共享缓冲区。

任何希望成为DMA缓冲区共享一部分的设备驱动程序,可以作为缓冲区的“导出者”或“用户”或“导入者”来实现。

假设驱动程序A想要使用由驱动程序B创建的缓冲区,那么我们称B为导出者,A为缓冲区用户/导入者。

导出者

  • 实现并管理缓冲区的struct dma_buf_ops中的操作,
  • 允许其他用户使用dma_buf共享API共享缓冲区,
  • 管理缓冲区分配的细节,封装在struct dma_buf中,
  • 决定实际的后备存储位置,
  • 并负责任何(共享)用户对该缓冲区的scatterlist的迁移。

缓冲区用户

  • 是缓冲区的(许多)共享用户之一。
  • 不需要担心缓冲区是如何分配的,或者在哪里。
  • 需要一种机制来访问组成该缓冲区的scatterlist,以便将其映射到自己的地址空间中,以便访问相同的内存区域。这个接口由struct dma_buf_attachment提供。

dma-buf缓冲区共享框架的任何导出者或用户必须在各自的Kconfigs中具有“select 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 FD上不支持llseek,则内核将对所有情况报告-ESPIPE。用户空间可以使用这一点来检测使用llseek发现dma-buf大小的支持。

  • 为了避免在执行时泄漏fd,文件描述符必须设置FD_CLOEXEC标志。这不仅仅是资源泄漏,还可能是一个潜在的安全漏洞。它可能会使新执行的应用程序通过泄漏的fd访问缓冲区,而本来不应该被允许访问。

  • 通过单独的fcntl()调用来做这件事,与在创建fd时原子地进行相比,这在多线程应用程序中是固有的竞争。当是库代码打开/创建文件描述符时,问题变得更加严重,因为应用程序甚至可能不知道fd的存在。

  • 为了避免这个问题,用户空间必须有一种方法来请求在创建dma-buf fd时设置O_CLOEXEC标志。因此,提供给导出驱动程序创建dmabuf fd的任何API必须提供一种让用户空间控制传递给dma_buf_fd()的O_CLOEXEC标志设置的方法。

  • 还支持内存映射DMA缓冲区的内容。有关完整详情,请参阅下面关于CPU访问DMA缓冲区对象的讨论。

  • DMA缓冲区FD也是可轮询的,请参阅下面的隐式fence轮询支持以获取详细信息。

  • DMA缓冲区FD还支持一些特定于dma-buf的ioctl,请参阅下面的DMA缓冲区ioctl以获取详细信息。

基本操作和设备DMA访问

对于设备DMA访问共享DMA缓冲区,通常的操作序列相当简单:

  • 导出者使用DEFINE_DMA_BUF_EXPORT_INFO()定义他的导出者实例,并调用dma_buf_export()将私有缓冲区对象封装成dma_buf。然后通过调用dma_buf_fd()将该dma_buf作为文件描述符导出到用户空间。

  • 用户空间将这些文件描述符传递给所有希望共享该缓冲区的驱动程序:首先,文件描述符通过dma_buf_get()转换为dma_buf。然后使用dma_buf_attach()将缓冲区附加到设备上。

  • 在这个阶段,导出者仍然可以自由迁移或重新分配后备存储。

  • 一旦缓冲区附加到所有设备上,用户空间就可以启动对共享缓冲区的DMA访问。在内核中,这是通过调用dma_buf_map_attachment()和dma_buf_unmap_attachment()来完成的。

  • 一旦驱动程序完成了对共享缓冲区的使用,它需要调用dma_buf_detach()(在清理任何映射之后),然后通过调用dma_buf_put()释放通过dma_buf_get()获得的引用。

对于导出者预期实现的详细语义,请参见dma_buf_ops。

CPU对DMA缓冲区对象的访问

支持CPU访问dma缓冲区对象有多个原因:

  • 内核中的回退操作,例如当设备通过USB连接时,内核需要先对数据进行处理然后再发送。缓存一致性由在任何事务中调用dma_buf_begin_cpu_access()和dma_buf_end_cpu_access()来处理。

  • 由于大多数内核内部dma-buf访问需要整个缓冲区,引入了一个vmap接口。请注意,在非常旧的32位体系结构上,vmalloc空间可能有限,导致vmap调用失败。

  • 接口:

    void \*dma_buf_vmap(struct dma_buf \*dmabuf, struct iosys_map \*map)
    void dma_buf_vunmap(struct dma_buf \*dmabuf, struct iosys_map \*map)
  • 如果导出者中没有vmap支持,或者vmalloc空间用尽,vmap调用可能会失败。请注意,dma-buf层为所有vmap访问保留了引用计数,并且只有在没有vmapping存在时才调用导出者的vmap函数,并且只在一次调用后取消映射。通过获取dma_buf.lock互斥锁来提供对并发vmap/vunmap调用的保护。

  • 为了与现有用户空间接口的导入子系统完全兼容,这些接口可能已经支持对缓冲区进行mmap。这在许多处理管道中是必需的(例如将软件渲染的图像输入到硬件管道中,缩略图创建,快照等)。此外,Android的ION框架已经支持这一点,因此需要DMA缓冲区文件描述符来替换ION缓冲区的mmap支持。

  • 没有特殊的接口,用户空间只需在dma-buf fd上调用mmap。但与CPU访问一样,需要对实际访问进行分隔,这由ioctl(DMA_BUF_IOCTL_SYNC)处理。请注意,DMA_BUF_IOCTL_SYNC可能会因为-EAGAIN或-EINTR而失败,在这种情况下必须重新启动。

  • 一些系统可能需要某种缓存一致性管理,例如当CPU和GPU域同时通过dma-buf访问时。为了避免这个问题,引入了开始/结束一致性标记,直接转发到现有的dma-buf设备驱动程序的vfunc挂钩。用户空间可以通过DMA_BUF_IOCTL_SYNC ioctl使用这些标记。该序列将如下使用:

    • mmap dma-buf fd
    • 对于CPU中的每个绘制/上传周期 1. SYNC_START ioctl,2. 读/写到mmap区域 3. SYNC_END ioctl。这可以重复进行(新数据被GPU或扫描设备消耗)。
    • 一旦不再需要缓冲区,可以进行munmap。

    为了正确性和最佳性能,在访问映射地址之前和之后,总是需要使用SYNC_START和SYNC_END。即使在某些系统中,即使没有调用这些ioctl,用户空间也不能依赖一致的访问。

  • 以及用户空间处理管道中的CPU回退。

    与内核CPU访问的动机类似,重要的是给定导入子系统的用户空间代码可以使用与导入的dma-buf缓冲区对象相同的接口。这对于drm来说尤其重要,因为当代OpenGL、X和其他驱动程序的用户空间部分非常庞大,重新设计它们以使用不同的方式来mmap缓冲区会相当具有侵入性。

    当前dma-buf接口的假设是重定向初始mmap就足够了。对一些现有子系统的调查显示,没有驱动程序似乎会做任何像与设备上的未完成的异步处理同步,或者在错误时分配特殊资源之类的坏事。因此,希望这已经足够好了,因为添加接口来拦截页错误并允许pte shootdown将会增加相当多的复杂性。

    接口:

    int dma_buf_mmap(struct dma_buf \*, struct vm_area_struct \*,
                   unsigned long);
    

如果导入子系统只是提供了一个特殊用途的mmap调用来在用户空间设置映射,那么使用dma_buf.file调用do_mmap同样可以为dma-buf对象实现这一点。

隐式fence轮询支持

为了支持缓冲区访问的跨设备和跨驱动程序同步,可以将隐式fence(在内核中用struct dma_fence表示)附加到dma_buf上。dma_resv结构提供了这方面的支持。

用户空间可以使用poll()和相关的系统调用来查询这些隐式跟踪的fence的状态:

  • 检查EPOLLIN,即读访问,可以用来查询最近写入或独占fence的状态。

  • 检查EPOLLOUT,即写访问,可以用来查询所有附加的fence,包括共享的和独占的。

请注意,这只是信号相应fence的完成,即DMA传输完成。在CPU访问开始之前,仍然需要进行缓存刷新和任何其他必要的准备工作。

作为对poll()的替代,可以使用dma_buf_sync_file_export将DMA缓冲区上的一组fence导出为sync_file。

DMA-BUF统计信息

/sys/kernel/debug/dma_buf/bufinfo提供了系统中每个DMA-BUF的概述。但是,由于debugfs不安全,不能在生产环境中挂载,因此可以使用procfs和sysfs来收集生产系统上的DMA-BUF统计信息。

procfs中的/proc/<pid>/fdinfo/<fd>文件可用于收集关于DMA-BUF fd的信息。有关接口的详细文档在/proc文件系统中有详细说明。

不幸的是,现有的procfs接口只能提供关于那些进程持有fd或将缓冲区映射到其地址空间的DMA-BUF的信息。这促使创建了DMA-BUF sysfs统计接口,以在生产系统上提供每个缓冲区的信息。

当启用CONFIG_DMABUF_SYSFS_STATS时,/sys/kernel/dmabuf/buffers接口会公开有关每个DMA-BUF的信息。

该接口公开了以下统计信息:

  • /sys/kernel/dmabuf/buffers/<inode_number>/exporter_name
  • /sys/kernel/dmabuf/buffers/<inode_number>/size

该接口中的信息也可以用于推导每个导出者的统计信息。该接口的数据可以在错误条件或其他重要事件发生时收集,以提供DMA-BUF使用情况的快照。它也可以通过遥测定期收集,以监视各种指标。

有关接口的详细文档在Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers中有详细说明。

DMA Buffer ioctls

https://www.kernel.org/doc/html/v6.6/driver-api/dma-buf.html#dma-buffer-ioctls