
Destaging ION
July 9, 2019
This article was contributed by Marta Rybczyńska
过去几年中,Android系统里面已经使用过多种DMA buffer分配方案了。首先是PMEM,然后被ION所替换。ION自从2012年开始商用以来,一直存在于kernel的staging代码里。从2013年就开始试着把ION加入Linux mainline,那时还有不少问题,因此没能合入。最近,John Stultz发布了一个patch set,实现了DMA-BUF heap,是ION的一个演进版本,希望能借此来把Android的DMA-buffer allocator推入Linux内核代码。
需要跟硬件设备进行交互的application通常都需要有一个memory buffer能跟设备驱动程序共享数据。最理想情况是这个buffer能够直接是通过memory map得到,并且是物理连续的,这样就能让DMA直接读写这块buffer,减少CPU或者外设来访问内存的开销。ION就是用来支持这种场景的,它实现了统一的API来定义、共享这类memory buffer,同时也把外设和平台对这种场景的一些限制考虑了进来。
ION提供了多个memory pool,称为“heaps”。它们有不同的属性,例如有的是物理连续的,有的不是。这些heap包括“system”heap,用来供vmalloc_user()分配;“system_contig”h eap,是用kzalloc()分配的。还有“carveout” heap,是在启动阶段就额外划分出来的物理连续区域。应用程序可以用一些user space的API来对这些heap分配、释放、共享内存。
ION开发的时候,kernel也有一个DMA buffer sharing (DMA-BUF)的API在同时进行开发,此外还有contiguous memory allocator (CMA)。所以功能上有些重复。此外,ION起初是在32-bit ARM处理器上为Android开发的,所以用了一些ARM特有的kernel API。这些都是合入linux mainline的阻碍。而这个新的基于DMA-BUF heap的patch就对ION的内部实现做了彻底的重构,使用CMA来基于某个特殊内存区域实现物理连续heap,也不再使用ARM特有的函数了。patch set还带了一个self-test,展示了API是如何使用的。
Heaps and allocations
每个heap都在/dev/dma_heap目录下有个对应的特殊文件。应用程序可以打开某个heap文件,然后就能从heap分配buffer了。相关的分配函数是DMA_HEAP_IOC_ALLOC这个ioctl()。有一个参数,是指向dma_heap_allocation_data结构的一个指针。
len是分配的byte数,fd在结构初始化的时候是0,在DMA_HEAP_IOC_ALLOC操作完成后会填入一个文件描述符,代表分配得到的DMA-BUF。fd_flags描述了文件描述符的一些配置,例如O_CLOEXEC,O_RDONLY,O_WRONLY,O_RDWR等,而heap_flags存放着传递给heap allocator的标志,应该设为0。最后还有2个reserved参数,也都设为0即可。ioctl()在成功后会返回0。
应用程序需要对返回的buffer的文件描述符调用mmap(),这样就能访问分配到的内存了。如果buffer后面不再需要用了,可以直接close这个文件描述符,就能释放这块内存了。
总之,application所用到的每个heap都有一个相关的文件描述符,每个分配出来的buffer也有对应的文件描述符。DMA-BUF heap里得来的buffer handle都是通用的DMA-BUF handle,可以直接传递给其他任何能处理这类buffer的驱动程序。这个API跟此前的ION方案不太一样,当时的分配器只有一个特殊文件节点来总管这些分配工作,而且buffer对应的handle都是各种非标准的handle。当时还有一个专用的ioctl()才能实现memory的共享,现在DMA-BUF heap里没有了。
Memory access synchronization
处理CPU和设备共享的buffer的时候,有一类比较复杂的问题,就是决定在某个时刻谁(device, or CPU)有权限访问这块buffer。这主要是跟cache的存在有关系。CPU访问memory的时候通常需要经过cache,而device访问memory就不需要。同时访问的话,可能会导致cache和内存里的数据对不上,从而导致数据错乱。相应的解决方案就是让driver和application在要读写shared memory的时候都要明确告知对方,这样才能让kernel维护好cache内容。
此前的ION方案不会管理这个双方同步的问题。DMA-BUF heap会用DMA-BUF API来实现同步。具体来说就是DMA_BUF_IOCTL_SYNC ioctl(),会传入一个包含flag的结构来描述具体的同步信息。在访问一个share buffer之前,application会先用DMA_BUF_SYNC_START flag,并且附带相关的访问模式(DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, 或者DMA_BUF_SYNC_RW就是前两者的组合)。访问结束之后,就用DMA_BUF_SYNC_END带着相同的flag来告知对方。
Available heaps and adding new ones
目前的实现方案里采用了模块化的方式来管理heap。定义了一个通用的框架,来管理每类heap的实现。patch set里面主要提供了两类heap:使用alloc_page()的system heap,还有使用CMA allocator的“cma” heap(如果系统里有这个heap的话)。
跟此前的ION方案类似,也是需要application开发者来选择应该用哪个heap,因为这个需要跟他要用到的各个device的需求有关。这算是一个限制吧,不过这个问题很复杂,Linux mainline上也没有什么现成的方案能解决此问题。在嵌入式系统的环境里,通常都会使用DMA heap,并且硬件的配置也都比较固定,所以device需要什么样的memory,在application开发之前对开发者都是已知的了。
Kernel开发者也得到了一个框架,用于添加新的heap,不过目前只能在系统启动的时候一次性加进来。每个heap都需要填写一个operation structure并且对外公告出去。。这个结构就是struct dma_heap_ops目前还很简单,只包含如下一个函数:
用于对外公告的structure格式如下:
name就是heap的名字,ops就是指向上面那个odma_heap_ops结构的指针,priv可以放置heap相关的数据。这些参数跟DMA_HEAP_IOC_ALLOC ioctl()的结构完全一样。allocator返回的就是对应这个分配好的DMA-BUF的文件描述符。
填好上述两个结构之后,heap的底层实现需要调用下面这个函数来增加heap:
Next steps
自从ION第一次出现以来,Linux kernel有了很多发展,也拥有了更完善的支撑函数,可以用在新的实现方案里了。DMA-BUF heap接口非常简单,这个patch set也特意没有包含某些功能(例如更多的heap类型)和性能优化部分。它的目的主要是要定义清楚API。优化和更多功能都可以今后再加进来。合理的下一步行为就是来对allocation阶段的性能进行优化。Stultz已经有patch了,不过决定先不提交,从而加快review流程。分配的性能应该是可以达到甚至超过此前ION方案的。
目前这个patch set已经达到了第6版,看起来目前的这个简单的、逐步推进的方案看起来很有效,相关的讨论也都进行的很顺利。很有可能不久之后就能看到kernel合入这个新的API了。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
极度欢迎将文章分享到朋友圈热烈欢迎转载以及基于现有协议上的修改再创作~
长按下面二维码关注:Linux News搬运工,希望每周的深度文章以及开源社区的各种新近言论,能够让大家满意~