热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【内存管理】CMA内存分配器(Contiguous

转自:https:www.cnblogs.comyibuyibup14806878.html什么是CMA参考这两篇博文,写得很好:http:www.wowotech.netmemo


转自:https://www.cnblogs.com/yibuyibu/p/14806878.html

什么是CMA
参考这两篇博文,写得很好:
http://www.wowotech.net/memory_management/cma.html
https://www.cnblogs.com/LoyenWang/p/12182594.html
https://biscuitos.github.io/blog/CMA/
CMA的初始化创建 * 默认cma创建(dma_contiguous_default_area),两种方式: 通过cmdline传递的参数"cma=",然后在kernel初始化阶段解析参数,并调用start_kernel()->setup_arch()->arm64_memblock_init()->dma_contiguous_reserve()完成创建(android中一般不通过cmdline传递): static phys_addr_t size_cmdline = -1; static phys_addr_t base_cmdline; static phys_addr_t limit_cmdline; //解析cmdline传递的cma参数 static int __init early_cma(char *p) { pr_debug("%s(%s)/n", __func__, p); size_cmdline = memparse(p, &p); if (*p != '@') return 0; base_cmdline = memparse(p + 1, &p); if (*p != '-') { limit_cmdline = base_cmdline + size_cmdline; return 0; } limit_cmdline = memparse(p + 1, &p); return 0; } early_param("cma", early_cma); 通过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的创建和初始化: static int __init __reserved_mem_init_node(struct reserved_mem *rmem) { extern const struct of_device_id __reservedmem_of_table[]; const struct of_device_id *i; //__reservedmem_of_table是初始化中的一个section段,通过RESERVEDMEM_OF_DECLARE定义的都会被链接到这个段中 //参考:https://blog.csdn.net/rikeyone/article/details/79975138 for (i = __reservedmem_of_table; i <&__rmem_of_table_sentinel; i++) { reservedmem_of_init_fn initfn = i->data; const char *compat = i->compatible; if (!of_flat_dt_is_compatible(rmem->fdt_node, compat)) continue; if (initfn(rmem) == 0) { pr_info("initialized node %s, compatible id %s/n", rmem->name, compat); return 0; } } return -ENOENT; } //dma-contiguous.c文件中定义了该默认cma的创建回调。 //如果dts中没有配置,那该回调也不会执行。 //参考:https://blog.csdn.net/rikeyone/article/details/79975138 RESERVEDMEM_OF_DECLARE(cma, "shared-dma-pool", rmem_cma_setup); 默认cma似乎在好些android平台上都没有创建。 *其他CMA区创建 其他CMA区域创建都应该类似默认cma一样,通过RESERVEDMEM_OF_DECLARE接口定义一个结构体变量在__reservedmem_of_table段中,开机启动时就会调用对应的initfn完成初始化,同时还需要在dts中配置对应的属性节点。 所有CMA的创建最终都会调用cma_init_reserved_mem()函数: 主要从cma全局数组cma_areas中分配一个cma实体并将传递过来的参数用于初始化该cam实体。 初始化参数包括,cma的name、起始页框号base_pfn,总共页数count,以及每个bit代表多少个页2^(order_per_bit)。 更新全局变量totalcma_pages,记录总的cma页面数量,在meminfo中CmaTotal就是这个值。 int __init cma_init_reserved_mem(phys_addr_t base, phys_addr_t size, unsigned int order_per_bit, const char *name, struct cma **res_cma) { struct cma *cma; phys_addr_t alignment; /* Sanity checks */ //判断cma数量是否已经满了,因为cma_areas数组指定了系统中总的cma数量,通过内核宏控制 if (cma_area_count == ARRAY_SIZE(cma_areas)) { pr_err("Not enough slots for CMA reserved regions!/n"); return -ENOSPC; } //判断该cma内存区间是否与reversed中的某个区间是交叉的?为什么要这样判断? if (!size || !memblock_is_region_reserved(base, size)) return -EINVAL; /* ensure minimal alignment required by mm core */ //对齐方式按pageblock,也就是1024页(4M) alignment = PAGE_SIZE << max_t(unsigned long, MAX_ORDER - 1, pageblock_order); /* alignment should be aligned with order_per_bit */ //判断对齐方式alignment本身的大小与单个bit表示的内存大小,是否对齐 if (!IS_ALIGNED(alignment >> PAGE_SHIFT, 1 <name = name; } else { cma->name = kasprintf(GFP_KERNEL, "cma%d/n", cma_area_count); if (!cma->name) return -ENOMEM; } cma->base_pfn = PFN_DOWN(base); //起始页号 cma->count = size >> PAGE_SHIFT; //总共页面数 cma->order_per_bit = order_per_bit; //一个bit代表的阶数 *res_cma = cma; cma_area_count++; totalcma_pages += (size / PAGE_SIZE); //totalcma_pages记录总的cma页面数量,在meminfo中CmaTotal就是这个值 return 0; } 到这里,只是完成对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: static int __init cma_init_reserved_areas(void) { int i; for (i = 0; i base_pfn, pfn = base_pfn; //i代表有多少个page block,一般一个pageblock是1024页 unsigned i = cma->count >> pageblock_order; struct zone *zone; //cma也是通过bitmap来管理,每个bit代表多大,由order_per_bit决定。 //默认的cma的order_per_bit为0,一个bit代表2^0个page。 //分配bitmap cma->bitmap = kzalloc(bitmap_size, GFP_KERNEL); if (!cma->bitmap) return -ENOMEM; WARN_ON_ONCE(!pfn_valid(pfn)); zOne= page_zone(pfn_to_page(pfn)); //以pageblock遍历, do { unsigned j; //记录当前pageblock的起始页 base_pfn = pfn; //判断当前pageblock中的所有页面是否满足要求:合法的页号、都在同一个zone中 for (j = pageblock_nr_pages; j; --j, pfn++) { WARN_ON_ONCE(!pfn_valid(pfn)); /* * alloc_contig_range requires the pfn range * specified to be in the same zone. Make this * simple by forcing the entire CMA resv range * to be in the same zone. */ if (page_zone(pfn_to_page(pfn)) != zone) goto not_in_zone; } //将当前pageblock初始化并释放给buddy 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 0; not_in_zone: pr_err("CMA area %s could not be activated/n", cma->name); kfree(cma->bitmap); cma->count = 0; return -EINVAL; } cma_activate_area()->init_cma_reserved_pageblock()函数设置pageblock类型并释放内存给buddy: void __init init_cma_reserved_pageblock(struct page *page) { unsigned i = pageblock_nr_pages; struct page *p = page; do { //清除页描述flag中的PG_Reserved标志位 __ClearPageReserved(p); //设置page->_refcount = 0 set_page_count(p, 0); } while (++p, --i); //设置pageblock的迁移类型为MIGRATE_CMA set_pageblock_migratetype(page, MIGRATE_CMA); if (pageblock_order >= MAX_ORDER) { i = pageblock_nr_pages; p = page; do { set_page_refcounted(p); __free_pages(p, MAX_ORDER - 1); p += MAX_ORDER_NR_PAGES; } while (i -= MAX_ORDER_NR_PAGES); } else { //设置page->_refcount = 1 set_page_refcounted(page); //释放pages到buddy中,以pageblock释放,order为10 __free_pages(page, pageblock_order); } //调整对应zone中的managed_pages可管理页面数,即加上一个pageblock数量 //调整总的内存数量totalram_pages,即加上一个pageblock数量 adjust_managed_page_count(page, pageblock_nr_pages); } CMA的分配 CMA分配通过统一接口cma_alloc函数,会从bitmap中先查找满足要求的连续bit,然后通过alloc_contig_range实现分配,成功后的页面会从buddy总摘出来: struct page *cma_alloc(struct cma *cma, size_t count, unsigned int align, gfp_t gfp_mask) { unsigned long mask, offset; unsigned long pfn = -1; unsigned long start = 0; unsigned long bitmap_maxno, bitmap_no, bitmap_count; struct page *page = NULL; int ret = -ENOMEM; if (!cma || !cma->count) return NULL; pr_debug("%s(cma %p, count %zu, align %d)/n", __func__, (void *)cma, count, align); if (!count) return NULL; mask = cma_bitmap_aligned_mask(cma, align); offset = cma_bitmap_aligned_offset(cma, align); bitmap_maxno = cma_bitmap_maxno(cma); bitmap_count = cma_bitmap_pages_to_bits(cma, count); if (bitmap_count > bitmap_maxno) return NULL; for (;;) { mutex_lock(&cma->lock); //1. 从cma->bitmap中查找连续bitmap_count个为0的bit bitmap_no = bitmap_find_next_zero_area_off(cma->bitmap, bitmap_maxno, start, bitmap_count, mask, offset); if (bitmap_no >= bitmap_maxno) { mutex_unlock(&cma->lock); break; } //2. 将查找到的连续bit设置为1,表示内存被分配占用 bitmap_set(cma->bitmap, bitmap_no, bitmap_count); /* * It's safe to drop the lock here. We've marked this region for * our exclusive use. If the migration fails we will take the * lock again and unmark it. */ mutex_unlock(&cma->lock); //3. 计算分配的起始页的页号 pfn = cma->base_pfn + (bitmap_no <order_per_bit); mutex_lock(&cma_mutex); //4. 分配从起始页开始的连续count个页,分配的migrate type为CMA类型 ret = alloc_contig_range(pfn, pfn + count, MIGRATE_CMA, gfp_mask); mutex_unlock(&cma_mutex); //5. 分配成功,就返回起始page if (ret == 0) { page = pfn_to_page(pfn); break; } cma_clear_bitmap(cma, pfn, count); if (ret != -EBUSY) break; pr_debug("%s(): memory range at %p is busy, retrying/n", __func__, pfn_to_page(pfn)); /* try again with a bit different memory target */ start = bitmap_no + mask + 1; } trace_cma_alloc(pfn, page, count, align); if (ret && !(gfp_mask & __GFP_NOWARN)) { pr_info("%s: alloc failed, req-size: %zu pages, ret: %d/n", __func__, count, ret); cma_debug_show_areas(cma); } pr_debug("%s(): returned %p/n", __func__, page); return page; } CMA的释放 释放操作也很清晰,通过cma_release函数实现,会将页面释放回buddy系统,并将cma的bitmap相应bit清零: bool cma_release(struct cma *cma, const struct page *pages, unsigned int count) { unsigned long pfn; if (!cma || !pages) return false; pr_debug("%s(page %p)/n", __func__, (void *)pages); pfn = page_to_pfn(pages); if (pfn base_pfn || pfn >= cma->base_pfn + cma->count) return false; VM_BUG_ON(pfn + count > cma->base_pfn + cma->count); //释放回buddy free_contig_range(pfn, count); //清零bit位,表示对应cma内存可用 cma_clear_bitmap(cma, pfn, count); trace_cma_release(pfn, pages, count); return true; } CMA与buddy 后续补充

推荐阅读
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • Linux磁盘的分区、格式化的观察和操作步骤
    本文介绍了如何观察Linux磁盘的分区状态,使用lsblk命令列出系统上的所有磁盘列表,并解释了列表中各个字段的含义。同时,还介绍了使用parted命令列出磁盘的分区表类型和分区信息的方法。在进行磁盘分区操作时,根据分区表类型选择使用fdisk或gdisk命令,并提供了具体的分区步骤。通过本文,读者可以了解到Linux磁盘分区和格式化的基本知识和操作步骤。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
  • 本文介绍了Linux系统中正则表达式的基础知识,包括正则表达式的简介、字符分类、普通字符和元字符的区别,以及在学习过程中需要注意的事项。同时提醒读者要注意正则表达式与通配符的区别,并给出了使用正则表达式时的一些建议。本文适合初学者了解Linux系统中的正则表达式,并提供了学习的参考资料。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • Windows7 64位系统安装PLSQL Developer的步骤和注意事项
    本文介绍了在Windows7 64位系统上安装PLSQL Developer的步骤和注意事项。首先下载并安装PLSQL Developer,注意不要安装在默认目录下。然后下载Windows 32位的oracle instant client,并解压到指定路径。最后,按照自己的喜好对解压后的文件进行命名和压缩。 ... [详细]
author-avatar
书友67997456_296
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有