最近开始学习slab
,感觉内容比较杂乱,花了不少时间稍微明白了一些,这里通过几篇笔记总结下学习的slab
知识。
为什么需要slab 关于这点,书上和网上都说了很多,我这里简单地说3点:
可以分配比PAGE_SIZE
更小的内存空间,大大地降低了内部碎片;
效率更高。第一,slab
的分配和释放比伙伴系统更轻量级,速度更快,使用资源更少;第二,slab
可以更快地分配和释放经常使用的对象(释放后并不还给伙伴系统,slab
可以之后再使用);
对物理缓存更友好,效率更高。slab
分配内存空间时会尽量减少物理缓存的冲突。
相关数据结构 slab
是多个结构体共同组成的系统,因此在理解上会有些困难,下图展示了一个简化版的slab
系统。我们可以看到,slab
系统至少需要4个结构体 - 高速缓存,slab
,对象和page
(外加一个array_cache
)。page
在这里就不介绍了,下面主要介绍其它几种结构体。
一定要注意,这里所指的高速缓存,是软件意义上的,和硬件缓存没有任何关系!同时,对象是没有结构体的,对象就是大小一定的连续内存块。
图一:简化版的slab结构体间的关系
struct kmem_cache_s 在kmem_cache_s
中,有两个结构体需要说明,分别是array_cache
和kmem_list3
。array_cache
是CPU本地高速缓存的描述符,kmem_list3
则包含了该高速缓存连接slab
的三种链表。
struct array_cache 1 2 3 4 5 6 struct array_cache { unsigned int avail; unsigned int limit; unsigned int batchcount; unsigned int touched; };
struct kmem_list3 1 2 3 4 5 6 7 8 9 struct kmem_list3 { struct list_head slabs_partial ; struct list_head slabs_full ; struct list_head slabs_free ; unsigned long free_objects; int free_touched; unsigned long next_reap; struct array_cache *shared ; };
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 struct kmem_cache_s { struct array_cache *array [NR_CPUS ]; unsigned int batchcount; unsigned int limit; struct kmem_list3 lists ; unsigned int objsize; unsigned int flags; unsigned int num; unsigned int free_limit; spinlock_t spinlock; unsigned int gfporder; unsigned int gfpflags; size_t colour; unsigned int colour_off; unsigned int colour_next; kmem_cache_t *slabp_cache; unsigned int slab_size; unsigned int dflags; void (*ctor)(void *, kmem_cache_t *, unsigned long ); void (*dtor)(void *, kmem_cache_t *, unsigned long ); const char *name; struct list_head next ; #if STATS unsigned long num_active; unsigned long num_allocations; unsigned long high_mark; unsigned long grown; unsigned long reaped; unsigned long errors; unsigned long max_freeable; unsigned long node_allocs; atomic_t allochit; atomic_t allocmiss; atomic_t freehit; atomic_t freemiss; #endif #if DEBUG int dbghead; int reallen; #endif };
struct slab 该结构体是slab
描述符,它通过kmem_list3
和kmem_cache_s
联系起来。在后面的笔记中会介绍slab
描述符和对象描述符如何一起工作的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 struct slab { struct list_head list ; unsigned long colouroff; void *s_mem; unsigned int inuse; kmem_bufctl_t free ; };
slab的基本组成 slab
各种操作的具体分析我会在之后的笔记中详细记录(和进程、内存回收相关的暂不记录在slab
系列),这里只是在大的方向上介绍下slab
是如何组成的。
初始化阶段和之后的使用是有一些区别的(初始化也需要分配内存空间,并且存在“鸡生蛋,蛋生鸡”的问题),要搞懂初始化,通过阅读源码,我总结出3点:
我们需要明确一点,在伙伴系统完成后,系统可以正常地按页进行分配内存空间。因此,只要是需要分配页的地方,即使slab
没有初始化完成也是可以用的;
对于结构体的内存空间申请(就像我们使用malloc()
给结构体申请内存空间),在slab
初始化完成前,内核是不能直接完成这项任务的。内核是通过使用全局变量(静态)的方法解决slab
初始化完成前需要给结构体申请内存空间的难题;
slab
分为两种,一种是专用高速缓存,一种是普通高速缓存。第一种是针对某一特定作用设置的高速缓存,第二种是普通的,任何结构体、任何设备都可以通过kmalloc()
来使用的高速缓存。初始化时就先建立了普通高速缓存池,我们可以使用kmalloc()
来给结构体等分配动态的内存空间。
有了这些基础,我们就可以看看下图了:
图二:高速缓存、slab和对象
内核通过cache_chain
将kmem_cache
链接起来,第一个kmem_cache
是cache_cache
,这是静态分配的缓存(这里只分配了描述符,并没有分配对应的slab
、对象以及CPU本地缓存描述符等)。之后的高速缓存也都是在此基础上连入cache_chain
的。每个kmem_cache
又有CPU本地slab
对象的地址以及三条slab
的链表。注意,上图和2.6.11的内核有些区别,比如2.6.11中没有nodelists
,但其它部分都比较类似。
[补充内容] cache_cache的分析 我这里对cache_cache
这个高速缓存进行简单的分析,这对后面理解slab
的初始化很有帮助。下面是和cache_cache
相关的初始化代码,我们来看看cache_cache
这些结构体变量有什么作用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #define BOOT_CPUCACHE_ENTRIES 1 struct arraycache_init { struct array_cache cache ; void * entries[BOOT_CPUCACHE_ENTRIES]; }; static struct arraycache_init initarray_cache __initdata = { { 0 , BOOT_CPUCACHE_ENTRIES, 1 , 0 } }; static struct arraycache_init initarray_generic = { { 0 , BOOT_CPUCACHE_ENTRIES, 1 , 0 } }; struct array_cache { unsigned int avail = 0 ; unsigned int limit = BOOT_CPUCACHE_ENTRIES; unsigned int batchcount = 1 ; unsigned int touched = 0 ; }; static kmem_cache_t cache_cache = { .lists = LIST3_INIT(cache_cache.lists), .batchcount = 1 , .limit = BOOT_CPUCACHE_ENTRIES, .objsize = sizeof (kmem_cache_t ), .flags = SLAB_NO_REAP, .spinlock = SPIN_LOCK_UNLOCKED, .name = "kmem_cache" , #if DEBUG .reallen = sizeof (kmem_cache_t ), #endif }; cache_cache.colour_off = cache_line_size(); cache_cache.array [smp_processor_id()] = &initarray_cache.cache; cache_cache.objsize = ALIGN(cache_cache.objsize, cache_line_size()); cache_estimate(0 , cache_cache.objsize, cache_line_size(), 0 , &left_over, &cache_cache.num); if (!cache_cache.num) BUG(); cache_cache.colour = left_over/cache_cache.colour_off; cache_cache.colour_next = 0 ; cache_cache.slab_size = ALIGN(cache_cache.num*sizeof (kmem_bufctl_t ) + sizeof (struct slab), cache_line_size());