Linux記憶體管理之slab機制(釋放物件)
阿新 • • 發佈:2019-02-18
Linux核心中將物件釋放到slab中上層所用函式為kfree()或kmem_cache_free()。兩個函式都會呼叫__cache_free()函式。
程式碼執行流程:
1,當本地CPU cache中空閒物件數小於規定上限時,只需將物件放入本地CPU cache中;
2,當local cache中物件過多(大於等於規定上限),需要釋放一批物件到slab三鏈中。由函式cache_flusharray()實現。
1)如果三鏈中存在共享本地cache,那麼首先選擇釋放到共享本地cache中,能釋放多少是多少;
2)如果沒有shared local cache,釋放物件到slab三鏈中,實現函式為 free_block()。對於free_block()函式,當三鏈中的空閒物件數過多時,銷燬此cache。不然,新增此slab到空閒連結串列。因為在分配的時候我們看到將slab結構從cache連結串列中脫離了,在這裡,根據page描述符的lru找到slab並將它新增到三鏈的空閒連結串列中。
主實現
/* * Release an obj back to its cache. If the obj has a constructed state, it must * be in this state _before_ it is released. Called with disabled ints. */ static inline void __cache_free(struct kmem_cache *cachep, void *objp) { /* 獲得本CPU的local cache */ struct array_cache *ac = cpu_cache_get(cachep); check_irq_off(); kmemleak_free_recursive(objp, cachep->flags); objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0)); kmemcheck_slab_free(cachep, objp, obj_size(cachep)); /* * Skip calling cache_free_alien() when the platform is not numa. * This will avoid cache misses that happen while accessing slabp (which * is per page memory reference) to get nodeid. Instead use a global * variable to skip the call, which is mostly likely to be present in * the cache. *//* NUMA相關 */ if (nr_online_nodes > 1 && cache_free_alien(cachep, objp)) return; if (likely(ac->avail < ac->limit)) { /* local cache中的空閒物件數小於上限時 ,只需將物件釋放回entry陣列中 */ STATS_INC_FREEHIT(cachep); ac->entry[ac->avail++] = objp; return; } else { /* 大於等於上限時, */ STATS_INC_FREEMISS(cachep); /* local cache中物件過多,需要釋放一批物件到slab三鏈中。*/ cache_flusharray(cachep, ac); ac->entry[ac->avail++] = objp; } }
釋放物件到三鏈中
/*local cache中物件過多,需要釋放一批物件到slab三鏈中。*/ static void cache_flusharray(struct kmem_cache *cachep, struct array_cache *ac) { int batchcount; struct kmem_list3 *l3; int node = numa_node_id(); /* 每次釋放多少個物件 */ batchcount = ac->batchcount; #if DEBUG BUG_ON(!batchcount || batchcount > ac->avail); #endif check_irq_off(); /* 獲得此cache的slab三鏈 */ l3 = cachep->nodelists[node]; spin_lock(&l3->list_lock); if (l3->shared) { /* 如果存在shared local cache,將物件釋放到其中 */ struct array_cache *shared_array = l3->shared; /* 計算shared local cache中還有多少空位 */ int max = shared_array->limit - shared_array->avail; if (max) { /* 空位數小於要釋放的物件數時,釋放數等於空位數 */ if (batchcount > max) batchcount = max; /* 釋放local cache前面的幾個物件到shared local cache中 ,前面的是最早不用的 */ memcpy(&(shared_array->entry[shared_array->avail]), ac->entry, sizeof(void *) * batchcount); /* 增加shared local cache可用物件數 */ shared_array->avail += batchcount; goto free_done; } } /* 無shared local cache,釋放物件到slab三鏈中 */ free_block(cachep, ac->entry, batchcount, node); free_done: #if STATS { int i = 0; struct list_head *p; p = l3->slabs_free.next; while (p != &(l3->slabs_free)) { struct slab *slabp; slabp = list_entry(p, struct slab, list); BUG_ON(slabp->inuse); i++; p = p->next; } STATS_SET_FREEABLE(cachep, i); } #endif spin_unlock(&l3->list_lock); /* 減少local cache可用物件數*/ ac->avail -= batchcount; /* local cache前面有batchcount個空位,將後面的物件依次前移batchcount位 */ memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail); }
無shared local cache,釋放物件到slab三鏈中
/*
* Caller needs to acquire correct kmem_list's list_lock
*/
/*釋放一定數目的物件*/
static void free_block(struct kmem_cache *cachep, void **objpp, int nr_objects,
int node)
{
int i;
struct kmem_list3 *l3;
/* 逐一釋放物件到slab三鏈中 */
for (i = 0; i < nr_objects; i++) {
void *objp = objpp[i];
struct slab *slabp;
/* 通過虛擬地址得到page,再通過page得到slab */
slabp = virt_to_slab(objp);
/* 獲得slab三鏈 */
l3 = cachep->nodelists[node];
/* 先將物件所在的slab從連結串列中摘除 */
list_del(&slabp->list);
check_spinlock_acquired_node(cachep, node);
check_slabp(cachep, slabp);
/* 將物件釋放到其slab中 */
slab_put_obj(cachep, slabp, objp, node);
STATS_DEC_ACTIVE(cachep);
/* 空閒物件數加一 */
l3->free_objects++;
check_slabp(cachep, slabp);
/* fixup slab chains */
if (slabp->inuse == 0) {
/* 如果slab中均為空閒物件 */
if (l3->free_objects > l3->free_limit) {
/* 如果slab三鏈中空閒物件數超過上限
,直接回收整個slab到記憶體
,空閒物件數減去每個slab中物件數 */
l3->free_objects -= cachep->num;
/* No need to drop any previously held
* lock here, even if we have a off-slab slab
* descriptor it is guaranteed to come from
* a different cache, refer to comments before
* alloc_slabmgmt.
*//* 銷燬struct slab物件 */
slab_destroy(cachep, slabp);
} else {
/* 將此slab新增到空slab連結串列中 */
list_add(&slabp->list, &l3->slabs_free);
}
} else {
/* Unconditionally move a slab to the end of the
* partial list on free - maximum time for the
* other objects to be freed, too.
*//*將此slab新增到部分滿slab連結串列中*/
list_add_tail(&slabp->list, &l3->slabs_partial);
}
}
}
將物件釋放到其slab中
static void slab_put_obj(struct kmem_cache *cachep, struct slab *slabp,
void *objp, int nodeid)
{ /* 獲得物件在kmem_bufctl_t陣列中的索引 */
unsigned int objnr = obj_to_index(cachep, slabp, objp);
#if DEBUG
/* Verify that the slab belongs to the intended node */
WARN_ON(slabp->nodeid != nodeid);
if (slab_bufctl(slabp)[objnr] + 1 <= SLAB_LIMIT + 1) {
printk(KERN_ERR "slab: double free detected in cache "
"'%s', objp %p\n", cachep->name, objp);
BUG();
}
#endif
/*這兩步相當於靜態連結串列的插入操作*/
/* 指向slab中原來的第一個空閒物件 */
slab_bufctl(slabp)[objnr] = slabp->free;
/* 釋放的物件作為第一個空閒物件 */
slabp->free = objnr;
/* 已分配物件數減一 */
slabp->inuse--;
}
輔助函式
/* 通過虛擬地址得到page,再通過page得到slab */
static inline struct slab *virt_to_slab(const void *obj)
{
struct page *page = virt_to_head_page(obj);
return page_get_slab(page);
}
static inline struct slab *page_get_slab(struct page *page)
{
BUG_ON(!PageSlab(page));
return (struct slab *)page->lru.prev;
}
可見,用page->lru.prev得到slab,和建立slab時相呼應。