slab原始碼分析--setup_cpu_cache函式

FreeeLinux發表於2017-01-16

之前剖析過了 slab 的初始化,以及 kmem_cache_create() 函式,留下了一個 setup_cpu_cache() 函式沒有處理,今天來分析一下。

說明:本文快取器指 kmem_cache 結構,slab 三鏈即 kmem_list3。

setup_cpu_cache() 函式和 slab 分配器的初始化狀態是息息相關的。我們知道,slab 分配器初始化會經歷以下狀態:

g_cpucache_up狀態 含義
NONE AC和三鏈快取器都沒建立好,仍使用靜態替代
PARTIAL_AC 本地快取的arraycache_init結構體快取器構造完畢
PARTIAL_L3 三鏈的kmem_list3結構體快取器構造完畢
FULL 所有grneral cache(通用快取器)構造完畢


首先提一下 arraycache_init 結構體,之前都沒說過。

/*
 * bootstrap: The caches do not work without cpuarrays anymore, but the
 * cpuarrays are allocated from the generic caches...
 */
#define BOOT_CPUCACHE_ENTRIES   1
struct arraycache_init {
    struct array_cache cache;
    void *entries[BOOT_CPUCACHE_ENTRIES];
};

就是上面那樣的,由於 array_cache 結構體末尾是一個柔性陣列,我們需要把該柔性陣列和 array_cache 包裝起來,因為它們組合而成了本地快取。否則單獨的 array_cache 結構體是不會包含 entries 陣列的,這是柔性陣列的特性,它只是一個佔位符。所以,本地快取的快取器真正要快取的物件是 arrarcache_init 結構體。該結構體在初始化前期,採用靜態初始化,如 BOOT_CPUACHE_ENTRIES。

下面來主要談一下初始化過程的步驟,這是在 kmem_cache_init() 函式之中進行的:

(1) 構建好了kmem_cache例項cache_cache(靜態分配),且構建好了kmem_cache的slab分配器,並由initkmem_list3[0]組織, 相應的array為initarray_cache;
(2) 構建好了kmem_cache例項(管理arraycache_init),且構建好了arraycache_init的slab分配器,並由initkmem_list3[1]組織,相應的array為initarray_generic;
(3) 構建好了kmem_cache例項(管理kmem_list3),此時還未構建好kmem_list3的slab分配器,但是一旦申請sizeof(kmem_list3)空間,將構建kmem_list3分配器,並由initkmem_list[2]組織,其array將通過kmalloc進行申請;
(4) 為malloc_sizes的相應陣列元素構建kmem_cache例項,並分配kmem_list3,用於組織slab連結串列,分配arraycache_init用於組織每CPU的同一個kmem_cache下的slab分配;
(5) 替換kmem_cache、malloc_sizes[INDEX_AC].cs_cachep下的arraycache_init例項;
(6) 替換kmem_cache、malloc_sizes[INDEX_AC].cs_cachep、malloc_sizes[INDEX_L3].cs_cachep下的kmem_list3例項;
(7) g_cpucachep_up = EARLY;

問題:

為什麼需要 initarray_cache 和 initarray_generic 兩個靜態 arraycache_init?它們靜態初始化的內容不是一樣的嗎?

因為 initarray_cache 是為 cache_cache 快取器準備的本地快取,而 initarray_generic 是為 arraycache_init 快取器準備的本地快取。雖然靜態初始化一樣,它們最終要被 kmalloc 申請的新內容替換掉,分別作為不同快取器的本地快取。顯然是不能共用的。


下面對 kmem_cache_init() 函式中執行 kmem_cache_create() 函式逐步分析(因為 setup_cpu_cache() 函式就是在後者中呼叫的)。

先宣告:

#define INDEX_AC index_of(sizeof(struct arraycache_init))
#define INDEX_L3 index_of(sizeof(struct kmem_list3))

INDEX_AC 和 INDEX_L3 分別是 arraycache_init 和三鏈的大小,用於在 malloc_sizes[] 表中進行查詢。

首先第一次呼叫:為 arraycache_init 構造快取器

sizes[INDEX_AC].cs_cachep =    kmem_cache_create(names[INDEX_AC].name,
                    sizes[INDEX_AC].cs_size,
                    ARCH_KMALLOC_MINALIGN,
                    ARCH_KMALLOC_FLAGS|SLAB_PANIC,   //#define ARCH_KMALLOC_FLAGS SLAB_HWCACHE_ALIGN,已經對齊過的標記
                    NULL, NULL);   

kmem_cache_create() 函式尾部呼叫 setup_cpu_cache() 進入該分支:

    //如果程式執行到這裡,那就說明當前還在初始化階段
    //g_cpucache_up記錄初始化的進度,比如PARTIAL_AC表示 struct array_cache 的 cache 已經建立
    //PARTIAL_L3 表示struct kmem_list3 所在的 cache 已經建立,注意建立這兩個 cache 的先後順序。在初始化階段只需配置主cpu的local cache和slab三鏈
    //若g_cpucache_up 為 NONE,說明 sizeof(struct array)大小的 cache 還沒有建立,初始化階段建立 sizeof(struct array) 大小的cache 時進入這流程
    //此時 struct arraycache_init 所在的 general cache 還未建立,只能使用靜態分配的全域性變數 initarray_eneric 表示的 local cache
    if (g_cpucache_up == NONE) {
        /*
         * Note: the first kmem_cache_create must create the cache
         * that's used by kmalloc(24), otherwise the creation of
         * further caches will BUG().
         */
        cachep->array[smp_processor_id()] = &initarray_generic.cache; //arraycache_init的快取器還沒有建立,先使用靜態的

        /*
         * If the cache that's used by kmalloc(sizeof(kmem_list3)) is
         * the first cache, then we need to set up all its list3s,
         * otherwise the creation of further caches will BUG().
         */
         //chuangjian struct kmem_list3 所在的cache是在struct array_cache所在cache之後
         //所以此時 struct kmem_list3 所在的 cache 也一定沒有建立,也需要使用全域性變數 initkmem_list3

         //#define SIZE_AC 1,第一次把arraycache_init的快取器和initkmem_list3[1]關聯起來
         //下一次會填充
        set_up_list3s(cachep, SIZE_AC);  

        //執行到這裡struct array_cache所在的 cache 建立完畢,
        //如果struct kmem_list3和struct array_cache 的大小一樣大,那麼就不用再重複建立了,g_cpucache_up表示的進度更進一步
        if (INDEX_AC == INDEX_L3) 
            g_cpucache_up = PARTIAL_L3;  //更新cpu up 狀態
        else
            g_cpucache_up = PARTIAL_AC;
}   

第一次呼叫kmem_cache_create,填充了initkmem_list3[0],該類連結串列上掛載了kmem_cache型別的slab分配器.

kmem_cache_create() 中會第一次呼叫setup_cpu_cache,initkmem_list3[1]將被分配給與arraycache_init匹配的kmem_cache,但是由於arraycache_init的slab分配器(三鏈)還未構建好,因此,在第一次申請sizeof(arraycache_init)空間時,會把arraycache_init的slab 分配器掛入initkmem_list3[1]類的連結串列下.


第二次:為 kmem_list3(三鏈)構造快取器


    if (INDEX_AC != INDEX_L3) {
    //如果struct kmem_list3 和 struct arraycache_init對應的kmalloc size索引不同,即大小屬於不同的級別,
    //則建立struct kmem_list3所用的cache,否則共用一個cache
        sizes[INDEX_L3].cs_cachep =
            kmem_cache_create(names[INDEX_L3].name,
                sizes[INDEX_L3].cs_size,
                ARCH_KMALLOC_MINALIGN,
                ARCH_KMALLOC_FLAGS|SLAB_PANIC,
                NULL, NULL);
    }

setup_cpu_cache() 函式進入該分支:

else {
        //g_cache_up至少為PARTIAL_AC時進入這流程,struct arraycache_init所在的general cache已經建立起來,可以通筸kalloc分配了。
        cachep->array[smp_processor_id()] =
            kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);

        //struct kmem_list3 所在的cache仍未建立完畢,還需使用全域性的slab三鏈
        if (g_cpucache_up == PARTIAL_AC) {
            set_up_list3s(cachep, SIZE_L3);
            g_cpucache_up = PARTIAL_L3;
    }

第二次呼叫kmem_cache_create,填充了initkmem_list3[1],該類連結串列上掛載了 arraycache_init型別的slab分配器.

這已是第二次呼叫kmem_cache_create.在第二次呼叫時,arraycache_init的kmem_cache已初始化,但是arraycache_init的slab分配器(三鏈)還未構建好(相當於都為空),而setup_cpu_cache中將開始通過kmalloc申請sizeof(arraycache_init)空間**,此時將同kmem_cache分配器初始化過程一樣,填充arraycache_init分配器.主要區被在於kmem_cache_create最後呼叫setup_cpu_cache,setup_cpu_cache中將設定g_cpucache_up,以標誌初始化的不同階段.

這時有一句:

slab_early_init = 0;    

此時我們已經做到了:

  1. 構建好了kmem_cache例項cache_cache,且構建好了kmem_cache的slab分配器,並由initkmem_list3[0]組織, 相應的array為initarray_cache.
  2. 構建好了kmem_cache例項(管理arraycache_init),且構建好了arraycache_init的slab分配器,並由initkmem_list3[1]組織,相應的array為initarray_generic.
  3. 構建好了kmem_cache例項(管理kmem_list3),此時還未構建好kmem_list3的slab分配器,但是一旦申請sizeof(kmem_list3)空間,將構建kmem_list3分配器,並由initkmem_list[2]組織,其array將通過kmalloc進行申請. 此時,所有的包括前兩步中的三鏈都是由靜態的 kmem_list3組織,不過已經足以建立其他大小的快取器了。

第三次呼叫:建立其他大小快取快取器
開始為malloc_sizes中的其它空間大小夠將kmem_cache例項.如下將是 第 3 次呼叫seup_cpu_cache,因為arraycache_init和kmem_list3的kmem_cache已構造完成,因此將會通過kmalloc進行申請,而不會再使用靜態的initarray_cache、initarray_generic、initkmem_list3等資料.

    //sizes->cs_size 初值為是malloc_sizes[0],值應該是從32開始
    while (sizes->cs_size != ULONG_MAX) {  //迴圈建立kmalloc各級別的通用快取器,ULONG_MAX 是最大值,
        /*
         * For performance, all the general caches are L1 aligned.
         * This should be particularly beneficial on SMP boxes, as it
         * eliminates(消除) "false sharing".
         * Note for systems short on memory removing the alignment will
         * allow tighter(緊的) packing of the smaller caches.
         */
        if (!sizes->cs_cachep) {   
            sizes->cs_cachep = kmem_cache_create(names->name,
                    sizes->cs_size,
                    ARCH_KMALLOC_MINALIGN,
                    ARCH_KMALLOC_FLAGS|SLAB_PANIC,
                    NULL, NULL);
        }
#ifdef CONFIG_ZONE_DMA   //如果配置DMA,那麼為每個kmem_cache 分配兩個,一個DMA,一個常規
        sizes->cs_dmacachep = kmem_cache_create(
                    names->name_dma,
                    sizes->cs_size,
                    ARCH_KMALLOC_MINALIGN,
                    ARCH_KMALLOC_FLAGS|SLAB_CACHE_DMA|
                        SLAB_PANIC,
                    NULL, NULL);
#endif
        sizes++;   //都是陣列名,直接++,進行迴圈迭代,由小到大分配各個大小的general caches,最大為ULONG_MAX
        names++;
    }

呼叫的setup_cpu_cache() 是這樣的:

    if (g_cpucache_up == NONE) {
        ... 由於不為NONE,走入下一分支
    } else {
        //g_cache_up至少為PARTIAL_AC時進入這流程,struct arraycache_init所在的general cache已經建立起來,可以通筸kalloc分配了。
        cachep->array[smp_processor_id()] =
            kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);

        //struct kmem_list3 所在的cache仍未建立完畢,還需使用全域性的slab三鏈
        if (g_cpucache_up == PARTIAL_AC) {
            set_up_list3s(cachep, SIZE_L3);
            g_cpucache_up = PARTIAL_L3;
        } else { 
        //能進入到這裡說明struct kmem_list3所在的cache和struct array_cache所在的cache都已建立完畢,無需全域性變數
            int node;
            for_each_online_node(node) {
                //通過kmalloc分配struct kmem_list3物件
                cachep->nodelists[node] =
                    kmalloc_node(sizeof(struct kmem_list3),
                        GFP_KERNEL, node);
                BUG_ON(!cachep->nodelists[node]);
                //初始化slab三鏈
                kmem_list3_init(cachep->nodelists[node]);
            }
        }
    }

注意這個函式是在 else 裡面巢狀了 if-else,也就是說,建立其他通用的快取器時,會直接執行 kmalloc 來分配 arraycache_init ,並由於此時 g_cpucache_up 已經為 PARTIAl_l3(因為之前第二步建立了三鏈的快取器),所以它還會 kmalloc 所要建立的快取器對應的三鏈並初始化。

所以,我們在 kmem_cache_init() 函式後期,替換所有的靜態量時,就無需替換這些普通大小的通用快取器的三鏈了。只需替換前兩步所用到的三鏈即可。

kmem_cache_init() 函式中最後的替換如下, 通過 kmalloc 申請記憶體,initarray_ache和initarray_generic, initkmem_list3[3]最終都會被替換掉:

/* 4) Replace the bootstrap head arrays */
    {
        struct array_cache *ptr;

        //現在要申請arraycache替換之前的initarray_cache
        ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);  //GFP_KERNEL 可睡眠申請

        //關中斷
        local_irq_disable();
        BUG_ON(cpu_cache_get(&cache_cache) != &initarray_cache.cache);
        memcpy(ptr, cpu_cache_get(&cache_cache),
               sizeof(struct arraycache_init));  //將cache_cache中per-cpu對應的array_cache拷貝到ptr
        /*
         * Do not assume that spinlocks can be initialized via memcpy:
         */
        spin_lock_init(&ptr->lock);

        cache_cache.array[smp_processor_id()] = ptr;  //再讓它指向ptr?
        local_irq_enable();

        ptr = kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);

        local_irq_disable();
        BUG_ON(cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep)
               != &initarray_generic.cache);
        memcpy(ptr, cpu_cache_get(malloc_sizes[INDEX_AC].cs_cachep),
               sizeof(struct arraycache_init));
        /*
         * Do not assume that spinlocks can be initialized via memcpy:
         */
        spin_lock_init(&ptr->lock);

        malloc_sizes[INDEX_AC].cs_cachep->array[smp_processor_id()] =
            ptr;
        local_irq_enable();
    }
    /* 5) Replace the bootstrap kmem_list3's */
    {
        int nid;

        /* Replace the static kmem_list3 structures for the boot cpu */
        init_list(&cache_cache, &initkmem_list3[CACHE_CACHE], node);

        for_each_online_node(nid) {
            init_list(malloc_sizes[INDEX_AC].cs_cachep,
                  &initkmem_list3[SIZE_AC + nid], nid);

            if (INDEX_AC != INDEX_L3) {
                init_list(malloc_sizes[INDEX_L3].cs_cachep,
                      &initkmem_list3[SIZE_L3 + nid], nid);
            }
        }
    }

    /* 6) resize the head arrays to their final sizes */
    {
        struct kmem_cache *cachep;
        mutex_lock(&cache_chain_mutex);
        list_for_each_entry(cachep, &cache_chain, next)
            if (enable_cpucache(cachep))
                BUG();
        mutex_unlock(&cache_chain_mutex);
    }

    /* Annotate slab for lockdep -- annotate the malloc caches */
    init_lock_keys();


    /* Done! */
    g_cpucache_up = FULL;

以上就是 kmem_cache_init()、kmem_cache_create()、setup_cpu_cache() 三者的對應關係,它們是靠列舉常量來進行識別處理的,初始化不同時期要設定不同的快取。

setup_cpu_cache() 函式全貌如下:

static int __init_refok setup_cpu_cache(struct kmem_cache *cachep)
{
    //此時初始化已經完畢,直接使能local cache
    if (g_cpucache_up == FULL)    
        return enable_cpucache(cachep);

    //如果程式執行到這裡,那就說明當前還在初始化階段
    //g_cpucache_up記錄初始化的進度,比如PARTIAL_AC表示 struct array_cache 的 cache 已經建立
    //PARTIAL_L3 表示struct kmem_list3 所在的 cache 已經建立,注意建立這兩個 cache 的先後順序。在初始化階段只需配置主cpu的local cache和slab三鏈
    //若g_cpucache_up 為 NONE,說明 sizeof(struct array)大小的 cache 還沒有建立,初始化階段建立 sizeof(struct array) 大小的cache 時進入這流程
    //此時 struct arraycache_init 所在的 general cache 還未建立,只能使用靜態分配的全域性變數 initarray_eneric 表示的 local cache
    if (g_cpucache_up == NONE) {
        /*
         * Note: the first kmem_cache_create must create the cache
         * that's used by kmalloc(24), otherwise the creation of
         * further caches will BUG().
         */
        cachep->array[smp_processor_id()] = &initarray_generic.cache; //arraycache_init的快取器還沒有建立,先使用靜態的

        /*
         * If the cache that's used by kmalloc(sizeof(kmem_list3)) is
         * the first cache, then we need to set up all its list3s,
         * otherwise the creation of further caches will BUG().
         */
         //chuangjian struct kmem_list3 所在的cache是在struct array_cache所在cache之後
         //所以此時 struct kmem_list3 所在的 cache 也一定沒有建立,也需要使用全域性變數 initkmem_list3

         //#define SIZE_AC 1,第一次把arraycache_init的快取器和initkmem_list3[1]關聯起來
         //下一次會填充
        set_up_list3s(cachep, SIZE_AC);  

        //執行到這裡struct array_cache所在的 cache 建立完畢,
        //如果struct kmem_list3和struct array_cache 的大小一樣大,那麼就不用再重複建立了,g_cpucache_up表示的進度更進一步
        if (INDEX_AC == INDEX_L3) 
            g_cpucache_up = PARTIAL_L3;  //更新cpu up 狀態
        else
            g_cpucache_up = PARTIAL_AC;
    } else {
        //g_cache_up至少為PARTIAL_AC時進入這流程,struct arraycache_init所在的general cache已經建立起來,可以通筸kalloc分配了。
        cachep->array[smp_processor_id()] =
            kmalloc(sizeof(struct arraycache_init), GFP_KERNEL);

        //struct kmem_list3 所在的cache仍未建立完畢,還需使用全域性的slab三鏈
        if (g_cpucache_up == PARTIAL_AC) {
            set_up_list3s(cachep, SIZE_L3);
            g_cpucache_up = PARTIAL_L3;
        } else { 
        //能進入到這裡說明struct kmem_list3所在的cache和struct array_cache所在的cache都已建立完畢,無需全域性變數
            int node;
            for_each_online_node(node) {
                //通過kmalloc分配struct kmem_list3物件
                cachep->nodelists[node] =
                    kmalloc_node(sizeof(struct kmem_list3),
                        GFP_KERNEL, node);
                BUG_ON(!cachep->nodelists[node]);
                //初始化slab三鏈
                kmem_list3_init(cachep->nodelists[node]);
            }
        }
    }
    //FIXME: 計算回收時間
    cachep->nodelists[numa_node_id()]->next_reap =
            jiffies + REAPTIMEOUT_LIST3 +
            ((unsigned long)cachep) % REAPTIMEOUT_LIST3;

    //初始化ac的一些變數
    cpu_cache_get(cachep)->avail = 0;
    cpu_cache_get(cachep)->limit = BOOT_CPUCACHE_ENTRIES;
    cpu_cache_get(cachep)->batchcount = 1;
    cpu_cache_get(cachep)->touched = 0;
    cachep->batchcount = 1;
    cachep->limit = BOOT_CPUCACHE_ENTRIES;
    return 0;
}

注意在 g_cpuup_cache = FULL 時會呼叫這個函式 enable_cpucache() 。
該函式在 kmem_cache_init() 函式中也會呼叫,不過是直接呼叫如下:

    /* 6) resize the head arrays to their final sizes */
    {
        struct kmem_cache *cachep;
        mutex_lock(&cache_chain_mutex);
        list_for_each_entry(cachep, &cache_chain, next)
            if (enable_cpucache(cachep))
                BUG();
        mutex_unlock(&cache_chain_mutex);
    }
    ...

    /* Done! */
    g_cpucache_up = FULL;
    ...
}

呼叫完畢還才設定 g_cpucache_up 為 FULL,並且設定完了 kmem_cache_init() 函式中已經不會再呼叫 kmem_cache_create() 了,也就是它只會顯示呼叫 enable_cpucache() 一次。由於使用 list_for_each_entyr(cachep, &cache_chain, next),可知它是在遍歷 cache_chain 連結串列,那麼它是做什麼呢?其實是在遍歷每一個快取器,並初始化每一個快取器的本地快取,本地共享快取,三鏈。比如利用被快取的物件確定本地快取 limit,先前我們預設 limit 都是 1, 現在就可以計算出來了,確定本地快取陣列的大小,然後重新 kmalloc 為其分配相應空間並替換。還有本地共享快取的設定,有了本地共享快取,還要修改三鏈的一些值。還有一些特點,比如某個 CPU 重啟,需要申請新的本地快取更新舊的本地快取,都是在下面這一大堆函式中做的。

/* Called with cache_chain_mutex held always */
static int enable_cpucache(struct kmem_cache *cachep)
{
    int err;
    int limit, shared;

    /*
     * The head array serves three purposes:
     * - create a LIFO ordering, i.e. return objects that are cache-warm
     * - reduce the number of spinlock operations.
     * - reduce the number of linked list operations on the slab and
     *   bufctl chains: array operations are cheaper.
     * The numbers are guessed, we should auto-tune as described by
     * Bonwick.
     */
     //根據每個快取器分配 物件 的大小計算  本地快取!!!  中的物件數目上限
    if (cachep->buffer_size > 131072)
        limit = 1;
    else if (cachep->buffer_size > PAGE_SIZE)
        limit = 8;
    else if (cachep->buffer_size > 1024)
        limit = 24;
    else if (cachep->buffer_size > 256)
        limit = 54;
    else
        limit = 120;

    /*
     * CPU bound(有義務的) tasks (e.g. network routing) can exhibit(展覽,展示) cpu bound
     * allocation behaviour: Most allocs on one cpu, most free operations    //大多數情況在本CPU申請快取,在其他CPU釋放快取。(正解)
     * on another cpu. For these cases, an efficient object passing between
     * cpus is necessary. This is provided by a shared array. The array
     * replaces Bonwick's magazine layer.
     * On uniprocessor(單程式), it's functionally equivalent(相等的) (but less efficient)
     * to a larger limit. Thus disabled by default.   //單處理器預設是關閉的
    */
    shared = 0;
    //多核系統,設定本地共享快取中物件數目
    if (cachep->buffer_size <= PAGE_SIZE && num_possible_cpus() > 1)
        shared = 8;   //設定為8

#if DEBUG
    /*
     * With debugging enabled, large batchcount lead to excessively long
     * periods with disabled local interrupts. Limit the batchcount
     */
    if (limit > 32)
        limit = 32;
#endif
    //配置本地快取
    err = do_tune_cpucache(cachep, limit, (limit + 1) / 2, shared);
    if (err)
        printk(KERN_ERR "enable_cpucache failed for %s, error %d.\n",
               cachep->name, -err);
    return err;
}

該函式首先要根據每個快取器快取物件的大小來計算本地快取的物件數目上限,本地共享快取也一樣。然後它會配置本地快取,本地共享快取,和三鏈,先看一個資料結構:

struct ccupdate_struct {
    struct kmem_cache *cachep;
    struct array_cache *new[NR_CPUS];
};

再看實際的函式:

/* Always called with the cache_chain_mutex held */
//配置本地快取、本地共享快取和三鏈
static int do_tune_cpucache(struct kmem_cache *cachep, int limit,
                int batchcount, int shared)
{
    struct ccupdate_struct *new;
    int i;

    //申請分配一個 ccupdate_struct 並清零,注意這裡 g_cpucache_up == FULL 才到這裡來的,所以可以用 kmalloc
    new = kzalloc(sizeof(*new), GFP_KERNEL);
    if (!new)
        return -ENOMEM;

    //為每個CPU分配新的array_cache物件
    for_each_online_cpu(i) {
        new->new[i] = alloc_arraycache(cpu_to_node(i), limit,
                        batchcount);
        if (!new->new[i]) {  //如果失敗
            for (i--; i >= 0; i--)  //commit and rollback
                kfree(new->new[i]);
            kfree(new);
            return -ENOMEM;
        }
    }
    new->cachep = cachep;   //用 new 把舊的快取器作為自己的成員,在on_each_cpu() 函式中方便更新舊的快取器的本地快取

    //用新的array_cache物件替換舊的array_cache物件,在支援CPU熱插拔的系統上,離線CPU可能沒有釋放本地快取,使用的仍是舊本地快取
    //參見__kmem_cache_destroy()函式。雖然cpu up 時要重新配置本地快取,也無濟於事。
    //考慮下面的情景; 共有CPUA 和 CPUB,CPUB down後,destroy Cache X,由於此時CPUB 是down狀態,
    //所以Cache X中的 CPUB 的本地快取未釋放,過一段時間後CPUB又啟動了,更新 cache_chain 鏈中所有cache的本地快取
    //但此時Cache X物件已經釋放回 cache_cache中了,其CPUB 的本地快取並未更新。又過了一段時間,系統需要建立新的cache,
    //將 Cache X物件分配出去,其CPUB 仍然是舊的本地快取,需要進行更新
    on_each_cpu(do_ccupdate_local, (void *)new, 1, 1);  //呼叫了do_ccpudate_local函式,用新的替換舊的

    check_irq_on();
    cachep->batchcount = batchcount;
    cachep->limit = limit;
    cachep->shared = shared;

    for_each_online_cpu(i) {
        struct array_cache *ccold = new->new[i];
        if (!ccold)
            continue;
        spin_lock_irq(&cachep->nodelists[cpu_to_node(i)]->list_lock);
        //釋放舊的本地快取中的  物件  
        free_block(cachep, ccold->entry, ccold->avail, cpu_to_node(i));
        spin_unlock_irq(&cachep->nodelists[cpu_to_node(i)]->list_lock);
        //釋放舊的array_cache
        kfree(ccold);
    }
    kfree(new);
    //初始化本地 共享 快取和三鏈
    return alloc_kmemlist(cachep);
}

do_ccipdate_local() 函式如下:

//更新每個CPU的array_cache物件
static void do_ccupdate_local(void *info)
{
    struct ccupdate_struct *new = info;  //額,和libevent一樣的強制轉換,換C++肯定報錯了:)
    struct array_cache *old;

    check_irq_off();
    //獲得舊的本地快取
    old = cpu_cache_get(new->cachep);

    //指向新的 array_cache 物件,new 是之前分配的本地快取的引用
    new->cachep->array[smp_processor_id()] = new->new[smp_processor_id()];
    //儲存舊的 array_cache 物件
    new->new[smp_processor_id()] = old;
}

alloc_kmemlist() 函式如下:

/*
 * This initializes kmem_list3 or resizes varioius caches for all nodes.
 */
 //初始化本地 共享 快取和三鏈,初始化不會為三鏈分配slab
static int alloc_kmemlist(struct kmem_cache *cachep)
{
    int node;
    struct kmem_list3 *l3;
    struct array_cache *new_shared;
    struct array_cache **new_alien = NULL;

    for_each_online_node(node) {
        //NUMA相關
                if (use_alien_caches) {
                        new_alien = alloc_alien_cache(node, cachep->limit);
                        if (!new_alien)
                                goto fail;
                }

        new_shared = NULL;
        if (cachep->shared) {
            //如果支援shared,就分配本地共享快取
            new_shared = alloc_arraycache(node,
                cachep->shared*cachep->batchcount,
                    0xbaadf00d);   //batchcount這麼大,3131961357
            if (!new_shared) {
                free_alien_cache(new_alien);
                goto fail;
            }
        }

        //獲得舊的三鏈
        l3 = cachep->nodelists[node];
        if (l3) {  //舊三鏈指標不為空,需要先釋放舊的資源
            struct array_cache *shared = l3->shared;

            spin_lock_irq(&l3->list_lock);

            if (shared)  //釋放舊的本地共享快取
                free_block(cachep, shared->entry,
                        shared->avail, node);

            //指向新的本地共享快取
            l3->shared = new_shared;
            if (!l3->alien) {
                l3->alien = new_alien;
                new_alien = NULL;
            }
            //計算快取器中空閒物件的上限
            l3->free_limit = (1 + nr_cpus_node(node)) *
                    cachep->batchcount + cachep->num;
            spin_unlock_irq(&l3->list_lock);
            //釋放舊的本地共享快取和本地快取
            kfree(shared);
            free_alien_cache(new_alien);
            continue;
        }
        //如果沒有舊的三鏈,那就要分配一個新的三鏈
        l3 = kmalloc_node(sizeof(struct kmem_list3), GFP_KERNEL, node);
        if (!l3) {
            free_alien_cache(new_alien);
            kfree(new_shared);
            goto fail;
        }

        //初始化三鏈
        kmem_list3_init(l3);
        l3->next_reap = jiffies + REAPTIMEOUT_LIST3 +
                ((unsigned long)cachep) % REAPTIMEOUT_LIST3;
        l3->shared = new_shared;
        l3->alien = new_alien;
        l3->free_limit = (1 + nr_cpus_node(node)) *
                    cachep->batchcount + cachep->num;
        cachep->nodelists[node] = l3;
    }
    return 0;

fail:
    if (!cachep->next.next) {
        /* Cache is not active yet. Roll back what we did */
        node--;
        while (node >= 0) {
            if (cachep->nodelists[node]) {
                l3 = cachep->nodelists[node];

                kfree(l3->shared);
                free_alien_cache(l3->alien);
                kfree(l3);
                cachep->nodelists[node] = NULL;
            }
            node--;
        }
    }
    return -ENOMEM;
}

相關文章