范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

CMA技术原理分析

  前言
  本文介绍CMA(Contiguous Memory Allocator)技术原理,从源码分析CMA的初始化和分配流程,同时讲解涉及到的页面迁移、LRU(Least Rencntly Used)缓存、PCP(per cpu page)缓存等知识。
  一、CMA概述
  CMA是什么?为什么需要CMA?
  Linux伙伴系统(Buddy)使用 Page 粒度来管理内存,每个页面大小为4K。伙伴系统按照空闲内存块的长度,把内存挂载到不同长度的 free_list链表中。free_list 的单位是以 (2^order个Page) 来递增的,即 1 page、2 page、… 2^n,通常情况下最大 order 为10 对应的空闲内存大小为 4M bytes。我们使用伙伴系统来申请连续的物理页面最大的页面最大小4M bytes,且系统内存碎片化严重的时候,也很难分配到高order的页面。
  嵌入式系统上一些外设设备,如GPU、Camera,HDMI等都需要预留大量连续内存才能正常工作,且很多情况下仅4M连续内存是不足以满足设备的需求的,当然我们也可以使用memblock预留内存的方法来保留更大的连续内存,但这部分内存只能被设备所使用而Buddy使用不到,会导致内存浪费。CMA由此而生,我们即要能分配连续的大的内存空间给设备使用,平时设备不用时又要把内存给系统用,最大化利用内存。
  CMA连续内存分配器,主要是为了可用于分配连续的大块内存。系统初始化的时候会通过保留一片物理内存区域,平时设备驱动不用时,内存管理系统将该区域用于分配和管理可移动类型页面,提供给APP或者内核movable页面使用。设备驱动使用时,此时已经分配的页面则进行迁移走,该区域用于连续内存分配。
  在后续的章节中,我们主要通过阅读源码的方式,来介绍CMA的初始化、分配和页面迁移流程等。
  注:后续文中所贴源码为kernel5.4版本,代码截图会省略次要代码,只保留关键代码。
  二、CMA主要数据结构和API
  2.1 struct cma
  使用struct cma来描述一个CMA区域:
  base_pfn:CMA区域物理地址的起始page frame number(页帧号)
  count: CMA区域的页面数量
  bitmap:描述cma区域页面的分配情况,1表示已分配,0为空闲。
  order_per_bit:表示bitmap中一个bit所代表的页面数量(2^order_per_bit)。
  2.2 cma_init_reserved_mem
  从保留内存块里面获取一块地址为base、大小为size的内存,用来创建和初始化struct cma。
  2.3 cma_init_reserved_areas
  为了提升内存利用率,该函数用来将这CMA内存标记后归还给 buddy 系统,供 buddy作为可移动页面内存申请。
  2.4 cma_alloc
  用来从指定的CMA 区域上分配count个连续的页面,按照align对齐。
  2.5 cma_release
  用来释放已经分配count个连续的页面。
  三、CMA主要流程分析
  3.1 CMA初始化流程
  3.1.1 系统初始化:
  系统初始化过程需要先创建CMA区域,创建方法有:dts的reserved memory或者通过命令行参数。这里我们看经常使用的通过dts的reserved memory方式,物理内存的描述放置在dts中配置,比如:
  linux,cma 为CMA 区域名称。
  compatible须为"shared-dma-pool"。
  resuable 表示 cma 内存可被 buddy 系统使用。
  size 表示cma区域的大小,单位为字节
  alignment指定 CMA 区域的地址对齐大小。
  linux,cma-default 属性表示当前 cma 内存将会作为默认的cma pool 用于cma 内存的申请。
  在系统启动过程中,内核对上面描述的dtb文件进行解析,从而完成内存信息注册,调用流程为:
  setup_arch
  arm64_memblock_init
  early_init_fdt_scan_reserved_mem
  __reserved_mem_init_node
  __reserved_mem_init_node会遍历__reservedmem_of_table section中的内容,检查到dts中有compatible匹配(CMA这里为"shared-dma-pool")就进一步执行对应的initfn。通过RESERVEDMEM_OF_DECLARE定义的都会被链接到__reservedmem_of_table这个section段中,最终会调到使用RESERVEDMEM_OF_DECLARE定义的函数,如下rmem_cma_setup:
  3.1.2 rmem_cma_setup
  @1 cma_init_reserved_mem 从保留内存块里面获取一块地址为base、大小为size的内存,这里用dtb中解析出来的地址信息来初始化CMA,用来创建和初始化struct cma,代码很简单:
  @2 如果dts指定了linux,cma-default,则将dma_contiguous_set_default指向这个CMA区域,使用dma_alloc_contiguous从CMA分配内存时,默认会从该区域分。
  执行到此, CMA和其它的保留内存是一样的,都是放在 memblock.reserved 中,这部分保留内存一样没能被 Buddy 系统用到。前面讲过为了提升内存利用率,还需要将CMA这部分内存标记后归还给 Buddy系统,供 Buddy作为可移动页面提供给APP或内核内存申请,由cma_init_reserved_areas来实现。
  3.1.3 cma_init_reserved_areas
  在内核初始化的后期会调用core_initcall描述的初始化函数:
  cma_init_reserved_areas,它直接调用cma_activate_area来实现。cma_activate_area根据cma大小分配bitmap,然后循环调用init_cma_reserved_pageblock来操作CMA区域中所有的页面, 看下源码:
  @1 CMA区域由一个bitmap来管理各个page的状态,cma_bitmap_maxno计算Bitmap需要多少内存,i变量表示该CMA eara有多少个pageblock(4M)。
  @2 遍历该CM区域中的所有的pageblock
  @3 确保CMA区域中的所有page都是在一个zone内
  @4 最终调用init_cma_reserved_pageblock,以pageblock为单位进行处理,设置migrate type为MIGRATE_CMA,将页面添加到伙伴系统中并更新zone管理的页面总数。如下:
  @1 将页面已经设置的reserved标志位清除掉。
  @2 将migratetype设置为MIGRATE_CMA
  @3 循环调用__free_pages函数,将CMA区域中所有的页面都释放到buddy系统中。
  @4 更新伙伴系统管理的内存数量。
  执行到此,后续这部分CMA内存就可以为buddy所申请。在伙伴系统中migratetype为movable并且分配flag带CMA,可以从CMA分配内存:
  3.2CMA分配流程
  在阅读cma分配流程代码前,我们先看下它的函数调用流程,后面将通过源码对流程及各个函数进行分析。
  3.2.1 cma_alloc
  @1 bitmap的计算,主要是获取bimap最大的可用bit数(bitmap_maxno),此次分配需要多大的bitmap(bitmap_count)等。
  @2 根据上面计算得到的bitmap信息,从bitmap中找到一块空闲的位置。
  @3 一些特别情况(在后面会讲到)经常会导致CMA分配失败,当分配返回EBUSY时,需要msleep(100)再retry,默认会retry 5次。
  @4 将要分配的页面的对应bitmap先置位为1,表示已经分配了。
  @5 使用alloc_config_range来进行内存分配,在后面节详细分析。
  @6 分配失败则清除掉bitmap。
  3.2.2 内核中的"批处理":LRU缓存和PCP缓存
  在分析alloc_config_range之前,先插讲两个知识点LRU缓存和PCP缓存,在阅读内核源码中,我们会发现内核很喜欢使用一些"批处理"的方法来提升效率,减少一些拿锁开销。
  1.LRU 缓存
  经典的LRU(Least Rencntly Used)链表算法如下图:
  注:详细的LRU算法介绍可以参考内核工匠之前的文章:kswapd介绍
  新分配的页面不断地加入ACTIVE LRU链表中,同时ACTIVE LRU链表也不断地取出将页面放入 INACTIVE LRU链表。链表中的锁(pgdat->lru_lock)竞争力度是非常强烈的,如果页面转移是一个一个进行的,那对锁的竞争将会十分严重。
  为了改善这种情况,内核加入了一个 PER-CPU的 LRU缓存(用 struct pagevec 表示),页面要加入LRU链表会先放入当前 CPU 的LRU缓存 中,直到LRU缓已经满了(一般为15个页面),再获取lru_lock,一次性将这些页面批量放入LRU链表。
  2.PCP(PER-CPU PAGES)缓存
  由于内存页面属于公共资源,系统中频繁分配释放页面,会因为获得释放锁(zone->lock),CPU之间的同步操作产生大量消耗。同样为了改善这种情况,内核加入了per cpu page 缓存(struct per_cpu_pages表示),每个CPU都从Buddy批发申请少量的页面存放在本地。当系统需要申请内存时,优先从PCP缓存拿,用完了再从buddy批发。释放时也优先放回该PCP缓存,缓存满了再放回buddy系统。
  内核之前只支持order=0的PCP,社区最新已经有补丁可以支持order>0的per-cpu。
  3.2.3 alloc_config_range函数:
  继续看cma_alloc流程的alloc_config_range要干哪些事情:
  简而言之,目的就是想从一块"脏的"连续内存块(已经被各种类型的内存使用),得到一块干净的连续内存块,要么是回收掉,要么是迁移走,最后将这块干净的连续内存返回给调用者使用,如下图:
  走读下代码:
  @1 start_isolate_page_range:将目标内存块的pageblock 的迁移类型由MIGRATE_CMA 变更为 MIGRATE_ISOLATE。因为buddy系统不会从 MIGRATE_ISOLATE 迁移类型的pageblock 分配页面,可以防止在cma分配过程中,这些页面又被人从Buddy分走。
  @2 drain_all_pages:回收per-cpu pages,前面已经有介绍过PCP,回收过程需要先将放在PCP缓存页面归还给Buddy。
  @3 __alloc_contig_migrate_range:将目标内存块已使用的页面进行迁移处理,迁移过程就是将页面内容复制到其他内存区域,并更新对该页面的引用。
  @3.1 lru_cache_disable: 因为在LRU缓存的页面是无法迁移的,需要先将pagevec页面刷到LRU,即将准备添加到LRU链表上,却还未加入LRU的页面(还待在LRU缓存)添加到LRU上,并关闭LRU缓存功能。
  @3.2 isolate_migratepages_range 隔离要分配区域已经被Buddy使用的page,存放到cc的链表中,返回的是最后扫描并处理的页框号。这里隔离主要是防止后续迁移过程,page被释放或者被LRU回收路径使用。
  @3.3 reclaim_clean_pages_from_list:对于干净的文件页,直接回收即可。
  @3.4 migrate_pages:该函数是页面迁移在内核态的主要接口,内核中涉及到页面迁移的功能大都会调到,它把可移动的物理页迁移到一个新分配的页面。
  在下一节详细介绍它。
  @3.5 lru_cache_enable迁移过程完成,重新使能LRU PAGEVEC
  @4.undo_isolate_page_range: @1的逆过程pageblock的迁移类型从 MIGRATE_ISOLATE 恢复为 MIGRATE_CMA。
  最后将这些页面返回给调用者。
  3.3 CMA释放流程
  cma_release释放CMA内存的代码很简单,就是把页面从新free给Buddy和清楚到cma的bitmap分配标识,这里直接贴一下代码:
  四、页面迁移
  系统要使用CMA区域的内存,内存上的页面必须是可迁移的,这样子当设备要使用CMA时页面才能迁移走,那么哪些页面可以迁移呢?有两种类型:
  1. LRU上的页面,LRU链表上的页面为用户进程地址空间映射的页面,如匿名页和文件页,都是从buddy分配器migrate type为movable的pageblock上分来的。
  2. 非LRU上,但是是movable页面。非LRU的页面通常是为kernel space分配的page,要实现迁移需要驱动实现page->mapping->a_ops中的相关方法。比如我们常见的zsmalloc内存分配器的页面就支持迁移。
  migrate_pages()是页面迁移在内核态的主要接口,内核中涉及到页面迁移的功能大都会调到它。如下图,migrate_pages()无非是要分配一个新的页面,断开旧页面的映射关系,重新简历映射到新的页面,并且要拷贝旧页面的内容到新页面、新页面的struct page属性要和旧页面设置得一样,最后释放旧的页面。下面来阅读下它的源码。
  4.1 migrate_pages:
  migrate_pages函数和参数:
  from: 准备迁移页面的链表
  get_new_page:申请新页面函数的指针
  putnew_page:释放新页面函数的指针
  private:传给get_new_page的参数,CMA这里没有使用到传NULL
  mode:迁移模式,CMA的迁移模式会设置为MIGRATE_SYNC。共有下面几种:
  reason:迁移原因,记录是什么功能触发了迁移的行为。因为内核许多路径都需要用migrate_pages来迁移比如还有内存规整、热插拔等。CMA传递的为MR_CONTIG_RANG,表示调用alloc_contig_range()分配连续内存。
  再看migrate_pages代码,它遍历 from链表,对每个page调用unmap_and_move来实现迁移处理。
  4.2 unmap_and_move
  unmap_and_move函数的参数同migrate_pages一模一样,它调用get_new_page分配一个新页面,然后使用__unmap_and_move迁移页面到这个新分配的页面中,我们主要看下__unmap_and_move
  4.3 __unmap_and_move:
  @1尝试获取old page的页面锁PG_locked,若页面已经被其它进程持有了锁,则这里会尝试获取锁失败,对于MIGRATE_ASYNC模式的为异步迁移拿不到锁就直接跳过此页面。CMA迁移模式为MIGRATE_SYNC,这里一定使用lock_page一定要等到锁。
  @2处理正在回写的页面,根据迁移模式判断是否等待页面回写完成。MIGRATE_SYNC_LIGHT和MIGRATE_ASYNC不等待,cma迁移模式为MIGRATE_SYNC,会调用wait_on_page_writeback()函数等待页面回写完成。
  @3 对于匿名页,为了防止迁移过程anon_vma数据结构被释放了,需要使用page_get_anon_vma增加anon_vma->refcount引用计数。
  @4 获取new page的页面锁PG_locked,正常情况都能获取到。
  @5 判断这个页面是否属于非LRU页面,
  如果页面为非LRU页面,则通过调用move_to_new_page来处理,该函数会回调驱动的miratepage函数来进行页面迁移。
  如果是LRU页面,继续执行@6
  @6 通过page_mapped()判断是否有用户PTE映射了改页面。如果有则调用try_to_unmap(),通过反向映射机制解除old page所有相关的PTE。
  @7 调用move_to_new_page,拷贝old page的内容和struct page属性数据到new page。对于LRU页面 move_to_new_page是通过调用migrate_page做了两件事:复制struct page的属性和页面内容。
  @8对页表进行迁移:remove_migration_ptes通过反向映射机制建立new page到进程的映射关系。
  @9 迁移完成释放old、new页面的PG_locked,当然对于匿名页我们也要put_anon_vma减少的anon_vma->refcount引用计数
  @10 对于非LRU页面,调用put_page,释放old page引用计数(_refcount减1)
  对于传统LRU putback_lru_page把newpage添加到LRU链表中。
  4.4 move_to_new_page
  在@5和@7中,非LRU和LRU页面都是通过move_to_new_page来复制页面,我们来看下他的实现:
  对于非LRU页面,该函数会回调驱动的miratepage函数来进行页面迁移
  比如在zsmalloc内存分配器会注册迁移回调函数,迁移流程这里会调用到zsmalloc的zs_page_migrate来迁移其申请的页面。zsmalloc内存分配器这里就不展开讲了,有兴趣的读者可以阅读zsmalloc的源码。
  2.对于LRU页面,调用migrate_page做了两件事:复制struct page的属性和页面内容。
  @7.1 struct page属性的复制:
  migrate_page_move_mapping 要先检查page的refcount是否符合预期,符合后之后会复制页面的映射数据,比如page->index、page->mapping以及PG_swapbacked
  这里顺带提一下refcount:refcount是struct page中重的引用计数,用来表示内核中引用改页面的次数。当refcount=0,表示该页面为空闲页面或即将要被释放的页面。当refcount的值>0,表示该页面已经被分配了且内核正在使用,暂时不会被释放。
  内核中使用get_page、pin_user_pages、get_user_pages等函数来增加_refcount的引用计数,可以防止在进行某些操作过程(比如添加入LRU)页面被其它路径释放了,同时他也会导致refcount不符合预期,也就是在这里不能迁移。
  @7.2 page页面内容的复制:
  copy_highpage就很简单了,使用kmap映射两个页面,再将旧页面的内存复制到新的页面。
  @7.3 migrate_page_states用来复制页面的flag,如PG_dirty,PG_XXX等标志位,也是属于struct page属性的复制。
  4.5 小结:
  整个迁移过程已经分析完,画出流程图如下,
  五、总结
  从上面章节分析,我们可以看到CMA的设计都是围绕这两点来做的:
  1. 平时设备驱动不用时,CMA内存交给Buddy管理,这是在初始化流程cma_init_reserved_areas()或cma_release()来实现的。
  2.设备驱动要使用时,通过cma_alloc来申请物理连续的CMA内存。对于已经在Buddy被APP或者内核movable分配走了的页面,要通过回收或迁移将这块内存清理"干净",最后将这块物理连续"干净"的内存返回给设备驱动使用。核心实现在alloc_config_range()和migrate_pages()函数。
  参考⽂献
  1. 本⽂引⽤的和解读的代码都来⾃kernel-5.4https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/?h=v5.4.234
  2. 《奔跑吧Linux内核》
  3. 宋宝华:论Linux的页迁移(Page Migration)完整版:
  https://blog.csdn.net/21cnbao/article/details/108067917

聪明人养花,别的花都不养,只养4种花,难怪身体健康又强壮聪明人养花,别的花都不养,只养4种花,难怪身体健康又强壮现在喜欢养花的人是越来越多了,大家都开始认识到养花的好处,室内环境离不开花草的装饰,花卉植物能让环境变得有生机,不再枯燥,而泰国大城府最独特的景观,被树环绕的佛,身体已经消失,只露头部泰国大城府最著名的旅游景点之一是古树包佛首(或称被树环绕的佛塔)。这座古老的佛塔位于瓦艾佛寺(WatPhraSriSanphet)的内部,被一棵巨大的菩提树的树根环绕着,形成了一道3月15日深夜!4名高官被抓,涉及厅长银行行长,来看看都是谁?导读一个人什么时候最容易犯错误?就是一切顺利春风得意的时候,由自我认可自我欣赏,再到自我膨胀唯我独尊,直至随心所欲目无纪法,逐步坠向违纪违法的犯罪深渊。第一位辽宁省水利厅原党组书记大瓜!女公安局长凌娅自首,揭开了太子奶案件,这群人不一般这些日子,投案自首的在职领导好几位,不知道是不是做官坐久了?不想干了?还是心里瑟瑟发抖害怕的不行,想争取宽大处理?3月初,就有一名59岁的女公安局局长投案自首,还有一年就要退休的她预付卡小知识丨发售预付卡是需要到商业主管部门备案的视频加载中充值办卡在我们生活中已经是很普遍的现象,那么从事哪些行业的企业法人发行预付卡需要到商务部门备案呢?单用途商业预付卡管理范围零售业,包括综合零售专门零售无店铺及其他零售业住宅男财经聚焦315,你遇到过哪些消费陷阱?视频加载中315如期而至,去年央视曝光的老坛酸菜乱象还历历在目,而今年又有大批陷阱屡屡重伤消费者。例如,视频平台套娃式收费科技与狠活揭露食品添加剂乱象张小泉菜刀一拍蒜就断,还有各种矿大捐款当事人发声学校先找我回馈母校,资金困难后送我上法庭前段时间,中国矿业大学关于学生吴幽1100万捐款未兑现一事闹得沸沸扬扬,事情一爆出来,吴幽就遭受网暴,众多网友都指责吴幽利用学校声誉谋私利。在2022年7月,双方就赠与合同纠纷在徐全国人大代表王春法为博物馆长途旅行已屡见不鲜中国青年报客户端讯(实习生顾靓楠中青报中青网记者张艺)博物馆是人类文明的橱窗。近年来,博物馆热悄然兴起,越来越多的人愿意走进博物馆,在承载着深厚历史底蕴的文物前驻足。从博物馆建设数李强总理对一切腐败坚决零容忍,掷地有声见回应两会刚刚结束,党中央国务院反腐败的回答掷地有声见回应,今天,相继有多名厅级领导干部官宣被查,既有行政官员国企高管,也有原来的技术权威,有男领导,也有女干部,再次彰显了反腐败没有死角浙江省1日内5名领导干部相继落马,其中1人主动投案1人退休多年据中央纪委国家监委网站3月15日消息浙江省台州市委副书记政法委书记林毅涉嫌严重违纪违法,接受审查调查图片来源于网络,侵权必删据浙江省纪委省监委网站3月15日消息浙江省药品稽查局局长高校毕业生就好业需多方合力1158万背后的就业关切据教育部统计数据,2023届全国普通高校毕业生规模再创新高,预计达1158万人,同比增加82万人。就业是最大的民生。每年全国两会,高校毕业生就业都是代表委员们关心的热门话题。今年亦
A股开市全球就大跌!历史为什么总是惊人的相似?最近几年每次A股长假,外围就开始狂欢!等到A股快要开盘了,就纷纷开始大幅回落,这不这个国庆节前期外围市场纷纷大涨,就在A股股民都在盼周一开盘时,昨天晚上美股再次大跌接近4,又给周一一条路通向何方六月的艳阳播撒着七月的晴光,七月的光照在这条陌生的路上,陌生的路绵延无尽,通向一个无人知晓的前方。初夏的徐风轻触嫩绿的叶片,谱出一曲轻盈的歌谣,歌谣悠长飘过田野,微风在歌谣前彳亍彷桂花味的师大,太!香!了!莫羡三春桃与李桂花成实向秋荣金秋的师大是桂花味儿的沁人心脾的桂花甜萦绕在鼻尖心头团团金蕊暗香浮动带着这一簇金黄走进秋日的师大清香环抱深吸一口气星点丹桂卸下一天的疲惫教学楼间石板路上一边忍耐,一边咬牙坚持,熬着等着人常说人在江湖,身不由己现在,我终于明白有些事,有的时候,不需要我们努力,因为,我们无论再怎么努力,都改变不了什么。我们只能一边忍耐,一边咬牙坚持。我们得慢慢地熬着,静静地等着,熬成年人最有水平的说话方式我在头条搞创作第二期沟通最好的利器,就是洋溢在你脸上的微笑。作者洞见Leyla看过这样一张寓意深刻的图片,题目叫做说话之前,请确保大脑已经连接。很多时候,我们就像图中那个人一样,讲python并发编程使用Queue代替线程锁实现线程共享安全1。1queue模块在多线程中的作用queue模块可以实现各种多个生产者多个消费者队列,可用于在执行的多个线程之间安全地交换信息1。2queue模块创建的三种队列类队列类说明Que售价34。9万,续航616km,ZEEKR001是有性价比还是在收智商税?极氪旗下的这款ZEEKR001,刚出世的时候就获得了不错的反馈,位于销售榜前列,综合成绩和各项表现都不错,2022年又推出了新款ZEEKR0012022款超长续航双电机ME版,今天胆子太大,无视国法!判了消防控制室持假证上岗,还借机牟利这胆子也太大了!近日,浙江嵊州消防部门在检查时发现一高层小区的消防控制室值班人员陈某持有的消防职业证书涉嫌伪造经查,该证件系陈某从钱某处购得而钱某为走高质量发展之路实现更加强劲绿色健康的发展习近平总书记强调立足新发展阶段贯彻新发展理念构建新发展格局,推动高质量发展,是当前和今后一个时期全党全国必须抓紧抓好的工作。可见,推动高质量发展,是保持经济持续健康发展的必然要求,水利部连续三届荣获政协全国委员会提案先进承办单位近日,政协第十三届全国委员会优秀提案和先进承办单位表彰会在京召开。水利部被评选为提案先进承办单位,自2012年起已连续三届当选。十三届全国政协期间,水利部共承办政协提案749件,承当前时点,乐观一些!多家公募基金发声今年国庆期间,中国基金报邀请多家基金公司旗下投资总监和知名基金经理,对前三季度股市进行分析总结,展望四季度A股投资机会,仅供投资者参考。平安基金权益投资中心执行总经理神爱前四季度市