首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

图解|Linux内存碎片整理

我们知道物理内存是以页为单位进行管理的,每个内存页大小默认是4K(大页除外)。申请物理内存时,一般都是按顺序分配的,但释放内存的行为是随机的。随着系统运行时间变长后,将会出现以下情况:

如上图所示,当用户需要申请地址连续的 3 个内存页时,虽然系统中空闲的内存页数量足够,但由于空闲的内存页相对分散,从而导致分配失败。这些地址不连续的内存页被称为:。

要解决这个问题也比较简单,只需要把空闲的内存块移动到一起即可。如下图所示:

网络上有句很有名的话:理想很美好,现实很骨感

内存整理也是这样,看起来很简单,但实现起来就不那么简单了。因为在内存整理后,需要修正进程的虚拟内存与物理内存之间的映射关系。如下图所示:

但由于 Linux 内核有个名为内存页反向映射的功能,所以内存整理就变得简单起来。

接下来,我们将会分析内存碎片整理的原理与实现。

内存碎片整理原理

内存碎片整理的原理比较简单:在内存碎片整理开始前,会在内存区的头和尾各设置一个指针,头指针从头向尾扫描可移动的页,而尾指针从尾向头扫描空闲的页,当他们相遇时终止整理。下面说说内存随便整理的过程(原理参考了内核文档):

初始时内存状态:

在上图中,白色块表示空闲的内存页,而红色块表示已分配出去的内存页。在初始状态时,内存中存在多个碎片。如果此时要申请 3 个地址连续的内存页,那么将会申请失败。

内存碎片整理扫描开始:

头部指针从头扫描可移动页,而尾部指针从从尾扫描空闲页。在整理时,将可移动页的内容复制到空闲页中。复制完成后,将可移动内存页释放即可。

最后结果:

经过内存碎片整理后,如果现在要申请 3 个地址连续的内存页,就能申请成功了。

内存碎片整理实现

接下来,我们将会分析内存碎片整理的实现过程。

注:本文使用的是 Linux-2.6.36 版本的内存

1. 内存碎片整理时机

当要申请多个地址联系的内存页时,如果申请失败,将会进行内存碎片整理。其调用链如下:

当调用  函数申请多个地址连续的内存页失败时,将会触发调用  函数来进行内存碎片整理。我们来看看  函数的实现:

函数是内存碎片整理的入口,其主要完成 3 个步骤:

先判断申请的内存块是否只有一个内存页,如果是,那么就没有整理碎片的必要(这说明是内存不足,而不是内存碎片导致)。

如果需要进行内存碎片整理,那么调用  函数进行内存碎片整理。

整理完内存碎片后,调用  函数继续尝试申请内存块。

2. 内存碎片整理过程

由于内存碎片整理的具体实现在  函数中进行,所以我们继续来看看  函数的实现:

可以看出, 函数最终会调用  函数来进行内存碎片整理。我们只能进行来分析  函数:

到这里,我们还没有看到内存碎片整理的具体实现(调用链可真深啊 ^_^!), 函数也是构造了一些参数,然后继续调用  来进行内存碎片整理:

在  函数里,我们终于看到内存碎片整理的逻辑了。 函数主要完成 2 个步骤:

调用  函数收集可移动的内存页列表。

调用  函数将可移动的内存页列表迁移到空闲列表中。

这两个函数非常重要,我们分别来分析它们是怎么实现的。

isolate_migratepages() 函数

函数用于收集可移动的内存页列表,我们来看看其实现:

函数主要完成 5 个步骤,分别是:

扫描内存区所有的内存页(与内存碎片整理原理一致)。

通过内存页的编号获取内存页对象。

判断内存页是否可移动内存页,如果不是可移动内存页,那么就跳过。

将内存页从 LRU 队列中删除,这样可避免被其他进程回收这个内存页。

添加到可移动内存页列表中。

当完成这 5 个步骤后,内核就收集到可移动的内存页列表。

migrate_pages() 函数

函数负责将可移动的内存页列表迁移到空闲列表中,我们来分析一下其实现过程:

函数的逻辑很简单,主要完成 2 个步骤:

遍历可移动内存页列表,这个列表就是通过  函数收集的可移动内存页列表。

调用  函数将可移动内存页迁移到空闲内存页中。

可以看出,具体的内存迁移过程在  函数中实现。我们来看看  函数的实现:

由于  函数的实现比较复杂,所以我们对其进行了简化。可以看出, 函数主要完成 3 个工作:

从内存区中找到一个空闲的内存页。根据内存碎片整理算法,会从内存区最后开始扫描,找到合适的空闲内存页。

由于将可移动内存页迁移到空闲内存页后,进程的虚拟内存映射将会发生变化。所以,这里要调用  函数来解开所有使用了当前可移动内存页的映射。

调用  函数将可移动内存页的数据复制到空闲内存页中。在  函数中,还会重新建立进程的虚拟内存映射,这样使用了当前可移动内存页的进程就能够正常运行。

至此,内存碎片整理的过程已经分析完毕。

不过细心的读者可能发现,在文中并没有分析重新构建虚拟内存映射的过程。是的,因为重新构建虚拟内存映射要涉及到  的知识点,后续的文章会介绍这个知识点,所以这里就不作详细分析了。

总结

从上面的分析可知, 是为了解决:在申请多个地址连续的内存页时,空闲内存页数量充足,但还是分配失败的情况。

但由于内存碎片整理需要消耗大量的 CPU 时间,所以我们在申请内存时,可以通过指定  标志位(不等待)来避免内存碎片整理过程。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20230413A03BGX00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券