剖析linux的内存管理与分配

伙伴算法

Linux采用著名的伙伴算法来解决外碎片的问题

1、伙伴算法原理

Linux的伙伴算法把所有的空间页面分为10个块组,每个组中块的大小是2的幂次方个页面,比如第0组中块的额大小为2的次方(1个页面),第1组中块的大小为2的1次方(2个页面)…,第9组中块的大小为2的9次方(512个页面),每一组中块的大小是相同的,且同样大小的块形成一个链表

2、物理页的分配

情景模拟:如果此时我们需要申请一块128个页面大小的内存,根据伙伴算法的规则,会先去128个页大小的块所构成的链表中寻找,如果存在空闲的块,那就把把这个块进行分配。 如果发现这个链表已经没有空闲的块了,那接着就去256个页大小的块所构成的链表中寻找,如果找到了空闲的块,那么将这个块分为2个128页大小的块,一个用来分配使用,另一个挂载到到128个页大小的块所构成的链表中去。如果还是没有,那接着就去512个页大小的块所构成的链表中寻找,如果找到了空闲的块,那么将其分为2个128页块,1个256页块。一个128的页块用来分配和使用,剩下的分别挂载到128的页块链表和256的页块链表下去…,当然如果这样一级一级的找下去,没有得到可以使用的空闲块,算法就放弃并发出错误信息

**3、 物理页的释放 **

满足如下条件的块称之为伙伴

(1) 两个块的大小相同

(2) 两个块的物理地址连续

伙伴算法把满足上述两个条件的块合并成一个块,该算法是迭代算法,如果合并后块还可以跟相邻的块进行合并,那此就继续合并

情景模拟:假设要释放一个256个页框的块,算法就把其插入到256个页框的链表中,然后检查与该内存相邻的内存,如果存在同样大小为256个页框的并且空闲的内存,就将这两块内存合并成512个页框,然后插入到512个页框的链表中,如果不存在,就没有后面的合并操作。然后再进一步检查,如果合并后的512个页框的内存存在大小为512个页框的相邻且空闲的内存,则将两者合并,然后插入到1024个页框的链表中。

总结

假设系统中有 1MB 大小的内存需要动态管理,按照伙伴算法的要求:需要将这1M大小的内存进行划分。这里,我们将这1M的内存分为 64K、64K、128K、256K、和512K 共五个部分,如下图 a 所示

img

1.此时,如果有一个程序A想要申请一块45K大小的内存,则系统会将第一块64K的内存块分配给该程序(产生内部碎片为代价),如图b所示;

2.然后程序B向系统申请一块68K大小的内存,系统会将128K内存分配给该程序,如图c所示;

3.接下来,程序C要申请一块大小为35K的内存。系统将空闲的64K内存分配给该程序,如图d所示;

4.之后程序D需要一块大小为90K的内存。当程序提出申请时,系统本该分配给程序D一块128K大小的内存,但此时内存中已经没有空闲的128K内存块了,于是根据伙伴算法的原理,系统会将256K大小的内存块平分,将其中一块分配给程序D,另一块作为空闲内存块保留,等待以后使用,如图e所示;

5.紧接着,程序C释放了它申请的64K内存。在内存释放的同时,系统还负责检查与之相邻并且同样大小的内存是否也空闲,由于此时程序A并没有释放它的内存,所以系统只会将程序C的64K内存回收,如图f所示;

6.然后程序A也释放掉由它申请的64K内存,系统随机发现与之相邻且大小相同的一段内存块恰好也处于空闲状态。于是,将两者合并成128K内存,如图g所示;

7.之后程序B释放掉它的128k,系统也将这块内存与相邻的128K内存合并成256K的空闲内存,如图h所示;

8.最后程序D也释放掉它的内存,经过三次合并后,系统得到了一块1024K的完整内存,如图i所示。

Slab分配机制

...有了伙伴系统 buddy,我们可以以页为单位获取连续的物理内存了,即 4K 为单位的获取,但如果需要频繁的获取/释放并不大的连续物理内存怎么办,如几十字节几百字节的获取/释放,这样的话用 buddy 就不太合适了因为这样会造成极大的内存浪费,这就引出了 slab,SLAB分配器实际上是建立在伙伴系统算法之上的,SLAB分配器使用的内存空间是通过伙伴算法进行分配的,只不过SLAB对这些内存空间实现了自己的算法进而对小块内存进行管理。
   场景模拟: 比如我需要一个 100 字节的连续物理内存,那么内核 slab 分配器会给我提供一个相应大小的连续物理内存单元,为 128 字节大小(不会是整好 100 字节,而是这个档的一个对齐值,如 100 字节对应 128 字节, 30 字节对应 32 字节, 60 字节对应 64 字节),这个物理内存实际上还是从伙伴系统获取的物理页;当我不再需要这个内存时应该释放它,释放它并非把它归还给伙伴系统,而是归还给 slab 分配器,这样等再需要获取时无需再从伙伴系统申请,这也就是为什么 slab 分配器往往会把最近释放的内存(即所谓“热” )分配给申请者,这样效率是比较高的。

1、Slab如何对内存进行管理?

我们可以将Slab管理机制分为三个部分:内核对象(object);高速缓存(cache);每个 slab 由一个或多个物理连续的页面组成(slab中对页进行更细的划分),每个 cache 由一个或多个 slab 组成,每个内核数据结构都有一个 cache。如下图所示

slab分配

2、Slab中如何实现对页进行更细的划分?

以4k页为例,slab该内存页分组管理,每个组分别包含23、24、…2^11个字节(B),在4K页大小的默认情况下,另外还有两个特殊的组,分别是96b和192b个位,共11组。如下图所示:

slab

3、Slab如何对内存进行分配

上面我们可以看到,我们将一个 页分成11个块,每个块的大小都不一样,如何对这些块(obj)进行管理, 这里引入一个概念,Slab分配的最小单位是obj,也就是上图中所示。我们将大小相同的obj,连接成一条链表。用数据结构slab进行管理,

4、Slab如何对数据结构slab进行管理

如下图所示,通俗点来讲,在我们通过struct kmem_list3 将所以的slab组织起来,根据obj部分分配,全部分配,全部未分配的特性,将所有的slab组织成三条链表

相关的数据结构
img

5、什么是kmem_cache?

...如上图所示,在结构体kmem_cache中我们重点关注,struct kmem_list3 和 struct array_cache这两个结构体成员,struct kmem_list3 我们已经知道是干啥了的。那struct array_cache的作用是啥,通俗点来讲,struct array_cache维护的是一个包含obj链表的slab结构体,申请内存时,我们会先到struct array_cache中寻址合适的obj块,如果没找到,在去struct kmem_list3找(先从obj部分分配的slab链表中找,没找到,再去全部未分配的slab链表找),找到后,将相应的slab挂载到struct array_cache中

6、什么是slab对象?(Slab中用slab对象来代表内存块)

在这里插入图片描述

7、slab着色

**CPU访问内存时使用哪个cache line是通过低地址的若干位确定的,比如cache line大小为32,那么是从bit5开始的若干位。因此相距很远的内存地址,如果这些位的地址相同,还是会被映射到同一个cache line。Slab cache中存放的是相同大小的对象,如果没有着色区,那么同一个cache内,不同slab中具有相同slab内部偏移的对象,其低地址的若干位是相同的,映射到同一个cache line。如图所示。
**
在这里插入图片描述
如此一来,访问cache line冲突的对象时,就会出现cache miss,不停的在cache line和内存之间来回切换,与此同时,其他的cache line可能无所事事,严重影响了cache的效率。解决这一问题的方法是通过着色区使对象的slab内偏移各不相同,从而避免cache line冲突。
着色貌似很好的解决了问题,实质不然,当slab数目不多时,着色工作的很好,当slab数目很多时,着色发生了循环,仍然存在cache line冲突的问题。

总结

...整个Slab系统中共有12个零售商(kmem_cache),每个“零售商”只“零售”特定大小的内存,例如:有的“零售商”只"零售"8Byte大小的内存,有的只”零售“16Byte大小的内存。
   每个零售商(kmem_cache)有两个“部门”,一个是“仓库”:struct kmem_list3,一个“营业厅”:struct array_cache。“营业厅”里只保留一个slab,只有在营业厅(kmem_cache_cpu)中没有空闲内存的情况下才会从仓库中换出其他的slab。
    所谓slab就是零售商(kmem_cache)批发的连续的整页内存,零售商把这些整页的内存分成许多小内存,然后分别“零售”出去,一个slab可能包含多个连续的内存页。slab的大小和零售商有关。(借鉴博客(24条消息) linux 内核 内存管理 slub算法 (一) 原理_lukuen的博客-CSDN博客_linux 内存管理算法

Slab与Slub的区别

从版本 2.6.24 开始,SLUB 分配器取代 SLAB,成为 Linux 内核的默认分配器。SLUB 通过减少 SLAB 分配器所需的大量开销,来解决 slab 分配的性能问题,一个改变是,在 SLAB 分配下每个 slab 存储的元数据,移到 Linux 内核用于每个页面的结构 page。此外,对于 SLAB 分配器,每个 CPU 都有队列以维护每个 cache 内的对象,SLUB 会删除这些队列。对于具有大量处理器的系统,分配给这些队列的内存量是很重要的。因此,随着系统处理器数量的增加,SLUB 性能也更好。

Slab的缺点

1、 缓存队列管理复杂;

2、管理数据存储开销大;

3、对NUMA支持复杂;

4、 调试调优困难;

5、摒弃了效果不太明显的slab着色机制;

... 针对这些SLAB不足,内核开发人员Christoph Lameter在Linux内核2.6.22版本中引入一种新的解决方案:SLUB分配器。SLUB分配器特点是简化设计理念,同时保留SLAB分配器的基本思想:每个缓冲区由多个小的slab 组成,每个 slab 包含固定数目的对象。SLUB分配器简化kmem_cache,slab等相关的管理数据结构,摒弃了SLAB 分配器中众多的队列概念,并针对多处理器、NUMA系统进行优化,从而提高了性能和可扩展性并降低了内存的浪费。为了保证内核其它模块能够无缝迁移到SLUB分配器,SLUB还保留了原有SLAB分配器所有的接口API函数。
  总结: 相对Slab来说,Slub的内存分配思想其实差不太多,其实就是Slab的优化版

参考文献(本次的一些图片来源于以下文章,如有侵权,联系删除)

Linux伙伴算法 - AlanTu - 博客园 (cnblogs.com)

linux 内核 内存管理 slub算法 (一) 原理_lukuen的博客-CSDN博客_linux 内存管理算法

Linux内存管理之SLAB原理浅析。_阿曼的博客-CSDN博客_linux slab

linux内核之slob、slab、slub_rtoax的博客-CSDN博客

linux内核之slob、slab、slub_rtoax的博客-CSDN博客

Linux内存管理之slab机制(概述)

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值