Linux内存管理之SLAB原理浅析。

时间:2021-05-07 23:40:26

前言

1.以下这篇文章是2015年我所在的via-telecom小组学习内存知识时整理的笔记。最近复习内存管理又拿出来看了一遍,庆幸当时阅读时留了这份笔记,不然重头看又要花费很多时间。当时画了很多visio图片且加上了via-telecom的copyright。虽然目前via-telecom被intel收购了,为了纪念在via-telecom的岁月暂且保留这些copyright。如果涉及了版权问题,请联系我。
2。虽然最新的内核默认支持的是SLUB,但理解SLAB的原理依然对我们有帮助。所以先把SLAB的原理贴出来,下一篇分析SLUB,然后对比两者的优缺点。
3.当时分析的内核版本是基于3.x,现在已经到4.x了。源码中很多变量名/接口函数已经改变了。但是最最基本的管理方法 分配逻辑都没变,阅读时注意辨别。

为什么有了Buddy还需要SLAB?

Buddy提供了以page为单位的内存分配接口,这对内核来说颗粒度还太大了,所以需要一种新的机制,将page拆分为更小的单位来管理。
Linux中支持的主要有:slab、slub、slob。其中slob分配器的总代码量比较少,但分配速度不是最高效的,所以不是为大型系统设计,适合内存紧张的嵌入式系统。(ARM架构新内核的menucofnig中居然找不到slob的配置了,神奇。。。by 2018)
Linux内存管理之SLAB原理浅析。
伙伴系统,通用分配器和一般内核代码之间的关系如上图所示。
slab分配器的管理数据主要划分为两大结构,管理数据结构kmem_cache(称之为缓存)和保存obj对象的各个slab,如下图片所示:
Linux内存管理之SLAB原理浅析。
内核中所有的kmem_cache通过cache_chain连接起来。
内核初始化的时候首先创建了一个cache_cache的静态kmem_cache结构,内核后续创建新的kmem时候直接从这个cache_cache管理slab的obj中取出。

CACHE SIZE

内核在初始化的时候根据kmalloc_sizes.h文件中定义的obj大小,初始化了管理这些obj的slab,以及相关kmem_cache。
Linux内存管理之SLAB原理浅析。
在初始化之后,系统中kmem_cache的链表大致如下。cache_chain上挂着系统中所有的kmem_cache。前面我们提到初始化的时候会根据kmalloc_sizes.h中的定义初始化好固定大小obj的slab以及对应的kmem_cache,这些kmem_cache称之为通用缓存,提供给kmalloc来使用的。kmalloc根据传入size大小来选择合适的kmem_cache,然后从他的array_cache中取出obj。所以我们可以发现kmalloc并没有malloc那么强大,kmalloc分配的时候只是去找和自己size最接近的obj,所以kmalloc分配有可能会造成内存浪费,甚至在某些情况下浪费的还不低!
Linux内存管理之SLAB原理浅析。

两个重要数据结构(per node和per cpu)

slab分配器的两大数据结构slab和kmem_cache的连接关系如下图所示。
Linux内存管理之SLAB原理浅析。
我们主要关心数据结构之间的连接关系,所以先看kmem_cache中的kmem_list3和array_cache这两个成员。
struct kmem_list3
kmem_list3是per node类型数据,一个node对应一个kmem_list3结构,我们的系统是UMA,所以只有一个kmem_list3结构。
kmem_list3中有三个链表slabs_partial、slabs_full、slabs_free,每个链表中挂着都是slab结构。其中,已经有部分obj被分配出去的slab均挂在slabs_partial下;全部obj都被分配出去的slab挂在slabs_full下;全部obj都未分配出去的slab挂在slabs_free下。
struct array_cache
array_cache是per cpu类型数据,每个core对应一个array_cache结构。
当array_cache中的obj为空时,系统会以batchcount值为准,一次性从kmem_list3中搬运batchcount个obj到arry_cache中,存放在entry里。
slab
slab主要包含两大部分,管理性数据和obj对象,其中管理性数据包括struct slab和kmem_bufctl_t。
slab有两种形式的结构,管理数据外挂式或内嵌式。如果obj比较小,那么struct slab和kmem_bufctl_t可以和obj分配在同一个物理page中,可称为内嵌式;如果obj比较大,那么管理性数据需要单独分配一块内存来存放,称之为外挂式。我们在上图中所画的slab结构为内嵌式。

kmem_bufctl_t

管理性数据kmem_bufctl_t的类型是unsigned int,本质上是一个空闲obj链表,用于描述下一个可用obj序号。初始化时,当前slab中的obj均可用,所以图中kmem_bufctl_t中的值依次就是下一个可用的obj序。
kmem_bufctl_t中使用方法可参考下图场景。
Linux内存管理之SLAB原理浅析。
系统初始化时slab->free执行0#slab,某时刻系统已将0#—3# obj分配出去,此时slab->free指向4# slab,而4#slab对应的kmem_bufctl_t中存放的值是5。表明slab中current可用obj是4#,next可用obj是5#。
系统运行一段时间后,1# obj需回收。此时,1#obj对应的kmem_bufctl_t中填入slab->free值4,并将slab->free修正为1。这表明slab中current可用obj是1#,next可用的obj为4#。

SLAB分配流程以及管理逻辑

分配过程
分配的流程如下图所示:
Linux内存管理之SLAB原理浅析。
obj分配优先考虑从array_cache的entry中取,取的规则遵循LIFO原则,先从enry的末尾取出obj,因为这个obj很有可能还在硬件cache中,是热的。如果entry为空,则说明是第一次从array_cache中分配obj或者是array_cache中的所有obj都已分配,所以需要先从kmem_cache的kmem_list3中取出batchcount个obj,把这些obj全部填充到enty中,然后再分配。
obj释放也是优先考虑释放到array_cache中,而不是直接释放到kmem_list3中。只有array_cache中的obj超过了limit上限,系统才会将enty中的头batchcount个obj搬到kmem_cache所在的kmem_list3中,然后将entry中剩余obj向前移动,然后再将准备释放的obj放到entry的末尾。