Edit online

预留内存管理流程

4 Dec 2024
Read time: 4 minute(s)

CMA

Linux-3.5 引入了一套 Contiguous Memory Allocator,简称 CMA,基于 DMA 映射框架为内核提供连续大块内存的申请和释放。CMA 主要思路是将预留内存纳入 DMA 映射管理,可以给系统内所有设备共享使用,这样就既解决了为 GPU、Camera、显示等图像处理类模块预留大块的连续内存,又解决了预留内存被空置的问题,提升内存使用率。

CMA 本身不是一套分配内存的算法,它的底层仍然要依赖伙伴算法系统来支持,可以理解为 CMA 是介于 DMA mapping 和内存管理之间的中间层。


cma

CMA 的具体功能有:

  1. 在系统的启动过程中,根据内核编译配置、或者 DTS 配置将内存中某块区域用于 CMA,然后内核中其他模块可以通过 DMA 的接口 API 申请连续内存,这块区域我们称之为 CMA area。

  2. 提供 cma_alloc()和 cma_release()两个接口函数用于分配和释放 CMA pages。

  3. 记录和跟踪 CMA area 中各个 pages 的状态。

  4. 调用伙伴系统接口,进行真正的内存分配。

CMA 主要接口

  1. CNA area 的声明

    setup_bootmem() -> dma_contiguous_reserve(),定义在 kernel/dma/contiguous.c,其中有确定 CMA area size 的代码如下:
    #ifdef CONFIG_CMA_SIZE_SEL_MBYTES
           selected_size = size_bytes;
    #elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
           selected_size = cma_early_percent_memory();
    #elif defined(CONFIG_CMA_SIZE_SEL_MIN)
           selected_size = min(size_bytes, cma_early_percent_memory());
    #elif defined(CONFIG_CMA_SIZE_SEL_MAX)
           selected_size = max(size_bytes, cma_early_percent_memory());
    #endif

    然后会调用 dma_contiguous_reserve_area() -> cma_declare_contiguous() 去初始化 CMA 的配置参数。

    注:

    计算 CMA 内存大小的过程中,size_bytes 来源于内核编译配置中 CONFIG_CMA_SIZE_MBYTES,通过计算将 size 限定在 4 MB 对齐。 参考CMA 配置 进行设置。

  2. CMA 初始化

    mm/cma.c 中的 cma_init_reserved_areas() -> init_cma_reserved_pageblock(),其中会设置 page 属性为 MIGRATE_CMA。

  3. 申请 CMA

    使用 DMA 标准接口 dma_alloc_coherent() 和 dma_alloc_wc(),会间接调用 dma_alloc_from_contiguous()。

  4. 释放 CMA

    使用 DMA 标准接口 dma_free_coherent()。

DMA-BUF

CMA 解决的是预留内存空闲期间如何给其他设备共享的问题,DMA-BUF 解决的是使用期间多个设备共享的问题、以及内核态和用户态如何共享内存的问题。DMA-BUF 可减少多余的拷贝,提升系统运行效率。

DMA-BUF 最初原型是 hrbuf,于 2011 年首次提出,实现了 “Buffer Sharing” 的概念验证。shrbuf 被社区重构变身为 DMA-BUF,2012 年合入 Linux-3.3 主线版本。

DMA-BUF 被广泛用在多媒体驱动中,尤其在 V4L2、DRM 子系统中经常用到。

DMA-BUF vs ION

从 Linux-5.6 开始,DMA-BUF 正式合入了原来 ION 的 heap 管理功能,社区的主分支是打算抛弃 ION 了。在 Linux-5.11,主分支已经删除了 drivers/staging/android/ion 代码,原因是“原厂对 ION 的支持在社区中不太活跃,很难持续更新 ION”,而且 ION 还带来了几个 break。

  1. ION 有大量的 heap 逻辑管理,而 DMA-BUF 的 heap 更多是分配接口上的管理(社区更容易接受)。

  2. ION 是生产一个字符设备 /dev/ion,而 DMA-BUF 为每个 heap 生成一个字符设备,便于用户态的 heap 区分和权限管理。

  3. ION 限制最多 32 个 heap,DMA-BUF 没有这个限制。

  4. DMA-BUF 中实现了两个初始的 heap:system heap 和 cma heap,与原 ION 中的功能类似,但做了很大简化(为了方便社区 review),删掉原来 ION 中针对 system、cma 做过的一些优化,涉及 uncached buffers、large page allocation、page pooling 和 freeing。 DMA-BUF 中的 CMA,只添加了 CMA 区域。原 ION 中的 CMA 是添加了所有 DMA 区域。

工作机制

为了解决各个驱动之间的 buffer 共享问题,DMA-BUF 将 buffer 与 file 结合使用,让 DMA-BUF 既是一块物理 buffer,同时也是个 Linux 标准 file。典型的应用框图如下:


dma_buf1

分配 buffer 的模块为 exporter,使用该 buffer 的模块为 importer。

DMA-BUF 支持连续物理内存、散列物理内存的 buffer 管理。ArtInChip 平台目前只支持连续物理内存的 DMA-BUF。

主要接口
  • 内核空间
    • 作为 exporter

      注册接口 dma_buf_export()

    • 作为 importer

      获取 buf 的接口:dma_buf_get,``dma_buf_attach()``,``dma_buf_map_attachment``。

  • 用户空间

    通过 ioctl 来管理 DMA-BUF。

基于 DMA-BUF 的 Video Layer buffer 管理

UI Layer 的 buffer,由 driver 申请,通过 /dev/fb0 透传到用户态,用户态使用 mmap() 实现 buffer 共享。

但对于 Video Layer 的 buffer,情况有所不同,申请多大 buffer、多少个 buffer 都是由应用场景确定,所以应该是用户态发起申请。同时这个 buffer 还要满足物理连续的特性,DE 硬件才能用来做图层叠加处理。


dma_buf_heap

左图,是 ION 时代的使用方法,新版 Linux(Linux-5.6 以后)的 DMA-BUF 已经支持了 heap 功能,从功能上完全可以替代 ION。对用户态来说,看到的差别不再是单一的 /dev/ion 设备节点,而是有多个 /dev/* 设备节点,比如 CMA heap 会生成 /dev/dma_heap/reserved,System heap 会生成 /dev/dma_heap/system。

使用 DMA-BUF 的情况下,APP 和 fb 驱动共享 buffer 的初始化流程如下图所示:


dma_buf_app

其中第 3 步的操作比较啰嗦,fb 驱动根据 APP 传来的一组 fd[],逐个去向 DMA-BUF 模块申请以下资源:dma_buf -> attatch -> sg_table -> dma_addr_t,并将这组资源保存在本地(struct aic_de_dmabuf),用于释放 DMA-BUF。

这里面有一个假设:sg_table 中只有一个 sg。也就是说单个 DMA-BUF 必须是物理连续的,不能是多块 buf 拼起来的,否则物理地址 dma_addr_t 就不能代表多块 buf 的起始地址。

对于释放过程,APP 需要先通知 FB 驱动要释放的 fb[],然后再使用用户态文件接口 close()逐个关闭 fb[]。

注:

之所以申请“一组 fd[]”,是因为 video 播放过程中为了边显示边解码,至少需要两套 Buffer 来实现乒乓效果(实际应用中为了更流畅,可能还需要申请多套 Buffer 构成循环 Buffer)。这里说的“一套 Buffer”对应视频的一帧数据,而一帧视频数据往往分成 YUV 三个分量,每个分量在解码过程中是需要分别处理(对应 DE 寄存器中 addr0、addr1、addr2),所以“一套 Buffer”应该是 Y、U、V 共 3 个 Buffer。对于乒乓结构的 Buffer,就需要申请 3*2=6 个 BUF 的 fd。

UI Layer 的 buffer 申请,是直接调用通用 DMA 接口 dma_alloc_coherent(),最终在 CMA 内存中分配。这样并不影响 Video Layer 的 buffer 走 DMA-BUF 申请,CMA 模块内部会处理好来自各种接口的 Buffer 申请。