20.slab分配器原理(缓存和slab结构详解)
基本上,slab缓存由图3-44所示的两部分组成:保存管理性数据的缓存对象和保存被管理对象的各个slab。
每个缓存只负责一种对象类型(例如struct unix_sock实例),或提供一般性的缓冲区。各个缓存中slab的数目各有不同,这与已经使用的页的数目、对象长度和被管理对象的数目有关。
1. 缓存的精细结构
图3-45给出了缓存各组成部分的概述。
除了管理性数据(如已用和空闲对象或标志寄存器的数目),缓存结构包括两个特别重要的成员。
指向一个数组的指针,其中保存了各个CPU最后释放的对象。
每个内存结点都对应3个表头,用于组织slab的链表。第1个链表包含完全用尽的slab,第2个是部分空闲的slab,第3个是空闲的slab。
缓存结构指向一个数组,其中包含了与系统CPU数目相同的数组项。每个元素都是一个指针,指向一个进一步的结构称之为数组缓存(array cache),其中包含了对应于特定系统CPU的管理数据(就总体来看,不是用于缓存)。管理性数据之后的内存区包含了一个指针数组,各个数组项指向slab中未使用的对象。
为最好地利用CPU高速缓存,这些per-CPU指针是很重要的。在分配和释放对象时,采用后进先出原理(LIFO,last in first out)。内核假定刚释放的对象仍然处于CPU高速缓存中,会尽快再次分配它(响应下一个分配请求)。仅当per-CPU缓存为空时,才会用slab中的空闲对象重新填充它们。这样,对象分配的体系就形成了一个三级的层次结构,分配成本和操作对CPU高速缓存和TLB的负面影响逐级升高。
(1) 仍然处于CPU高速缓存中的per-CPU对象。
(2) 现存slab中未使用的对象。
(3) 刚使用伙伴系统分配的新slab中未使用的对象。
2. slab的精细结构
对象在slab中并非连续排列,而是按照一个相当复杂的方案分布。图3-46说明了相关细节。
用于每个对象的长度并不反映其确切的大小。相反,长度已经进行了舍入,以满足某些对齐方式的要求。
填充字节可以加速对slab中对象的访问。如果使用对齐的地址,那么在几乎所有的体系结构上,内存访问都会更快。
管理结构位于每个slab的起始处,保存了所有的管理数据(和用于连接缓存链表的链表元素)。其后面是一个数组,每个(整数)数组项对应于slab中的一个对象。只有在对象没有分配时,相应的数组项才有意义。在这种情况下,它指定了下一个空闲对象的索引。由于最低编号的空闲对象的编号还保存在slab起始处的管理结构中,内核无需使用链表或其他复杂的关联机制,即可轻松找到当前可用的所有对象。① 数组的最后一项总是一个结束标记,值为BUFCTL_END。
图3-47对此给出了图示。
大多数情况下,slab内存区的长度(减去了头部管理数据)是不能被(可能填补过的)对象长度整除的。因此,内核就有了一些多余的内存,可以用来以偏移量的形式给slab“着色”,如上文所述。
缓存的各个slab成员会指定不同的偏移量,以便将数据定位到不同的缓存行,因而slab开始和结束处的空闲内存是不同的。在计算偏移量时,内核必须考虑其他的对齐因素。例如,L1高速缓存中数据的对齐(下文讨论)。
最后,内核需要一种方法,通过对象自身即可识别slab(以及对象驻留的缓存)。根据对象的物理内存地址,可以找到相关的页,因此可以在全局mem_map数组中找到对应的page实例。我们已经知道,page结构包括一个链表元素,用于管理各种链表中的页。对于slab缓存中的页而言,该指针是不必要的,可用于其他用途。
page->lru.next指向页驻留的缓存的管理结构。
page->lru.prev指向保存该页的slab的管理结构。
此外,内核还对分配给slab分配器的每个物理内存页都设置标志PG_SLAB。
总结:对象高速缓存的组织,高速缓存的内存区被划分为多个slab,每个slab由一个或多个连续的页框组成,这些页框中既包含已分配的对象,也包含空闲的对象。slab 分配器首先从部分空闲的slab 进行分配。如有,则从空的slab 进行分配。如没有,则从物理连续页上分配新的slab,并把它赋给一个cache ,然后再从新slab 分配空间。注意:头部管理数据即struct slab+数组,理由见22.slab分配器(创建缓存kmem_cache_create)