本博文只对SLUB做概念上的介绍,贴上少量的代码,大家有个初步认知即可。若想深入了解,可以走读内核代码,推荐学习该博客图解SLUB。
1.why need SLUB?Linux的物理内存管理采用了以页为单位的buddy system(伙伴系统)。在编写用户空间程序,当需要申请内存/释放时,我们只需要调用malloc/free接口,内存分配器(ptmalloc2、jemalloc等)就会根据自己的策略向操作系统申请内存(也可能不需要OS,比如首次申请20Bytes,ptmalloc2会直接给你128KB,后续申请的40Bytes就不会涉及到OS)。在内核空间,也需要有类似的内存分配器,不然在需要申请小对象时就只能分配一整页了;同时,内核对对象的管理又有一定的特殊性,有些对象的访问非常频繁,需要采用缓冲机制。因此SLAB分配器就登场了。
Linux提供了3中分配器方案,2.6.23之后的版本,SLUB成了默认的分配器。在rgos系统,有使用SLAB和SLUB的,可以看内核配置宏,或者直接在设备看是否有/sys/kernel/slab目录,有就是SLUB没有就是SLAB。
2. what is SLUB?Linux使用数据结构struct kmem_cache来描述缓存池,系统内所有的kmem_cache都挂到一个链表上,每个缓存池都包含若干个slab,每个slab由若干个连续的物理页帧组成,每个slab包含固定数目的对象。如下所示:
kmem_cache包含两个关键的数据结构: struct kmem_cache_cpu和struct kmem_cache_node,结构体都比较简单:
struct kmem_cache_cpu {
void **freelist; /* 指向下一个可用的object */
unsigned long tid; /* 主要用来同步作用 */
struct page *page; /* slab内存的page指针 */
#ifdef CONFIG_SLUB_CPU_PARTIAL
struct page *partial; /* 本地slab partial链表。主要是一些部分使用object的slab */
#endif
};
struct kmem_cache_node {
spinlock_t list_lock; /* 自旋锁,保护数据 */
unsigned long nr_partial; /* slab节点中的slab数量 */
struct list_head partial; /* slab节点的slab partial链表,和struct kmem_cache_cpu的partial链表功能类似 */
};
/* SLUB接口 */
struct kmem_cache *kmem_cache_create(const char *name,
size_t size,
size_t align,
unsigned long flags,
void (*ctor)(void *)); /* 创建一个缓存池 */
void kmem_cache_destroy(struct kmem_cache *); /* 销毁一个缓冲池 */
void *kmem_cache_alloc(struct kmem_cache *cachep, int flags); /* 从指定缓存池中分配一个对象 */
void kmem_cache_free(struct kmem_cache *cachep, void *objp); /* 对象交还给缓存池 */
基于SLUB的完整视图如下所示:
在这里简要描述下申请对象的过程:fast-path、slow-path以及very-slow-path。
- fast-path:当kmem_cache_cpu(本地缓存池)的slab(数据结构中的page)有空闲对象时,也即freelist不为空,则直接将该空闲对象分配出去。
- slow-path:当freelist为空时,也即这个slab没有空闲对象,若此时本地slab partial链表不为空(表明首个slab有空闲的对象),则将该slab挂到page上,从中分配空闲的对象。
- very-slow-path:当本地缓存池没有空闲对象,此时发现per node partial链表中有可用slab用于分配,那么就会从per node partial链表中取下一个slab挂到page上,用于分配对象;如果连node链表也为空,这是最糟糕的情形,则需要向伙伴系统申请一个slab,挂到per cpu的page上,从其中分配对象。
网上有个非常贴切的比喻:kmem_cache是“零售商”,包含两个部门,kmem_cache_cpu是“营业厅”,kmem_cache_node是“仓库”。“营业厅”又包含“展示厅”*page和“储物间”*partial,其中“展示厅”仅包含一个slab。当展示厅的物品(即对象)售完时直接从储物间拿货(slab),若储物间都售尽,则需要从总部仓库拿货(slab),若总部仓库都售尽,则需要“工厂”(即伙伴分配系统)生产新的货品(slab)。
3.kmallockmalloc用于内核获取物理地址连续(虚拟地址也是连续的)的空间,其也是基于SLAB机制实现的。
/* /mm/slab_common.c */
struct kmem_cache *kmalloc_caches[KMALLOC_SHIFT_HIGH + 1];
EXPORT_SYMBOL(kmalloc_caches);
/*
* kmalloc 缓存池
* kmalloc_info[] is to make slub_debug=,kmalloc-xx option work at boot time.
* kmalloc_index() supports up to 2^26=64MB, so the final entry of the table is
* kmalloc-67108864.
*/
static struct {
const char *name;
unsigned long size;
} const kmalloc_info[] __initconst = {
{NULL, 0}, {"kmalloc-96", 96},
{"kmalloc-192", 192}, {"kmalloc-8", 8},
{"kmalloc-16", 16}, {"kmalloc-32", 32},
{"kmalloc-64", 64}, {"kmalloc-128", 128},
{"kmalloc-256", 256}, {"kmalloc-512", 512},
{"kmalloc-1024", 1024}, {"kmalloc-2048", 2048},
{"kmalloc-4096", 4096}, {"kmalloc-8192", 8192},
{"kmalloc-16384", 16384}, {"kmalloc-32768", 32768},
{"kmalloc-65536", 65536}, {"kmalloc-131072", 131072},
{"kmalloc-262144", 262144}, {"kmalloc-524288", 524288},
{"kmalloc-1048576", 1048576}, {"kmalloc-2097152", 2097152},
{"kmalloc-4194304", 4194304}, {"kmalloc-8388608", 8388608},
{"kmalloc-16777216", 16777216}, {"kmalloc-33554432", 33554432},
{"kmalloc-67108864", 67108864}
};
/* 创建cache */
static void __init new_kmalloc_cache(int idx, unsigned long flags)
{
kmalloc_caches[idx] = create_kmalloc_cache(kmalloc_info[idx].name,
kmalloc_info[idx].size, flags);
}
我们在调用kmalloc接口时,会根据入参size判断,选择一个最合适的kmalloc-cache。
4.SLUB 的debug机制SLUB提供非常强大的内存检测功能(但KASAN才是最强的),包括:
- 内存泄露(leak),alloc之后忘了free,导致内存占用不断增长
- 越界(overrun),访问了alloc分配的区域之外的内存,覆盖了不属于自己的数据
- 使用已经释放的内存(use after free)
- 使用未经初始化的数据(use uninitialised bytes)