内存管理
基于msm-5.4
关键词 cma_alloc/cma_release MIGRATE_ISOLATE MIGRATE_CMA
一、概述
1. CMA简介
CMA(Contiguous Memory Alloctor)主要用于分配大块连续的物理内存。伙伴系统能分配的最大连续物理内存块是2^10,即一个pageblock大小4MB。为了提高内存的使用率,嵌入式平台上一般使用CMA.。
在伙伴系统的 free_area[] 链表中有一个 MIGRATE_CMA 类型,当不需要分配大块连续物理内存时,会将CMA内存通过伙伴系统提供给其它模块使用。
和DMA功能类似,CMA也是预先预留出一块连续的物理内存,给多媒体等需要大块连续物理内存的模块使用,如果不使用,CMA内存区也不会空闲着,通过伙伴系统的 free_page()/alloc_pages() 接口释放到伙伴系统,给其它模块使用。当需要大块连续物理内存时,才会从CMA中分配,当CMA内存不足时,会将其释放到伙伴系统中的内存再拿回来,若属于CMA的一些页面已经被其它程序使用了,那么还要做一个迁移操作,腾出连续的区域。
简化理解,CMA区域就是对DMA区域的改进,在不使用时不让其空闲着,释放给伙伴系统给其它程序使用,当需要使用时再收回来,一方面满足了分配大块连续物理内存的需求,另一方面提高了内存的使用率。
2. CMA对外接口
cma_alloc(): 从CMA区域分配一大块连续的物理内存,若此内存已经被伙伴系统给别人使用了,还要回收回来用作CMA内存使用。
cma_release(): 当使用完CMA内存时,调用此函数将内存返还给CMA,CMA会调用伙伴系统的 free_page() 将这块内存释放到伙伴系统中,供其它模块使用。
实际上驱动很少通过 cma_alloc()/cma_release() 接口去分配CMA内存了,主要是通过DMA接口间接调用到CMA接口。
二、CMA实现
1. 数据结构
//mm/cma.h
struct cma {
unsigned long base_pfn; //此CMA区域起始页帧号
unsigned long count; //物理页的个数
unsigned long *bitmap; //表示此CMA中物理页的使用情况,每一个bit位表示一个物理页是否被分出去了还是空闲的。
unsigned int order_per_bit; //和上面bitmap配合使用,若为0则每个bit代表一个物理页,若为1则表示每个bit代表两个物理页
struct mutex lock;
#ifdef CONFIG_CMA_DEBUGFS
struct hlist_head mem_head;
spinlock_t mem_head_lock;
#endif
const char *name;
};
//mm/cma.c
struct cma cma_areas[MAX_CMA_AREAS]; //17 CONFIG_CMA_AREAS+1
unsigned cma_area_count;
内核允许系统上有多个CMA区域,因此定义了一个数组 cma_areas[] 来保存所有的CMA区域,最大支持的个数可配置。
假设 order_per_bit=1 的,每个bit代表2个物理页,为0则每个bit代表1个物理页。
CMA初始化的时候,也会将CMA内存以pageblock 4MB的大小,插入到伙伴系统的 MIGRATE_CMA 类型的空闲链表中。只有用户向伙伴系统申请的内存是可移动的,才可能会去CMA链表上去拿内存。
伙伴系统在初始化的时候将内存分为4MB大小的pageblock,每个pageblock内存块都有物理页的类型,MIGRATE_UNMOVABLE、MIGRATE_MOVABLE 等。
假设DMA模块现在要去申请两个pageblock大小的连续物理内存,系统会找到CMA内存在伙伴系统中的位置,将其对应的pageblock设置为 MIGRATE_ISOLATE 类型(此时其上所有物理页都变为这个类型),表示此pageblock已经被当做CMA内存拿去使用了,伙伴系统不要分配它了。
2. CMA初始化
初始化有两种方式,一个是通过内核配置,另一个是通过设备树dts文件。
设备树中 reserved-memory 节点也是受伙伴系统管理的,但是不会通过伙伴系统的接口给用户使用了。保留下来这块内存用于专门的用途。compatible = "shared-dma-pool" 指定的可以用作CMA区域,内核根据这个字眼去解析然后初始化成一个struct cma结构。
/ {
reserved-memory {
#address-cells = <0x02>;
#size-cells = <0x02>;
ranges;
phandle = <0x2ea>;
linux,cma {
compatible = "shared-dma-pool";
alloc-ranges = <0x00 0x00 0x00 0xffffffff>;
reusable;
alignment = <0x00 0x400000>;
size = <0x00 0x2000000>; //32M
linux,cma-default;
phandle = <0x2eb>;
};
qseecom_region {
compatible = "shared-dma-pool";
alloc-ranges = <0x00 0x00 0x00 0xffffffff>;
no-map;
alignment = <0x00 0x400000>;
size = <0x00 0x1400000>; //20M
phandle = <0x02>;
};
qseecom_ta_region {
compatible = "shared-dma-pool";
alloc-ranges = <0x00 0x00 0x00 0xffffffff>;
no-map;
alignment = <0x00 0x400000>;
size = <0x00 0x1000000>; //16M
phandle = <0x03>;
};
mem_dump_region {
compatible = "shared-dma-pool";
alloc-ranges = <0x00 0x00 0x00 0xffffffff>;
reusable;
size = <0x00 0x1000000>; //16M
phandle = <0x05>;
};
cnss_wlan_region {
compatible = "shared-dma-pool";
alloc-ranges = <0x00 0x00 0x00 0xffffffff>;
reusable;
alignment = <0x00 0x400000>;
size = <0x00 0x6000000>; //96M
phandle = <0x18b>;
};
pmem_shared_region {
reg = <0x01 0x66500000 0x00 0x51400000>;
label = "pmem_shared_mem";
phandle = <0x2ec>;
};
ramoops@800000000 {
compatible = "ramoops";
reg = <0x08 0x00 0x00 0x200000>;
record-size = <0x40000>;
console-size = <0x40000>;
pmsg-size = <0x40000>;
};
};
};
注:设备树见 https://www.cnblogs.com/hellokitty2/p/18284414
内核启动log打印:
# cat kernel.txt | grep OF:
<6>[ 0.000000] (0)[0:swapper]OF: reserved mem: initialized node qseecom_region, compatible id shared-dma-pool
<6>[ 0.000000] (0)[0:swapper]OF: reserved mem: initialized node qseecom_ta_region, compatible id shared-dma-pool
<6>[ 0.000000] (0)[0:swapper]OF: reserved mem: initialized node mem_dump_region, compatible id shared-dma-pool
<6>[ 0.000000] (0)[0:swapper]OF: reserved mem: initialized node cnss_wlan_region, compatible id shared-dma-pool
<6>[ 0.000000] (0)[0:swapper]OF: reserved mem: initialized node linux,cma, compatible id shared-dma-pool
<6>[ 0.000000] (0)[0:swapper]Memory: 13692116K/14227228K available (18344K kernel code, 3350K rwdata, 12772K rodata, 4224K init, 2509K bss, 387656K reserved, 147456K cma-reserved)
注:147456K=144MB,和 cat /proc/pagetypeinfo 对应的上。设备树前面加起来是180MB,对应不上 #####。
三、CMA相关接口
/sys # find ./ -name "*cma*"
./kernel/debug/tracing/events/cma
./kernel/debug/tracing/events/cma/cma_alloc_busy_retry
./kernel/debug/tracing/events/cma/cma_alloc
./kernel/debug/tracing/events/cma/cma_alloc_start
./kernel/debug/tracing/events/cma/cma_release
/sys/firmware/devicetree/base/reserved-memory # ls -l
drwxr-xr-x 2 root root 0 2024-07-02 20:26 cnss_wlan_region
drwxr-xr-x 2 root root 0 2024-07-02 20:26 linux,cma
drwxr-xr-x 2 root root 0 2024-07-02 20:26 mem_dump_region
drwxr-xr-x 2 root root 0 2024-07-02 20:26 pmem_shared_region
drwxr-xr-x 2 root root 0 2024-07-02 20:26 qseecom_region
drwxr-xr-x 2 root root 0 2024-07-02 20:26 qseecom_ta_region
drwxr-xr-x 2 root root 0 2024-07-02 20:26 ramoops@800000000
注:还有/sys/kernel/debug 路径下的。
/sys/kernel/debug/cma # ls
cma-cnss_wlan_r cma-linux,cma cma-mem_dump_re
/sys/kernel/debug/cma/cma-cnss_wlan_r # ls
alloc base_pfn bitmap count free maxchunk order_per_bit used
四、CMA内存释放
1. 概述
CMA内存也属于reversed类型的内存的一部分,但是其特殊之处在于它也会释放进伙伴系统。
reserved-dma-pool "linux,dma-default"no-map
设备树中的这些属性告诉系统这块区域是预留DMA专用区域,不需要映射。
shared-dma-pool "linux,cma-default" reusable
告诉系统这部分区域可以重新利用,可以交给伙伴系统去管理,使用时不够的话再回收回来就可以了。
2. 释放调用路径
start_kernel
setup_arch
setup_machine_fdt
early_init_dt_scan
early_init_dt_scan_memory //memblock.memory初始化
arm64_memblock_init
early_init_fdt_scan_reserved_mem //memblock.reserved初始化
mm_init
mem_init
memblock_free_all //释放到伙伴系统
arch_call_rest_init
rest_init
kernel_init
kernel_init_freeable
do_basic_setup
do_initcalls
core_initcall
cma_init_reserved_areas //cma内存释放
free_initmem //这里释放.init段内存
cma_init_reserved_areas() 用于将CMA区域的内存释放给伙伴系统。看其实现:
static int __init cma_init_reserved_areas(void) //cma.c
{
/* 一个循环,将所有的CMA区域都释放到伙伴系统中 */
for (i = 0; i < cma_area_count; i++)
cma_activate_area(&cma_areas[i]);
}
static void __init cma_activate_area(struct cma *cma)
{
unsigned long base_pfn = cma->base_pfn, pfn = base_pfn;
unsigned i = cma->count >> pageblock_order;
struct zone *zone;
/* 给CMA创建一个bitmap,记录page的使用情况。因为后续还可能要回收回来,因此需要知道哪些被伙伴系统分配出去了 */
cma->bitmap = bitmap_zalloc(cma_bitmap_maxno(cma), GFP_KERNEL);
zone = page_zone(pfn_to_page(pfn));
/* 以pageblock为单位释放到伙伴系统中,此CMA区域有count个page就循环count>>pageblock_order次 */
do {
unsigned j;
base_pfn = pfn;
for (j = pageblock_nr_pages; j; --j, pfn++) {
if (page_zone(pfn_to_page(pfn)) != zone)
goto not_in_zone;
}
init_cma_reserved_pageblock(pfn_to_page(base_pfn));
} while (--i);
mutex_init(&cma->lock);
#ifdef CONFIG_CMA_DEBUGFS
INIT_HLIST_HEAD(&cma->mem_head);
spin_lock_init(&cma->mem_head_lock);
#endif
return;
}
void __init init_cma_reserved_pageblock(struct page *page)
{
unsigned i = pageblock_nr_pages;
struct page *p = page;
do {
/* 清除页面的reserved标记 */
__ClearPageReserved(p);
set_page_count(p, 0);
} while (++p, --i);
/* 设置页面迁移类型 MIGRATE_CMA */
set_pageblock_migratetype(page, MIGRATE_CMA);
/* 这是大页的释放,我们走else流程 */
if (pageblock_order >= MAX_ORDER) {
...
} else {
/* 设置page的引用计数 page->_refcount */
set_page_refcounted(page);
/* 根据order将这个page添加到伙伴系统对应链表上 */
__free_pages(page, pageblock_order);
}
/* 更新伙伴系统中page的数量 */
adjust_managed_page_count(page, pageblock_nr_pages);
}