slab分配器

430 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情

伙伴系统在分配内存时是以物理页面为单位的,在实际中有很多内存需求是以字节为单位的,那么如果我们需要分配以字节为单位的小内存块,该如何分配呢?slab分配器就是用来解决小内存块分配问题的,也是内存分配中非常重要的角色之一。

slab分配器最终还使用伙伴系统来分配实际的物理页面,只不过slab分配器在这些连续的物理页面上实现了自己的机制,以此来对小内存块进行管理。

slab分配器产生的背景

内核常常需要分配几十字节的小内存块,若为其分配一个物理页面,则非常浪费内存。早期Linux内核实现了以2n字节为大小的内存块分配机制,这个机制非常类似于伙伴系统。这个简单的机制虽然减少了内存浪费,但是并不高效。 一个更好的机制是Sun公司发明的slab机制,最早实现在Solaris 2.4操作系统中。slab机制有如下新特性。

把分配的内存块当作对象(object)来看待。对象可以自定义构造函数(constructor)和析构函数(destructor)来初始化对象的内容并释放对象的内容。 slab对象被释放之后不会马上丢弃而是继续保留在内存中,可能稍后会被用到,这样不需要重新向伙伴系统申请内存。 slab机制可以根据特定大小的内存块来创建slab描述符,如内存中常见的数据结构、打开文件对象等,这样可以有效地避免内存碎片的产生,也可以快速获得频繁访问的数据结构。另外,slab机制也支持按2n字节大小分配内存块。 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 *, gfp_t flags);
 
#释放缓存对象
void kmem_cache_free(struct kmem_cache *, void *);

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()函数用于创建自己的缓存描述符,kmalloc()函数用于创建通用的缓存,类似于用户空间中C标准库malloc()函数。