内存管理-13-CMA内存-1-初探

基于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);
}
复制代码

 

posted on   Hello-World3  阅读(433)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示