【ARM Linux 内存管理入门及渐进 3 - CMA】

上篇文章:ARM Linux 内存管理入门及渐进 2 - 缺页异常
下篇文章:ARM Linux 内存管理入门及渐进 4 - 常用接口实现(memcpy/copy_to_user)

1.1.1 历史背景

历史背景

1.1.2 device tree reserved memory node

一开始,CMA area 的概念是全局的,通过内核配置参数和命令行参数,内核可以定位到 Global CMA area 在内存中的起始地址和大小(这里的Global的意思是针对所有的driver而言的)。并在初始化的时候,调用 dma_contiguous_reserve 函数,将指定的 memory region 保留给 Global CMA area 使用。

人性是贪婪的,驱动亦然,很快,有些驱动想吃独食,不愿意和其他驱动共享CMA,因此出现两种 CMA area:

  • Global CMA area给大家共享,
  • per device CMA 可以给指定的一个或者几个驱动使用。

这时候,命令行参数不是那么合适了,因此引入了 device tree 中的 reserved memory node 的概念。当然,为了兼容,内核仍然支持CMA的 command line 参数。

1.1.3 CMA 结构描述符

在CMA模块中,struct cma 数据结构用来抽象一个CMA area,具体定义如下:

struct cma { 
    unsigned long   base_pfn; 
    unsigned long   count; 
    unsigned long   *bitmap; 
    /* Order of pages represented by one bit */ 
    unsigned int order_per_bit; 
    struct mutex    lock; 
};

cma 模块使用 bitmap来管理其内存的分配,0表示free,1表示已经分配。具体内存管理的单位和struct cma中的order_per_bit成员相关:
如果 order_per_bit 等于0,表示按照一个一个page来分配和释放,
如果 order_per_bit等于 1,表示按照2个page组成的block来分配和释放,以此类推。

struct cma 中的bitmap成员就是管理该cma area内存的bit map。
count 成员说明了该cma area内存有多少个page。它和order_per_bit一起决定了bitmap指针指向内存的大小。
base_pfn 定义了该CMA area的起始 page frame number,base_pfn和count一起定义了该CMA area在内存在的位置。

我们前面说过了,CMA模块需要管理若干个CMA area,有gloal的,有per device的,代码如下:

struct cma cma_areas[MAX_CMA_AREAS];

每一个struct cma抽象了一个CMA area,标识了一个物理地址连续的memory area。
调用cma_alloc分配的连续内存就是从CMA area中获得的。

具体有多少个CMA area是编译时决定了,而具体要配置多少个CMA area是和系统设计相关,你可以为特定的驱动准备一个CMA area,也可以只建立一个通用的CMA area,供多个驱动使用(本文重点描述这个共用的CMA area)。

1.1.4 配置CMA内存

如上文提到的,配置CMA内存区有两种方法:

  • 一种是通过dts的reserved memory;
  • 一种是通过command line参数和内核配置参数。

device tree 中可以包含 reserved-memory node,在该节点的 child node中,可以定义各种保留内存的信息。compatible属性是shared-dma-pool的那个节点是专门用于建立 global CMA area的,而其他的child node都是for per device CMA area的。

Global CMA area 的初始化可以参考定义如下:

RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);

具体的setup过程倒是比较简单,从device tree中可以获取该memory range的起始地址和大小,调用cma_init_reserved_mem函数即可以注册一个CMA area。需要补充说明的是:CMA对应的reserved memory节点必须有reusable属性,不能有no-map的属性。

1.1.5 reusable 属性

reusable 属性的 reserved memory 有这样的特性:即在驱动不使用这些内存的时候,OS可以使用这些内存(当然有限制条件),而当驱动从这个CMA area分配memory的时候,OS可以reclaim这些内存,让驱动可以使用它。

no-map属性和地址映射相关,如果没有no-map属性,那么OS会为这段memory创建地址映射,象其他普通内存一样。但是有no-map属性的往往是专用于某个设备驱动,在驱动中会进行io remap,如果OS已经对这段地址进行了mapping,而驱动又一次mapping,这样就有不同的虚拟地址mapping到同一个物理地址上去,在某些ARCH上(ARMv6之后的cpu),会造成不可预知的后果。而CMA这个场景,reserved memory必须要mapping好,这样才能用于其他内存分配场景,例如page cache。

1.1.6 per device CMA

per device CMA area的注册过程和各自具体的驱动相关,但是最终会dma_declare_contiguous这个接口函数,为一个指定的设备而注册CMA area,这里就不详述了。

1.1.7 cmdline cma

通过命令行参数也可以建立cma area。我们可以通过cma=nn[MG]@[start[MG][-end[MG]]]这样命令行参数来指明Global CMA area在整个物理内存中的位置。

在初始化过程中,内核会解析这些命令行参数,获取CMA area的位置(起始地址,大小),并调用cma_declare_contiguous接口函数向CMA模块进行注册(当然,和device tree传参类似,最终也是调用cma_init_reserved_mem接口函数)。除了命令行参数,通过内核配置(CMA_SIZE_MBYTES和CMA_SIZE_PERCENTAGE)也可以确定CMA area的参数。

1.1.8 memblock 与 CMA的关系

内存管理子系统进行初始化的时候,首先是memblock掌控全局的,这时候需要确定整个系统的的内存布局,简单说就是了解整个memory的分布情况,哪些是memory block是memory type,哪些memory block是reserved type。毫无疑问,CMA area对应的当然是reserved type。最先进行的是memory type的内存块的建立,可以参考如下代码:

setup_arch--->setup_machine_fdt--->early_init_dt_scan
		--->early_init_dt_scan_nodes--->memblock_add

随后会建立reserved type的memory block,可以参考如下代码:

setup_arch--->arm64_memblock_init
		--->early_init_fdt_scan_reserved_mem
				--->__fdt_scan_reserved_mem--->memblock_reserve

完成上面的初始化之后,memblock模块已经通过device tree构建了整个系统的内存全貌:哪些是普通内存区域,哪些是保留内存区域。

对于那些reserved memory,我们还需要进行初始化,代码如下:

setup_arch--->arm64_memblock_init
	--->early_init_fdt_scan_reserved_mem
		--->fdt_init_reserved_mem
			--->__reserved_mem_init_node

上面的代码会scan内核中的一个特定的section(还记得前面 RESERVEDMEM_OF_DECLARE 的定义吗?),如果匹配就会调用相应的初始化函数,而对于Global CMA area而言,这个初始化函数就是rmem_cma_setup。当然,如果有需要,具体的驱动也可以定义自己的CMA area,初始化的思路都是一样的。

1.2 CMA 工作流程

1.2.1 准备知识

如果想要了解CMA是如何运作的,你可能需要知道一点点关于migrate types和pageblocks的知识。当从伙伴系统请求内存的时候,我们需要提供了一个gfp_mask的参数。它有很多的功能,不过在CMA这个场景,它用来指定请求页面的迁移类型(migrate type)。

migrate type有很多中,其中有一个是MIGRATE_MOVABLE类型,被标记为MIGRATE_MOVABLE的page说明该页面上的数据是可以迁移的。也就是说,如果需要,我们可以分配一个新的page,copy数据到这个new page上去,释放这个page。而完成这样的操作对系统没有任何的影响。我们来举一个简单的例子:对于内核中的data section,其对应的page不是是movable的,因为一旦移动数据,那么内核模块就无法访问那些页面上的全局变量了。而对于page cache这样的页面,其实是可以搬移的,只要让指针指向新的page就OK了。

伙伴系统不会跟踪每一个page frame的迁移类型,实际上它是按照pageblock为单位进行管理的,memory zone中会有一个bitmap,指明该zone中每一个pageblock的migrate type。在处理内存分配请求的时候,一般会首先从和请求相同migrate type(gfp_mask)的pageblocks中分配页面。如果分配不成功,不同migrate type的pageblocks中也会考虑,甚至可能改变pageblock的migrate type。这意味着一个non-movable页面请求也可以从migrate type是movable的pageblock中分配。这一点CMA是不能接受的,所以我们引入了一个新的migrate type:MIGRATE_CMA。这种迁移类型具有一个重要性质:只有可移动的页面可以从MIGRATE_CMA的pageblock中分配。

1.2.2 初始化 CMA area

static int __init cma_activate_area(struct cma *cma)
{ 
    int bitmap_size = BITS_TO_LONGS(cma_bitmap_maxno(cma)) 
    					* sizeof(long); 
    unsigned long base_pfn = cma->base_pfn, pfn = base_pfn; 
    unsigned i = cma->count >> pageblock_order; 
    struct zone *zone; -------(1)
    cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL); --分配内存
    zone = page_zone(pfn_to_page(pfn)); --找到page对应的memory zone
    do {-----(2unsigned j;
        base_pfn = pfn; 
        for (j = pageblock_nr_pages; j; --j, pfn++) {------(3if (page_zone(pfn_to_page(pfn)) != zone) 
                goto err; 
        } 
        init_cma_reserved_pageblock(pfn_to_page(base_pfn));--(4} while (--i);
    mutex_init(&cma->lock);
    return 0;
}

(1)CMA area有一个bitmap来管理各个page的状态,这里bitmap_size给出了bitmap需要多少的内存。i变量表示该CMA area有多少个pageblock。
(2)遍历该CMA area中的所有的pageblock。
(3)确保CMA area中的所有page都是在一个memory zone内,同时累加了pfn,从而得到下一个pageblock的初始page frame number。
(4)将该pageblock导入到伙伴系统,并且将migrate type设定为MIGRATE_CMA

  • 分配连续内存
    cma_alloc用来从指定的CMA area上分配count个连续的page frame,按照align对齐。具体的代码就不再分析了,比较简单,实际上就是从bitmap上搜索free page的过程,一旦搜索到,就调用alloc_contig_range向伙伴系统申请内存。需要注意的是,CMA内存分配过程是一个比较“重”的操作,可能涉及页面迁移、页面回收等操作,因此不适合用于atomic context。
  • 释放连续内存
    分配连续内存的逆过程,除了bitmap的操作之外,最重要的就是调用free_contig_range,将指定的pages返回伙伴系统。

上篇文章:ARM Linux 内存管理入门及渐进 2 - 缺页异常
下篇文章:ARM Linux 内存管理入门及渐进 4 - 常用接口实现(memcpy/copy_to_user)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值