不浮躁
分类: LINUX
2014-11-21 21:19:35
kernel 3.10内核源码分析--slab原理及相关代码
1、基本原理
我们知道,Linux保护模式下,采用分页机制,内核中物理内存使用buddy system(伙伴系统)进行管理,管理的内存单元大小为一页,也就是说使用buddy system分配内存最少需要分配一页大小。那如果需要分配小于一页的内存该怎么办呢?
另一方面,内核中经常需要大量的数据结构(比如struct task_strcut),这些数据结构的频繁分配和释放对性能影响较大。
Slab正是用于解决上述的两个问题, Slab 分配器源于 Solaris 2.4 的分配算法,工作于buddy system之上,用于管理特定大小对象的缓存,提高小块内存或特定对象内存分配效率。
Slab的两个用途如前面所述:1、缓存和管理内核中经常使用的数据结构对象,内核中使用slab提供的专用的接口,可以实现数据结构对象的快速分配,大大减少相关开销,提升效率。2、缓存和管理小块内存,也称通用缓存,用于kmalloc的底层实现和支撑。小块内存即小于一页大小的内存,以2^n字节对齐,比如512B、128B、64B、32B等。
2、slab管理结构图
3、缓冲区(kmem_cache)
Slab分配器为每种内核对象建立单独的缓冲区(kmem_cache),每个缓冲区包含多个 slab ,每个 slab包含一组连续的物理内存页,这些内存页被划分成固定数目的对象。根据对象大小的不同,缺省情况下一个 slab 最多可以由 1024 个物理内存页框构成。充分利用硬件特性,需要对每个对象进行一定的对齐处理(比如cache line对齐),所以slab 中分配给对象的内存可能大于用户要求的对象实际大小,可能会有一定的内存浪费。
内核使用 kmem_cache 数据结构管理缓冲区。每个缓冲区中,对每个NUMA node都有三个slab链表:
Full 链表,该链表中的所有slab中的对象都已经被完全分配,没有空闲对象。
Partial 链表,该链表中的slab中还有空闲对象,从slab中分配对象时,优先从此链表中分配。当 slab 的最后一个已分配对象被释放时,该 slab 将从 Partial 链表移入 free链表;当 slab 的最后一个空闲对象被分配时,该 slab 将从Partial 链表移入Full 链表里。
Free链表, 该链表中的slab中全是空闲对象,当partial链表中slab用尽时,从这里分配,并将相应的slab从free链表移入partial链表。
当缓冲区中空闲对象总数不足时,则按需从伙伴系统中分配更多的page,用于slab;当空闲对象比较富余,free链表中的的部分 slab 可能被定期回收。
由于 kmem_cache 自身也是一种内核对象,所以需要一个专门的缓冲区。所有缓冲区的 kmem_cache 控制结构被组织成以 cache_chain 为队列头的一个双向循环队列,同时 cache_cache 全局变量指向kmem_cache 对象缓冲区的 kmem_cache 对象。
kmem_cache结构定义如下:
点击(此处)折叠或打开
Slab使用 struct slab 数据结构(slab描述符)来描述其状态及进行相关管理。Slab描述符位于每个slab区域的起始,在为slab分配内存时,会先从buddy system中分配预定义数量的page作为slab区域,为充分利用硬件高速缓存,使用了着色机制,在slab区域的首部保留一定字节(通常根据cache line)长度的区域作为着色偏移,而slab描述符就位于此偏移之后(当slab管理数据位于slab内时)。
slab数据结构定义如下:
点击(此处)折叠或打开
Slab描述符和kmem_bufctl_t对象数组一起组成了slab的管理数据。Slab管理数据可以直接存放于slab区域内,也可以单独放置。单独放置即放到用kmalloc分配的不同的slab区域中。内核如何选择,取决于slab对象的长度和已用对象的数量,简单算法如下:如果slab对象不超过 1/8 个物理内存页框的大小,那么slab管理数据直接存放于slab区域首部(着色偏移之后);否则的话,存放在slab区域外部,位于由 kmalloc 分配的通用对象缓冲区中。
在kmem_bufctl_t对象数组之后,就是存放真正的slab对象(object)的区域,由slab->s_mem指向。slab->coloroff即为slab区域首部到slab->s_mem的偏移。slab->free中存放了该slab中第一个空闲对象的索引。
5、空闲对象管理
slab中的对象有 2 种状态:已用或空闲。Slab中使用了一个静态数组来管理空闲对象,该数组元素为kmem_bufctl_t对象(定义为无符号整数),每个数组元素对应一个slab对象,数组元素中存放下一个空闲对象的索引,该slab中的第一个空闲对象的索引保存在slab->free中,而该数组最后一个元素中总是几个结束标记:BUFCTL_END,如此,slab中利用此巧妙而简洁的方式有效的管理了slab中的空闲对象。相比SunOS中使用链表来管理slab空闲对象,Linux中的设计更巧妙和简洁、效率更高。
点击(此处)折叠或打开
7、slab着色
7、per-CPU slab对象缓存
为更好的利用CPU高速缓存,slab实现中,为每个CPU分配了一个slab对象的per-CPU缓存。当slab对象被释放时,会首先放入该缓存,当该缓存中的对象数目超限时,会将其移入相应的slab链表中;当分配slab时,也会首先冲该缓存中查找,如果找到直接返回,如果未找到,才从partial链表中开始查找。当该缓存为空时,会从slab 中批量分配对象到该缓存。使用该per-CPU缓存,能进一步提升slab的分配和释放效率。
该缓存由kmem_cache->array[]管理,kmem_cache->array定义为array_cache结构(数组缓存)。 array_cache结构定义如下:
点击(此处)折叠或打开
该结构中的entry数组用于存放slab对象指针,用于指向最近释放的slab对象。
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
点击(此处)折叠或打开
kmem_bufctl_t定义如下:
8、代码分析
主要分析slab对象的分配流程:kmem_cache_alloc()
kmem_cache_alloc()->slab_alloc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()->cache_alloc_refill()
kmem_cache_alloc()->slab_alloc()->__do_cache_alloc()->____cache_allc()->cache_alloc_refill()->cache_grow()