24 | 物理内存管理(下):会议室管理员如何分配会议室?
下载APP
关闭
渠道合作
推荐作者
24 | 物理内存管理(下):会议室管理员如何分配会议室?
2019-05-22 刘超 来自北京
《趣谈Linux操作系统》
课程介绍
讲述:刘超
时长13:46大小12.57M
前一节,前面我们解析了整页的分配机制。如果遇到小的对象,物理内存是如何分配的呢?这一节,我们一起来看一看。
小内存的分配
前面我们讲过,如果遇到小的对象,会使用 slub 分配器进行分配。那我们就先来解析它的工作原理。
还记得咱们创建进程的时候,会调用 dup_task_struct,它想要试图复制一个 task_struct 对象,需要先调用 alloc_task_struct_node,分配一个 task_struct 对象。
从这段代码可以看出,它调用了 kmem_cache_alloc_node 函数,在 task_struct 的缓存区域 task_struct_cachep 分配了一块内存。
在系统初始化的时候,task_struct_cachep 会被 kmem_cache_create 函数创建。这个函数也比较容易看懂,专门用于分配 task_struct 对象的缓存。这个缓存区的名字就叫 task_struct。缓存区中每一块的大小正好等于 task_struct 的大小,也即 arch_task_struct_size。
有了这个缓存区,每次创建 task_struct 的时候,我们不用到内存里面去分配,先在缓存里面看看有没有直接可用的,这就是 kmem_cache_alloc_node 的作用。
当一个进程结束,task_struct 也不用直接被销毁,而是放回到缓存中,这就是 kmem_cache_free 的作用。这样,新进程创建的时候,我们就可以直接用现成的缓存中的 task_struct 了。
我们来仔细看看,缓存区 struct kmem_cache 到底是什么样子。
在 struct kmem_cache 里面,有个变量 struct list_head list,这个结构我们已经看到过多次了。我们可以想象一下,对于操作系统来讲,要创建和管理的缓存绝对不止 task_struct。难道 mm_struct 就不需要吗?fs_struct 就不需要吗?都需要。因此,所有的缓存最后都会放在一个链表里面,也就是 LIST_HEAD(slab_caches)。
对于缓存来讲,其实就是分配了连续几页的大内存块,然后根据缓存对象的大小,切成小内存块。
所以,我们这里有三个 kmem_cache_order_objects 类型的变量。这里面的 order,就是 2 的 order 次方个页面的大内存块,objects 就是能够存放的缓存对象的数量。
最终,我们将大内存块切分成小内存块,样子就像下面这样。
每一项的结构都是缓存对象后面跟一个下一个空闲对象的指针,这样非常方便将所有的空闲对象链成一个链。其实,这就相当于咱们数据结构里面学的,用数组实现一个可随机插入和删除的链表。
所以,这里面就有三个变量:size 是包含这个指针的大小,object_size 是纯对象的大小,offset 就是把下一个空闲对象的指针存放在这一项里的偏移量。
那这些缓存对象哪些被分配了、哪些在空着,什么情况下整个大内存块都被分配完了,需要向伙伴系统申请几个页形成新的大内存块?这些信息该由谁来维护呢?
接下来就是最重要的两个成员变量出场的时候了。kmem_cache_cpu 和 kmem_cache_node,它们都是每个 NUMA 节点上有一个,我们只需要看一个节点里面的情况。
在分配缓存块的时候,要分两种路径,fast path 和 slow path,也就是快速通道和普通通道。其中 kmem_cache_cpu 就是快速通道,kmem_cache_node 是普通通道。每次分配的时候,要先从 kmem_cache_cpu 进行分配。如果 kmem_cache_cpu 里面没有空闲的块,那就到 kmem_cache_node 中进行分配;如果还是没有空闲的块,才去伙伴系统分配新的页。
我们来看一下,kmem_cache_cpu 里面是如何存放缓存块的。
在这里,page 指向大内存块的第一个页,缓存块就是从里面分配的。freelist 指向大内存块里面第一个空闲的项。按照上面说的,这一项会有指针指向下一个空闲的项,最终所有空闲的项会形成一个链表。
partial 指向的也是大内存块的第一个页,之所以名字叫 partial(部分),就是因为它里面部分被分配出去了,部分是空的。这是一个备用列表,当 page 满了,就会从这里找。
我们再来看 kmem_cache_node 的定义。
这里面也有一个 partial,是一个链表。这个链表里存放的是部分空闲的内存块。这是 kmem_cache_cpu 里面的 partial 的备用列表,如果那里没有,就到这里来找。
下面我们就来看看这个分配过程。kmem_cache_alloc_node 会调用 slab_alloc_node。你还是先重点看这里面的注释,这里面说的就是快速通道和普通通道的概念。
快速通道很简单,取出 cpu_slab 也即 kmem_cache_cpu 的 freelist,这就是第一个空闲的项,可以直接返回了。如果没有空闲的了,则只好进入普通通道,调用 __slab_alloc。
在这里,我们首先再次尝试一下 kmem_cache_cpu 的 freelist。为什么呢?万一当前进程被中断,等回来的时候,别人已经释放了一些缓存,说不定又有空间了呢。如果找到了,就跳到 load_freelist,在这里将 freelist 指向下一个空闲项,返回就可以了。
如果 freelist 还是没有,则跳到 new_slab 里面去。这里面我们先去 kmem_cache_cpu 的 partial 里面看。如果 partial 不是空的,那就将 kmem_cache_cpu 的 page,也就是快速通道的那一大块内存,替换为 partial 里面的大块内存。然后 redo,重新试下。这次应该就可以成功了。
如果真的还不行,那就要到 new_slab_objects 了。
在这里面,get_partial 会根据 node id,找到相应的 kmem_cache_node,然后调用 get_partial_node,开始在这个节点进行分配。
acquire_slab 会从 kmem_cache_node 的 partial 链表中拿下一大块内存来,并且将 freelist,也就是第一块空闲的缓存块,赋值给 t。并且当第一轮循环的时候,将 kmem_cache_cpu 的 page 指向取下来的这一大块内存,返回的 object 就是这块内存里面的第一个缓存块 t。如果 kmem_cache_cpu 也有一个 partial,就会进行第二轮,再次取下一大块内存来,这次调用 put_cpu_partial,放到 kmem_cache_cpu 的 partial 里面。
如果 kmem_cache_node 里面也没有空闲的内存,这就说明原来分配的页里面都放满了,就要回到 new_slab_objects 函数,里面 new_slab 函数会调用 allocate_slab。
在这里,我们看到了 alloc_slab_page 分配页面。分配的时候,要按 kmem_cache_order_objects 里面的 order 来。如果第一次分配不成功,说明内存已经很紧张了,那就换成 min 版本的 kmem_cache_order_objects。
好了,这个复杂的层层分配机制,我们就讲到这里,你理解到这里也就够用了。
页面换出
另一个物理内存管理必须要处理的事情就是,页面换出。每个进程都有自己的虚拟地址空间,无论是 32 位还是 64 位,虚拟地址空间都非常大,物理内存不可能有这么多的空间放得下。所以,一般情况下,页面只有在被使用的时候,才会放在物理内存中。如果过了一段时间不被使用,即便用户进程并没有释放它,物理内存管理也有责任做一定的干预。例如,将这些物理内存中的页面换出到硬盘上去;将空出的物理内存,交给活跃的进程去使用。
什么情况下会触发页面换出呢?
可以想象,最常见的情况就是,分配内存的时候,发现没有地方了,就试图回收一下。例如,咱们解析申请一个页面的时候,会调用 get_page_from_freelist,接下来的调用链为 get_page_from_freelist->node_reclaim->__node_reclaim->shrink_node,通过这个调用链可以看出,页面换出也是以内存节点为单位的。
当然还有一种情况,就是作为内存管理系统应该主动去做的,而不能等真的出了事儿再做,这就是内核线程 kswapd。这个内核线程,在系统初始化的时候就被创建。这样它会进入一个无限循环,直到系统停止。在这个循环中,如果内存使用没有那么紧张,那它就可以放心睡大觉;如果内存紧张了,就需要去检查一下内存,看看是否需要换出一些内存页。
这里的调用链是 balance_pgdat->kswapd_shrink_node->shrink_node,是以内存节点为单位的,最后也是调用 shrink_node。
shrink_node 会调用 shrink_node_memcg。这里面有一个循环处理页面的列表,看这个函数的注释,其实和上面我们想表达的内存换出是一样的。
这里面有个 lru 列表。从下面的定义,我们可以想象,所有的页面都被挂在 LRU 列表中。LRU 是 Least Recent Use,也就是最近最少使用。也就是说,这个列表里面会按照活跃程度进行排序,这样就容易把不怎么用的内存页拿出来做处理。
内存页总共分两类,一类是匿名页,和虚拟地址空间进行关联;一类是内存映射,不但和虚拟地址空间关联,还和文件管理关联。
它们每一类都有两个列表,一个是 active,一个是 inactive。顾名思义,active 就是比较活跃的,inactive 就是不怎么活跃的。这两个里面的页会变化,过一段时间,活跃的可能变为不活跃,不活跃的可能变为活跃。如果要换出内存,那就是从不活跃的列表中找出最不活跃的,换出到硬盘上。
从上面的代码可以看出,shrink_list 会先缩减活跃页面列表,再压缩不活跃的页面列表。对于不活跃列表的缩减,shrink_inactive_list 就需要对页面进行回收;对于匿名页来讲,需要分配 swap,将内存页写入文件系统;对于内存映射关联了文件的,我们需要将在内存中对于文件的修改写回到文件中。
总结时刻
好了,对于物理内存的管理就讲到这里了,我们来总结一下。对于物理内存来讲,从下层到上层的关系及分配模式如下:
物理内存分 NUMA 节点,分别进行管理;
每个 NUMA 节点分成多个内存区域;
每个内存区域分成多个物理页面;
伙伴系统将多个连续的页面作为一个大的内存块分配给上层;
kswapd 负责物理页面的换入换出;
Slub Allocator 将从伙伴系统申请的大内存块切成小块,分配给其他系统。
课堂练习
内存的换入和换出涉及 swap 分区,那你知道如何检查当前 swap 分区情况,如何启用和关闭 swap 区域,如何调整 swappiness 吗?
欢迎留言和我分享你的疑惑和见解,也欢迎你收藏本节内容,反复研读。你也可以把今天的内容分享给你的朋友,和他一起学习、进步。
分享给需要的人,Ta购买本课程,你将得20元
生成海报并分享
赞 13
提建议
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
上一篇
23 | 物理内存管理(上):会议室管理员如何分配会议室?
下一篇
25 | 用户态内存映射:如何找到正确的会议室?
精选留言(40)
- why2019-05-24- 小内存分配, 例如分配 task_struct 对象 - 会调用 kmem_cache_alloc_node 函数, 从 task_struct 缓存区域 task_struct_cachep(在系统初始化时, 由 kmem_cache_create 创建) 分配一块内存 - 使用 task_struct 完毕后, 调用 kmem_cache_free 回收到缓存池中 - struct kmem_cache 用于表示缓存区信息, 缓存区即分配连续几个页的大块内存, 再切成小内存 - 小内存即缓存区的每一项, 都由对象和指向下一项空闲小内存的指针组成(随机插入/删除+快速查找空闲) - struct kmem_cache 中三个 kmem_cache_order_objects 表示不同的需要分配的内存块大小的阶数和对象数 - 分配缓存的小内存块由两个路径 fast path 和 slow path , 分别对应 struct kmem_cache 中的 kmem_cache_cpu 和 kmem_cache_node - 分配时先从 kmem_cache_cpu 分配, 若其无空闲, 再从 kmem_cache_node 分配, 还没有就从伙伴系统申请新内存块 - struct kmem_cache_cpu 中 - page 指向大内存块的第一个页 - freelist 指向大内存块中第一个空闲项 - partial 指向另一个大内存块的第一个页, 但该内存块有部分已分配出去, 当 page 满后, 在 partial 中找 - struct kmem_cache_node - 也有 partial, 是一个链表, 存放部分空闲的多个大内存块, 若 kmem_cacche_cpu 中的 partial 也无空闲, 则在这找 - 分配过程 - kmem_cache_alloc_node->slab_alloc_node - 快速通道, 取出 kmem_cache_cpu 的 freelist , 若有空闲直接返回 - 普通通道, 若 freelist 无空闲, 调用 `__slab_alloc` - `__slab_alloc` 会重新查看 freelist, 若还不满足, 查看 kmem_cache_cpu 的 partial - 若 partial 不为空, 用其替换 page, 并重新检查是否有空闲 - 若还是无空闲, 调用 new_slab_objects - new_slab_objects 根据节点 id 找到对应 kmem_cache_node , 调用 get_partial_node - 首先从 kmem_cache_node 的 partial 链表拿下一大块内存, 替换 kmem_cache_cpu 的 page, 再取一块替换 kmem_cache_cpu 的 partial - 若 kmem_cache_node 也没有空闲, 则在 new_slab_objects 中调用 new_slab->allocate_slab->alloc_slab_page 根据某个 kmem_cache_order_objects 设置申请大块内存 - 页面换出 - 触发换出: - 1) 分配内存时发现没有空闲; 调用 `get_page_from_freelist->node_reclaim->__node_reclaim->shrink_node` - 2) 内存管理主动换出, 由内核线程 kswapd 实现 - kswapd 在内存不紧张时休眠, 在内存紧张时检测内存 调用 balance_pgdat->kswapd_shrink_node->shrink_node - 页面都挂在 lru 链表中, 页面有两种类型: 匿名页; 文件内存映射页 - 每一类有两个列表: active 和 inactive 列表 - 要换出时, 从 inactive 列表中找到最不活跃的页换出 - 更新列表, shrink_list 先缩减 active 列表, 再缩减不活跃列表 - 缩减不活跃列表时对页面进行回收: - 匿名页回收: 分配 swap, 将内存也写入文件系统 - 文件内存映射页: 将内存中的文件修改写入文件中展开33
- 超超2019-07-171.系统在什么情况下才会使用SWAP? 实际上,并不是等所有的物理内存都消耗完毕之后,才去使用swap的空间,什么时候使用是由swappiness 参数值控制。 [root@rhce ~]# cat /proc/sys/vm/swappiness 60 该值默认值是60. swappiness=0的时候表示最大限度使用物理内存,然后才是 swap空间, swappiness=100的时候表示积极的使用swap分区,并且把内存上的数据及时的搬运到swap空间里面。 现在服务器的内存动不动就是上百G,所以我们可以把这个参数值设置的低一些,让操作系统尽可能的使用物理内存,降低系统对swap的使用,从而提高系统的性能。展开22
- W.jyao2019-05-22真心看不懂了,😔😔😔
作者回复: 多看几遍就好了
共 4 条评论21 - 鲍勃2019-05-22果然和你的网络课程一样,越到后面越hold不住😂
作者回复: 其实哪门课都一样哈
16 - 超超2019-07-172. 如何修改swappiness参数? --临时性修改: [root@rhce ~]# sysctl vm.swappiness=10 vm.swappiness = 10 [root@rhce ~]# cat /proc/sys/vm/swappiness 10 这里我们的修改已经生效,但是如果我们重启了系统,又会变成60. --永久修改: 在/etc/sysctl.conf 文件里添加如下参数: vm.swappiness=10 或者: [root@rhce ~]# echo 'vm.swappiness=10' >>/etc/sysctl.conf 保存,重启,就生效了。展开
作者回复: 如此认真,典范啊
15 - 欠债太多2019-05-22看到现在,代码越来越多,关于代码的阅读需要么?怎么去做呢
作者回复: 重点关注流程,不关注细节
共 2 条评论14 - 活的潇洒2019-05-22花了4个多小时终于把笔记做完 分享给大家: https://www.cnblogs.com/luoahong/p/10907734.html
作者回复: 赞
11 - 刘強2019-05-22这几节看起来吃力了,需要理解的外围知识很多!
作者回复: 加油,多看几遍
9 - 超超2019-07-173. 如何查看swap分区 swapon -s cat /proc/swaps free vmstat 以上4类命令都可以查看swap分区的大小和使用情况展开
作者回复: 赞
8 - guojiun2019-05-22https://events.static.linuxfound.org/sites/events/files/slides/slaballocators.pdf 這裡有清楚的視意圖,對照著看會更清楚!
作者回复: 赞
8 - sundy2020-06-03真的应该少粘代码 多讲原理、多抽象,不然很难将您讲的东西串联起来,连基本原理都不了解硬生生的讲代码实在是让人困惑
作者回复: 其实是有一个整体思路的,被代码打断了,可以讲代码总结一个段落大意,然后再穿起来看,是不是好一些
6 - 超超2019-07-17参考文章:https://mp.weixin.qq.com/s/6JVpke8bDKI9lvh_l6QF-w
作者回复: 赞
4 - 绿箭侠2021-02-22这个专栏最大的好处就是能将那些专业的厚的书籍的宽泛知识点串起来,能帮助深入理解!!!共 1 条评论2
- chengzise2019-05-23老师好,这两节讲的是物理内存的管理和分配,大概逻辑是看的懂的,细节需要继续反复研读才行。其中有个问题,kmem_cache部分已经属于页内小内存分配,这个分配算法属于虚拟内存分配,不算物理内存管理范畴吧? 就是说先分配物理内存页,映射到虚拟内存空间,再在这个虚拟内存分配小内存给程序逻辑使用。希望老师解答一下。
作者回复: 是的,会映射成为虚拟地址,是整页会有虚拟地址
2 - 徐少文2021-03-04看到这里非常有一种看Windows内核原理与实现的感受了,就是感觉经常摆出一大堆数据结构,看的太痛苦了。打算记好笔记然后每天打开看一看。1
- Spring2020-07-26有了快速通道,为什么还需要普通通道,这一点搞不明白共 1 条评论1
- stevensafin2020-06-05对于slab分配内存的机制,是不是可以用个流程图来表示会更好一点?还有就是快慢通道的速度差距和具体设计原因能不能说明一下,感谢老师
作者回复: 其实通过里面的图,基本能够理解整个流程。大概的思想和咱们通过多级缓存加速数据库访问差不多,一级找不到找二级,二级找不到找数据库。
1 - 奔跑的码仔2019-08-29老师,slub存在回收机制吧?就是slub中空闲很多的时候,是不是会这些内存块释放,返回给系统呢?
作者回复: 一个内存块的slub全部释放之后,整个页都会回收
1 - 奔跑的码仔2019-08-29老师,kmem_cache_cpu中的page和partial不是指向同一块内存区域的吧,为什么会这么设计呢?partial的作用是什么呢?
作者回复: 不是同一块的。备用列表,当 page 满了,就会从这里找
1 - 无咎2019-07-10kswapd 在内存紧张的时候,开始工作,那么这个紧张的条件是什么?总的物理内存还是按照 zone 来区分呢?
作者回复: 对每个NUMA结点来说,都有一个单独的kswapd守护进程
1