为什么需要CMA?
CMA的设计思路
要实现大块连续物理地址内存的分配,首先面临的问题就是:大块连续的物理内存从哪里找?
这里有两个方案:
方案一:在系统开机时,从整机内存中划分一块出来作为 CMA 区域的预留。
方案二:当多媒体业务启动时,再清理出一块物理地址连续的空闲内存。
显然,第一个方案对多媒体业务是最可靠、最友好的。但是缺点也很明显,即在非多媒体业务场景,预留的 CMA 区域无法得到利用,整机内存利用率变低了。第二个方案对整机友好,因为没有 CMA 预留,整机内存利用率相比方案一更高。但是由于碎片化问题,随着系统运行获取连续物理内存越来越耗时,影响多媒体业务的性能,甚至当内存分配失败时,会导致业务不可用。
综上所述,问题本质上是整机内存利用率与 CMA 业务性能之间的平衡。那么,CMA 如果解决上述问题呢?它采取了折中的办法:
• 开机时,系统预留出 CMA 区域。
• 在 CMA 业务不使用时,允许其他业务有条件地使用 CMA 区域,条件是申请页面的属性必须是可迁移的。
• 当 CMA 业务使用时,把其他业务的页面迁移出 CMA 区域,以满足 CMA 业务的需求。
CMA是如何工作的?
了解了 CMA 的产生背景和设计思路后,下面来看看 CMA 具体是如何工作的。
1.前置知识
在了解 CMA 如何工作之前,我们需要先了解一点有关迁移类型和 pageblock 的知识。
在 buddy 系统中管理着多种迁移类型(migrate type)的内存页面,不同的迁移类型有各自的用途。举例来说, MIGRATE_MOVABLE 表示保存在其页面的数据是可以被迁移的,这种迁移类型在磁盘缓存、进程页等方面贡献很大。MIGRATE_UNMOVABLE 表示该页面不能被迁移。
pageblock 是一个物理上连续的内存区域,它包含多个页面。例如,在 ARM64 中,一个 pageblock 的大小是 4MB,一个页面的大小为 4KB,每个 pageblock 管理着 1024 个页面。
页面的迁移类型与其所在的 pageblock 的迁移类型一致。当 buddy 系统接收到内存分配请求时,会根据请求标识从对应迁移类型的 pageblock 中分配页面。
当对应迁移类型的 pageblock 中页面不够分配时,buddy 系统会从其他的 pageblock 中获取页面。在一定条件下,这可能会改变一个 pageblock 的迁移类型。比如,请求一个不可移动的页面时,如果系统从 MIGRATE_MOVABLE 迁移类型的 pageblock 中分配页面,会导致这个 pageblock 的迁移类型变成 MIGRATE_UNMOVABLE。这个过程被称为 fallback。
对于 CMA 区域的内存页面,它并不希望被改变迁移类型,因此 CMA 引入了一个新的 MIGRATE_CMA 迁移类型。这种迁移类型具有一个重要的性质:不是所有的内存申请都能到该区域分配内存,只有可移动的页面才能从 MIGRATE_CMA 类型的 pageblock 中分配。
掌握了以上知识之后,下面我们来看看 CMA 的三个主要工作流程:创建 CMA 区域、分配 CMA 内存和释放 CMA 区域内存。
2. 创建CMA区域
创建 CMA 区域的流程如下:
(1)在启动阶段, CMA 通知 memblock 保留一部分内存。
(2)CMA 将这部分内存设置为 MIGRATE_CMA 迁移类型,并其返还给 buddy 系统。
系统中可能存在多个 CMA 区域。这些被预留的 CMA 区域由 buddy 系统管理。buddy 系统可以利用这部分页面用于满足可移动页面的分配请求,这样既保证了预留内存是物理连续的,又提高了整机内存利用率。
3. 分配CMA区域内存
CMA 业务通过 cma_alloc() 接口申请 CMA 区域内存。CMA 区域内存的分配过程如下:
(1)根据分配请求选择要分配的页面范围。
(2)将涉及该页面范围的 pageblock 从 buddy 系统中隔离。由于 buddy 系统不会从 MIGRATE_ISOLATE 迁移类型的 pageblock 分配页面,所以 CMA 将 pageblock 的迁移类型由 MIGRATE_CMA 变更为 MIGRATE_ISOLATE,从而实现隔离。如图 1 所示。
(3)在隔离后,将分配范围内已使用的页面进行迁移处理。迁移过程就是将页面内容复制到其他内存区域,并更新对该页面的引用。
(4)迁移完成后,pageblock 的迁移类型从 MIGRATE_ISOLATE 恢复为 MIGRATE_CMA,最后将这些页面返回给调用者。如图 2 所示。
4. 释放CMA区域内存
CMA 业务通过 cma_release () 接口释放 CMA 区域内存。释放过程就是简单迭代这些页面,将它们一一放回 buddy 系统。
代码流程讲解
上面为大家介绍了 CMA 的工作原理,接下来我们来看看 CMA 的代码是如何实现的。
(备注:这部分只是讲解主流程,并未面面俱到展开。具体细节,读者可以自行查阅代码。)
1. CMA管理结构
首先让我们来看一下 CMA 区域在代码中是怎么表示的。
CMA 区域抽象为 struct cma 结构:
struct cma {
unsigned long base_pfn; // 区域的起始页帧号
unsigned long count; // 区域所包含的总页面数量
unsigned long *bitmap; // 位图,用于描述区域的分配单元的分配情况
unsigned int order_per_bit; // 表示位图中一个bit所代表的页面数量(2^order_per_bit)
char name[CMA_MAX_NAME]; // 区域名称
};
extern struct cma cma_areas[MAX_CMA_AREAS]; // CMA区域数组
extern unsigned cma_area_count; // CMA区域总个数
复制
CMA 区域通过 bitmap 进行管理,每个 bit 表示 2^order_per_bit 个页面。bit 为 0 则表示空闲,为 1 表示已占用。
系统中存在多个 CMA 区域, cma_areas[MAX_CMA_AREAS] 表示 CMA 区域数组,cma_area_count 表示 CMA 区域总个数。
2. 创建CMA区域
简单介绍完 CMA 的管理结构,下面我们来看创建过程。
CMA 区域的创建常用两种方式:
start_kernel()
`-|setup_arch()
`-|arm_memblock_init()
`-|dma_contiguous_reserve()
`-|dma_contiguous_reserve_area()
`-|cma_declare_contiguous()
`-|cma_declare_contiguous_nid()
`-|cma_init_reserved_mem()
复制
2. 使用 DTS 方式
这里以共享 CMA 区域(cma-default)创建流程为例:
首先,使用 DTS(Device Tree Source)描述当前要创建的 CMA 区域。
Reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* global autoconfigured region for contiguous allocations */
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x04000000>;
alignment = <0x2000>;
alloc-ranges = <0x00000000 0x20000000>;
linux,cma-default;
};
};
复制
RESERVEDMEM_OF_DECLARE(dma, "shared-dma-pool", rmem_dma_setup);
rmem_cma_setup()
`-|cma_init_reserved_mem()
复制
core_initcall(cma_init_reserved_areas);
cma_init_reserved_areas()
`-|for (i = 0; i < cma_area_count; i++)
`-|cma_activate_area()
`-|for (pfn = base_pfn; pfn < base_pfn + cma->count; pfn += pageblock_nr_pages)
`-|init_cma_reserved_pageblock()
`-|set_pageblock_migratetype(page, MIGRATE_CMA);
|__free_pages(page, pageblock_order);
复制
3.分配CMA区域内存
cma_alloc()
`-|for (;;)
`-|alloc_contig_range()
`-|start_isolate_page_range()
|__alloc_contig_migrate_range()
|isolate_freepages_range()
|undo_isolate_page_range()
复制
cma_release()
`-|free_contig_range(pfn, count);
|cma_clear_bitmap(cma, pfn, count);
复制
OpenHarmony对CMA的增强
问题2:部分Movable内存无法迁移导致cma_alloc失败
结束语
本文参考资料:
https://lwn.net/Articles/396707/
https://lwn.net/Articles/486301/
https://lwn.net/Articles/447405/
https://lwn.net/Articles/684611/
【转自OpenHarmony】