22.slab分配器(创建缓存kmem_cache_create)
创建新的slab缓存必须调用kmem_cache_create。该函数需要很多参数。
mm/slab.c struct kmem_cache *kmem_cache_create (const char *name, size_t size, size_t align,unsigned long flags,void (*ctor)(struct kmem_cache *, void *))
除了可读的name随后会出现在/proc/slabinfo以外,该函数需要被管理对象以字节计的长度,在对齐数据时使用的偏移量(align,几乎所有的情形下都是0),flags中是一组标志,而ctor和dtor中是构造/析构函数。
创建新缓存是一个冗长的过程,kmem_cache_create的代码流程图如图3-49所示。
首先,对象长度向上舍入到处理器字长的倍数:
kmem_cache_t* kmem_cache_create (...) { ... if (size & (BYTES_PER_WORD-1)) { size += (BYTES_PER_WORD-1); size &= ~(BYTES_PER_WORD-1); }
对象对齐(在align中)通常也是基于处理器的字长。但如果设置了SLAB_HWCACHE_ALIGN标志,则内核按照特定于体系结构的函数cache_line_size给出的值,来对齐数据。内核还尝试将尽可能多的对象填充到一个缓存行中,只要对象长度允许,则会一直尝试将对齐值除以2。因此,会有2、4、6…个对象放入一个缓存行,而不是只有一个对象。
mm/slab.c /* 1) 体系结构推荐: */ if (flags & SLAB_HWCACHE_ALIGN) { /* 默认对齐值,由特定于体系结构的代码指定。 * 如果一个对象比较小,则会将多个对象挤到一个缓存行中。 */ ralign = cache_line_size(); while (size <= ralign/2) ralign /= 2; } else { ralign = BYTES_PER_WORD; } ...
内核也考虑到下述事实:某些体系结构需要一个最小值作为数据对齐的边界,由ARCH_SLAB_MINALIGN定义。用户所要求的对齐也可以接受。
mm/slab.c /* 2) 体系结构强制的最小对齐值 */ if (ralign < ARCH_SLAB_MINALIGN) { ralign = ARCH_SLAB_MINALIGN; } /* 3) 调用者强制的对齐值 */ if (ralign < align) { ralign = align; } /* 4) 存储最后计算出的对齐值。 */ align = ralign; ...
在数据对齐值计算完毕之后,分配struct kmem_cache的一个新实例。确定是否将slab头存储在slab之上(参见3.6.3节)相对比较简单。如果对象长度大于页帧的1/8,则将头部管理数据存储在slab之外,否则存储在slab上。
mm/slab.c if (size >= (PAGE_SIZE>>3)) /* * 对象长度比较大,那么最好将slab管理数据放置在slab之外(能够更好地填充实际对象)。 */ flags |= CFLGS_OFF_SLAB; size = ALIGN(size, align); ...
在kmem_cache_create调用时显式设置CFLGS_OFF_SLAB,那么对较小的对象,也可以将slab头存储在slab之外。最后,增加对象的长度size,直至对应到上文计算的对齐值。
到目前为止,我们只定义了对象的长度,而没有定义slab的长度。因此在下一步中,会尝试找到适当的页数用作slab长度,不太小,也不太大。slab中对象太少会增加管理开销,降低方法的效率,而过大的slab内存区则对伙伴系统不利。
内核试图通过calculate_slab_order实现的迭代过程,找到理想的slab长度。基于给定对象长度,cache_estimate针对特定的页数,来计算对象数目、浪费的空间、着色所需的空间。该函数会循环调用,直至内核对结果满意为止。通过系统地不断摸索,cache_estimate找到一个slab布局,可以由下列要素描述。size是对象长度,gfp_order是页的分配阶,num是slab上对象的数目,wastage是该分配阶下因浪费而不可用的空间数量(当然,总是有wastage < size;否则,可以在slab上在放一个对象)。head指定了slab头需要多少空间。该布局对应于以下公式:
PAGE_SIZE<<gfp_order = head + num*size + left_over
如果slab头存储在slab外,则head值为0,因为无需为头部分配空间。如果存储在slab上,则该值计算如下:
head = sizeof(struct slab) + num*sizeof(kmem_bufctl_t)
按20.slab分配器原理(缓存和slab结构详解)的讨论,每个slab头之后都是一个数组,数组项的数目与slab中对象的数目相同。内核利用该数组来查找下一个空闲对象的位置。数组项的数据类型为kmem_bufctl_t,该类型不过是普通的unsigned int通过typedef适当抽象的结果。
下列步骤用于对slab着色:
mm/slab.c cachep->colour_off = cache_line_size(); /* 偏移量必须是对齐值的倍数。 */ if (cachep->colour_off < align) cachep->colour_off = align; cachep->colour = left_over/cachep->colour_off;
内核使用L1缓存行的长度作为偏移量,该值可通过特定于体系结构的函数cache_line_size确定。还必须保证偏移量是所用对齐值的倍数,否则就没有数据对齐的效果。
slab的颜色数目(即,潜在的不同偏移量值的数目),即slab上的浪费空间(称之为left_over)除以颜色偏移量(colour_off)的商(余数略去)。
)。首先,内核根据对象长度定义缓存中的对象指针的数目。
0 < size ≤ 256:120个对象
256 < size ≤ 1 024:54个对象
1 024 < size ≤ PAGE_SIZE:24个对象
PAGE_SIZE < size:8个对象
size > 131 072:1个对象
为各个处理器分配所需的内存:一个array_cache的实例和一个指针数组,数组项数目在上述的计算中给出;并初始化数据结构,这些任务委托给do_tune_cpucache。我们特别感兴趣的一个方面是,batchcount字段总是设置为缓存中对象数目的一半。
为完成初始化,将初始化过的kmem_cache实例添加到全局链表,表头为cache_chain,定义在mm/slab.c中。