首发于术道经纬
内存分配[四] - Linux中的Slab(1)

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

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可能跨越2个page frames。

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

可见啊,slab cache除了和硬件cache一样都使用了“缓存”的思想,它还在实现中充分利用了硬件cache提供的特性,以进一步提高运行效率。但付出的代价就是,不管对齐还是填充,都需要额外的字节,这对内存资源也会造成一定的消耗。

统计信息

可通过"slabtop"命令查看当前系统的slab分配情况:

如上图所示,系统中slab分配器一共占据了46059.92KB内存,包括115个"caches",12126个"slabs"(平均每个cache拥有105个slabs),170077个"objects"(平均每个slabs拥有14个objects)。在所有的objects中,最小的为0.02KB,最大的为4096KB,平均值是0.27KB。

此外,还有细分的每类objects的信息,比如从"inode cache"可以得知打开了9150个文件,从"vm_area_struct"可以得知所有进程一共使用了2140个VMA。并且,还能大致知道每类object的控制结构的大小,比如一个"struct dentry"大约占据0.19KB的内存空间。

虽然没有per-slab包含的pages数目信息,不过完全可以通过"OBJ/SLAB"乘以"SIZE"计算出来。比如通过这种方法算出来"dentry"的就是大约4KB,说明每个"dentry"的slab包含一个page frame。

其更重要的意义是体现在调试的时候:当你发现当前内存资源比较紧张,通过"/proc/meminfo"查到是"Slab"占据了较大内存,根据"slabtop"就可以快速知道是哪一类object消耗的最多(比如建立了过多的socket)。

"slabtop"其实和可以显示CPU和内存占用率的"top"命令是类似的,都是动态地显示目前资源占用率最高的,只不过"top"针对的是进程,而"slabtop"针对的是"caches"。如果要获取全部"caches"的信息,应查看"/proc/slabinfo"文件。

【创建和初始化

创建一个新的"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申请和释放内存的过程是怎样的呢?请看下文分解。


参考:


原创文章,转载请注明出处。

编辑于 08-01

文章被以下专栏收录