slab原始碼分析--銷燬函式

FreeeLinux發表於2017-01-16

這次來談一下slab機制中的所有銷燬函式。

釋放物件

注意釋放物件僅僅是回收回slab,並不會將記憶體還給夥伴系統。

回收物件時有以下原則:

  1. 本地快取記憶體的空間還可以容納空閒物件,則直接將物件放回本地快取記憶體。
  2. 本地快取記憶體的空間已滿,則按batchcount的值將物件從本地快取記憶體轉移到本地共享快取shared中,如果沒有設定本地共享快取,那麼就轉移到slab三鏈中。轉移時基於先進先出原則的,也就是轉移entry陣列最前面的batchcount個空閒物件,因為這些物件在陣列中存在的時間相對較長,為“冷資料”,不大可能仍然駐留在CPU快取記憶體中。

釋放物件比如呼叫:

    //底層呼叫__cache_free()函式
    __cache_free(cachep, objp);

那麼釋放物件就正式開始了,首先是__cache_free()函式,在該函式中首先確定ac有沒有超過上限,如果沒有超過上限,那就把該物件放入ac即可。否則,就按照batchcount轉移出ac一大批物件,為這一個物件騰地方(一大批為一個,這樣效率高一些,免得以後再轉移)。

/*
 * 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的本地快取
    struct array_cache *ac = cpu_cache_get(cachep);

    check_irq_off();
    objp = cache_free_debugcheck(cachep, objp, __builtin_return_address(0));

    //NUMA相關,目前版本是空函式
    if (cache_free_alien(cachep, objp))
        return;

    //下面開始選擇釋放位置進行釋放

    //本地快取中的空閒物件小於上限時,只需將物件釋放回entry陣列中
    if (likely(ac->avail < ac->limit)) {
        STATS_INC_FREEHIT(cachep);
        ac->entry[ac->avail++] = objp;
        return;
    } else {
        //這是本地快取空閒物件大於上限的情況,先調整本地快取
        STATS_INC_FREEMISS(cachep);
        cache_flusharray(cachep, ac);
        //不過之後還是要把該物件釋放給本地快取
        ac->entry[ac->avail++] = objp; 
    }
}

再看cache_flusharray()函式:

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();
    //獲得此快取器的三鏈
    l3 = cachep->nodelists[node];
    spin_lock(&l3->list_lock);
    //看是否存在本地共享快取
    if (l3->shared) {
        struct array_cache *shared_array = l3->shared;
        //本地 共享 快取還可承載的最大數目
        int max = shared_array->limit - shared_array->avail;
        if (max) {
            //最大隻能為max
            if (batchcount > max)
                batchcount = max;
            //將本地快取前面的幾個物件轉入本地共享快取中,因為前面的是最早不用的
            memcpy(&(shared_array->entry[shared_array->avail]),
                   ac->entry, sizeof(void *) * batchcount);
            //更新本地共享快取
            shared_array->avail += batchcount;
            goto free_done;
        }
    }

    //沒有配置本地共享快取,只能釋放物件到三鏈中
    //注意此時的 batchcount 就是原始的 batchcount,也就是說可以把達到本地快取一次性轉出 batchcount 的目標
    //而上面的本地共享快取如果使用的話,有可能達不到這個目標,因為它也有 limit
    //不過即便達不到,由於本地共享快取效率比三鏈高,這種情況也不會在到三鏈來,而是直接goto free_done。
    free_block(cachep, ac->entry, batchcount, node);
free_done:
#if STATS
    //...DEBUG
#endif
    spin_unlock(&l3->list_lock);
    //更新本地快取的情況
    ac->avail -= batchcount;
    //把後面的移動到本地快取陣列前面來
    memmove(ac->entry, &(ac->entry[batchcount]), sizeof(void *)*ac->avail);
}

如果沒有本地共享快取,或者本地共享快取達到limit了,那就把該物件回收到三鏈中:

/*
 * 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;

    //逐一釋放物件到三鏈中
    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 */
        //如果slab中全都是空閒物件
        if (slabp->inuse == 0) {
            //如果三鏈中空閒物件數目超過上限,直接回收整個 slab 到記憶體,空閒物件數減去每個slab中物件數
            if (l3->free_objects > l3->free_limit) {
                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.
                 */
                 //銷燬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新增到部分滿的連結串列中
            list_add_tail(&slabp->list, &l3->slabs_partial);
        }
    }
}

整個流程就是這樣,回收優先順序:本地快取>本地共享快取>三鏈。

銷燬slab

銷燬slab就是釋放slab管理區和物件佔用的空間,還給夥伴系統。

**
 * slab_destroy - destroy and release all objects in a slab
 * @cachep: cache pointer being destroyed   
 * @slabp: slab pointer being destroyed      
 *
 * Destroy all the objs in a slab, and release the mem back to the system.
 * Before calling the slab must have been unlinked from the cache.  The
 * cache-lock is not held/needed. 
 */
 //銷燬slab,需要釋放管理物件和slab物件
static void slab_destroy(struct kmem_cache *cachep, struct slab *slabp)
{
    //獲得slab頁面的首地址,是用第一個物件的地址colouroff(對於內建式slab,colouroff已經將slab管理者包括在內了)
    void *addr = slabp->s_mem - slabp->colouroff;
    //debug用
    slab_destroy_objs(cachep, slabp);

    //使用SLAB_DESTROY_BY_RCU來建立的快取記憶體
    if (unlikely(cachep->flags & SLAB_DESTROY_BY_RCU)) {
        //rcu方式釋放,暫時不做分析,主要是做並行優化
        struct slab_rcu *slab_rcu;

        slab_rcu = (struct slab_rcu *)slabp;
        slab_rcu->cachep = cachep;
        slab_rcu->addr = addr;
        //註冊一個回撥來延期釋放slab
        call_rcu(&slab_rcu->head, kmem_rcu_free);
    } else {
        //釋放slab佔用的頁面到夥伴系統中
        //如果是內建式,slab管理者和slab物件在一起,可以同時釋放
        kmem_freepages(cachep, addr);
        if (OFF_SLAB(cachep))
            //外接式,還需釋放slab管理物件
            kmem_cache_free(cachep->slabp_cache, slabp);
    }
}

與夥伴系統互動的函式暫不再本文討論範圍之內。

銷燬快取器

銷燬快取器首先要保證的一點就是當前快取器中所有的物件都是空閒的,也就是之前分配出去的物件都已經釋放回來了,其主要的步驟如下:

  1. 將快取器 kmem_cache 從 cache_chain 連結串列中刪除。
  2. 將本地快取記憶體,align快取記憶體和本地共享快取中的物件都回收到slab三鏈,並釋放所有的free連結串列,然後判斷full連結串列以及partial連結串列是否都為空,如果有一個不為空說明存在非空閒slab,也就是說**還有物件未釋放,此時無法銷燬快取器,必須重新將快取器新增到 cache_chain 連結串列中。
  3. 確定所有的物件都為空閒狀態後,將快取器涉及到的所有描述符都釋放(這些描述符都是儲存在通用快取器中的,如slab管理者)。

負責銷燬快取器的函式為kmem_cache_destroy():

/**
 * kmem_cache_destroy - delete a cache
 * @cachep: the cache to destroy
 *
 * Remove a &struct kmem_cache object from the slab cache.
 *
 * It is expected this function will be called by a module when it is
 * unloaded.  This will remove the cache completely, and avoid a duplicate
 * cache being allocated each time a module is loaded and unloaded, if the
 * module doesn't have persistent in-kernel storage across loads and unloads.
 *
 * The cache must be empty before calling this function.
 *
 * The caller must guarantee that noone will allocate memory from the cache
 * during the kmem_cache_destroy().
 */
 //銷燬一個快取器,通常這隻發生在解除安裝module時
void kmem_cache_destroy(struct kmem_cache *cachep)
{
    BUG_ON(!cachep || in_interrupt());

    /* Find the cache in the chain of caches. */
    mutex_lock(&cache_chain_mutex);
    /*
     * the chain is never empty, cache_cache is never destroyed
     */
     //將快取器從cache_chain的連結串列中摘除
    list_del(&cachep->next);
    if (__cache_shrink(cachep)) {  //釋放空連結串列中的slab,並檢查其他兩個連結串列。在銷燬快取器前,必須先銷燬其中的slab
        //滿slab鏈或部分滿slab鏈不為空
        slab_error(cachep, "Can't free all objects");
        //快取器非空,不能銷燬,重新加入到cache_chain連結串列中
        list_add(&cachep->next, &cache_chain);

        mutex_unlock(&cache_chain_mutex);
        return;
    }

    //有關rcu
    if (unlikely(cachep->flags & SLAB_DESTROY_BY_RCU))
        synchronize_rcu();

    //底層呼叫__kmem_cache_destroy()函式來實現
    __kmem_cache_destroy(cachep);
    mutex_unlock(&cache_chain_mutex);
}

釋放空連結串列的slab由下面這個函式負責:

/* Called with cache_chain_mutex held to protect against cpu hotplug */
//釋放空連結串列中的slab
static int __cache_shrink(struct kmem_cache *cachep)
{
    int ret = 0, i = 0;
    struct kmem_list3 *l3;

    //釋放本地快取中物件
    drain_cpu_caches(cachep);

    check_irq_on();
    for_each_online_node(i) {
        l3 = cachep->nodelists[i];
        if (!l3)
            continue;

        //釋放空連結串列中的slab
        drain_freelist(cachep, l3, l3->free_objects);
        //檢查滿slab連結串列和部分滿slab連結串列是否還有slab
        ret += !list_empty(&l3->slabs_full) ||
            !list_empty(&l3->slabs_partial);
    }
    return (ret ? 1 : 0);
}

其中用到了這幾個函式:

//釋放本地快取和本地共享快取中的物件
static void drain_cpu_caches(struct kmem_cache *cachep)
{
    struct kmem_list3 *l3;
    int node;

    //釋放每個本地快取中的物件,注意沒有 "online"
    on_each_cpu(do_drain, cachep, 1, 1);  //呼叫了do_drain()函式
    check_irq_on();

    //NUMA相關,釋放每個NUMA節點的alien
    for_each_online_node(node) {
        l3 = cachep->nodelists[node];
        if (l3 && l3->alien)
            //本版本目前是空函式,暫不支援
            drain_alien_cache(cachep, l3->alien);
    }

    //釋放本地共享快取中的物件
    for_each_online_node(node) {
        l3 = cachep->nodelists[node];
        if (l3)
            drain_array(cachep, l3, l3->shared, 1, node);
    }
}
//釋放本地快取中的物件
static void do_drain(void *arg)
{
    struct kmem_cache *cachep = arg;
    struct array_cache *ac;
    int node = numa_node_id();
    check_irq_off();

    //獲得本地快取
    ac = cpu_cache_get(cachep);
    spin_lock(&cachep->nodelists[node]->list_lock);

    //釋放本地快取中的物件
    free_block(cachep, ac->entry, ac->avail, node);

    spin_unlock(&cachep->nodelists[node]->list_lock);
    ac->avail = 0;
}
/*
 * Drain an array if it contains any elements taking the l3 lock only if
 * necessary. Note that the l3 listlock also protects the array_cache
 * if drain_array() is used on the shared array.
 */
void drain_array(struct kmem_cache *cachep, struct kmem_list3 *l3,
             struct array_cache *ac, int force, int node)
{
    int tofree;

    if (!ac || !ac->avail)
        return;
    if (ac->touched && !force) {
        ac->touched = 0;
    } else {
        spin_lock_irq(&l3->list_lock);
        if (ac->avail) {
            //計算釋放物件的數目,可見這個函式還支援部分釋放,取決於force的bool屬性
            //從 drain_cpu_caches()進入時,force=1,是要全部釋放的
            tofree = force ? ac->avail : (ac->limit + 4) / 5;
            if (tofree > ac->avail)
                tofree = (ac->avail + 1) / 2;
            //釋放物件,從entry前面開始
            free_block(cachep, ac->entry, tofree, node);
            ac->avail -= tofree;
            //後面的物件前移
            memmove(ac->entry, &(ac->entry[tofree]),
                sizeof(void *) * ac->avail);
        }
        spin_unlock_irq(&l3->list_lock);
    }
}

drain_array()函式是把本地快取和本地共享快取釋放到三鏈中,所以會用到:

/*
 * 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;

    //逐一釋放物件到三鏈中
    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 */
        //如果slab中全都是空閒物件
        if (slabp->inuse == 0) {
            //如果三鏈中空閒物件數目超過上限,直接回收整個 slab 到記憶體,空閒物件數減去每個slab中物件數
            if (l3->free_objects > l3->free_limit) {
                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.
                 */
                 //銷燬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新增到部分滿的連結串列中
            list_add_tail(&slabp->list, &l3->slabs_partial);
        }
    }
}

不管怎麼樣,每次銷燬快取器最終都會釋放其空連結串列中的slab,然後檢測滿連結串列和部分滿連結串列是否還有slab,如果還有,那麼不能銷燬快取器。如果沒有,那麼底層呼叫下面的函式:

/快取器的銷燬很簡單,依次檢查和釋放本地CPU快取,本地共享,三鏈,以及快取器本身。
//該函式通常只發生在解除安裝module(模組)的時候
static void __kmem_cache_destroy(struct kmem_cache *cachep)
{
    int i;
    struct kmem_list3 *l3;

    //釋放每個CPU本地快取,注意此時CPU是 online 線上狀態,如果是down狀態,並沒有釋放。( 對離線無法釋放感到無語:) )
    for_each_online_cpu(i)   // online
        kfree(cachep->array[i]);

    /* NUMA: free the list3 structures */
    for_each_online_node(i) {  //對每個線上的節點
        l3 = cachep->nodelists[i];
        if (l3) {
            //釋放本地共享快取使用的array_cache物件
            kfree(l3->shared);
            free_alien_cache(l3->alien);
            kfree(l3);  //釋放三鏈
        }
    }
    //釋放快取器,因為快取器是屬於 cache_cache 的物件,所以呼叫物件釋放函式,該函式釋放slab之前申請過的某個物件
    kmem_cache_free(&cache_cache, cachep);
}


這樣所有的銷燬工作都已經全部展現,日後我打算分析夥伴系統的記憶體管理機制。因為我覺得我已經分析了slab機制了,如果不往下深入,我可能不會滿足。

嗯,大概花了整整一星期時間。


參考:http://blog.csdn.net/vanbreaker/article/details/7674601

相關文章