5. slab分配器概述 linux4.0

slab分配器是Linux内核中用于高效管理小内存块的机制,它减少了对伙伴系统的调用,避免缓存污染。slab通过着色技术优化缓存利用,确保对象均匀分布。slab分配器维护了per-CPU对象,减少高速缓存的无效使用,并且对象在slab中非连续排列,以满足对齐要求。slab管理结构包括缓存对象和slab链表,通过kmem_cache_create等接口进行操作。
摘要由CSDN通过智能技术生成

伙伴系统用于分配内存时以page为单位的,在实际中有很多内存需求是以Byte为单位的,那么如果我们需要分配以Byte为单位的小内存块时,该如何分配呢?slab分配器就是用来解决小内存块分配问题的,也是内存分配中非常重要的角色之一。slab分配器最终还是由伙伴系统来分配出实际的物理页面,只不过slab分配器在这些连续的物理页面上实现了自己的算法,以此来对小内存块进行管理。关于slab分配器,我们需要思考如下几个问题。

  • slab分配器是如何分配和释放小内存块的?

答:

  • slab分配器中有一个着色的概念(cache color),着色有什么作用?

答:通过cache color可以更好的利用缓存,slab分配器能够均匀地分布对象,以实现均匀地缓存利用,着色这个术语是隐喻性的。它与颜色无关,只是表示slab中的对象需要移动的特定偏移量,以便使对象放置到不同的缓存行。

  • slab分配器中的slab对象有没有根据Per-CPU做一些优化?

答:

  • slab增长并导致大量不用的空闲对象,该如何解决?

答:


slab分配器提供如下接口来创建、释放slab描述符合分配缓存对象。

//创建slab描述符
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t align,
          unsigned long flags, void (*ctor)(void *))

//释放slab描述符
void kmem_cache_destroy(struct kmem_cache *s)

//分配缓存对象
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags)

//释放缓存对象
void kmem_cache_free(struct kmem_cache *cachep, void *objp)

kmem_cache_create()函数中有如下参数:

  • name: slab描述符的名称。

  • size: 缓存对象的大小。

  • align:缓存对象需要对齐的字节数。

  • flags: 分配掩码。

  • ctor: 对象的构造函数。

例如, 在Intel显卡驱动中就大量使用kmem_cache_create()来创建自己的slab描述符。

[drivers/gpu/drm/i915/i915_gem.c]

//创建名为"i915_gem_object" slab描述符
void i915_gem_load(struct drm_device *dev)
{
    ......
    dev_priv->slab =
        kmem_cache_create("i915_gem_object",
                  sizeof(struct drm_i915_gem_object), 0,
                  SLAB_HWCACHE_ALIGN,
                  NULL);

    ......
}
void *i915_gem_object_alloc(struct drm_device *dev)
{
    //分配缓存对象
    return kmem_cache_zalloc(dev_priv->slab, GFP_KERNEL);
}

另外一个大量使用slab机制的是kmalloc()函数接口。kmem_cache_create()函数用于创建自己的缓存描述符,类似于创建通用的缓存,类似于用户空间中C标准库malloc()函数。

slab分配器有两个好处

  • 调用伙伴系统的操作对系统的数据和指令高速缓存有相当的影响。内核越浪费这些资源,这些资源对用户空间进程就越不可用。更轻量级的slab分配器在可能的情况下减少了对伙伴系统的调用,有助于防止不受欢迎的缓存"污染".

  • 如果数据存储在伙伴系统直接提供的页中,那么其地址总是出现在2的幂次的整数倍附近(许多将页划分为更小块的其他分配方法,也有同样的特征)。这对CPU高速缓存的利用有负面影响,由于这种地址分布,使得某些缓存行为过度使用,而其他的则几乎为空。多处理器系统可能会加剧这种不利情况,因为不同的内存地址可能在不同的总线上传输,上述情况会导致某些总线拥塞,而其他总线则几乎没有使用。

    通过slab着色(slab coloring),slab分配器能够均匀地分布对象,以实现均匀地缓存利用,如下所示。

        经常使用的内核对象保存在CPU高速缓存中, 这是我们想要的效果。从slab分配器的角度进行衡量,伙伴系统的高速缓存和TLB占用较大,这是一个负面效应。因为这会导致不重要的数据驻留在CPU高速缓存中,而重要的数据则被置换到内存,显然应该防止这种情况出现。

着色这个术语是隐喻性的。它与颜色无关,只是表示slab中的对象需要移动的特定偏移量,以便使对象放置到不同的缓存行。

    slab分配器由何得名?各个缓存管理的对象,会合并为较大的组,覆盖一个或多个连续页,这中组称作slab,每个缓存由几个这中slab组成。简单说:slab就是一组缓存对象

slab分配的原理:

    slab分配器由一个紧密地交织的数据结构和内存结构的网络组成,初看起来不容易理解其运作方式。因此在考察其实现前,重要的是获得各个结构之间关系的概观。

    基本上,slab缓存由下图的两部分组成:保存管理性数据的缓存对象和保存被管理对象的各个slab。

每个缓存只负责一种对象类型(例如:struct unix_sock实例),或提供一般性的缓冲区。各个缓存中slab的数目各有不同,这与已经使用的页的数目、对象长度、和被管理对象的数目有关。

    另外,系统中所有的缓存都保存在一个双向链表中。这使得内核有机会依次遍历所有的缓存。这是有必要的,例如在即将发生内存不足时,内核可能需要缩减分配给缓存的内存数量。

  1. 缓存的精细结构

    如果我们更仔细地研究缓存的结构,就可以注意到一些更重要的细节。如下图

    

除了管理性数据(如已用和空闲对象或标志寄存器数目),缓存结构包括两个特别重要的成员。

  • 指向一个数组的指针,其中保存了各个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中未使用的对象。

slab的精细结构

    对象在slab中并非连续排列,而是按照一个相当复杂的方案分布。如下图:

用于每个对象的长度并不反应其确切地大小。相反,长度已经进行了舍入,以满足某些对齐方式的要求。有两种可用的备选对齐方案。

  • slab创建时使用表示SLAB_HWCACHE_ALIGN, slab用户可以要求对象按硬件缓存行对齐。那么会按照cache_line_size的返回值进行对齐,该函数返回特定于处理器的L1缓存大小。如果对象小于缓存行长度的一半,那么将多个对象放入一个缓存行。

  • 如果不要求按硬件缓存行对齐,那么内核保证对象按BYTES_PER_WORD对齐,该值是表示void 指针所需字节的数目。

    在32位处理器上,void指针需要4个字节。因此,对于6字节的对象,则需要8 = 2*4 个字节,15个字节的对象需要16 = 4*4 个字节。多余的字节称为填充字节。

    填充字节可以加速对slab中对象的访问。如果使用对齐的地址,那么在几乎所有的体系结构上,内存访问都会更快。这弥补了使用填充字节必然导致需要更多内存的不利情况。

    管理结构位于每个slab的起始处,保存了所有的管理数据(和用于连接缓存链表的元素)。其后面是一个数组,每个(整数)数组项对应于slab中的一个对象的索引。由于最低编号的空闲对象的标号还保存在slab起始处的管理结构中,内存无需使用链表或其他复杂的关联机制,即可轻松找到当前可用的所有对象。数组的最后一项总是一个结束标记,值为BUFCTL_END。slab中空闲兑现的管理图:

    大多数情况下,slab内存区的长度(减去头部管理数据)是不能被(可能填补过的)对象长度整除的。因此内存就有了一些多余的内存,可以用来以偏移量的形式给slab"着色",如上图所述。缓存的各个slab成员会指定不同的偏移量,以便将数据定位到不同的缓存行,因而slab开始和结束处的空闲内存是不同的。在计算偏移量时,内存必须考虑其他的对齐因素。例如,L1高速缓存中数据的对齐。

    管理数据可以放置在slab自身,也可以放置到使用kmalloc分配的不同内存区中。内核如何选择,取决于slab的长度和已用对象的数量。管理数据和slab内存之间的关联很容易建立,因为slab头包含了一个指针,指向slab数据区的起始处(无论管理数据是否在slab上)。

slab首部位于slab外部的情形:

    最后,内核需要一种方法,通过对象自身即可识别slab(以及对象驻留的缓存)。根据对象的物理内存地址,可以找到相关的页,因此可以在全局mem_map数组中找到对应的page实例。

    我们已经知道,page结构包含一个链表元素,用于管理各种链表中的页。对于slab缓存中的页而言,该指针是不必要的,可用于其他用途。

  • page->lru.next 指向页驻留的缓存管理结构。

  • page->lru.prev 指向保存该页的slab管理结构。

    

从下面三幅图可以观察出slab的基本数据结构逻辑关系。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byd yes

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值