LWN:DMA-BUF cache handling: Off the DMA API map (part 1)

关注了就能看到更多这么棒的文章哦~

DMA-BUF cache handling: Off the DMA API map (part 1)

June 4, 2020 This article was contributed by John Stultz 原文来自:https:/lwn.net/Articles/822052主译:DeepL

最近,5.6内核中加入了DMA-BUF heap interface(https://lwn.net/Articles/792733/ )。这个接口类似于Android厂商使用多年了的ION。然而,在试图推动厂商使用DMA-BUF heap的过程中,我们已经开始看到DMA API模型在一些方面不是非常适合现代移动设备的需求了。此外,在如何有效处理cache操作方面缺乏明确的指导,这导致厂商使用了自己定制的优化,每个设备都不相同,这些优化不够通用,从而无法合入upstream。本文将描述这个问题的本质,后续发表的续篇将探讨解决方向。

内核的DMA API都是为CPU和设备之间的内存共享这种场景而提供的。近年来,传统的DMA API已经扩展出了更多的接口,比如ION、DMA-BUF和DMA-BUF heap。但是,正如我们接下来谈到的,还是没有完全解决高效共享内存的需求。

从作为一个接口的角度来看,ION的接口不够固定,它允许应用程序将自定义的、含义不清的flag和参数传递给厂商自己特定的heap底层实现代码,并且这部分代码也都没有合入mainline。此外,由于这些接口的用户全部都是只在厂商自己的设备上使用,也都是用厂商自己定制过的内核代码,所以他们很少关注去创建一些对大家都有用的通用接口。因此,多个厂商可能会将相同的heap ID用在不同的用途上,或者他们可能会实现相同的heap功能,但使用了各不相同的heap ID和flag选项。更糟糕的是,许多厂商大幅改变了ION的接口定义和底层实现代码,因此除了名称和基本功能外,各个厂商的ION实现之间几乎没有共同点。ION基本上成了out-of-tree代码以及特定设备厂商黑客的游乐场。

同时,upstream的开发者普遍不喜欢接受合入接口,其实这些反对意见往往掩盖了厂商使用ION需要解决的更深层次的问题。但现在DMA-BUF heap接口已经在upstream了,一些厂商正试图从他们的ION heap实现中迁移过来(并且希望将结果提交到upstream)。在移植的过程中,他们看到DMA-BUF heap接口更加受限,因此也需要知道如何能实现他们使用ION时可以拥有的一些功能和优化。

诱惑厂商将他们的heap功能推向upstream带来的一个伴随影响就是,大家更加了解厂商使用DMA-BUF的细节和复杂性。由于性能对移动厂商来说很重要,所以他们花了很多时间和精力来优化数据如何在设备中传递。具体来说,他们使用buffer sharing不仅仅是为了在CPU和device之间移动数据,也是为了能在一个处理流水线中各个device之间传递数据。通常情况下,数据由一个device生成,然后由多个其他device处理,而CPU在这个过程中并未访问过这些数据。

例如,一个相机传感器(camera sensor)可以捕获原始数据(raw data)到一个buffer;然后,该buffer被传递给一个图像信号处理器(image signal processor, ISP),ISP会进行一些修正、调整,然后将生成一个新的buffer,该buffer直接传递给显示合成器(display compositor),并直接渲染生成图像显示到屏幕上。ISP也会产生另一个buffer,由编码器(encoder)进行格式转换来产生另一个buffer,然后可以传给神经网络引擎进行人脸检测(会用于后续帧的对焦修正)。

这种多个device共享buffer的模式在移动系统中很常见,但在upstream并不常见,而且它暴露了现有DMA API的一些局限性,尤其是在buffer处理方面。请注意,虽然CPU和device都可以有自己的buffer,但在本文中,我特别关注CPU buffer;设备buffer则由各自的设备驱动程序处理。

The DMA API

当我们仔细审视现有的DMA API时,我们会发现它实现了一个清晰的模型来处理CPU和单个device之间的内存共享。DMA API对如何处理缓冲区的 "ownership"的时候(跟CPU cache情况相关)特别谨慎,以避免数据被破坏。默认情况下,内存被认为是CPU虚拟内存空间的一部分,CPU是它事实上的owner。这里有个假设是CPU可以随时读写内存,只有当允许设备对内存进行DMA操作时,内存的所有权才会交给device。

DMA API描述了两种类型的内存架构,称为 "consistent(一致性) "和 "non-consistent(non-coherent)"(有时也称为 "coherent "和 "non-coherent")。在consistent-memory架构下,对内存内容的更改(即使是由device完成的)都会导致那些cached data被update或invalidate。因此,device或CPU向内存写入内容后,可以立即读取内存,而不必担心CPU cache的影响(尽管DMA API指出在设备读取前可能需要flush CPU cache)。x86世界的大部分情况都是属于consistent memory(有一些例外,通常是GPU相关的),然而在Arm界,我们看到很多device与CPU是non-consistent的,因此是non-consistent memory架构。也就是说,随着Arm64设备也开始使用PCIe这类功能,同一个系统上可能经常会有coherent和non-coherent device组合使用。

对于non-coherent内存,必须额外注意处理好CPU的cache状态,以避免破坏数据。如果不遵循DMA API的ownership规则,device可能会在CPU不知情的情况下向内存写入数据,这可能导致CPU仍旧使用CPU中过时的数据。类似地,CPU也可能会从其cache中做cache flush(clean)操作来把过时的数据写入内存,从而覆盖了device写入的新数据。这两种情况都会导致数据损坏。

想了解更多的话,Laurent Pinchart在ELC 2014上关于DMA API的演讲非常棒,幻灯片[PDF]在此 https://elinux.org/images/3/32/Pinchart--mastering_the_dma_and_iommu_apis.pdf

因此,DMA API规则有助于以通用的方式建立正确的CPU cache处理,确保设备向内存写入之前会对CPU cache进行invaldiate操作,而在device读取内存之前会flush CPU cache来写入内存。通常,这些缓存操作是在CPU和设备之间转移buffer所有权时完成的,比如内存被map后又从DMA设备上unmap操作时进行的(利用了 dma_map_single() 等函数)。

从DMA API的角度来看,跟多个设备共享buffer与跟单个设备共享是一样的,只是共享是在一系列独立操作来实现的。CPU先分配一个buffer,然后将该buffer的所有权传递给第一个设备(可能会flush CPU cache)。此后就允许device进行DMA操作,并在操作完成后对buffer进行unmap操作(可能需要invalidate CPU cache),CPU重新取得所有权。然后对下一个设备和之后的设备重复这个过程。

这里的问题是,这些cache操作会累加起来,特别是当CPU在这个过程中根本没有实际接触过buffer内容的情况下。理想的情况是,如果我们与一系列cache-incoherent device共享buffer,那么CPU cache在最开始flush之后,buffer可以被这一串device直接使用,而无需额外的cache操作。DMA API在这里确实允许一些灵活性,所以有一些方法可以让map操作跳过CPU sync的动作。也有 dma_sync_*_for_cpu/device() 这些API,允许在已经有mapping的情况下进行显式的cache操作。但这些都是 "expert-only"的工具,并没有给出多少指导,开发者相信驱动程序在使用这些优化时都会特别特别小心。

DMA-BUFs

引入DMA-BUFs的目的是为应用程序和驱动程序提供一种通用的方式来共享内存buffer的句柄(handle)。这些DMA-BUFs本身是由DMA-BUF exporter创建的,这些exporter是一个分配特定类型的内存的驱动程序,同时它也提供了hook来处理对kernel, user space, device等情况下的各种buffer mapping/unmapping操作。

针对一个设备的DMA-BUFs的一般使用流程如下(详见 dma_buf_ops structure):

  • dma_buf_attach()

将buffer 连接(attach)到将来会使用这个buffer的设备上。如果有需要的话,exporter可以对buffer进行移动,让它可以供一个新的device来访问(也可能返回错误值)。一个buffer可以连接到多个设备。

  • dma_buf_map_attachment()

将buffer映射到attached device的地址空间。buffer可以被多个外接设备map。

  • dma_buf_unmap_attachment()

从attached device的地址空间中unmap这个buffer。

  • dma_buf_detach()

标志着设备已经完成了对buffer的处理,exporter可以做任何清理工作了。

如果我们从经典的DMA API角度来看,我们会认为一个DMA-BUF通常是由CPU拥有的。只有当 dma_buf_map_attachment() 被调用时,缓冲区的所有权才会转移到设备上(并进行相关的cache flush)。然后在调用 dma_buf_unmap_attachment() 时,缓冲区将被取消映射,所有权将回到CPU手中(同样需要适当的cache invalidate)。这实际上是让DMA-BUF exporter成为了负责保证DMA API所有权规则的角色。

这个方案的局限之处在这种情况下会出现:buffer pipeline由许多device组成,而CPU实际上并不访问buffer。按照DMA API,每次调用 dma_map_sg() 和 dma_unmap_sg() ,都会导致大量的cache-maintainous操作,非常影响性能。这一点在4.12的一系列清理工作(让ION正确使用DMA API)落地后,ION的用户们都有直观的感受。之前,ION有很多hack用法,不符合DMA API的要求,导致某些情况下buffer会损坏,详情请看Laura Abbott演讲的幻灯片( http://www.linuxplumbersconf.net/2014/ocw/system/presentations/2409/original/04%20-%20iondma_foreview_v2.pdf )。这次合规性清理导致ION使用时的性能急剧下降,导致一些厂商在基于4.14的产品中把ION代码恢复到4.9的版本,还有一些厂商又创建了更多的hack改动来提高性能。

那么,我们如何才能让DMA-BUF exporter更好地与DMA API保持一致,但在多个设备对buffer进行流水线操作时又能保证现代设备所需达到的性能呢?在本文的第二部分中,我们将继续讨论DMA-BUF中的一些独特语义和灵活性,可以让驱动程序能避免这些性能负面影响(不过可能有些违背DMA API使用指南中的通常做法),也会介绍这种灵活性所带来的缺点。最后,我们将分享一些关于如何避免这些缺点的想法。

请静待LWN后续part 2

LWN文章遵循CC BY-SA 4.0许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值