一、DMA-BUF等概念的介绍
首先需要明确DMA-BUF,Dma buffer,ION和DMA-BUF Heap是不同的概念。
在Android 多媒体系统中为了减少因不同进程之间内存的多次拷贝而产生的不必要的开销,最直接的想法是希望跟硬件设备进行交互的应用能有一个内存能跟设备驱动程序共享数据,理想情况是这个buffer能够直接是通过memory map得到,并且是物理连续的,这样就能让DMA直接读写这块buffer,减少CPU或者外设来访问内存的开销。
为此kernel引入了DMA-BUF这个框架(或者说是子系统),来解决CPU和各种不同外设驱动之间buffer共享的问题。
-
其中Dma buffer是一块允许在CPU和其他子系统或各种IO外设间共享的buffer。
-
而DMA-BUF是这个buffer的描述和使用框架。框架提供了对dma-buffer的描述结构体,和基于file的dma-buf使用接口,同时完成了操作时刷cache的动作。
DMA-BUF框架实现了一个dma-buf的文件系统(filesystem),这个文件系统直接通过调用挂载接口挂载到了内核(没有提供挂载节点),每个dma-buf实例,会在文件系统中创建出inode及file,并将fd返回给调用者。调用者通过对file的ioctrl、map等操作,来操作dma-buffer。
也就是说为了实现buffer的共享,DMA-BUF将buffer与file关联起来,通过file来间接的引用和共享buffer, 即 dma-buf 既是块物理 buffer,又是个 linux file。buffer 是内容,file 是媒介,只有通过 file 这个媒介才能实现同一 buffer 在不同驱动之间的流转。
代码位置:
common/driver/dma-buf/
//或
/kernel/msm-5.4/drivers/dma-buf/dma-buf.c
如上图中所示,在DMA-BUF框架中有两个很重要的概念,一个是exporter,另一个是importer。
dma buffer 本身由 DMA-BUF exporter 创建,DMA-BUF exporter 是一个驱动程序,它可以分配特定类型的内存,而且还为 kernel、user space、device 提供了多种回调函数来处理 buffer 的 map 和 unmap 问题,所以exporter相当于一个buffer的生产者或者说是提供者。
而DMA-BUF importer则对应dma buffer的使用者,比如上层应用,而因为dma buffer是一种共享内存,所以同一个buffer的importer可以有多个,二者的特性说明:
-
exporter:
- 实现struct dma_buf_ops中的buffer管理回调函数;
- 允许其他消费者通过dma-buf的sharing APIS来共享buffer缓冲区;
- 通过struct dma_buf结构体管理buffer的分配、包装等细节工作;
- 决策buffer的实际后端内存的来源;
- 管理scatterlist迁徙;
-
importer:
- 是共享buffer的使用者之一;
- 无需担心缓冲区是如何/在哪里创建的;
- 通过struct dma_buf_attachment结构体访问用于构建buffer的scatterlist,并且提供将buffer映射到自己地址空间的机制,这样可以访问到内存的同块区域,实现共享内存
二、基于DMA-BUF框架的内存分配器
过去几年中,Android系统里面已经使用过多种DMA buffer分配方案:首先是PMEM,然后被Android 系统下专用于分配 Graphic Buffer 的内存分配器ION所替换。
ION 是基于 DMA-BUF 实现的,可以在多个设备之间实现 buffer 共享,它的代码一直存放在 staging 目录下,不过再过几年可能就不复存在了,因为ION自从2012年开始商用以来也存在着不少问题。
随后John Stultz发布了一个patch set,实现了DMA-BUF heap,是ION的一个演进版本,在之前的 Android 12 中,GKI 2.0 将 ION 分配器已经被替换为了 DMA-BUF Heap,因为DMA-BUF HEAP的分配器具有更具有稳定的 ABI和安全性,也更标准化。
综上需要明确的是,不管是ION还是DMA-BUF heap都是基于DMA-BUF框架的一种分配器allocator,也就是说ION和DMA-BUF heap是上面DMA-BUF中提到的exporter驱动。
- ION分配器:
ION是基于DMA-BUF框架实现的一种内存分配器,在多媒体部分中使用的最多,从 Camera 到 Display,从 Mediaserver 到 Surfaceflinger,都会利用 ION 进行内存分配管理。
ION提供了多个memory pool,称为“heaps”。它们有不同的属性,例如有的是物理连续的,有的不是。这些heap包括“system”heap,用来供vmalloc_user()分配;“system_contig”h eap,是用kzalloc()分配的。还有“carveout” heap,是在启动阶段就额外划分出来的物理连续区域。应用程序可以用一些user space的API来对这些heap分配、释放、共享内存。
ION的特点是它可以被用户空间的进程之间或者内核空间的模块之间进行内存共享,且这种共享方式是零拷贝的。
代码位置:
//本文的许多结构都是以内核4.14(kernel-4.14)为例,因为在内核5(kernel-5.10)以后的版本中,ION逐渐被弃用,许多代码已经不太相同
common/drivers/staging/android/ion/
kernel-4.14/drivers/staging/android/ion/
//或
/kernel/msm-5.4/drivers/staging/android/ion/
kernel-5.10/drivers/staging/android/ion/
ION 是在各种 heaps 上分配内存,通过 ion_buffer 来描述所分配的内存。
每个heap中分配了若干个buffer,不同的client是通过文件描述符fd来实现内存共享的。
ION是在各种heaps上分配内存,其中定义了四种不同的heap,实现不同的内存分配策略。
- ION_HEAP_TYPE_SYSTEM : 通过vmalloc分配内存(vmalloc : 虚拟地址连续但物理不连续内存)
- ION_HEAP_TYPE_SYSTEM_CONTIG: 通过kmalloc分配内存(kmalloc : 物理连续内存)
- ION_HEAP_TYPE_CARVEOUT: 在保留内存块中(reserve memory)分配内存
- ION_HEAP_TYPE_CUSTOM: 由客户自己定义
- DMA-BUF heaps分配器:
DMA-BUF heaps也是一种基于DMA-BUF框架实现的内存分配器,是一种取代ION分配的方式。
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是如何使用的。
DMA-BUF Heap的代码主要位置:
common/driver/dma-buf/heaps/
//主要文件
common/include/linux/dma-heap.h
common/include/uapi/linux/dma-heap.h
common/driver/dma-buf/dma-heap.c //或/msm-kernel/drivers/dma-buf/dma-heap.c
common/drivers/dma-buf/heaps/system_heap.c
common/drivers/dma-buf/heaps/cma_heap.c
三、ION heap和 DMA-BUF heap的区别
- 分配方式不同
-
ION需要指定堆 ID/掩码和标志(heap id和heap flag)才能进行分配。
-
DMA-BUF heap只需要指定heap的名称就能分配内存
- DMA-BUF heap可通过不同的字符设备名控制访问权限,更安全
-
对于 ION heap而言,所有的内存分配都是从通过访问同一个字符设备 /dev/ion 来完成的。
-
对于DMA-BUF heap来说,每个DMA-BUF heap都对应生成一个位于/dev/dma_heap/<heap_name> 的字符设备,因此可以通过 sepolicy 单独控制对每个堆的访问权限。
- DMA-BUF heap不支持堆专用标志,所以更容易对外提供统一的接口,利于标准化
- 对于ION heap来说,会通过专用标志来实现不同的内存分配策略。
如ION 在 SYSTEM_HEAP 上分配内存时,使用ION_HEAP_SYSTEM来分配,而对于是否分配带 cache 的 buffer,则是通过传入 ION_FLAG_CACHED 标志位来实现的。(也就是说可以支持同一个heap中传入vendor私有的heap flags来实现不同的分配方式)
//ION中系统堆中的缓存分配
//libion
ion_alloc_fd(ionfd, size, 0, ION_HEAP_SYSTEM, ION_FLAG_CACHED, &fd)
因为是专用标志,也就是私有化,不同的vendor厂商实现不同不容易统一。
- 对于 DMA-BUF heap来说,则不支持堆的专用标志。每种不同的分配都是在不同的堆中完成的。
例如,对于在ION中system heap的cached和uncached两种分配方式在DMA-BUF heap中则是会生成两个独立的heap: /dev/dma_heap/system 和 /dev/dma_heap/system_uncached
//DMA-BUF中系统堆中的缓存分配
//libdmabufheap
allocator->Alloc("system", size)
对于HEAP堆类型的简单说明:
-
CMA HEAP(物理连续 buffer)
-
SYSTEM HEAP(物理非连续 buffer)
SYSTEM HEAP 默认只支持带 Cached 的 buffer,只有 CPU 需要参与访问的 buffer 才需要带 cached,这样可以加快 CPU 的访问速度,但是在 CPU 和 DMA 设备之间共享内存时,需要来回刷 Cache,这会增加系统开销。 -
SYSTEM-UNCACHED HEAP(应用程序申请不带 cache 的 buffer)
不带 cached 的 buffer 则通常只用于 DMA 设备之间的 buffer 共享,因此省去了 Cache 同步所带来的额外开销,极大的提高了 DMA 设备访问 buffer 的效率。
参考
dma-buf 由浅入深(八) —— ION 简化版
Buffer Sharing and Synchronization
Camera Buffer Management
dma-heap: Add a system-uncached heap
DMA_HEAP源码