什么是CMA

参考这两篇博文,写得很好:

http://www.wowotech.net/memory_management/cma.html

https://www.cnblogs.com/LoyenWang/p/12182594.html

CMA的初始化创建

* 默认cma创建(dma_contiguous_default_area),两种方式:

  1. 通过cmdline传递的参数"cma=",然后在kernel初始化阶段解析参数,并调用start_kernel()->setup_arch()->arm64_memblock_init()->dma_contiguous_reserve()完成创建(android中一般不通过cmdline传递):
  1. static phys_addr_t size_cmdline = -1;
  2. static phys_addr_t base_cmdline;
  3. static phys_addr_t limit_cmdline;
  4. //解析cmdline传递的cma参数
  5. static int __init early_cma(char *p)
  6. {
  7. pr_debug("%s(%s)\n", __func__, p);
  8. size_cmdline = memparse(p, &p);
  9. if (*p != '@')
  10. return 0;
  11. base_cmdline = memparse(p + 1, &p);
  12. if (*p != '-') {
  13. limit_cmdline = base_cmdline + size_cmdline;
  14. return 0;
  15. }
  16. limit_cmdline = memparse(p + 1, &p);
  17. return 0;
  18. }
  19. early_param("cma", early_cma);
  1. 通过dts中配置cma节点,属性中包含"shared-dma-pool"以及"linux,cma-default",在kernel初始化阶段,通过调用start_kernel()->setup_arch()->arm64_memblock_init()->early_init_fdt_scan_reserved_mem()->fdt_init_reserved_mem()->__reserved_mem_init_node()完成对默认cma的创建和初始化:
  1. static int __init __reserved_mem_init_node(struct reserved_mem *rmem)
  2. {
  3. extern const struct of_device_id __reservedmem_of_table[];
  4. const struct of_device_id *i;
  5. //__reservedmem_of_table是初始化中的一个section段,通过RESERVEDMEM_OF_DECLARE定义的都会被链接到这个段中
  6. //参考:https://blog.csdn.net/rikeyone/article/details/79975138
  7. for (i = __reservedmem_of_table; i < &__rmem_of_table_sentinel; i++) {
  8. reservedmem_of_init_fn initfn = i->data;
  9. const char *compat = i->compatible;
  10. if (!of_flat_dt_is_compatible(rmem->fdt_node, compat))
  11. continue;
  12. if (initfn(rmem) == 0) {
  13. pr_info("initialized node %s, compatible id %s\n",
  14. rmem->name, compat);
  15. return 0;
  16. }
  17. }
  18. return -ENOENT;
  19. }
  1. //dma-contiguous.c文件中定义了该默认cma的创建回调。
  2. //如果dts中没有配置,那该回调也不会执行。
  3. //参考:https://blog.csdn.net/rikeyone/article/details/79975138
  4. RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup);
  1. 默认cma似乎在好些android平台上都没有创建。

*其他CMA区创建

  • 其他CMA区域创建都应该类似默认cma一样,通过RESERVEDMEM_OF_DECLARE接口定义一个结构体变量在__reservedmem_of_table段中,开机启动时就会调用对应的initfn完成初始化,同时还需要在dts中配置对应的属性节点。

  • 所有CMA的创建最终都会调用cma_init_reserved_mem()函数:

    1. 主要从cma全局数组cma_areas中分配一个cma实体并将传递过来的参数用于初始化该cam实体。
    2. 初始化参数包括,cma的name、起始页框号base_pfn,总共页数count,以及每个bit代表多少个页2^(order_per_bit)。
    3. 更新全局变量totalcma_pages,记录总的cma页面数量,在meminfo中CmaTotal就是这个值。
  1. int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size,
  2. unsigned int order_per_bit,
  3. const char *name,
  4. struct cma **res_cma)
  5. {
  6. struct cma *cma;
  7. phys_addr_t alignment;
  8. /* Sanity checks */
  9. //判断cma数量是否已经满了,因为cma_areas数组指定了系统中总的cma数量,通过内核宏控制
  10. if (cma_area_count == ARRAY_SIZE(cma_areas)) {
  11. pr_err("Not enough slots for CMA reserved regions!\n");
  12. return -ENOSPC;
  13. }
  14. //判断该cma内存区间是否与reversed中的某个区间是交叉的?为什么要这样判断?
  15. if (!size || !memblock_is_region_reserved(base, size))
  16. return -EINVAL;
  17. /* ensure minimal alignment required by mm core */
  18. //对齐方式按pageblock,也就是1024页(4M)
  19. alignment = PAGE_SIZE <<
  20. max_t(unsigned long, MAX_ORDER - 1, pageblock_order);
  21. /* alignment should be aligned with order_per_bit */
  22. //判断对齐方式alignment本身的大小与单个bit表示的内存大小,是否对齐
  23. if (!IS_ALIGNED(alignment >> PAGE_SHIFT, 1 << order_per_bit))
  24. return -EINVAL;
  25. //判断base和size以aligment方式对齐后,得到的值是否还是原来的值,也就是判断base和size是否基于alignment对齐
  26. if (ALIGN(base, alignment) != base || ALIGN(size, alignment) != size)
  27. return -EINVAL;
  28. /*
  29. * Each reserved area must be initialised later, when more kernel
  30. * subsystems (like slab allocator) are available.
  31. */
  32. //1. memblock是系统最初的内存管理器,分为memory type和reserved type,CMA最开始就属于reserved type
  33. //2. 运行到这里,就表示memblock已经建立,并且buddy还没建立,CMA在buddy前建立OK
  34. //3. CMA建立OK后,接着memblock中的memory type会释放给buddy,reserved type则不会
  35. //4. CMA作为特殊的reserved type,最终通过系统初始化调用cma_init_reserved_areas,将内存归还给buddy
  36. //从cma_areas数组中分配一个cma对象
  37. cma = &cma_areas[cma_area_count];
  38. if (name) {
  39. cma->name = name;
  40. } else {
  41. cma->name = kasprintf(GFP_KERNEL, "cma%d\n", cma_area_count);
  42. if (!cma->name)
  43. return -ENOMEM;
  44. }
  45. cma->base_pfn = PFN_DOWN(base); //起始页号
  46. cma->count = size >> PAGE_SHIFT; //总共页面数
  47. cma->order_per_bit = order_per_bit; //一个bit代表的阶数
  48. *res_cma = cma;
  49. cma_area_count++;
  50. totalcma_pages += (size / PAGE_SIZE); //totalcma_pages记录总的cma页面数量,在meminfo中CmaTotal就是这个值
  51. return 0;
  52. }

到这里,只是完成对cma内存的保留和初始化,cma区最终还需要释放给buddy。

CMA区域释放给buddy

  • 释放也是在kernel初始化过程中,会比cma的创建稍晚一些,是通过cma_init_reserved_areas接口完成的所有cma的初始化并将内存返还给buddy。

    • core_initcall(cma_init_reserved_areas)定义在kernel的init段中,通过start_kernel()->rest_init()创建内核线程kernel_init->kernel_init_freeable()->do_basic_setup()->do_initcalls()完成对各个init level的初始化。core init属于level1。
  • cma_init_reserved_areas()函数,遍历当前cma全局数组中已经分配的cma实体,通过调用cma_activate_area函数完成激活初始化,同时将内存释放给buddy:

  1. static int __init cma_init_reserved_areas(void)
  2. {
  3. int i;
  4. for (i = 0; i < cma_area_count; i++) {
  5. int ret = cma_activate_area(&cma_areas[i]);
  6. if (ret)
  7. return ret;
  8. }
  9. return 0;
  10. }
  11. core_initcall(cma_init_reserved_areas);
  • cma_activate_area()函数:

    以pageblock为单位,设置migrate type为MIGRATE_CMA,然后将其整个pageblock包含的页全部释放给buddy,并更新整个系统的可用内存总数
  1. static int __init cma_activate_area(struct cma *cma)
  2. {
  3. int bitmap_size = BITS_TO_LONGS(cma_bitmap_maxno(cma)) * sizeof(long);
  4. unsigned long base_pfn = cma->base_pfn, pfn = base_pfn;
  5. //i代表有多少个page block,一般一个pageblock是1024页
  6. unsigned i = cma->count >> pageblock_order;
  7. struct zone *zone;
  8. //cma也是通过bitmap来管理,每个bit代表多大,由order_per_bit决定。
  9. //默认的cma的order_per_bit为0,一个bit代表2^0个page。
  10. //分配bitmap
  11. cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL);
  12. if (!cma->bitmap)
  13. return -ENOMEM;
  14. WARN_ON_ONCE(!pfn_valid(pfn));
  15. zone = page_zone(pfn_to_page(pfn));
  16. //以pageblock遍历,
  17. do {
  18. unsigned j;
  19. //记录当前pageblock的起始页
  20. base_pfn = pfn;
  21. //判断当前pageblock中的所有页面是否满足要求:合法的页号、都在同一个zone中
  22. for (j = pageblock_nr_pages; j; --j, pfn++) {
  23. WARN_ON_ONCE(!pfn_valid(pfn));
  24. /*
  25. * alloc_contig_range requires the pfn range
  26. * specified to be in the same zone. Make this
  27. * simple by forcing the entire CMA resv range
  28. * to be in the same zone.
  29. */
  30. if (page_zone(pfn_to_page(pfn)) != zone)
  31. goto not_in_zone;
  32. }
  33. //将当前pageblock初始化并释放给buddy
  34. init_cma_reserved_pageblock(pfn_to_page(base_pfn));
  35. } while (--i);
  36. mutex_init(&cma->lock);
  37. #ifdef CONFIG_CMA_DEBUGFS
  38. INIT_HLIST_HEAD(&cma->mem_head);
  39. spin_lock_init(&cma->mem_head_lock);
  40. #endif
  41. return 0;
  42. not_in_zone:
  43. pr_err("CMA area %s could not be activated\n", cma->name);
  44. kfree(cma->bitmap);
  45. cma->count = 0;
  46. return -EINVAL;
  47. }
  • cma_activate_area()->init_cma_reserved_pageblock()函数设置pageblock类型并释放内存给buddy:
  1. void __init init_cma_reserved_pageblock(struct page *page)
  2. {
  3. unsigned i = pageblock_nr_pages;
  4. struct page *p = page;
  5. do {
  6. //清除页描述flag中的PG_Reserved标志位
  7. __ClearPageReserved(p);
  8. //设置page->_refcount = 0
  9. set_page_count(p, 0);
  10. } while (++p, --i);
  11. //设置pageblock的迁移类型为MIGRATE_CMA
  12. set_pageblock_migratetype(page, MIGRATE_CMA);
  13. if (pageblock_order >= MAX_ORDER) {
  14. i = pageblock_nr_pages;
  15. p = page;
  16. do {
  17. set_page_refcounted(p);
  18. __free_pages(p, MAX_ORDER - 1);
  19. p += MAX_ORDER_NR_PAGES;
  20. } while (i -= MAX_ORDER_NR_PAGES);
  21. } else {
  22. //设置page->_refcount = 1
  23. set_page_refcounted(page);
  24. //释放pages到buddy中,以pageblock释放,order为10
  25. __free_pages(page, pageblock_order);
  26. }
  27. //调整对应zone中的managed_pages可管理页面数,即加上一个pageblock数量
  28. //调整总的内存数量totalram_pages,即加上一个pageblock数量
  29. adjust_managed_page_count(page, pageblock_nr_pages);
  30. }

CMA的分配

  • CMA分配通过统一接口cma_alloc函数,会从bitmap中先查找满足要求的连续bit,然后通过alloc_contig_range实现分配,成功后的页面会从buddy总摘出来:
  1. struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align,
  2. gfp_t gfp_mask)
  3. {
  4. unsigned long mask, offset;
  5. unsigned long pfn = -1;
  6. unsigned long start = 0;
  7. unsigned long bitmap_maxno, bitmap_no, bitmap_count;
  8. struct page *page = NULL;
  9. int ret = -ENOMEM;
  10. if (!cma || !cma->count)
  11. return NULL;
  12. pr_debug("%s(cma %p, count %zu, align %d)\n", __func__, (void *)cma,
  13. count, align);
  14. if (!count)
  15. return NULL;
  16. mask = cma_bitmap_aligned_mask(cma, align);
  17. offset = cma_bitmap_aligned_offset(cma, align);
  18. bitmap_maxno = cma_bitmap_maxno(cma);
  19. bitmap_count = cma_bitmap_pages_to_bits(cma, count);
  20. if (bitmap_count > bitmap_maxno)
  21. return NULL;
  22. for (;;) {
  23. mutex_lock(&cma->lock);
  24. //1. 从cma->bitmap中查找连续bitmap_count个为0的bit
  25. bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap,
  26. bitmap_maxno, start, bitmap_count, mask,
  27. offset);
  28. if (bitmap_no >= bitmap_maxno) {
  29. mutex_unlock(&cma->lock);
  30. break;
  31. }
  32. //2. 将查找到的连续bit设置为1,表示内存被分配占用
  33. bitmap_set(cma->bitmap, bitmap_no, bitmap_count);
  34. /*
  35. * It's safe to drop the lock here. We've marked this region for
  36. * our exclusive use. If the migration fails we will take the
  37. * lock again and unmark it.
  38. */
  39. mutex_unlock(&cma->lock);
  40. //3. 计算分配的起始页的页号
  41. pfn = cma->base_pfn + (bitmap_no << cma->order_per_bit);
  42. mutex_lock(&cma_mutex);
  43. //4. 分配从起始页开始的连续count个页,分配的migrate type为CMA类型
  44. ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA,
  45. gfp_mask);
  46. mutex_unlock(&cma_mutex);
  47. //5. 分配成功,就返回起始page
  48. if (ret == 0) {
  49. page = pfn_to_page(pfn);
  50. break;
  51. }
  52. cma_clear_bitmap(cma, pfn, count);
  53. if (ret != -EBUSY)
  54. break;
  55. pr_debug("%s(): memory range at %p is busy, retrying\n",
  56. __func__, pfn_to_page(pfn));
  57. /* try again with a bit different memory target */
  58. start = bitmap_no + mask + 1;
  59. }
  60. trace_cma_alloc(pfn, page, count, align);
  61. if (ret && !(gfp_mask & __GFP_NOWARN)) {
  62. pr_info("%s: alloc failed, req-size: %zu pages, ret: %d\n",
  63. __func__, count, ret);
  64. cma_debug_show_areas(cma);
  65. }
  66. pr_debug("%s(): returned %p\n", __func__, page);
  67. return page;
  68. }

CMA的释放

  • 释放操作也很清晰,通过cma_release函数实现,会将页面释放回buddy系统,并将cma的bitmap相应bit清零:
  1. bool cma_release(struct cma *cma, const struct page *pages, unsigned int count)
  2. {
  3. unsigned long pfn;
  4. if (!cma || !pages)
  5. return false;
  6. pr_debug("%s(page %p)\n", __func__, (void *)pages);
  7. pfn = page_to_pfn(pages);
  8. if (pfn < cma->base_pfn || pfn >= cma->base_pfn + cma->count)
  9. return false;
  10. VM_BUG_ON(pfn + count > cma->base_pfn + cma->count);
  11. //释放回buddy
  12. free_contig_range(pfn, count);
  13. //清零bit位,表示对应cma内存可用
  14. cma_clear_bitmap(cma, pfn, count);
  15. trace_cma_release(pfn, pages, count);
  16. return true;
  17. }

CMA与buddy

后续补充

【内存管理】CMA内存分配器(Contiguous Memory Allocator)的更多相关文章

  1. [内存管理]连续内存分配器(CMA)概述

    作者:Younger Liu, 本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可. 原文地址:http://lwn.net/Articles/396657/ 1 ...

  2. 启动期间的内存管理之引导分配器bootmem--Linux内存管理(十)

    在内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到保护模式, 然后内核才能检 ...

  3. Linux内存管理之bootmem分配器

    为什么要使用bootmem分配器,内存管理不是有buddy系统和slab分配器吗?由于在系统初始化的时候需要执行一些内存管理,内存分配的任务,这个时候buddy系统,slab分配器等并没有被初始化好, ...

  4. SAP专家培训之Netweaver ABAP内存管理和内存调优最佳实践

    培训者:SAP成都研究院开发人员Jerry Wang 1. Understanding Memory Objects in ABAP Note1: DATA itab WITH HEADER LINE ...

  5. Linux内存管理之slab分配器

    slab分配器是什么? 参考:http://blog.csdn.net/vanbreaker/article/details/7664296 slab分配器是Linux内存管理中非常重要和复杂的一部分 ...

  6. Linux内存管理 (5)slab分配器

    专题:Linux内存管理专题 关键词:slab/slub/slob.slab描述符.kmalloc.本地/共享对象缓冲池.slabs_partial/slabs_full/slabs_free.ava ...

  7. Linux内核笔记——内存管理之slab分配器

    内核版本:linux-2.6.11 内存区和内存对象 伙伴系统是linux用于满足对不同大小块内存分配和释放请求的解决方案,它为slab分配器提供页框分配请求的实现. 如果我们需要请求具有连续物理地址 ...

  8. Android内存管理机制之一:low memory killer

    转载自http://www.miui.com/thread-29268-1-1.html 准备写这个专题之前,心里是有点忐忑的.首先Android内存管理机制相当复杂,想要讲清楚比较困难:其次对于绝大 ...

  9. 内存管理之slab分配器

    基本思想 与传统的内存管理模式相比, slab 缓存分配器提供了很多优点.首先,内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配.slab 缓存分配器通过对类似大小的对象进行缓存而提 ...

  10. 启动期间的内存管理之bootmem_init初始化内存管理–Linux内存管理(十二)

    1. 启动过程中的内存初始化 首先我们来看看start_kernel是如何初始化系统的, start_kerne定义在init/main.c?v=4.7, line 479 其代码很复杂, 我们只截取 ...

随机推荐

  1. Linux安装证书

    Linux安装 vCenter root CA: 1.访问vCenter管理页面,下载"下载受信任的根 CA 证书" 2.压缩文件内带有数字作为扩展名(.0..1 等)的文件是根证 ...

  2. ABC136 E - Max GCD 题解

    题面 给定一个长度为 $N$ 的整数序列:$A_1, A_2, \ldots, A_n$. 您可以执行以下操作 $0 \sim K$ 次: 选择两个整数 $i$ 和 $j$,满足 $i \ne j$ ...

  3. JavaScript 基础学习(三)

    BOM对象 BOM(浏览器对象模型),可以对浏览器窗口进行访问和操作.使用 BOM,开发者可以移动窗口.改变状态栏中的文本以及执行其他与页面内容不直接相关的动作. 使 JavaScript 有能力与浏 ...

  4. 想通过anconda来创建一个虚拟环境,结果发现一直报错。An unexpected error has occurred. Conda has prepared the above report.

    本来想要通过conda create -n drf-admin python==3.8 来创建一个虚拟环境,结果一直报错. An unexpected error has occurred. Cond ...

  5. java字符串和图片相互转换

    package com.thinkgem.jeesite.modules.api.wechat; import sun.misc.BASE64Decoder; import sun.misc.BASE ...

  6. laravel 软删除的使用

    1.模型层 引用类use Illuminate\Database\Eloquent\SoftDeletes;class类中引用软删除use SoftDeletes;然后执行正常的删除,列表已经不显示, ...

  7. 【linux】grep命令检索大批量日志中的堆栈日志

    记得3年前,我为了查看100M日志文件里面的错误堆栈信息,百度了许久都毫无结果 没想到今天再次百度时,一下子看到了grep -A 命令,激动不已. 原来只需要用, grep -A 100 'KeyWo ...

  8. cookie和session、token的区别

    原作者:施杨(施杨's Think out)出处:http://shiyangxt.cnblogs.com ************** 本文版权归原作者和博客园共有,欢迎转载,转载请保留该申明 ** ...

  9. Mysql Dao

    1 package dao; 2 3 import java.sql.Connection; 4 import java.sql.PreparedStatement; 5 import java.sq ...

  10. Linux 库的使用

    Linux 库的使用 -I头文件的路径-L动态库的路径   命名 使用 静态库 lib名字.a 静态库路径/lib名字.a 动态库 lib名字.so -L动态库路径 -l名字 编译 #静态编译 # g ...