之前追CMA代码的时候,写了一篇文章基于ARM32的 linux内存调节之CMA 。今天就从使用的角度来看看CMA和普通内存的区别。默认CONFIG_CMA
尝试思考几个问题,来揭开面纱吧。
CMA在未加入内存的时候,是否是存放在memblock.reserved region中?
众所周知CMA是由伙伴系统管理的,何以见得?
伙伴系统如何对这部分页做分配处理的?
当CMA要被使用的时候,如何真正变成CMA?
目录
简要回顾下上一篇博文
在设备树中 /reserved-memory 节点下定义一块reserved内存后,开机启动内核在 early_init_fdt_scan_reserved_mem 中会去获取这段reserved 内存;或者在cmaline中定义了CMA(见early_cma),会在dma_contiguous_reserve初始这段CMA。在ARM64中,这段reserved空间不会像ARM32一样,单独去做映射。
第一个问题
在 early_init_fdt_scan_reserved_mem 中 通过函数__fdt_scan_reserved_mem中的__reserved_mem_reserve_reg会将设备数中定义的reserved mem添加到 memblock.reserved region中。后面会一步步将这段内存添加到reserved-mem[],其次cma_areas[].
在dma_contiguous_reserve中会解析cmdline参数,通过函数dma_contiguous_reserve将解析到的内存添加到memblock.reserved region中。
第二个问题
1 在内存初始化的时候第一次看见CMA的影子是在 zone_init_free_lists 中,free_area每个order中初始了一块 MIGRATE_CMA 链表,用于存放迁移类型为CMA的页。既然内核都做了这个打算,肯定是对CMA有想法的。
获取到的CMA内存块会存放在数组 cam_areas 中。函数 cma_init_reserved_areas 使用 core_initcall 加载,会将cma_areas里的每个CMA块以page的方式释放到free_area,并且会清除page的reserved标记,并设置迁移类型为MIGRATE_CMA,最终CMA的内存存放在了伙伴系统中,因为看到了__free_pages。注意:释放memblock到伙伴系统的时候,是不会将reserved region中的page添加到free_area的
cma_init_reserved_areas:
cma_activate_area:
init_cma_reserved_pageblock:
__free_page
2 cma 内存释放,同样可以找到free_page的影子。
cma_release:
free_contig_range:
{
unsigned int count = 0;
for (; nr_pages--; pfn++) {
struct page *page = pfn_to_page(pfn);
count += page_count(page) != 1;
__free_page(page);
}
}
第三个问题
从分配使用角度:我们从__alloc_pages_nodemask开始看起,找到 get_page_from_freelist。一层层拨开洋葱皮一样找到分配page的函数__rmqueue。
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
unsigned int alloc_flags)
{
struct page *page;
retry:
//A计划:从free_area中找到目标order的目标migratetype链表获取page
page = __rmqueue_smallest(zone, order, migratetype);
if (unlikely(!page)) {
//B计划:A计划失败拿不到页,如果migratetype == MIGRATE_MOVABLE,那可以从CMA中获取page
if (migratetype == MIGRATE_MOVABLE)
page = __rmqueue_cma_fallback(zone, order);
//C计划:B计划失败,就和CMA没啥关系了,CMA不参与备用选择。
if (!page && __rmqueue_fallback(zone, order, migratetype,
alloc_flags))
goto retry;
}
return page;
}
我们可以找到第一次页分配失败会尝试去从__rmqueue_cma_fallback获取页,只不过是以MIGRATE_CMA来分配页面,注意分配到的页属性不再是MIGRATE_MOVABLE。
虽然CMA交给了伙伴系统管理,但是使用它的资源还要遵守它的规定,毕竟生来不是服务于伙伴系统的,而是为大内存设备准备的。
从分配水位角度:get_page_from_freelist在分配页前先会对水位做处理。在zone_watermark_fast中会找到cma_pages,分配页的时候默认不会算上CMA页第四个问题:
static inline bool zone_watermark_fast(struct zone *z, unsigned int order,
unsigned long mark, int classzone_idx, unsigned int alloc_flags)
{
long free_pages = zone_page_state(z, NR_FREE_PAGES);
long cma_pages = 0;
#ifdef CONFIG_CMA
if (!(alloc_flags & ALLOC_CMA))
cma_pages = zone_page_state(z, NR_FREE_CMA_PAGES);
#endif
//order为0的情况,去除cma_pages
if (!order && (free_pages - cma_pages) > mark + z->lowmem_reserve[classzone_idx])
return true;
return __zone_watermark_ok(z, order, mark, classzone_idx, alloc_flags,
free_pages);
}
空闲页数是会剔除cma_pages的。order不为0也是同样的判断。所以有时候需要注意,一些log中打印的free_pages会大于min水位,但还是分配页失败,这时需要看看cma_pages是多少了。
第四个问题
看一下CMA分配函数 cma_alloc,具体的分配动作在 alloc_contig_range 中。
struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align,
bool no_warn)
{
pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit); //首先获取目标CMA的区域
mutex_lock(&cma_mutex);
//隔离这部分页,将被使用的页面迁移出去,腾出CMA的空间
ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA,
GFP_KERNEL | (no_warn ? __GFP_NOWARN : 0));
}
alloc_contig_range 中 会有 migrate_pages 将旧页面的内容复制到新页面,然后使用旧页面作为cma使用。这部分涉及内存迁移。