水平有限,描述不当之处还请指出,转载请注明出处http://blog.csdn.net/vanbreaker/article/details/7705559
上节介绍了Slob分配器的相关概念和思想,这节来看Slob分配器是如何分配对象的。kmem_cache_alloc_node()函数用来分配一个专用缓存的对象:
void *kmem_cache_alloc_node(struct kmem_cache *c, gfp_t flags, int node)
{
void *b;
if (c->size < PAGE_SIZE) {//对象小于PAGE_SIZE,由Slob分配器进行分配
b = slob_alloc(c->size, flags, c->align, node);
trace_kmem_cache_alloc_node(_RET_IP_, b, c->size,
SLOB_UNITS(c->size) * SLOB_UNIT,
flags, node);
} else {//否则通过伙伴系统分配
b = slob_new_pages(flags, get_order(c->size), node);
trace_kmem_cache_alloc_node(_RET_IP_, b, c->size,
PAGE_SIZE << get_order(c->size),
flags, node);
}
if (c->ctor)//如果定义了构造函数则调用构造函数
c->ctor(b);
kmemleak_alloc_recursive(b, c->size, 1, c->flags, flags);
return b;
}
由于slob为PAGE_SIZE大小,因此首先要判断要求分配的对象的大小是否在这个范围内,如果是,则通过Slob分配器来分配,否则的话通过伙伴系统分配。
来看Slob分配对象的具体过程
static void *slob_alloc(size_t size, gfp_t gfp, int align, int node)
{
struct slob_page *sp;
struct list_head *prev;
struct list_head *slob_list;
slob_t *b = NULL;
unsigned long flags;
/*根据分配对象的大小选择从哪个链表的slob中进行分配*/
if (size < SLOB_BREAK1)
slob_list = &free_slob_small;
else if (size < SLOB_BREAK2)
slob_list = &free_slob_medium;
else
slob_list = &free_slob_large;
spin_lock_irqsave(&slob_lock, flags);
/* Iterate through each partially free page, try to find room */
list_for_each_entry(sp, slob_list, list) {//遍历slob链表
#ifdef CONFIG_NUMA
/*
* If there's a node specification, search for a partial
* page with a matching node id in the freelist.
*/
if (node != -1 && page_to_nid(&sp->page) != node)//节点不匹配
continue;
#endif
/* Enough room on this page? */
if (sp->units < SLOB_UNITS(size))//slob中的空间不够
continue;
/* Attempt to alloc */
prev = sp->list.prev;
b = slob_page_alloc(sp, size, align);//分配对象
if (!b)
continue;
/* Improve fragment distribution and reduce our average
* search time by starting our next search here. (see
* Knuth vol 1, sec 2.5, pg 449) */
/*这里将slob_list链表头移动到prev->next前面,以便下次遍历时能够从prev->next开始遍历*/
if (prev != slob_list->prev &&
slob_list->next != prev->next)
list_move_tail(slob_list, prev->next);
break;
}
spin_unlock_irqrestore(&slob_lock, flags);
/* Not enough space: must allocate a new page */
if (!b) {//没有分配到对象,也就是说slob_list中没有可以满足分配要求的slob了
b = slob_new_pages(gfp & ~__GFP_ZERO, 0, node);//创建新的slob
if (!b)
return NULL;
sp = slob_page(b);//获取slob的地址
set_slob_page(sp);
spin_lock_irqsave(&slob_lock, flags);
sp->units = SLOB_UNITS(PAGE_SIZE);//计算单元数
sp->free = b; //设置首个空闲块的地址
INIT_LIST_HEAD(&sp->list);
set_slob(b, SLOB_UNITS(PAGE_SIZE), b + SLOB_UNITS(PAGE_SIZE));
set_slob_page_free(sp, slob_list); //将sp链入slob_list
b = slob_page_alloc(sp, size, align);//从新的slob中分配块
BUG_ON(!b);
spin_unlock_irqrestore(&slob_lock, flags);
}
if (unlikely((gfp & __GFP_ZERO) && b))
memset(b, 0, size);
return b;
}
- 首先要根据对象的大小来决定从哪个全局链表中寻找slob进行分配
- 遍历选取的链表,找到一个空间足够满足分配要求的slob
- 从选取的slob中分配对象块(slob_page_alloc())
- 如果遍历完整个链表都没能分配到对象,则创建一个新的slob(slob_new_page()),然后设置slob的属性,再进行分配,可以看到一个新的slob中只有一个块,并且下一个空闲对象的指针指向了下一页的起始处,也就是页对齐的
来看分配的细节操作slab_page_alloc()
static void *slob_page_alloc(struct slob_page *sp, size_t size, int align)
{
slob_t *prev, *cur, *aligned = NULL;
int delta = 0, units = SLOB_UNITS(size);
for (prev = NULL, cur = sp->free; ; prev = cur, cur = slob_next(cur)) {
slobidx_t avail = slob_units(cur);//计算获取的空闲块的容量
/*如果设置了对齐值则先将块进行对齐*/
if (align) {
aligned = (slob_t *)ALIGN((unsigned long)cur, align);
delta = aligned - cur;//计算对齐后的对象增加了多少字节的内存
}
/*空闲块内存不小于要求分配的 units+对齐增量*/
if (avail >= units + delta) { /* room enough? */
slob_t *next;
/*确实进行了对齐操作*/
if (delta) { /* need to fragment head to align? */
next = slob_next(cur);//获取下一个空闲块
/*这里将原本的一个块分裂成了两个块*/
set_slob(aligned, avail - delta, next);//设置空闲对象偏移aligned-->next
set_slob(cur, delta, aligned);//设置空闲对象偏移cur--->aligned
/*调整prev指针以及cur指针,都向后移动一个块*/
prev = cur;
cur = aligned;
avail = slob_units(cur);//重新获取单元数
}
next = slob_next(cur);//获取下一个空闲块
if (avail == units) { /* 空闲块的大小和要求的大小完全相符 */
if (prev)//存在先驱块,则将先驱块的指针指向next块
set_slob(prev, slob_units(prev), next);
else//不存在先驱块说明为第一个块,则将free直接指向next
sp->free = next;
} else { /* 大小不相符,则要将块分裂*/
if (prev)
set_slob(prev, slob_units(prev), cur + units);
else
sp->free = cur + units;
set_slob(cur + units, avail - units, next);
}
sp->units -= units;//减少slob的单元数
if (!sp->units)//单元数为0表明slob没有空闲单元,则从链表中删除
clear_slob_page_free(sp);
return cur;
}
if (slob_last(cur))
return NULL;
}
}
几个辅助计算函数:
static slobidx_t slob_units(slob_t *s)
{
if (s->units > 0)
return s->units;
return 1;
}
static slob_t *slob_next(slob_t *s)
{
slob_t *base = (slob_t *)((unsigned long)s & PAGE_MASK);
slobidx_t next;
if (s[0].units < 0)//s[0].units小于0表明该块是一个小块,其偏移量是用负数表示的
next = -s[0].units;
else //否则为大块,s[0]用来存放size,s[1]用来存放下个空闲块的偏移
next = s[1].units;
return base+next;
}
static void set_slob(slob_t *s, slobidx_t size, slob_t *next)
{
slob_t *base = (slob_t *)((unsigned long)s & PAGE_MASK);
slobidx_t offset = next - base;
if (size > 1) {//块大小大于1个单元,则要设置两个管理单元
s[0].units = size;
s[1].units = offset;
} else//否则只设置下一个空闲块的偏移,且要取相反数
s[0].units = -offset;
}
static int slob_last(slob_t *s)
{
return !((unsigned long)slob_next(s) & ~PAGE_MASK);
}
这里判断一个空闲块是不是slob中的最后一个空闲块的方法就是看s的下一个空闲块地址是不是页对齐的!
再回到slob_alloc()中,当成功分配到一个对象块后,要试图将slob_list链表头转移到刚分配出去的块所处的位置。因为从上面的代码中可以看到,分配一个块有可能会因为对齐的原因产生小的碎片,移动链表头可以使得下次分配对象时从产生碎片的地方开始遍历空闲块,尽可能将碎片给分配出去。
当遍历了整个链表都没能获取对象时,则创建新的slob
static void *slob_new_pages(gfp_t gfp, int order, int node)
{
void *page;
#ifdef CONFIG_NUMA
if (node != -1)
page = alloc_pages_exact_node(node, gfp, order);
else
#endif
page = alloc_pages(gfp, order);
if (!page)
return NULL;
return page_address(page);
}