CMA 详细分析

关于CMA的config
@LINUX/android/kernel/arch/arm/configs/msm8909_defconfig
CONFIG_CMA=y 已经打开
# CONFIG_CMA_DEBUG is not set
#
# Default contiguous memory area size:
#
CONFIG_CMA_SIZE_MBYTES=8 //两个配对定义
CONFIG_CMA_SIZE_SEL_MBYTES=y
# CONFIG_CMA_SIZE_SEL_PERCENTAGE is not set
# CONFIG_CMA_SIZE_SEL_MIN is not set
# CONFIG_CMA_SIZE_SEL_MAX is not set
CONFIG_CMA_ALIGNMENT=8
CONFIG_CMA_AREAS=15
# CONFIG_CMA_RESERVE_DEFAULT_AREA is not set

一,简介
1. 什么是CMA内存分配技术
CMA(Contiguous Memory Allocator)是智能连续内存分配技术,是Linux Kernel内存管理系统的扩展,目的在于解决视频播放(特别对于4K视频)需要预留大量连续内存导致运行内存紧张的问题。
连续内存分配器(CMA - Contiguous Memory Allocator)是一个框架,允许建立一个平台无关的配置,用于连续内存的管理。然后,设备所需内存都根据该配置进行分配。
这个框架的主要作用不是分配内存,而是解析和管理内存配置,以及作为在设备驱动程序和可插拔的分配器之间的中间组件。因此,它是与任何内存分配方法和分配策略没有依赖关系的。
从设备驱动作者的角度看,任何事情都不应该被影响。因为CMA是被集成到DMA子系统,所以以前调用DMA API(例如dma_alloc_coherent())的地方应该照常工作。事实上,设备驱动永远不需要直接调用CMA API,因为它是在页和页帧编号(PFNs)上操作而无关总线地址和内核映射,并且也不提供维护缓存一致性的机制。
 获取更多信息,可以参考Documentation/DMA-API.txt和Documentation/DMA-API-HOWTO.txt这两份有用的文档。这两篇文档描述了DMA提供的方法接口及使用用例。

2、为什么需要?
在嵌入式设备中,很多设备都没有支持scatter-getter和IO map,都需要连续内存块的操作。如设备:摄像机,硬件视频解码器,编码器等。
这些设备往往需要较大的内存缓冲区(如:一个200万像素的高清帧摄像机,需要超过6M的内存),该kmalloc内存分配机制对于这么大的内存是没有效果的。
一些嵌入式设备对缓冲区有一些额外的要求,比如:在含有多个内存bank的设备中,要求只能在特定的bank中中分配内存;而还有一些要定内存边界对齐的缓存区。
近来,嵌入式设备有了较大的发展(特别是V4L领域),并且这些驱动都有自己的内存分配代码。它们众多的大多数都是采用bootmem分配方法。CMA框架企图采用统一的连续内存分配机制,并为这些设备驱动提供简单的API,而且是可以定制化和模块化的。

3、设计
CMA主要设计目标是提供一个可定制的模块化框架,并且是可以配置的,以适应个别系统的需要。配置指定的内存区域,然后将这些内存分配给制定的设备。这些内存区域可以共享给多个设备驱动,也可以专门分配一个。这是通过以下方式实现的:
1) CMA的核心不是处理内存分配和空闲空间管理。专用分配器是用来处理内存分配和空闲内存管理的。因此,如果现有的解决方案不符合给定的系统,那么可以开发一种新的算法,这种算饭可以很容易地插入到CMA框架中。所提出的解决方案中包括一个最适算法(best-fit)的一个实现。
2)CMA允许运行时配置即将分配的内存区域。内存区域都是经由命令行给出的,所以可以很容易地改变它,而不需要重新编译内核。每个地区都有自己的大小,对齐标准,起始地址(物理地址)和对应该内存区域的内存分配算法。这意味着同一时刻可以有多中机制在运行,如果在一个平台上同时运行多个不同的设备,这些设备具有不同的存储器使用特性,那么局可以匹配最好的算法。
3)当设备请求内存时,设备必须“自我介绍”,即附带自己的信息以告知CMA。这样CMA可以知道谁分配内存。这允许系统架构师来指定哪个移动设备应该使用哪些存储区。设备也可以指定一个“类”内存区域,这使得系统更容易配置,进而一个单一的设备可能使用来自不同内存区域的内存。例如,一个视频解码器驱动程序可能要分配一些共享的缓冲区,那么从第一个bank中分配一些,再从第二个bank中分配一些,可以获得尽可能高的内存吞吐量
4)通过这套机制,我们可以做到不预留内存,这些内存平时是可用的,只有当需要的时候才被分配给Camera,HDMI等设备。

二,关键点
1.如何保证内存被复用
CMA通过在启动阶段预先保留内存。这些内存叫做CMA区域或CMA上下文,稍后返回给伙伴系统从而可以被用作正常申请。如果要保留内存,则需要恰好在底层“memblock”分配器初始化之后,及大量内存被占用之前调用,并在伙伴系统建立之前调用:void dma_contiguous_reserve(phys_addr_t limit)

2. 要理解CMA如何工作,需要了解一些迁移类型和页块的知识。
 当从伙伴系统申请内存的时候,需要提供一个gfp_mask参数。不管其他事情,这个参数指定了要申请内存的迁移类型。一个前一类型是MIGRATE_MOVABLE。它背后的意思是在可移动页面上的数据可以被迁移(或者移动,因此而命名),这对于磁盘缓存或者进程页面来说很有效。
 为了使相同迁移类型的页面在一起,伙伴系统把页面组成“页面块”,每组都有一个指定的迁移类型。分配器根据请求的类型在不同的页面块上分配页。如果尝试失败,分配器会在其它页面块上分配并甚至修改页面块的迁移类型。这意味着一个不可移动的页可能分配自一个MIGRATE_MOVABLE页面块,并导致该页面块的迁移类型改变。这不是CMA想要的,所以它引入了一个MIGRATE_CMA类型,该类型又一个重要的属性:只有可移动页可以从MIGRATE_CMA页面块种分配。
那么,在启动期间,当dma_congiguous_reserve()和/或者dma_declare_contiguous()方法被调用的时候,CMA在memblock中预留一部分RAM,并在随后将其返还给伙伴系统,仅将其页面块的迁移类型置为MIGRATE_CMA。最终的结果是所有预留的页都在伙伴系统里,所以它们都可以用于可移动页的分配
在CMA分配的时候,dma_alloc_from_contiguous()选择一个页范围并调用:
int alloc_contig_range(unsigned long start, unsigned long end,unsigned migratetype);
 start和end参数指定了目标内存的页框个数(或PFN范围)。最后一个参数migratetype指定了潜在的迁移类型;在CMA的情况下,这个参数就是MIGRATE_CMA。这个函数所做的第一件事是将包含[start, end)范围内的页面块标记为MIGRATE_ISOLATE。伙伴系统不会去触动这种类型的页面块。改变迁移类型不会魔法般地释放页面,因此接下来需要调用__alloc_conting_migrate_range()。它扫描PFN范围并寻找可以迁移的页面。
 迁移是将页面复制到系统其它内存部分并更新相关引用的过程。前一部份很直接,后面的部分需要内存管理子系统来完成。当数据迁移完成,旧的页面被释放并回归伙伴系统。这就是为什么之前那些需要包含的页面块一定要标记为MIGRATE_ISOLATE的原因。如果指定了其它的迁移类型,伙伴系统会毫不犹豫地将它们用于其它类型的申请。
 现在所有alloc_contig_range关心的页都(希望如此)是空闲的了。该方法将从伙伴系统中取出它们,并将这些页面块的类型改为MIGRATE_CMA。然后将这些页返回给调用者。
 释放内存就更简单了。dma_release_from_contiguous()将其工作转交给:void free_contig_range(unsigned long pfn, unsigned nr_pages); 这个函数迭代所有的页面并将其返还给伙伴系统

3. CMA机制能保证系统分配连续内存的成功率
CMA是被集成到DMA子系统, 设备驱动永远不需要直接调用CMA API,因为它是在页和页帧编号(PFNs)上操作而无关总线地址和内核映射,并且也不提供维护缓存一致性的机制

三,代码分析
1. @Setup.c (arch\arm\kernel)
void __init setup_arch(char **cmdline_p)
{
	early_paging_init(mdesc, lookup_processor_type(read_cpuid_id()));
	setup_dma_zone(mdesc);
	arm_memblock_init(mdesc);

	sanity_check_meminfo();
	paging_init(mdesc);
}

2. @Setup.c (arch\arm\kernel)
void __init arm_memblock_init(const struct machine_desc *mdesc)
{
	/*
	 * reserve memory for DMA contigouos allocations,
	 * must come from DMA area inside low memory
	 */
	dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));
}

3. @Dma-contiguous.c (drivers\base)
#ifdef CONFIG_CMA_SIZE_MBYTES
#define CMA_SIZE_MBYTES CONFIG_CMA_SIZE_MBYTES
#else
#define CMA_SIZE_MBYTES 0
#endif
static const phys_addr_t size_bytes = CMA_SIZE_MBYTES * SZ_1M;
void __init dma_contiguous_reserve(phys_addr_t limit)
{
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
		selected_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
。。。。
if (selected_size && !dma_contiguous_default_area) {
		dma_contiguous_reserve_area(selected_size, selected_base,
					    selected_limit,
					    &dma_contiguous_default_area,
					    fixed);
}
}

int __init cma_declare_contiguous(phys_addr_t base,
			phys_addr_t size, phys_addr_t limit,
			phys_addr_t alignment, unsigned int order_per_bit,
			bool fixed, struct cma **res_cma)
{
addr = memblock_alloc_range(size, alignment, base, limit)
kmemleak_ignore(phys_to_virt(addr));
ret = cma_init_reserved_mem(base, size, order_per_bit, res_cma);
}

4. @Cma.c (mm)
static int __init cma_init_reserved_areas(void)
{
	int i;

	for (i = 0; i < cma_area_count; i++) {
		int ret = cma_activate_area(&cma_areas[i]);

		if (ret)
			return ret;
	}

	return 0;
}
core_initcall(cma_init_reserved_areas);

CMA的初始化

CMA的连续内存分配和释放


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭