多年来,Android系统已经为DMA缓冲区提供了几个分配器;首先是PMEM,然后是其替代ION。ION分配器在2012年左右就开始使用了,但是它仍然停留在内核的staging树中。将ION加入主线的工作始于2013年;在那个时候,分配程序有多个问题,这使得mianline包含ION仍然不可能。最近,John Stultz发布了一个补丁集,引入了DMA-BUF堆,这是ION的一个进化,它的设计就是要做到这一点——将Android的dma缓冲区分配程序合入到主流Linux内核中。
与设备交互的应用程序通常需要与设备驱动程序共享的内存缓冲区。理想情况下,它将是物理上连续的内存映射,允许直接DMA访问,并且在双方同时访问数据时开销最小。ION的主要目标是支持该用例;它实现了定义和共享这些内存缓冲区的统一方式,同时考虑了设备和平台施加的约束。
ION提供了许多称为“堆”的内存池,它们具有不同的属性,例如它们在物理上是连续的。这些堆包括:
- “ system-heap”(包含通过vmalloc_user()分配的内存);
- “ system_contig-heap”(使用kzalloc());
- “ carveout-heap”(管理在引导时预留的物理连续区域)。
- 用户可以自己创建heap
用户空间API允许应用程序从任何这些堆中分配,释放和共享缓冲区。
ION是在主线外与DMA缓冲区共享(DMA-BUF)和连续内存分配器(CMA)之类的主线内内核API并行开发的。它自然会复制其部分功能。此外,由于ION的第一个平台是基于32位ARM处理器的Android,因此在没有通用通用API的情况下,它使用了ARM特定的内核API。显然,这对上游流程没有帮助。新的DMA-BUF堆修补程序集是对ION内部的完全改造:它使用CMA从特殊的内存区域实现物理上连续的堆,并且不使用任何特定于体系结构的功能。 包含一个随附的自检功能API。
Heaps and allocations
每个堆由/dev/dma_heap 目录中的一个特殊文件表示。应用程序将打开一个特定的堆文件,以便能够从该堆进行分配。使用对应的文件描述符加上DMA_HEAP_IOC_ALLOC ioctl()完成分配。此命令采用一个参数,即指向dma_heap_allocation_data结构的指针:
struct dma_heap_allocation_data {
__u64 len;
__u32 fd;
__u32 fd_flags;
__u64 heap_flags;
__u32 reserved0;
__u32 reserved1;
};
---可以看到,这个和ion的使用方式完全不一样,ion是打开/dev/ion拿到fd + flag实现分配
- len是所需分配的大小(以字节为单位)。
- fd,设置结构时应将fd设为零;它将由DMA_HEAP_IOC_ALLOC操作用一个表示分配的DMA-BUF的文件描述符填充。
- fd_flags描述了如何设置文件描述符(有效标志是O_CLOEXEC,O_RDONLY, O_WRONLY和O_RDWR),
- heap_flags存储传递给堆分配器的标志;它应该设置为零。
- 最后,有两个保留字段也应设置为零。
- 当分配成功该ioctl返回0。
要访问已分配的内存,应用程序需要在返回的缓冲区文件描述符上调用mmap()。当不再需要缓冲区时,用户代码应该关闭其文件描述符,这将释放已分配的内存。
总的来说,应用程序使用的每个堆都有一个相关的文件描述符,每个分配的缓冲区也是如此。DMA-BUF堆中的缓冲区句柄是通用的DMA-BUF句柄,可以传递给理解这些缓冲区的驱动程序。这个API与原来的ION方法不同,在原来的ION方法中,分配器本身有一个集中的特殊文件,缓冲区有单独的非标准句柄。还有一个特定的ioctl()来共享DMA-BUF堆中不存在的内存。
Memory access synchronization
在处理设备和cpu之间共享的缓冲区时,一个复杂的问题是决定在任何给定时间谁可以访问它。这是因为缓存:处理器的访问通常涉及缓存,而设备访问可能不涉及。并发访问可能会导致缓存和内存不匹配,从而导致数据损坏。为了处理这个问题,驱动程序和应用程序必须在他们需要访问共享内存进行读写的时候声明他们需要访存;这允许内核正确地管理缓存。
原始ION无法处理同步;为此,DMA-BUF堆直接使用DMA-BUF API。同步由DMA_BUF_IOCTL_SYNC ioctl()控制,该结构采用带有标志的结构来描述同步类型。在访问共享缓冲区之前,用户代码应将DMA_BUF_SYNC_START标志与所需的访问模式(DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE或DMA_BUF_SYNC_RW两者结合使用)一起使用。访问完成后,应使用具有相同访问模式标志的DMA_BUF_SYNC_END。
---和fence不同的地方,是保证cache和内存的一致性,而不是不同设备之间的同步。“并发访问可能会导致缓存和内存不匹配”;cpu访问的是cache,而外设访问的是memory,这两者可能有差异;或者可能说的是dma的cache和memory之间的不一致?
Available heaps and adding new ones
该实现使用模块化的方法来处理堆;它定义了每个特定堆实现所使用的通用框架。补丁集提供了两种堆类型:使用alloc_page()的系统堆和使用cma分配器(如果系统中可用)的“cma”堆。
与原始的ION一样,由应用程序开发人员选择正确的堆,它必须与所涉及的所有设备的需求相对应。这是一个限制,但问题是复杂的,目前没有主流的解决方案存在。在最可能使用DMA堆的嵌入式系统中,硬件配置是固定的,因此设备的内存限制是预先知道的。
内核开发人员得到了一个框架来添加更多的堆,目前这只能在引导时完成。每个堆需要填充操作结构和导出结构。操作结构体,结构体dma_heap_ops,目前很简单,只包含一个函数:
struct dma_heap_ops {
int (*allocate)(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags);
};
导出结构的格式如下:
struct dma_heap_export_info {
const char *name;
struct dma_heap_ops *ops;
void *priv;
};
- name是堆的名称,
- ops是指向上面所示操作结构的指针,
- priv是存放特定于堆的数据的地方。
- 参数完全匹配DMA_HEAP_IOC_ALLOC ioctl();
- 分配器应该返回已分配的DMA-BUF的文件描述符。
填完这两个结构后,堆实现需要使用以下方法添加堆:
struct dma_heap *dma_heap_add(const struct dma_heap_export_info *exp_info);
Next steps
自从ION首次问世以来,Linux内核已经获得了可以在新实现中使用的通用功能。DMA-BUF堆接口很简单,补丁集有意排除了某些功能(包括一些堆类型)和性能优化。它的目标是定义API。优化和新功能预计将在以后发布。合理的下一步是优化分配性能。Stultz已准备好补丁程序,但决定不将其包括在提交中,以简化审核过程。预期分配性能将与原始ION相似甚至更好。
该补丁集目前处于第六版本,简单,分步的方法似乎正在起作用。讨论没有重大争议。我们可以期望新的API很快就会出现在内核中。