暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

内存分配[四] - Linux中的Slab(1)

术道经纬 2020-05-07
567
Linux中的buddy分配器是以page frame为最小粒度的,而现实的应用多是以内核objects(比如描述文件的"struct inode")的大小来申请和释放内存的,这些内核objects的大小通常从几十字节到几百字节不等,远远小于一个page的大小。

那可不可以把一个page frame再按照buddy的原理,以更小的尺寸(比如128字节,256字节)组织起来,形成一个二级分配系统呢?

假设一个object的大小是96字节,如果使用这样的二级分配器,申请时将从128字节的free list开始查找,没有则继续寻找更高order的free list。

首先,这将在每个被使用到的128字节内存块中留下32字节难以使用的“碎片”,造成内存资源的浪费。另外,一个object在释放后将归还到128字节的free list上,根据buddy的规则,可能被合并为更高order的内存块,如果这个object马上又要使用,则需要再次从free list上分配。

一个更好的方案是使用slab分配器,它是由Sun公司的雇员Jeff  Bonwick在Solaris  2.4中设计并实现的。由于他公开了其方法,因而后来被Linux所借鉴,用于实现更小粒度的内存分配。

【cache和slab】

在slab分配器中,每一类objects拥有一个"cache"(比如inode_cache, dentry_cache)。之所以叫做"cache",是因为每分配一个object,都从包含若干空闲的同类objects的区域获取,释放时也直接回到这个区域,这样可以缓存和复用相同的objects,加快分配和释放的速度。

object从"cache"获取内存,那"cache"的内存又是从哪里来的呢?还是得从buddy分配器来。slab层直接面向程序的分配需求,相当于是前端,而buddy系统则成为slab分配器的后端

由于"cache"的内存是从buddy系统获得的,因此在物理上是连续的。如果一个"cache"中objects的数目较多,那么"cache"的体积较大,需要占用的连续物理内存较多。当object的数量增加或减少时,也不利于动态调整。

因此,一个"cache"分成了若干个slabs,同一"cache"中的slabs都存储相同的objects。

【数据结构】

一个"cache"在Linux中由"struct kmem_cache"结构体描述:
struct kmem_cache {
unsigned int gfporder; /* order of pages per slab (2^n) */
gfp_t allocflags
; /* force GFP flags */

unsigned int num; /* objects per slab */
int object_size;
unsigned int colour_off; /* colour offset */
size_t colour
; /* cache colouring range */

struct list_head list; /* cache creation & removal */
}
一个slab由一个或多个page frame组成(通常为一个),根据buddy系统的限制,在数量上必须是2的幂次方,"gfporder"实际就定义了一个"cache"中每个slab的大小

既然涉及到page frame的分配,那自然离不开GFP flags,比如要求从DMA中分配,就需要指定"GFP_DMA"。

每个slab区域包含多个objects,数量由"num"表示,每个object的大小由"object_size"给出。

如果object的内存地址能按一定字节数(比如总线宽度)或者按硬件cache line的大小对齐,将可以提高读写性能。此外,不同slabs中具有相同偏移的objects,大概率会落在同一cache line上,造成cache line的争用,所以最好加上不同的填充,以错开对cache line的使用。

这些偏移和填充,共同构成了slab的coloring机制。

可见啊,slab cache除了和硬件cache一样都使用了“缓存”的思想,它还在实现中充分利用了硬件cache提供的特性,以进一步提高运行效率。

但付出的代价就是,不管对齐还是填充,都需要额外的字节,这对内存资源也会造成一定的消耗。
创建和初始化
创建一个新的"cache"的函数接口是kmem_cache_create(),主要就是为"kmem_cache"的控制结构分配内存空间并初始化。而这个控制结构本身也是一个内核object,按理也应该从slab cache中获取,那第一个"kmem_cache"从哪里来?

这就形成了一个“先有鸡还是先有蛋”的问题,解决的办法是在slab子系统生效之前的启动阶段,用一段特殊的boot cache来分配。

"cache"创建后,一开始不含有任何的slab,也就没有任何空闲的objects,只有当产生分配一个新的object的需求时,才开始创建slab。

创建一个slab除了需要分配容纳objects的内存(相当于user data),还需要生成管理这个slab的控制信息(相当于meta data,这里称为slab descriptor)。

一个slab区域包括若干个大小相同的objects,以及它们的coloring消耗的字节。一个"cache"的多个slabs通过双向链表连接,因而需要存储链表指针的空间。

根据object的大小不同,slab descriptor本身占据的内存可以位于其管理的slab内存区域内部,也可以位于外部(off-slab) 。当位于内部时,链表指针存储在一个slab区域的末尾。
if (OFF_SLAB(cachep)) {
/* Slab management obj is off-slab */
freelist
= kmem_cache_alloc_node(cachep->freelist_cache, ...);
}
else {
/* We will use last bytes at the slab for freelist */
freelist
= addr + (PAGE_SIZE << cachep->gfporder) - cachep->freelist_size;
}
那cache和slab都创建好后,一个object申请和释放内存的过程是怎样的呢?请看下文分解。

文章转载自术道经纬,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论