天天看点

display:Integrating the ION memory allocator-2013

作为2013 Linux Plumbers Conference大会上Android + Graphics会议的一部分,我们将讨论ION内存分配器及其功能如何上传到主流内核。由于时间有限,我想创建一些背景文档,以提供我们将在会议上讨论并试图解决的问题的背景。

ION overview

Android ION子系统的主要目标是允许在硬件设备和用户空间之间分配和共享缓冲区,以便在设备之间实现零拷贝内存共享。这听起来很简单,但实际上是一个困难的问题。在片上系统(SoC)硬件上,通常有许多不同的设备具有直接内存访问(DMA)。然而,这些设备可能具有不同的功能,并且可以在不同的约束条件下查看和访问内存。例如,一些设备可能处理分散聚集的列表,而另一些设备可能只能访问内存中物理上相邻的页面。一些设备可以访问所有的内存,而另一些设备只能访问内存的一小部分。最后,一些设备可能位于I/O内存管理单元(IOMMU)后面,这可能需要配置来让设备访问内存中的特定页面。

如果你有一个缓冲区,你想与一个设备共享,并且缓冲区没有被分配到设备可以访问的内存中,你必须使用反弹缓冲区复制该内存的内容到其他设备可以访问它的位置。这可能是昂贵的,并极大地损害性能。因此,在所有使用缓冲区的设备都可访问的位置分配缓冲区的能力是很重要的。

因此,ION提供了一个接口,允许集中分配不同“类型”的内存(或“堆”)。在目前没有ION的内核中,如果您试图在DRM图形设备和video4linux (V4L)摄像机之间共享内存,则需要确保使用管理最受限设备的子系统来分配内存。因此,如果相机是最受限制的设备,则需要通过V4L内核接口进行分配,而如果图形是最受限制的设备,你就必须通过图形执行管理器(GEM)接口进行分配。相反,ION提供了一个单一的集中接口,允许应用程序分配满足所需约束的内存。

但是,ION没有提供一种方法来确定哪种类型的内存满足相关硬件的约束。这是一个留给特定设备的用户空间实现分配的问题(“Gralloc”,在Android的情况下)。这种硬编码的约束解决方案并不理想,但对于GEM和V4L分配缓冲区,没有更好的主流解决方案了。用户空间只需要知道什么是最受限制的设备。在大多数静态硬件设备上,如手机和平板电脑,这些信息是预先知道的,但这种限制使得ION不太适合以当前的形式在上游应用。

为了共享这些缓冲区,ION导出了一个链接到特定缓冲区的文件描述符。然后,这些文件描述符可以在应用程序之间传递,也可以传递给支持ION的驱动程序。最初这些是特定于ION的文件描述符,但是ION已经被重新设计,以利用dma-buf结构来共享。需要注意的是,虽然ION可以export dma-bufs,但它不会从其他驱动器import dma-bufs。

ION cache management

ION作为中央缓冲区分配器和管理器的另一个主要角色是处理DMA的缓存维护。由于许多设备维护它们自己的内存缓存,所以在序列化设备和CPU访问共享内存时,这些设备和CPU在让其他设备访问缓冲区之前刷新它们的私有缓存是很重要的。提供关于缓存的完整背景已经超出了本文的范围,所以如果读者有兴趣了解更多,我将向他们推荐这篇LWN文章https://lwn.net/Articles/282250/。

ION允许缓冲区用户设置一个标志,该标志描述分配时所需的缓存行为。这样,这些用户可以指定是否应使用ION进行缓存维护来缓存到该缓冲区的映射,是否要取消缓存但使用写合并(有关详细信息,请参阅这篇文章https://lwn.net/Articles/440221/),或者是否要取消缓存和显式管理缓冲区通过ION的同步ioctl()。相反,它提供了一个错误处理程序,以便仅在访问页面时才将其映射进来。此方法允许ION跟踪已更改的页面,并且只刷新实际接触的页面。

在缓冲区已经缓存并且ION执行缓存维护的情况下,ION进一步尝试通过在mmap()时延迟映射的创建来达到优化的目的。它提供了一个错误处理程序,因此仅在访问页面时才映射页面。此方法使ION可以跟踪已更改的页面,并且只刷新实际接触的页面。

此外,当ION为未缓存的缓冲区分配内存时,它正在管理尚未映射到内核空间的物理页面。由于这些缓冲区可能在它们被映射到内核空间之前被DMA使用,所以在映射时刷新它们是不正确的;这可能会导致数据损坏。因此,在分配这些缓冲区时,必须为DMA预先刷新它们。为此,ION提供的另一个性能优化是为DMA预刷新页面池。在一些系统上,在频繁的小缓冲区分配上为DMA刷新内存是一个主要的性能损失。因此,ION使用了一个页面池,它允许预先分配大量未缓存的页面并一次性刷新,然后当进行较小的分配时,它们只从池中挑选页面。

不幸的是,从上游的角度来看,这两种优化都有些问题。

  • 延迟的映射创建是有问题的,因为DMA API要么使用分散聚集的列表,要么使用更大的连续DMA区域;没有用于刷新单个页面的通用接口。正因为如此,当ION试图仅刷新已碰过的页面时,它最终会使用特定于arm的__dma_page_cpu_to_dev()函数,因为遍历分散聚集的列表来找到有问题的页面代价太高。这个接口的使用使得ION只能在32位的ARM系统上构建。
  • 预刷新的页面池也有问题:因为这些内存池是提前分配的,所以并不一定清楚哪个设备将使用它们。通常,当为DMA刷新页面时,必须指定下一个将访问内存的设备,因此,在IOMMU后面的设备的情况下,可以设置IOMMU,以便设备可以访问这些页面。ION通过使用32位arm特有的__dma_page_cpu_to_dev()接口再次解决了这个问题,该接口不接受设备参数。因此,这进一步限制了ION在更常见的IOMMUs环境中发挥作用的能力。

对于Android的使用来说,这一限制并不成问题。32位ARM是它的主要目标,而且,在英特尔系统上有一致的内存和较少的设备特定的限制,所以在那里不需要ION。此外,对于Android的用例,IOMMUs可以静态配置到特定的堆(例如,启动时预留的分离内存),所以没有必要动态地重新配置IOMMUs。但是这些限制对于上游ION来说是有问题的。问题是,如果没有这些优化,性能损失将会太大,所以Android不太可能使用更多上游友好的方法而忽略这些优化。

Other ION details

由于ION是一个集中式分配器,它必须具有一定的灵活性,以便处理所有类型的硬件。因此,ION允许实现定义自己的堆,而不是默认提供的公共堆。另外,由于许多设备可能有古怪的分配规则,例如在特定的内存库上分配,ION允许由堆实现定义一些分配标志。它还提供了一个ION_IOC_CUSTOM ioctl()多路复用器,允许ION实现自己的缓冲区操作,比如更细粒度的缓存管理或特殊的分配器。然而,这样做的缺点是,它使ION界面实际上是相当特定的硬件-在某些情况下,特定的设备需要对ION核心进行相当大的改变。因此,必须对使用ION接口的用户空间应用程序进行定制,使其为运行在其上的硬件使用特定的ION实现。同样,这对于内核和用户空间一起交付的嵌入式设备来说并不是一个真正的问题,所以不需要严格的ABI一致性,但对于上游合并来说是一个问题。

ION的这种特定于硬件和实现的特性也给ION使用的集中式分配器方法的可行性带来了问题。为了支持所有不同硬件的各种特性,它基本上具有特定于硬件的接口,从而迫使编写特定于硬件的用户空间应用程序。这就消除了使用集中式分配器而不是使用特定于设备的分配器的一些概念上的好处。然而,Android开发人员认为,ION是一个集中的内存管理器,它们可以减少每个设备驱动程序的复杂代码的必须实现,并且允许优化,而不是一遍又一遍实现各种不同的驱动程序。

总结一下围绕ION的问题:

  • 它没有提供发现设备约束的方法。
  • 该接口向用户空间公开特定于硬件的堆id。
  • 集中式接口并不是对所有设备都足够通用,因此它为特定于设备的选项公开了ioctl()多路复用器。
  • ION只从自身进口dma-bufs。
  • 它没有正确地使用DMA API,在为DMA刷新缓存时未能指定设备。
  • ION只建立在32位ARM系统上。

ION compared to current upstream solutions

在某些方面,GEM是一个类似的内存分配和共享系统。它提供了一个用于分配图形缓冲区的API,应用程序可以使用该缓冲区与图形驱动程序通信。另外,GEM为应用程序提供了一种将已分配的缓冲区传递给另一个进程的方法。为此,需要使用DRM_IOCTL_GEM_FLINK操作,该操作提供了特定于gem的引用,在概念上类似于可以通过套接字传递给另一个进程的文件描述符。这样做的一个缺点是,这些特定于gem的“flink”引用只是一个32位的全局值,因此可以被本来不应该访问它们的应用程序猜测,从而进行hack操作。GEM分配缓冲区的另一个问题是,它们是特定于它们被分配的设备的。因此,虽然GEM缓冲区可以在应用程序之间共享,但没有办法在不同的设备之间共享GEM缓冲区。

随着混合图形实现的出现(通常是独立的NVIDIA gpu与集成的Intel gpu相结合),设备之间共享缓冲区的需求出现了,于是创建了dma-bufs和PRIME(用于设备之间共享缓冲区的gem特定机制)。

在大多数情况下,dma-bufs可以被认为是缓冲区的封送结构。dma-buf系统不提供任何分配方法,但提供了一个通用结构,可用于在许多不同的设备和应用程序之间共享缓冲区。dma-buf结构使用文件描述符共享给用户空间,这避免了GEM flink id的潜在安全问题。

DRM PRIME基础设施允许驱动程序通过dma-bufs共享GEM缓冲区,从而使Nouveau驱动程序能够直接渲染到英特尔驱动程序将显示在屏幕上的缓冲区中。通过这种方式,GEM和PRIME一起提供了类似于ION的功能,允许ION在更传统的桌面机器上的soc上实现的缓冲区共享类型(都利用dma-bufs)。然而,PRIME并不处理设备可以访问的内存类型的任何信息,它只是允许GEM驱动程序利用dma-buf共享,前提是所有共享缓冲区的设备都可以访问它。

V4L子系统用于摄像机和视频录像机,也具有集成的dma-buf功能,允许相机缓冲器与图形卡和其他设备共享。它提供了自己的分配接口,但是,像GEM一样,这些分配接口只确保被分配的缓冲区与驱动程序管理的设备一起工作,而不知道缓冲区可能与其他驱动程序共享的约束。

因此,在当前的上游方法中,为了在设备之间共享缓冲区,用户空间必须知道哪些设备将共享缓冲区,以及哪些设备有最严格的限制;然后它必须使用最受约束的驱动程序使用的API来分配缓冲区。同样,就像在ION的情况下,用户空间没有可用的方法来确定哪个设备是最受限制的。

因此,上游问题可以这样总结:.

  • 没有现有的解决方案来解决设备之间共享缓冲区的约束。
  • 对于不同的设备有不同的分配API,因此,一旦用户确定了最受限制的设备,他们就必须使用该设备的匹配API进行分配。
  • IOMMU 和 DMA API 接口当前不允许在 ION 中使用的 DMA 优化。

Possible solutions

以前,在社区讨论 ION 时,提出了一些可能的方法。以下是我所知的。

一个想法是尝试将一个集中式的ION式分配器上游合并,保持相似的界面。为了解决有问题的约束发现问题,设备将通过sysfs和/或通过ioctl()导出不透明的堆cookie,这取决于设备的需求(设备可能有不同的需求,取决于设备特定的配置)。位的含义不会被定义到用户空间,而是可以被用户空间应用程序和在一起并传递给分配器,就像堆掩码当前是ION的一样。这为用户空间提供了一种解决约束的方法,但避免了将堆类型固定到ABI中的问题;它还允许内核定义给定机器的哪个位代表哪个堆,使接口更加灵活和可扩展。然而,对于用户空间来说,这是一个更复杂的界面,许多人不喜欢将约束信息公开给用户空间的想法,即使是以不透明的cookie的形式。

另一个可能的解决方案是允许dma-buf导出器不立即分配支持缓冲区。这将允许多个驱动程序在分配之前附加到dma-buf上。然后,当缓冲区第一次被使用时,分配完成;此时,分配器可以扫描附加的驱动程序列表,并能够确定附加设备的约束并相应地分配内存。这将允许用户空间不必处理任何约束问题。

虽然在最初设计dma-bufs时就计划了这种方法,但仍然缺少急需的基础设施,而且目前还没有使用这种解决方案的驱动程序。Android开发者担心这种延迟分配可能会导致应用程序热路径的不确定性延迟,但是,在没有实现的情况下,这还没有被量化.另一个缺点是,并非所有dma-buf导出器都需要这种延迟分配,因此它只适用于实际实现该特性的驱动程序。然而存在一种可能性,并不是所有的驱动想分享一个缓冲区都支持延迟分配,应用程序必须以某种方式检测功能,确保分配内存共享使用dma-buf出口者支持这个延迟分配功能。这种方法还要求导出驱动程序分配器分别处理这个约束问题(虽然可以提供一些常见的辅助函数)。

另一种可能的方法是使用通用的dma-buf导出器来原型dma-buf后期分配约束解决方案。这在某种程度上有点像ION,因为它是一个集中的导出器,但不会向用户空间公开堆id。然后,缓冲区将被附加到各种硬件驱动程序上,并且,在第一次使用时,导出程序将确定附加的约束并分配缓冲区。这将为上面的延迟分配方法提供一个试验场,同时在概念上与ION有一些相似之处。这种方法的缺点是,集中的接口可能无法处理可能需要的更复杂的特定于硬件的分配标记。

最后,这些建议都没有解决非通用缓存优化的使用,所以这些问题还需要进一步讨论。

Conference Discussion

我怀疑在Linux Plumbers Android + Graphics 会议上,我们不会找到一个神奇或简单的解决方案来让Android的ION功能上游。但我希望,通过关键开发人员从Android团队和上游内核社区能够讨论他们的需求和约束,能够倾听对方,我们可以了解子问题必须得到解决,我们可以采取什么方向前进。为此,我提出了几个问题供大家思考和讨论,希望我们能在讨论过程中找到答案:

  • 当前上游的dma-buf共享使用,比如PRIME,似乎集中在x86用例上(比如两个设备共享缓冲区)。这些接口真的能以通用的方式扩展到arm风格的用例(许多设备共享缓冲区)吗?由于非集中式分配要求导出器管理更多的逻辑并理解设备约束,因此这种方法似乎有可能最终无法维护。
  • ION的集中式分配风格在许多情况下都有问题,但也提供了显著的性能提高。这是一个太大的僵局还是有办法前进?
  • 如果集中式的dma-buf分配API是前进的方向,那么什么是最好的方法(比如:堆-cookie, vs后附加分配)?
  • 还有哪些潜在的解决方案还没有被考虑?
  • 有没有办法实现一些缓存优化使用的方式,也是更普遍适用的?可能延长IOMMU / DMA-API吗?
  • 考虑到Android的需求,下一步该怎么做才能集中到一个解决方案上呢?我们如何测试附加时间解决方案是否适用于Android开发者?ION还能提供什么呢?
  • Android开发者打算如何处理IOMMUs和非32位ARM架构问题?

Some general reference links follow:

  • General graphics overview [PDF]
  • Caching overview
  • Write combining
  • ION overviews: 1, 2, 3
  • DRM overview
  • GEM: 1, 2
  • DMA-buf
  • PRIME

Credits

Thanks to Laurent Pinchart, Jesse Barker, Benjamin Gaignard and Dave Hansen for reviewing and providing feedback on early drafts of this document, and many thanks to Jon Corbet for his careful editing.

https://lwn.net/Articles/565469/

继续阅读