23.slab分配器(分配对象kmem_cache_alloc和缓存的增长cache_grow)

kmem_cache_alloc用于从特定的缓存获取对象。类似于所有的malloc函数,其结果可能是指向分配内存区的指针,也可能分配失败,返回NULL指针。该函数需要两个参数:用于获取对象的缓存,以及精确描述分配特征的标志变量。
<slab.h>
void *kmem_cache_alloc (kmem_cache_t *cachep, gfp_t flags)

 

 

图3-50清楚地说明了,可以跟循下面列举的一条途径完成工作。第一个使用更为频繁也更方便,如果per-CPU缓存中有空闲对象,则从中获取。但如果其中的所有对象都已经分配,则必须重新填充缓存。在最坏的情况下,可能需要新建一个slab。
 选择被缓存的对象
如果在per-CPU缓存中有对象,那么____cache_alloc检查相对容易,如下列代码片段所示:
mm/slab.c 
static inline void *____cache_alloc(kmem_cache_t *cachep, gfp_t flags) 
{ 
  ac = ac_data(cachep); 
  if (likely(ac->avail)) { 
    ac->touched = 1; 
    objp = ac->entry[--ac->avail]; 
  } 
  else { 
    objp = cache_alloc_refill(cachep, flags); 
  } 
  return objp;
cachep是一个指针,指向缓存使用的kmem_cache_t实例。ac_data宏通过返回cachep->array[smp_processor_ id()],从而获得当前活动CPU相关的array_cache实例。
因为内存中的对象紧跟array_cache实例之后,内核可以借助于该结构末尾的伪数组访问对象,而无需指针运算。通过将ac->avail减1,可以将对象从缓存移除。
 
 重新填充per-CPU缓存
在per-CPU缓存中没有对象时,工作负荷会加重。该情形下所需的重新填充操作由cache_alloc_refill实现,在per-CPU缓存无法直接满足分配请求时,则调用该函数。
内核现在必须找到array_cache->batchcount个未使用对象重新填充per-CPU缓存。首先扫描所有部分空闲slab的链表(slabs_partial),然后通过slab_get_obj依次获取所有的对象,直至相应的slab中没有空闲对象为止。内核接下来对slabs_partial链表中的所有其他slab执行同样的过程。如果仍未找到所需数目的对象,内核会遍历slabs_free链表中所有未使用的slab。在从slab获取对象时,内核还必须将slab放置到正确的slab链表中(slabs_full或slabs_partial,取决于slab已经完全用尽还是仍然包含一些空闲对象)。上述逻辑由下列代码实现:
mm/slab.c 
static void *cache_alloc_refill(kmem_cache_t *cachep, gfp_t flags) 
{ 
... 
  while (batchcount > 0) { 
  /* 选择获取对象的slab链表(首先是slabs_partial,然后是slabs_free) */ 
... 
    slabp = list_entry(entry, struct slab, list); 
    while (slabp->inuse < cachep->num && batchcount--) {   //cachep->num是每个slab中的数量
        /* 获取对象指针 */ 
        ac->entry[ac->avail++] = slab_get_obj(cachep, slabp, node); 
    } 
  check_slabp(cachep, slabp); 
  /*将slabp移动到正确的slab链表: */ 
  list_del(&slabp->list); 
  if (slabp->free == BUFCTL_END) 
  list_add(&slabp->list, &l3->slabs_full);
  else 
  list_add(&slabp->list, &l3->slabs_partial); 
 } 
... 
}
按次序移除slab中对象的关键在于slab_get_obj:
mm/slab.c 
static void *slab_get_obj(struct kmem_cache *cachep, struct slab *slabp, int nodeid) 
{ 
  void *objp = index_to_obj(cachep, slabp, slabp->free); 
  kmem_bufctl_t next; 
  slabp->inuse++; 
  next = slab_bufctl(slabp)[slabp->free]; 
  slabp->free = next; 
  return objp; 
}

 

回想20.slab分配器原理(缓存和slab结构详解),内核在跟踪空闲项时使用了一个有趣的系统:当前考虑的空闲对象的索引保存在slabp->free,而下一个空闲对象的索引,则保存在管理数组中。
获取对应于给定索引的对象,不过是index_to_obj执行的一些简单指针操作而已。slab_bufctl是一个宏,返回一个指向slabp之后的kmem_bufctl数组的指针。
我们回到cache_alloc_grow。如果扫描了所有的slab仍然没有找到空闲对象,那么必须使用cache_grow扩大缓存。这是一个代价较高的操作,将在下一节讲述。
缓存的增长
图3-51给出了cache_grow的代码流程图。

 

 

首先计算颜色和偏移量:
mm/slab.c 
static int cache_grow(struct kmem_cache *cachep,gfp_t flags, int nodeid, void *objp) 
{ 
... 
  l3 = cachep->nodelists[nodeid]; 
... 
  offset = l3->colour_next; 
  l3->colour_next++; 
  if (l3->colour_next >= cachep->colour) 
  l3->colour_next = 0; 
  offset *= cachep->colour_off; 
... 
}
如果达到了颜色的最大数目,则内核重新开始从0计数,这自动导致了零偏移。所需的内存空间是使用kmem_getpages辅助函数从伙伴系统逐页分配的。该函数唯一的目的就是用适当的参数调用13.分配页之选择页(zone_watermark_ok和get_page_from_freelist  讨论的alloc_pages_node函数。各个页都设置了PG_slab标志位,表示该页属于slab分配器。在一个slab用于满足短期或可回收分配时,则将标志__GFP_RECLAIMABLE传递到伙伴系统。回想11.伙伴系统(避免碎片及内存的迁移类型 的内容,我们知道重要的是从适当的迁移列表分配页。

如果slab头存储在slab之外,则调用相关的alloc_slabmgmt函数分配所需空间。否则,相应的空间已经在slab中分配。在两种情况下,都必须用适当的值初始化slab数据结构的colouroff、s_mem和inuse成员。
 
接下来,内核调用slab_map_pages创建slab的各页与slab或缓存之间的关联。该函数遍历新分配的所有page实例,分别调用page_set_cache和page_set_slab。这两个函数如下操作(或滥用)page实例的lru成员:
mm/slab.c 
static inline void page_set_cache(struct page *page, struct kmem_cache *cache) 
{ 
  page->lru.next = (struct list_head *)cache; 
} 
static inline void page_set_slab(struct page *page, struct slab *slab) 
{ 
  page->lru.prev = (struct list_head *)slab; 
}
slab的kmem_bufctl数组也会初始化,在数组位置i存储i+1:因为slab至今完全未使用,下一个空闲的对象总是下一个对象。根据惯例,最后一个数组元素的值为BUFCTL_END。现在slab已经完全初始化,可以添加到缓存的slabs_free链表。新产生的对象的数目也加到缓存中空闲对象的数目上(cachep->free_objects)。
 
posted @ 2022-03-22 23:55  while(true);;  阅读(499)  评论(0编辑  收藏  举报