1. 程式人生 > >Linux記憶體管理:CMA

Linux記憶體管理:CMA

某些驅動需要用到一大塊連續的實體記憶體,但使用kmalloc等很分配很大的連續記憶體。
所以這裡有一種三星實現叫CMA的方式,來連續的大記憶體分配。

Why is it needed?

  • Issue 1: Camera, Video Codec等Multimedia Device需要連續的數MB大小的Memory,但kmalloc/alloc_page不能保證分配到連續的數MB或者更大的記憶體

  • Issue 2: 在Booting Time預留記憶體的方式也可以(像早前的PMEM方式),但這麼預留的Memory只能被特定的Device驅動所使用,System不能分配這部分內容。如果該Device不用或者所使用的記憶體沒有預留的大的時候,就造成Memory浪費。

  • Issue 3: Multimedia Device之間需要共享Reserve的Memory
    E.g: Camera(FIMC)利用Video Codec(MFC)去Endoding的時候,需要從FIMC分配MFC Reserve的Memory。

CMA Solution

  • Issue 1: 可以分配連續的大的記憶體(Device需要的連續的大的記憶體,可以在Boot的時候進行Reserve之後再進行分配)

  • Issue 2: 防止Reserve方式的Memory浪費
    1) 支援Migration功能,所以即使是被某個驅動裝置Reserve的區域,在驅動沒有使用的時候System可以對該段記憶體進行分配使用
    2) 在System使用這段記憶體的時候,如果驅動要求分配這個預留的記憶體,System memory就會被Migration到其他記憶體區域,之後這段記憶體被分配給驅動裝置

  • Issue 3: 驅動裝置間的記憶體共享(通過CMA被Reserve的記憶體會通過CMA進行管理,所以可以驅動裝置間共享該段,比如FIMC可以共享MFC預留的記憶體區域等)

CMA記憶體Reserve

1.檢視整個實體記憶體大小

在系統啟動的時候,整個實體記憶體儲存在memblock變數裡。通過在sanity_check_meminfo()函式裡新增log來列印memblock的內容,這裡可以看到整個實體記憶體大小為1.5GB。

<6>[0.000000]  [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=2
<6>[0.000000]  [0
:swapper:0] pys_addr vmalloc_limit = 0xa9c00000 <6>[0.000000] [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000 <6>[0.000000] [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000 <6>[0.000000] [0:swapper:0] arm_lowmem_limit =0xa9c00000

2.預留CMA相關的記憶體

之後在arm_memblock_init()->dma_contiguous_reserve()函式中,讀取device tree相關的設定來預留記憶體給CMA

void __init arm_memblock_init(const struct machine_desc *mdesc) {
    /* Register the kernel text, kernel data and initrd with memblock. */
#ifdef CONFIG_XIP_KERNEL
    memblock_reserve(__pa(_sdata), _end - _sdata);
#else
    memblock_reserve(__pa(_stext), _end - _stext); //預留核心程式碼區域
#endif
#ifdef CONFIG_BLK_DEV_INITRD //預留initramfs區域
    if (phys_initrd_size &&
        !memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {
        pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",
               phys_initrd_start, phys_initrd_size);
        phys_initrd_start = phys_initrd_size = 0;
    }
    if (phys_initrd_size &&
        memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {
        pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",
               phys_initrd_start, phys_initrd_size);
        phys_initrd_start = phys_initrd_size = 0;
    }
    if (phys_initrd_size) {
        memblock_reserve(phys_initrd_start, phys_initrd_size);

        /* Now convert initrd to virtual addresses */
        initrd_start = __phys_to_virt(phys_initrd_start);
        initrd_end = initrd_start + phys_initrd_size;
    }
#endif

    arm_mm_memblock_reserve();//預留page table區域

    /* reserve any platform specific memblock areas */
    if (mdesc->reserve)
        mdesc->reserve();

    early_init_fdt_scan_reserved_mem();//??reserved-memory什麼的,但現在好像不用了

    /*
     * reserve memory for DMA contigouos allocations,
     * must come from DMA area inside low memory
     */
    //這個就是讀取相關的device tree來預留相關的記憶體的
    dma_contiguous_reserve(min(arm_dma_limit, arm_lowmem_limit));

    arm_memblock_steal_permitted = false;
    memblock_allow_resize();
    memblock_dump_all();
}

以下看dma_contiguous_reserve()函式都怎麼讀device tree並預留記憶體給cma的

void __init dma_contiguous_reserve(phys_addr_t limit)
{
    phys_addr_t sel_size = 0;
    int i;

#ifdef CONFIG_OF
    of_scan_flat_dt(cma_fdt_scan, NULL); //這裡就是讀取device tree的內容的,具體看下面的函式說明
#endif
    pr_debug("%s(limit %pa)\n", __func__, &limit);

    if (size_cmdline != -1) {
        sel_size = size_cmdline;
    } else {
    //這裡留8MB,
    //cma: CMA: reserved 8 MiB at 0xa9400000 for default region
#ifdef CONFIG_CMA_SIZE_SEL_MBYTES
        sel_size = size_bytes;
#elif defined(CONFIG_CMA_SIZE_SEL_PERCENTAGE)
        sel_size = cma_early_percent_memory();
#elif defined(CONFIG_CMA_SIZE_SEL_MIN)
        sel_size = min(size_bytes, cma_early_percent_memory());
#elif defined(CONFIG_CMA_SIZE_SEL_MAX)
        sel_size = max(size_bytes, cma_early_percent_memory());
#endif
    }

    //再呼叫sanity_check_meminfo()
    dma_contiguous_early_removal_fixup();
    allow_memblock_alloc = true;

    for (i = 0; i < cma_area_count; i++) {
        if (cma_areas[i].base == 0) {
            int ret;

            ret = __dma_contiguous_reserve_memory(
                        cma_areas[i].size,
                        cma_areas[i].alignment,
                        cma_areas[i].limit,
                        &cma_areas[i].base);
            if (ret) {
                pr_err("CMA: failed to reserve %ld MiB for %s\n",
                       (unsigned long)cma_areas[i].size / SZ_1M,
                       cma_areas[i].name);
                memmove(&cma_areas[i], &cma_areas[i+1],
                   (cma_area_count - i)*sizeof(cma_areas[i]));
                cma_area_count--;
                i--;
                continue;
            }
            //沒有基地址的部分,都通過__dma_contiguous_reserve_memory分配
            //地址之後,儲存到dma_mmu_remap區域中!!
            dma_contiguous_early_fixup(cma_areas[i].base,
                            cma_areas[i].size);
        }

        pr_info("CMA: reserved %ld MiB at %pa for %s\n",
            (unsigned long)cma_areas[i].size / SZ_1M,
            &cma_areas[i].base, cma_areas[i].name);
    }

    if (sel_size) {
        phys_addr_t base = 0;
        pr_debug("%s: reserving %ld MiB for global area\n", __func__,
             (unsigned long)sel_size / SZ_1M);
        //由於已經使能了CONFIG_CMA_SIZE_SEL_MBYTES,
        //而且CONFIG_CMA_SIZE_MBYTES = 8
        //所以會列印列印的 cma: CMA: reserved 8 MiB at 0xa9400000 for default region
        if (dma_contiguous_reserve_area(sel_size, &base, limit, NULL,
            CMA_RESERVE_AREA ? 0 : 1, false) == 0) {
            pr_info("CMA: reserved %ld MiB at %pa for default region\n",
                (unsigned long)sel_size / SZ_1M, &base);
            dma_contiguous_def_base = base;
        }
    }
}
//
int __init cma_fdt_scan(unsigned long node, const char *uname,
                int depth, void *data)
{
    phys_addr_t base, size;
    int len;
    const __be32 *prop;
    const char *name;
    bool in_system;
    bool remove;
    unsigned long size_cells = dt_root_size_cells;
    unsigned long addr_cells = dt_root_addr_cells;
    phys_addr_t limit = MEMBLOCK_ALLOC_ANYWHERE;
    const char *status;

    //每個需要預留的記憶體必須有"linux,reserve-contiguous-region"!!
    //看實際的device tree也是,每個部分都有"linux,reserve-contiguous-region"
    if (!of_get_flat_dt_prop(node, "linux,reserve-contiguous-region", NULL))
        return 0;

    //檢視是否有status,沒有就算了,但如果有的話,必須是"ok",不然這個區域就不會被讀取
    status = of_get_flat_dt_prop(node, "status", NULL);
    /*
     * Yes, we actually want strncmp here to check for a prefix
     * ok vs. okay
     */
    if (status && (strncmp(status, "ok", 2) != 0))
        return 0;

    prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
    if (prop)
        size_cells = be32_to_cpup(prop);

    prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
    if (prop)
        addr_cells = be32_to_cpup(prop);

    prop = of_get_flat_dt_prop(node, "reg", &len);
    if (!prop || depth != 2)
        return 0;

    base = dt_mem_next_cell(addr_cells, &prop);
    size = dt_mem_next_cell(size_cells, &prop);


    name = of_get_flat_dt_prop(node, "label", NULL);

    //以下讀取幾個重要的設定,這些會在dma_contiguous_reserve_area()函式中被用到,
    in_system =
        of_get_flat_dt_prop(node, "linux,reserve-region", NULL) ? 0 : 1;

    prop = of_get_flat_dt_prop(node, "linux,memory-limit", NULL);
    if (prop)
        limit = be32_to_cpu(prop[0]);

    remove =
         of_get_flat_dt_prop(node, "linux,remove-completely", NULL) ? 1 : 0;

    pr_info("Found %s, memory base %pa, size %ld MiB, limit %pa\n", uname,
            &base, (unsigned long)size / SZ_1M, &limit);

    //
    dma_contiguous_reserve_area(size, &base, limit, name,
                    in_system, remove);

    return 0;
}

//cma_fdt_scan()函式讀取的device tree內容
    memory {
        #address-cells = <2>;
        #size-cells = <2>;

/* Additionally Reserved 6MB for TIMA and Increased the TZ app size
 * by 2MB [total 8 MB ]
 */
        //高通平臺trustzone存放的地址,如果這個地址改了,必須要修改
        //TZBSP_EBI1_TZ_APP_BASE和TZBSP_EBI1_TZ_APP_END等的地址
        //不然kernel啟動不了!!
        external_image_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            linux,reserve-region;
            linux,remove-completely;
            reg = <0x0 0x85500000 0x0 0x01300000>;
            label = "external_image_mem";
        };

        //高通平臺(msm8916)modem的存放地址開始地址和大小取決於
        //modem binary
        modem_adsp_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            linux,reserve-region;
            linux,remove-completely;
            reg = <0x0 0x86800000 0x0 0x05800000>;
            label = "modem_adsp_mem";
        };
        //這個部分是wcnss相關的存放地址,這部分記憶體需要緊挨著modem記憶體。
        //modem大小修改的時候,這個記憶體區域的開始地址也需要相應調整
        peripheral_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            linux,reserve-region;
            linux,remove-completely;
            reg = <0x0 0x8C000000 0x0 0x0600000>;
            label = "peripheral_mem";
        };

        venus_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            linux,reserve-region;
            linux,remove-completely;
            reg = <0x0 0x8C600000 0x0 0x0500000>;
            label = "venus_mem";
        };

        secure_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            reg = <0 0 0 0x6D00000>;
            label = "secure_mem";
        };

        qseecom_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            reg = <0 0 0 0xD00000>;
            label = "qseecom_mem";
        };

        audio_mem: [email protected]0 {
            linux,reserve-contiguous-region;
            reg = <0 0 0 0x314000>;
            label = "audio_mem";
        };

        cont_splash_mem: [email protected]8E000000 {
            linux,reserve-contiguous-region;
            linux,reserve-region;
            reg = <0x0 0x8E000000 0x0 0x1400000>;
            label = "cont_splash_mem";
        };
    };

以下簡單說一下高通msm8916平臺,modem大小檢查以及修改方法。
1) modem binary的大小可以從以下編譯的log裡邊看出來!!(modem_proc/build/ms目錄下的pplk-XXX.log或者build_xxxx.log)。
根據大小對齊1MB大小,就是modem binary需要流出來的大小。看如下例子裡邊的log,總的大小是77.04,
所以需要在上面的dtsi檔案中留出來78MB就可以。

  Image loaded at virtual address 0xc0000000 
  Image:                                   55.44 MiB 
  AMSS Heap:                                7.50 MiB (dynamic) 
  MPSS Heap:                                4.00 MiB (dynamic) 
  DSM Pools:                                5.06 MiB  
  Q6Zip RO, Swap Pool:                      2.00 MiB (dynamic) 
  Q6Zip RW, Swap Pool:                      1.00 MiB (dynamic) 
  Q6Zip RW, dlpager Heap:                   1.00 MiB 
  Extra:                                    0.54 MiB 
  Pad ding:                                  0.37 MiB 
  End Address Alignment:                    0.13 MiB 
  Total:                                   77.04 MiB 
  Available:                                7.96 MiB

2) 然後去修改modem_proc/config/xxx/ 目錄下的cust_config.xml檔案中修改modem大小

    <!-- 85 MB of physical pool-->  
        <physical_pool name="DEFAULT_PHYSPOOL"> 
             <region base="0x88000000"  size="0x5500000" /> 
             <region base="0x88000000" size="0x4E00000" /> 
        </physical_pool>  

3) 重新編譯,然後在第一步中編譯出來的檔案中重新確認大小

以下就是實際操作cma_areas變數的函式

//有"linux,reserve-region"的區域 to_system就是false
//有"linux,remove-completely"的區域,remove就是true
int __init dma_contiguous_reserve_area(phys_addr_t size, phys_addr_t *res_base,
                       phys_addr_t limit, const char *name,
                       bool to_system, bool remove)
{
    phys_addr_t base = *res_base;
    phys_addr_t alignment = PAGE_SIZE;
    int ret = 0;

    pr_debug("%s(size %lx, base %pa, limit %pa)\n", __func__,
         (unsigned long)size, &base,
         &limit);

    /* Sanity checks */
    if (cma_area_count == ARRAY_SIZE(cma_areas)) {
        pr_err("Not enough slots for CMA reserved regions!\n");
        return -ENOSPC;
    }

    if (!size)
        return -EINVAL;

    /* Sanitise input arguments */
    if (!remove)
        alignment = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
    base = ALIGN(base, alignment);
    size = ALIGN(size, alignment);
    limit &= ~(alignment - 1);

    //有base地址的設定,如果不與其他預留的地址衝突的話,就呼叫memblock_reset從memblock.memory中去掉!!
    //所以上面device tree設定中external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem被
    //直接從memblock給去掉了,,這個可以看一下前後對比的memblock列印的內容
    /* Reserve memory */
    if (base) {
        if (memblock_is_region_reserved(base, size) ||
            memblock_reserve(base, size) < 0) {
            ret = -EBUSY;
            goto err;
        }
    } else {
        //如果是沒有指定base地址的,就要通過__dma_contiguous_reserve_memory()函式
        //從memblock相關的介面分配到滿足大小的地址,並返回base地址了
        ret = __dma_contiguous_reserve_memory(size, alignment, limit,
                            &base);
        if (ret)
            goto err;
    }

    //有base地址,有"linux,remove-completely",但沒有"linux,reserve-region"就會報錯!
    //所以檢視上面的device tree內容,external_image_mem,modem_adsp_mem,
    //peripheral_mem,venus_mem等有base地址的都是既有"linux,remove-completely"
    //又有"linux,reserve-region"的就會跑到下面的if(!to_system)內
    if (base && remove) {
        if (!to_system) {
            //這裡邊就是把上面的區域從memblock.memory裡邊去掉,加到membloc.reserve裡邊等操作的
            memblock_free(base, size);
            memblock_remove(base, size);
        } else {
            WARN(1, "Removing is incompatible with staying in the system\n");
        }
    }

    /*
     * Each reserved area must be initialised later, when more kernel
     * subsystems (like slab allocator) are available.
     */
    cma_areas[cma_area_count].base = base;
    cma_areas[cma_area_count].size = size;
    cma_areas[cma_area_count].name = name;
    cma_areas[cma_area_count].alignment = alignment;
    cma_areas[cma_area_count].limit = limit;
    cma_areas[cma_area_count].to_system = to_system;
    cma_area_count++;
    *res_base = base;


    //以下是有base地址,且沒有定義"linux,remove-completely"的
    //比如fb相關的,上面的定義是"cont_splash_mem"
    //這些記憶體都會呼叫dma_contiguous_early_fixup()函式
    //這個函式很簡單,就是把相關的記憶體都儲存在dma_mmu_remap中。
    //有多少區域,用dma_mmu_remap_num來表示。
    /* Architecture specific contiguous memory fixup. */
    if (!remove && base)
        dma_contiguous_early_fixup(base, size);
    return 0;
err:
    pr_err("CMA: failed to reserve %ld MiB\n", (unsigned long)size / SZ_1M);
    return ret;
}
//這個函式可以總結一些規律:
//舉例來說,像msm8916這種,如果不是AP這邊控制的記憶體,像modem相關的記憶體,有base地址
//而且都是定義了remove,從記憶體中整個去掉,因為不是AP這邊需要控制的部分,只要預留出來
//就好。像external_image_mem,modem_adsp_mem,peripheral_mem,venus_mem就是這樣的。
//這些部分看上面的函式,就是直接從memblock.memory中挖掉,放到memblock.reserve裡。
//不會在paging_init的時候被map到lowmemory裡邊。

//但frambuffer對應的記憶體(cont_splash_mem),既要預留,又要AP這邊控制的,就只定義了base地址,但沒有定義
//"linux,remove-completely"來去掉這部分記憶體!!這種會通過dma_contiguous_early_fixup()把這部分記憶體
//放到dma_mmu_remap中!!這部分會在paging_init()->dma_contiguous_remap()中map起來(這部分在dma部分細說)

//當然cma_areas和cma_area_count是會儲存所有上面所說的內容的!!

這裡寫圖片描述

<6>[0.000000]  [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=2
<6>[0.000000]  [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000]  [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x2fd00000
<6>[0.000000]  [0:swapper:0] count = 2 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000]  [0:swapper:0] arm_lowmem_limit =0xa9c00000
<6>[0.000000]  [0:swapper:0] cma: Found external_image__region@0, memory base 0x85500000, size 19 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found modem_adsp_region@0, memory base 0x86800000, size 88 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found pheripheral_region@0, memory base 0x8c000000, size 6 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found venus_region@0, memory base 0x8c600000, size 5 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found secure_region@0, memory base 0x00000000, size 109 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found qseecom_region@0, memory base 0x00000000, size 13 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found audio_region@0, memory base 0x00000000, size 3 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] cma: Found splash_region@8E000000, memory base 0x8e000000, size 20 MiB, limit 0xffffffff
<6>[0.000000]  [0:swapper:0] sanity_check_meminfo memblock.memory.cnt=3
<6>[0.000000]  [0:swapper:0] pys_addr vmalloc_limit = 0xa9c00000
<6>[0.000000]  [0:swapper:0] count = 1 , reg->base =0x80000000 , reg->size =0x5500000
<6>[0.000000]  [0:swapper:0] count = 2 , reg->base =0x8cb00000 , reg->size =0x23200000
<6>[0.000000]  [0:swapper:0] count = 3 , reg->base =0xb0000000 , reg->size =0x30000000
<6>[0.000000]  [0:swapper:0] arm_lowmem_limit =0xb1200000
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 19 MiB at 0x85500000 for external_image_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 88 MiB at 0x86800000 for modem_adsp_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 6 MiB at 0x8c000000 for peripheral_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 5 MiB at 0x8c600000 for venus_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 112 MiB at 0xd9000000 for secure_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 16 MiB at 0xd8000000 for qseecom_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 4 MiB at 0xd7c00000 for audio_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 20 MiB at 0x8e000000 for cont_splash_mem
<6>[0.000000]  [0:swapper:0] cma: CMA: reserved 8 MiB at 0xa9400000 for default region
<4>[0.000000]  [0:swapper:0] Memory policy: ECC disabled, Data cache writealloc

CMA記憶體的分配和使用

相關的介面都在dma-contiguous.c檔案裡邊

//以下函式為每個cma_areas初始化cma並儲存到cma_areas[i].cma裡邊
//cma的定義如下:
struct cma {
    unsigned long   base_pfn;
    unsigned long   count;
    unsigned long   *bitmap;
    bool in_system;
    struct mutex lock;
};

static int __init cma_init_reserved_areas(void)
{
    struct cma *cma;
    int i;

    for (i = 0; i < cma_area_count; i++) {
        phys_addr_t base = PFN_DOWN(cma_areas[i].base);
        unsigned int count = cma_areas[i].size >> PAGE_SHIFT;
        bool system = cma_areas[i].to_system;

        cma = cma_create_area(base, count, system);
        if (!IS_ERR(cma))
            cma_areas[i].cma = cma;
    }
    //預設的8MB的cma區域儲存在dma_contiguous_def_area裡邊,
    //這個cma區域在dev_get_cma_area()函式中,如果沒有找到dev對應的cma區域的話,就會使用
    //dma_contiguous_def_area這個預設的cma區域
    dma_contiguous_def_area = cma_get_area(dma_contiguous_def_base);

    for (i = 0; i < cma_map_count; i++) {
        cma = cma_get_area(cma_maps[i].base);
        dev_set_cma_area(cma_maps[i].dev, cma);
    }
//註冊platform_bus_type的notifier函式,在每個platform裝置驅動註冊的時候,檢查是否有
//"linux,contiguous-region",有的話會根據相應的名字分配對應的cma並儲存到dev->cma裡邊
//這個在後面有具體說明
#ifdef CONFIG_OF 
    bus_register_notifier(&platform_bus_type, &cma_dev_init_nb);
#endif
    return 0;
}
core_initcall(cma_init_reserved_areas);

bus_register_notifier(&platform_bus_type, &cma_dev_init_nb) 這樣註冊platform_bus的notifier函式之後,
有以下platform裝置在註冊的時候,會通過cma_assign_device_from_dt()函式找到相應的cma區域並
賦值給dev->cma

static void cma_assign_device_from_dt(struct device *dev)
{
    struct device_node *node;
    struct cma *cma;
    const char *name;
    u32 value;

    //找到相應的device tree設定裡邊有沒有"linux,contiguous-region",
    node = of_parse_phandle(dev->of_node, "linux,contiguous-region", 0);
    if (!node)
        return;
    if (of_property_read_u32(node, "reg", &value) && !value)
        return;

    //找到label的名字
    if (of_property_read_string(node, "label", &name))
        return;

    //根據名字找到cma area
    cma = cma_get_area_by_name(name);
    if (!cma)
        return;
    //cma賦值給dev->cma_area
    dev_set_cma_area(dev, cma);

    //如果有"linux,remove-completely",就把removed_dma_ops賦值給
    //dev->archdata.dma_ops = removed_dma_ops
    if (of_property_read_bool(node, "linux,remove-completely"))
        set_dma_ops(dev, &removed_dma_ops);

    pr_info("Assigned CMA region at %lx to %s device\n", (unsigned long)value, dev_name(dev));
}

以下是所有有cma區域的platform裝置在初始化的時候找到的cma區域

<6>[0.487642]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 1de0000.qcom,venus device
<6>[0.489469]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 4080000.qcom,mss device
<6>[0.490756]  [0:swapper/0:1] cma: Assigned CMA region at 0 to a21b000.qcom,pronto device
<6>[1.125342]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 8.qcom,ion-heap device
<6>[1.125793]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 1b.qcom,ion-heap device
<6>[1.126233]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 1c.qcom,ion-heap device
<6>[1.126671]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 17.qcom,ion-heap device
<6>[1.127298]  [0:swapper/0:1] cma: Assigned CMA region at 0 to 1a.qcom,ion-heap device

這些CMA劃分出去的區域,除了external_image_mem是存放trustzone的區域不用管之外,其他的由msm_ion.c統一管理。這個在ION Memory相關的說明中再說。。。

相關推薦

Linux記憶體管理CMA

某些驅動需要用到一大塊連續的實體記憶體,但使用kmalloc等很分配很大的連續記憶體。 所以這裡有一種三星實現叫CMA的方式,來連續的大記憶體分配。 Why is it needed? Issue 1: Camera, Video Codec等Multimedia Device需要連續的數MB大小的Me

Linux記憶體管理HighMemory

HighMemory介紹 Linux一般把整個4GB可以map的記憶體中的1GB用於低端記憶體。從0xC0000000開始的話(CONFIG_PAGE_OFFSET配置),低端記憶體的地址範圍就是0xC0000000到high_memory地址。 high_

【原創】(十六)Linux記憶體管理CMA

背景 Read the fucking source code! --By 魯迅 A picture is worth a thousand words. --By 高爾基 說明: Kernel版本:4.14 ARM64處理器,Contex-A53,雙核 使用工具:Source Insight 3.5,

Linux記憶體管理 Slab分配器(一概述)

採用夥伴演算法分配記憶體時,每次至少分配一個頁面。但當請求分配的記憶體大小為幾十個位元組或幾百個位元組時應該如何處理?如何在一個頁面中分配小的記憶體區,小記憶體區的分配所產生的內碎片又如何解決?Linux採用Slab。 Linux 所使用的 slab 分配器的基礎是 Jef

linux記憶體管理2記憶體對映和需求分頁(英文名字demand Paging,又叫缺頁中斷)

        圖 10-5 vm_area_struct 資料結構示意圖當可執行映象對映到程序的虛擬地址空間時,將產生一組 vm_area_struct 結構來描述虛擬記憶體區域的起始點和終止點,每個 vm_struct 結構代表可執行映象的一部分,可能是可執行程式碼,也可能是初始化的變數或未初始化的資料。

linux記憶體管理演算法 夥伴演算法和slab

良好的作業系統效能部分依賴於作業系統有效管理資源的能力。在過去,堆記憶體管理器是實際的規範,但是其效能會受到記憶體碎片和記憶體回收需求的影響。現在,Linux® 核心使用了源自於 Solaris 的一種方法,但是這種方法在嵌入式系統中已經使用了很長時間了,它是將記憶體作為物件按照大小進行分配。本文將探索

Linux記憶體管理(最透徹的一篇)

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之

【轉】Linux記憶體管理

摘要:本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理與使用者記憶體管理之間的

linux記憶體管理的 夥伴系統和slab機制

夥伴系統 Linux核心中採用了一種同時適用於32位和64位系統的記憶體分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表。四級頁表分別為:  頁全域性目錄(Page Global Directory) 頁上級目錄(Page Upper Director

linux記憶體管理---虛擬地址 邏輯地址 線性地址 實體地址的區別(一)

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux記憶體管理之malloc實現

程序虛擬地址空間由一個一個VMA來表示,這裡先接收VMA相關操作. 1.1 查詢VMA函式find_vma() struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr) 找到的vma結果需滿足條件:

Linux記憶體描述之記憶體節點node--Linux記憶體管理(二)

1 記憶體節點node 1.1 為什麼要用node來描述記憶體 這點前面是說的很明白了, NUMA結構下, 每個處理器CPU與一個本地記憶體直接相連, 而不同處理器之前則通過匯流排進行進一步的連線, 因此相對於任何一個CPU訪問本地記憶體的速度比訪問遠端記憶體的速度要快 Linux適用於各種不同的體系結

Linux服務管理 chkconfig 和 systemctl 命令

對於 Linux 管理員來說這是一個重要(美妙)的話題,所以每個人都必須知道,並練習怎樣才能更高效的使用它們。 在 Linux 中,無論何時當你安裝任何帶有服務和守護程序的包,系統預設會把這些服務的初始化及 systemd 指令碼新增進去,不過此時它們並沒有被啟用。 我們需要手動的開啟

Linux記憶體描述之記憶體區域zone--Linux記憶體管理(三)

1 記憶體管理域zone 為了支援NUMA模型,也即CPU對不同記憶體單元的訪問時間可能不同,此時系統的實體記憶體被劃分為幾個節點(node), 一個node對應一個記憶體簇bank,即每個記憶體簇被認為是一個節點 首先, 記憶體被劃分為結點. 每個節點關聯到系統中的一個處理器, 核心中表示為pg_

Linux記憶體描述之記憶體頁面page--Linux記憶體管理(四)

1 Linux如何描述實體記憶體 Linux把實體記憶體劃分為三個層次來管理 層次 描述 儲存節點(Node) CPU被劃分為多個節點(node), 記憶體則被分簇, 每個CPU對應一個本地實體記憶體, 即一個CPU-node對應

Linux分頁機制之概述--Linux記憶體管理(六)

1 分頁機制 在虛擬記憶體中,頁表是個對映表的概念, 即從程序能理解的線性地址(linear address)對映到儲存器上的實體地址(phisical address). 很顯然,這個頁表是需要常駐記憶體的東西, 以應對頻繁的查詢對映需要(實際上,現代支援VM的處理器都有一個叫TLB的硬體級頁表快取部件

Linux記憶體管理 (透徹分析)

摘要: 本章首先以應用程式開發者的角度審視Linux的程序記憶體管理,在此基礎上逐步深入到核心中討論系統實體記憶體管理和核心記憶體的使用方法。力求從外到內、水到渠成地引導網友分析Linux的記憶體管理與使用。在本章最後,我們給出一個記憶體對映的例項,幫助網友們理解核心記憶體管理

Linux記憶體管理 (26)記憶體相關工具

  1. vmstat 參照《Linux CPU佔用率監控工具小結-vmstat》 2. memstat memstat可以通過sudo apt install memstat安裝,安裝包括兩個檔案memstat和memstat.conf。 其中memstat.conf是memstat配置

Linux分頁機制之分頁機制的演變--Linux記憶體管理(七)

1 頁式管理 1.1 分段機制存在的問題 分段,是指將程式所需要的記憶體空間大小的虛擬空間,通過對映機制對映到某個實體地址空間(對映的操作由硬體完成)。分段對映機制解決了之前作業系統存在的兩個問題: 地址空間沒有隔離 程式執行的地址不確定 不過分段方法存在一個嚴重的問題:記憶體的使用效率

Linux-記憶體管理子系統

Linux-記憶體管理子系統 記憶體管理子系統職能: 1. 管理虛擬地址和實體地址的對映;2. 管理實體記憶體的分配 虛擬記憶體空間 空間分佈: 1. 使用者空間 如 0-3G地址空間   被使用者程序所使用與核心的直接對映區使用的是同