22 | 瞧一瞧Linux:伙伴系统如何分配内存?
LMOS
该思维导图由 AI 生成,仅供参考
你好,我是 LMOS。
前面我们实现了 Cosmos 的内存管理组件,相信你对计算机内存管理已经有了相当深刻的认识和见解。那么,像 Linux 这样的成熟操作系统,又是怎样实现内存管理的呢?
这就要说到 Linux 系统中,用来管理物理内存页面的伙伴系统,以及负责分配比页更小的内存对象的 SLAB 分配器了。
我会通过两节课给你理清这两种内存管理技术,这节课我们先来说说伙伴系统,下节课再讲 SLAB。只要你紧跟我的思路,再加上前面的学习,真正理解这两种技术也并不难。
伙伴系统
伙伴系统源于 Sun 公司的 Solaris 操作系统,是 Solaris 操作系统上极为优秀的物理内存页面管理算法。
但是,好东西总是容易被别人窃取或者效仿,伙伴系统也成了 Linux 的物理内存管理算法。由于 Linux 的开放和非赢利,这自然无可厚非,这不得不让我们想起了鲁迅《孔乙己》中的:“窃书不算偷”。
那 Linux 上伙伴系统算法是怎样实现的呢?我们不妨从一些重要的数据结构开始入手。
怎样表示一个页
Linux 也是使用分页机制管理物理内存的,即 Linux 把物理内存分成 4KB 大小的页面进行管理。那 Linux 用了一个什么样的数据结构,表示一个页呢?
早期 Linux 使用了位图,后来使用了字节数组,但是现在 Linux 定义了一个 page 结构体来表示一个页,代码如下所示。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
Linux内存管理中的伙伴系统是本文的重点内容。文章深入介绍了伙伴系统的实现原理,包括page结构和zone结构的工作方式。在快速分配路径和慢速分配路径中,通过遍历内存区、检查内存水位线和执行内存回收机制来尝试分配内存页面。文章详实地介绍了rmqueue和__rmqueue_smallest函数,以及expand函数的实现细节,帮助读者理解伙伴系统的核心原理。此外,文章还介绍了伙伴系统的数据结构和内存分配原理,以及伙伴系统能分配多大的连续物理内存。总的来说,本文对Linux内核中伙伴系统的实现方式和内存管理技术特点进行了深入解析,对于想深入了解Linux内存管理的读者来说,是一篇很有价值的技术文章。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《操作系统实战 45 讲》,新⼈⾸单¥68
《操作系统实战 45 讲》,新⼈⾸单¥68
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(11)
- 最新
- 精选
- neohope置顶一、整理一下思路 NUMA体系下,每个CPU都有自己直接管理的一部分内存,叫做内存节点【node】,CPU访问自己的内存节点速度,快于访问其他CPU的内存节点; 每个内存节点,按内存的迁移类型,被划分为多个内存区域【zone】;迁移类型包括ZONE_DMA、ZONE_DMA32、ZONE_NORMAL 、ZONE_HIGHMEM、ZONE_MOVABLE、ZONE_DEVICE等; 每个内存区域中,是一段逻辑上的连续内存,包括多个可用页面;但在这个连续内存中,同样有不能使用的地方,叫做内存空洞;在处理内存操作时,要避免掉到洞里; 二、整理一下结构 每个内存节点由一个pg_data_t结构来描述其内存布局; 每个pg_data_t有一个zone数组,包括了内存节点下的全部内存区域zone; 每个zone里有一个free_area数组【0-10】,其序号n的元素下面,挂载了全部的连续2^n页面【page】,也就是free_area【0-10】分别挂载了【1个页面,2个页面,直到1024个页面】 每个free_area,都有一个链表数组,按不同迁移类型,对所属页面【page】再次进行了划分 三、分配内存【只能按2^n页面申请】 alloc_pages->alloc_pages_current->__alloc_pages_nodemask ->get_page_from_freelist,快速分配路径,尝试直接分配内存 ->__alloc_pages_slowpath,慢速分配路径,尝试回收、压缩后,再分配内存,如果有OOM风险则杀死进程->实际分配时仍会调用get_page_from_freelist ->->所以无论快慢路径,都会到rmqueue ->->->如果申请一个页面rmqueue_pcplist->__rmqueue_pcplist 1、如果pcplist不为空,则返回一个页面 2、如果pcplist为空,则申请一块内存后,再返回一个页面 ->->->如果申请多个页面__rmqueue_smallest 1、首先要取得 current_order【指定页面长度】 对应的 free_area 区中 page 2、若没有,就继续增加 current_order【去找一个更大的页面】,直到最大的 MAX_ORDER 3、要是得到一组连续 page 的首地址,就对其脱链,然后调用expand函数对内存进行分割 ->->->->expand 函数分割内存 1、expand分割内存时,也是从大到小的顺序去分割的 2、每一次都对半分割,挂载到对应的free_area,也就加入了伙伴系统 3、直到得到所需大小的页面,就是我们申请到的页面了 四、此外 1、在整个过程中,有一个水位_watermark的概念,其实就是用于控制内存区是否需要进行内存回收 2、申请内存时,会先按请求的 migratetype 从对应类型的page结构块中寻找,如果不成功,才会从其他 migratetype 的 page 结构块中分配, 降低内存碎片【rmqueue->__rmqueue->__rmqueue_fallback】 3、申请内存时,一般先在CPU所属内存节点申请;如果失败,再去其他内存节点申请;具体顺序,和NUMA memory policy有关;
作者回复: 大佬 牛皮
2021-06-2932 - pedro迟到了,迟到了!每节都评论是我一直坚持的事,我以此为傲。 问题答案已经在文中了,最大1024页,每页4kb,答案也就呼之欲出了。 之前我一直在内心吐槽东哥写代码的函数名太难伺候了,现在来看linux的命名,不说优雅与否,就连基本的表意都办不到啊,东哥简直就是内核届的一股清流啊。
作者回复: 哈哈
2021-06-2828 - blentle最大order是11. 一个块是4k大小所以最大能分配 2的10次方乘以4 也就是4MB的的物理内存吧
作者回复: 对的
2021-06-284 - Shawn Duan请问内存空洞是如何形成的呢?
作者回复: 硬件实现的问题
2021-11-022 - springXu还是按照阅读理解题解答问的问题。 free_area 结构的数组,这个数组就是用于实现伙伴系统的。其中 MAX_ORDER 的值默认为 11,分别表示挂载地址连续的 page 结构数目为 1,2,4,8,16,32……最大为 1024。 所以是1024个pages,如果每个page是4k大小的话,那1024x4k=4m。 所以按MAX_ORDER默认值11,连续内存是4M
作者回复: 对的 对的
2021-06-282 - 卖薪沽酒打卡打卡
作者回复: 感谢
2022-07-03 - 艾恩凝先打卡这个吧,轻松一下,啃完之前的,这个理解起来轻车熟路
作者回复: 加油
2022-04-21 - ifelse膜拜评论区各位大神
作者回复: 学好课程 你也是大神
2022-02-16 - lzd最大连续内存 = MAX_ORDER_NR_PAGES * PAGE_SIZE MAX_ORDER_NR_PAGES : (1 << (MAX_ORDER-1)) MAX_ORDER : (11) PAGE_SIZE : 4k/16k/64k
作者回复: 是的
2021-10-14 - Geek2808老师好,请教一个问题,当内核分配内存时,__alloc_pages_slowpath找不到也没法回收或者整理出空闲页,在开启swap的情况下,其中slab/slub部分的匿名页会调用shrink_inactive_list 函数会扫描inactive list,将不活跃的page置换到swap分区。但是swap有的时候几M、几百M甚至几个G,这个内核置换的机制或算法逻辑是啥?为啥一次性会swap out这么多内存数据?(我理解这个时间点本身应用程序或内核不会一次性申请几个G的内存)
作者回复: 不可能 一次性换出几个G吧
2021-07-31
收起评论