六:kmem_cache_alloc 的实现分析:
我们在上面可以看到,创建一个cache 描述符的时候,并没有这之分配slab 数据。现在我们来看一下怎么从cache 中申请对象
void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)
{
return __cache_alloc(cachep, flags);
}
实际上会调用__cache_alloc
如下:
static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)
{
unsigned long save_flags;
void* objp;
struct array_cache *ac;
// 如果定义了__GFP_WAIT 。可能会引起睡眠
cache_alloc_debugcheck_before(cachep, flags);
local_irq_save(save_flags);
// 取得当前处理器所在的array_cache( 简称为AC ,我们下面也这样称呼它)
ac = ac_data(cachep);
//ac->avail:AC 中第后一个可用的对象索引
// 如果AC 中还有可用的对象
//在 kmem_cache_create时,ac->avail被赋值为0,所以第一次分配时,会重新填充。
if (likely(ac->avail)) {
STATS_INC_ALLOCHIT(cachep);
// 每次分配都会把ac->touched 置为1
ac->touched = 1;
objp = ac_entry(ac)[--ac->avail];
} else {
// 如果AC 中没有可用对象,那只能从l3 中“搬出”对象到AC 中
STATS_INC_ALLOCMISS(cachep);
objp = cache_alloc_refill(cachep, flags);
}
local_irq_restore(save_flags);
objp = cache_alloc_debugcheck_after(cachep, flags, objp, __builtin_return_address(0));
return objp;
}
首先,会从AC 中分配对象,如果AC 中无可用对象,那就从l3 链表中分配对象了,首先它会从share 链表中取对象,然后再从末满,空链表中取对象,如果都没有空闲对象的话,只能从伙伴系统中分配内存了. 接着看下面的代码:
static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)
{
int batchcount;
struct kmem_list3 *l3;
struct array_cache *ac;
check_irq_off();
ac = ac_data(cachep);
retry:
//batchcount: 一次向AC 填充的对象值
batchcount = ac->batchcount;
if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
batchcount = BATCHREFILL_LIMIT;
}
// 取得cache 所对象的l3
l3 = list3_data(cachep);
// 如果Ac 中依然有可用对象,则退出
BUG_ON(ac->avail > 0);
spin_lock(&cachep->spinlock);
// 首先会从shared 中取对象
if (l3->shared) {
struct array_cache *shared_array = l3->shared;
if (shared_array->avail) {
如果share 的剩余量不足batchcount 。则把它全部都移至AC 中
if (batchcount > shared_array->avail)
batchcount = shared_array->avail;
shared_array->avail -= batchcount;
ac->avail = batchcount;
// 把share 链中的object 拷贝到AC 中
memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],
sizeof(void*)*batchcount);
shared_array->touched = 1;
//AC 中已经有数据了,那么,可以直接从AC 中分配了
goto alloc_done;
}
}
// 运行到这里的话,那说明share 链中没有对象了
while (batchcount > 0) {
// 先从末满的链表中获取,若末满链为空的话,从全空链表中获取
struct list_head *entry;
struct slab *slabp;
/* Get slab alloc is to come from. */
entry = l3->slabs_partial.next;
// 判断slabs_partial 是否为空
if (entry == &l3->slabs_partial) {
l3->free_touched = 1;
// 判断slabs_free 链是否为空
entry = l3->slabs_free.next;
if (entry == &l3->slabs_free)
// 若全为空的话,就从伙伴系统中分配页面了
goto must_grow;
}
// 从链表中取得slab 描述符
slabp = list_entry(entry, struct slab, list);
check_slabp(cachep, slabp);
check_spinlock_acquired(cachep);
// 对象取尽,或者已经满尽分配要求
// slabp->inuse: slab 中的使用对象个数
// cachep->num :每个slab中对象的数目
while (slabp->inuse < cachep->num && batchcount--) {
// 从相应的slab 中分配对象
kmem_bufctl_t next;
STATS_INC_ALLOCED(cachep);
STATS_INC_ACTIVE(cachep);
STATS_SET_HIGH(cachep);
// 得到空闲对象指针
ac_entry(ac)[ac->avail++] = slabp->s_mem + slabp->free*cachep->objsize;
// 更新计数
slabp->inuse++;
next = slab_bufctl(slabp)[slabp->free];
#if DEBUG
slab_bufctl(slabp)[slabp->free] = BUFCTL_FREE;
#endif
// 使free 指向下一人空闲对像的索引
slabp->free = next;
}
check_slabp(cachep, slabp);
/* move slabp to correct slabp list: */
//slab 从链中脱落
list_del(&slabp->list);
if (slabp->free == BUFCTL_END)
// 如果slab 中没有空闲对象了,则把它加入slabs_full 链
list_add(&slabp->list, &l3->slabs_full);
else
// 如果slab 中没有空闲对象了,则把它加入slabs_partial 链
list_add(&slabp->list, &l3->slabs_partial);
}
must_grow:
// 更新free_objects 计数.( 如果三链都为空的情况下:ac->avail 为进入函数的初始值,即为0)
l3->free_objects -= ac->avail;
alloc_done:
spin_unlock(&cachep->spinlock);
if (unlikely(!ac->avail)) {
int x;
x = cache_grow(cachep, flags);
// cache_grow can reenable interrupts, then ac could change.
ac = ac_data(cachep);
// 如果grow 失败,返回NULL
if (!x && ac->avail == 0) // no objects in sight? abort
return NULL;
// 如果grow 成功,则重复上述操作,即从三链表中取空闲对象^_^
if (!ac->avail) // objects refilled by interrupt?
goto retry;
}
ac->touched = 1;
return ac_entry(ac)[--ac->avail];
}
这段代码涉及到slab_bufctl (),等我们看完分配,释放的全过程后。再来详细分析它涉及到的各项操作,cache_grow() 用来做slab 分配器与slab 的交互。它的代码如下示:
static int cache_grow (kmem_cache_t * cachep, int flags)
{
struct slab *slabp;
void *objp;
size_t offset;
int local_flags;
unsigned long ctor_flags;
if (flags & ~(SLAB_DMA|SLAB_LEVEL_MASK|SLAB_NO_GROW))
BUG();
if (flags & SLAB_NO_GROW)
return 0;
ctor_flags = SLAB_CTOR_CONSTRUCTOR;
local_flags = (flags & SLAB_LEVEL_MASK);
if (!(local_flags & __GFP_WAIT))
/*
* Not allowed to sleep. Need to tell a constructor about
* this - it might need to know...
*/
ctor_flags |= SLAB_CTOR_ATOMIC;
/* About to mess with non-constant members - lock. */
check_irq_off();
spin_lock(&cachep->spinlock);
// 取得下一个偏移索引(着色机制在前面已经详细分析了)
offset = cachep->colour_next;
cachep->colour_next++;
// 如果大于允许的最大颜色,那就把计数归位,即为0
if (cachep->colour_next >= cachep->colour)
cachep->colour_next = 0;
// 计算偏移量
offset *= cachep->colour_off;
spin_unlock(&cachep->spinlock);
if (local_flags & __GFP_WAIT)
local_irq_enable();
kmem_flagcheck(cachep, flags);
// 向伙伴系统申请内存
if (!(objp = kmem_getpages(cachep, flags, -1)))
goto failed;
// 分配slab 描述符,这里有两种情况,一种是slab 在缓存外部,另一种是内部
if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))
goto opps1;
set_slab_attr(cachep, slabp, objp);
// 初始化slab 的对像
cache_init_objs(cachep, slabp, ctor_flags);
if (local_flags & __GFP_WAIT)
local_irq_disable();
check_irq_off();
spin_lock(&cachep->spinlock);
// 将新构建的slab 加至slabs_free 链
list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));
STATS_INC_GROWN(cachep);
// 更新计数
list3_data(cachep)->free_objects += cachep->num;
spin_unlock(&cachep->spinlock);
return 1;
opps1:
// 发生了错误,把内存归还伙伴系统
kmem_freepages(cachep, objp);
failed:
if (local_flags & __GFP_WAIT)
local_irq_disable();
return 0;
}
我们看到了cache_grow 的概貌,接着分析它里面调用的子函数。
kmem_getpages ()用于slab 分配器向伙伴系统分配内存,代码如下:
//nodeid: 分配内面的cpu 结点。如果从当前CPU 分存,nodeid 置为-1
static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)
{
struct page *page;
void *addr;
int i;
flags |= cachep->gfpflags;
//__get_free_pages 与alloc_pages_node 在《linux 内存管理之伙伴系统分析》一文中已有详
// 细分析,请参考
if (likely(nodeid == -1)) {
// 从当前cpu 结点分配内存
addr = (void*)__get_free_pages(flags, cachep->gfporder);
if (!addr)
return NULL;
// 将地址转换为页描述符
page = virt_to_page(addr);
} else {
// 从指定结点分配内存
page = alloc_pages_node(nodeid, flags, cachep->gfporder);
if (!page)
return NULL;
addr = page_address(page);
}
// 计算页面个数。即为2^ cachep->gfporder
i = (1 << cachep->gfporder);
if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
atomic_add(i, &slab_reclaim_pages);
// 更新cpu nr_slab 状态计数
add_page_state(nr_slab, i);
while (i--) {
// 将页面标识为PG_slab, 表示该页面已被slab 使用
SetPageSlab(page);
page++;
}
return addr;
}
alloc_slabmgmt() 是一个slab 描述符分配器接口,代码如下:
static struct slab* alloc_slabmgmt (kmem_cache_t *cachep,
void *objp, int colour_off, int local_flags)
{
struct slab *slabp;
// 如果slab 描述符是外置的
if (OFF_SLAB(cachep)) {
// 从对应的cache 中分配slab 描述符
slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);
if (!slabp)
return NULL;
} else {
// 从偏移量后开始安置slab
slabp = objp+colour_off;
// 更新偏移量,即加上slab 的大小,这位置也是有效数据的起始偏移位置
colour_off += cachep->slab_size;
}
slabp->inuse = 0;
slabp->colouroff = colour_off;
slabp->s_mem = objp+colour_off;
return slabp;
}
cache_init_objs ()初始化分配得到的每一个对象,代码如下:
static void cache_init_objs (kmem_cache_t * cachep,
struct slab * slabp, unsigned long ctor_flags)
{
int i;
for (i = 0; i < cachep->num; i++) {
// 取slab 中的每一个对像
void* objp = slabp->s_mem+cachep->objsize*i;
#if DEBUG
// 忽略掉debug 信息
/* need to poison the objs? */
if (cachep->flags & SLAB_POISON)
poison_obj(cachep, objp, POISON_FREE);
if (cachep->flags & SLAB_STORE_USER)
*dbg_userword(cachep, objp) = NULL;
if (cachep->flags & SLAB_RED_ZONE) {
*dbg_redzone1(cachep, objp) = RED_INACTIVE;
*dbg_redzone2(cachep, objp) = RED_INACTIVE;
}
/*
* Constructors are not allowed to allocate memory from
* the same cache which they are a constructor for.
* Otherwise, deadlock. They must also be threaded.
*/
if (cachep->ctor && !(cachep->flags & SLAB_POISON))
cachep->ctor(objp+obj_dbghead(cachep), cachep, ctor_flags);
if (cachep->flags & SLAB_RED_ZONE) {
if (*dbg_redzone2(cachep, objp) != RED_INACTIVE)
slab_error(cachep, "constructor overwrote the"
" end of an object");
if (*dbg_redzone1(cachep, objp) != RED_INACTIVE)
slab_error(cachep, "constructor overwrote the"
" start of an object");
}
if ((cachep->objsize % PAGE_SIZE) == 0 && OFF_SLAB(cachep) && cachep->flags & SLAB_POISON)
kernel_map_pages(virt_to_page(objp), cachep->objsize/PAGE_SIZE, 0);
#else
// 如果有初始化函数,则调用之
if (cachep->ctor)
cachep->ctor(objp, cachep, ctor_flags);
#endif
// 更新bufctl 数组
slab_bufctl(slabp)[i] = i+1;
}
// 置末尾描述符
slab_bufctl(slabp)[i-1] = BUFCTL_END;
slabp->free = 0;
}
同样,slab_bufctl 的分析,等讲完释放对像的时候再继续
到此,我们已经看完到分配对象的全过程,接着来看怎么释放一个对象