slab原始碼分析--快取器的建立

FreeeLinux發表於2017-01-15

本文維護地址:http://blog.csdn.net/FreeeLinux/article/details/54511076

這篇部落格來剖析一下 kmem_cache_create() 函式,就是所謂合成快取器函式。

這個函式通常是在核心初始化時進行的,或者在首次載入模組時進行的。

struct kmem_cache *kmem_cache_create(const char* name, size_t size, size_t align, unsigned long flags, 
    void (*ctor)(void*, struct kmem_cache *, unsigned long), void (*dtor)(void *, struct kmem_cache *, unsigned long));

name 引數定義了快取器的名稱,proc 檔案系統(可以 cat /proc/slabinfo 檢視)使用它標識這個快取。size 引數指定了這個快取器負責分配的物件大小。align 引數定義了每個物件必須的對齊。flags 引數指定了為快取啟用的選項。

flags 的標誌如圖:

選項 說明
RED_ZONE 在物件頭、尾插入標誌,用來支援對緩衝區溢位的檢查
SLAB_POISON 使用一種已知模式(如0xa5a5a5a5)填充 slab,允許對快取中的物件進行監視,不過可以在外部進行修改)
SLAB_HWCACHE_ALIGN 指定快取物件必須與硬體快取行對齊


ctor 和 dtor 是建構函式和解構函式,這不用說。

在建立快取器之後,kmem_cache_create() 函式會返回對它的引用。注意這個函式並沒有向快取器提供任何用來分配物件記憶體。相反,在試圖從快取器(最初為空)分配物件時,會通過 cache_alloc_refill() 函式向夥伴系統申請記憶體。當所有物件都被分配出去後,可以再次這樣做。

首先給出該函式的呼叫機制流程圖:

Created with Raphaël 2.1.0kmem_cache_create()函式:建立快取器kmem_cache_zalloc()函式:申請快取器快取__cache_alloc()函式:kmem_cache_alloc和kmalloc申請記憶體的總介面__do_cache_alloc()函式:轉調函式____cache_alloc()函式:申請快取的核心函式 ac=cpu_cache_get()函式,獲得本地快取if(av->avail),本地快取足夠取ac末尾最熱資料,objp=ac->entry[--ac->avail]快取器快取申請成功cache_alloc_refill(),重新填充if(ls->shared),本地共享快取足夠transfer_objects()函式,本地共享快取轉給本地快取成功向ac中轉移了至少一個可用物件retry:在三鏈中搜尋可分配物件三鏈中有空閒物件cache_grow()函式,從夥伴系統獲取kmem_getpages()和alloc_slabmgmt()函式,並執行retryyesnoyesnoyesno

下面來看 kmem_cache_create()函式的實現:

/**
 * kmem_cache_create - Create a cache.
 * @name: A string which is used in /proc/slabinfo to identify this cache.
 * @size: The size of objects to be created in this cache.
 * @align: The required(必須的的) alignment for the objects.  //
 * @flags: SLAB flags
 * @ctor: A constructor for the objects.
 * @dtor: A destructor for the objects (not implemented anymore).
 *
 * Returns a ptr to the cache on success, NULL on failure.  //成功返回cache指標,失敗返回空
 * Cannot be called within a int, but can be interrupted.   //不能在中斷中呼叫,但是可以被打斷
 * The @ctor is run when new pages are allocated by the cache
 * and the @dtor is run before the pages are handed back.
 *
 * @name must be valid until the cache is destroyed. This implies that
 * the module calling this has to destroy the cache before getting unloaded.
 *
 * The flags are  //填充標記
 * 
 * %SLAB_POISON - Poison(使汙染) the slab with a known test pattern (a5a5a5a5)  //使用a5a5a5a5填充這片未初始化區域 
 * to catch references to uninitialised memory.
 *
 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check  //新增紅色警戒區,檢測越界
 * for buffer overruns.
 *
 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware    //物理快取行對齊,
 * cacheline.  This can be beneficial if you're counting cycles as closely
 * as davem.
 */
 //建立快取器
 /* gfporder: 取值0~11遍歷直到計算出cache的物件數量跳出迴圈,slab由2^gfporder個頁面組成
   buffer_size: 為當前cache中物件經過cache_line_size對齊後的大小
   align: 是cache_line_size,按照該大小對齊
   flags: 此處為0,用於標識內建slab還是外接slab
   left_over: 輸出值,記錄slab中浪費空間的大小
   num:輸出值,用於記錄當前cache中允許存在的物件數目
 */
struct kmem_cache *
kmem_cache_create (const char *name, size_t size, size_t align,
    unsigned long flags,
    void (*ctor)(void*, struct kmem_cache *, unsigned long),
    void (*dtor)(void*, struct kmem_cache *, unsigned long))
{
    size_t left_over, slab_size, ralign;
    struct kmem_cache *cachep = NULL, *pc;

    /*
     * Sanity checks... these are all serious usage bugs.
     */
     //引數檢查,名字不能為NULL,不能在中斷中呼叫本函式(本函式可能會睡眠)
     //獲取長度不得小於4位元組,即CPU字長,   獲取長度不得大於最大值(我剖析的這個版本是2^25,有的可能是2^22)
    if (!name || in_interrupt() || (size < BYTES_PER_WORD) ||
        size > KMALLOC_MAX_SIZE || dtor) {
        printk(KERN_ERR "%s: Early error in slab %s\n", __FUNCTION__,
                name);
        BUG();
    }

    /*
     * We use cache_chain_mutex to ensure a consistent view of
     * cpu_online_map as well.  Please see cpuup_callback
     */
    mutex_lock(&cache_chain_mutex);

#if 0    //DEBUG 部分被我註釋掉了,免得擋點      //一些檢查機制,無需關注
    ...
#endif   

    /*
     * Check that size is in terms of(依據) words.  This is needed to avoid
     * unaligned accesses for some archs(拱) when redzoning is used, and makes  //避免當紅色警戒區被使用時,避免未對齊的訪問接觸紅區
     * sure any on-slab bufctl's are also correctly aligned.   //同時確保任何on-slab的bfclt 正確對齊
     */ 

     ////
    //  為什麼kmem_cache_init函式已經計算過size,align了,這裡還要計算?
    //     因為這裡是用來建立快取器的,只是借用了cache_cache,而 kmem_cache_init函式中初始化的是cache_cahce的
    //     size,align等成員,所以無關係。
    ////

    //先檢查   物件 !!!!       是不是32位對齊,如果不是則進行調整
    if (size & (BYTES_PER_WORD - 1)) {    
        size += (BYTES_PER_WORD - 1);
        size &= ~(BYTES_PER_WORD - 1);                                  
    }

    /* calculate the final buffer alignment: */
    /* 1) arch recommendation: can be overridden for debug */
    //再檢查  物件!!!      要不要求按照緩衝行對齊 
    if (flags & SLAB_HWCACHE_ALIGN) {
        /*
         * Default alignment: as specified by the arch code.  Except if
         * an object is really small, then squeeze multiple objects into
         * one cacheline.
         */
        ralign = cache_line_size();
        while (size <= ralign / 2)    //進行對齊大小的調整,我們要保證物件的大小大於  針對硬體緩衝行對齊所需的大小
            ralign /= 2;
    } else {  //不需要按硬體緩衝行對齊,那就預設4位元組,即32位
        ralign = BYTES_PER_WORD;
    }

    /*
     * Redzoning and user store require word alignment or possibly larger.
     * Note this will be overridden by architecture or caller mandated
     * alignment if either is greater than BYTES_PER_WORD.
     */
     //如果開啟了DEBUG,則按需要進行相應的對齊
    if (flags & SLAB_STORE_USER)
        ralign = BYTES_PER_WORD;

    if (flags & SLAB_RED_ZONE) {
        ralign = REDZONE_ALIGN;
        /* If redzoning, ensure that the second redzone is suitably
         * aligned, by adjusting the object size accordingly. */
        size += REDZONE_ALIGN - 1;
        size &= ~(REDZONE_ALIGN - 1);
    }

    /* 2) arch mandated alignment */
    if (ralign < ARCH_SLAB_MINALIGN) {
        ralign = ARCH_SLAB_MINALIGN;
    }
    /* 3) caller mandated alignment */
    if (ralign < align) {
        ralign = align;
    }
    /* disable debug if necessary */
    if (ralign > __alignof__(unsigned long long))
        flags &= ~(SLAB_RED_ZONE | SLAB_STORE_USER);
    /*
     * 4) Store it.
     */
    align = ralign;   //通過上面一大堆計算,算出了align值

    /* Get cache's description obj. */
    //按照cache_cache的大小分配一個kmem_cache新例項,實際上cache_cache在核心初始化完成後就是kmem_cache了,為了核心初始化時可使用kmalloc,所以這裡要用cache_cache
    cachep = kmem_cache_zalloc(&cache_cache, GFP_KERNEL); //哈哈,這就是使用cache_cache
    //這裡會分配一塊乾淨的清零過的記憶體
    if (!cachep)
        goto oops;

#if DEBUG
    ...
#endif

    /*
     * Determine if the slab management is 'on' or 'off' slab.
     * (bootstrapping cannot cope with offslab caches so don't do
     * it too early on.)
     */
    //第一個條件通過PAGE_SIZE確定slab管理物件的儲存方式,內建還是外接。
    //初始化階段採用內建式(kmem_cache_init()中建立兩個普通快取記憶體後就把slab_early_init置0了
    if ((size >= (PAGE_SIZE >> 3)) && !slab_early_init)
        /*
         * Size is large, assume best to place the slab management obj
         * off-slab (should allow better packing of objs).
         */
        flags |= CFLGS_OFF_SLAB;


    size = ALIGN(size, align); //從這一步可知,slab機制先把物件針對及其字長進行對齊,然後再在此基礎上又針對硬體緩衝行進行對齊。
    //以後所有的對齊都要照這個總的對齊值對齊


    //計算碎片大小,計算slab由幾個頁面(order)組成,同時計算每個slab中有多少個物件
    left_over = calculate_slab_order(cachep, size, align, flags); //這次計算的不是cache_cache了

    if (!cachep->num) {
        printk(KERN_ERR
               "kmem_cache_create: couldn't create cache %s.\n", name);
        kmem_cache_free(&cache_cache, cachep);
        cachep = NULL;
        goto oops;
    }

    //計算slab管理物件的大小,包括struct slab物件和 kmem_bufctl_t 陣列
    slab_size = ALIGN(cachep->num * sizeof(kmem_bufctl_t)
              + sizeof(struct slab), align);

    /*
     * If the slab has been placed off-slab, and we have enough space then
     * move it on-slab. This is at the expense of any extra colouring.
     */
     //如果是一個外接slab,並且碎片大小大於slab管理物件的大小,則可將slab管理物件移到slab中,改造成一個內建slab!!!!!
    if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {
        flags &= ~CFLGS_OFF_SLAB;
        left_over -= slab_size;   //slab_size 就是 slab 管理物件大小
    }

    if (flags & CFLGS_OFF_SLAB) {
        //align是針對slab物件的,如果 slab管理者 是外接儲存,自然也不會像內建那樣影響到後面slab物件的儲存位置
        //slab管理者也就不需要對齊了
        /* really off slab. No need for manual alignment */
        slab_size =
            cachep->num * sizeof(kmem_bufctl_t) + sizeof(struct slab);
    }

    //著色塊單位,為L1_CACHE_BYTES,即32位元組
    cachep->colour_off = cache_line_size();
    /* Offset must be a multiple of the alignment. */
    //著色單位必須是對齊單位的整數倍
    if (cachep->colour_off < align)
        cachep->colour_off = align;
    //計算碎片區域需要多少個著色塊
    cachep->colour = left_over / cachep->colour_off;
    //管理物件的大小
    cachep->slab_size = slab_size;
    cachep->flags = flags;
    cachep->gfpflags = 0;
    if (CONFIG_ZONE_DMA_FLAG && (flags & SLAB_CACHE_DMA))   //與夥伴系統互動的DMA標誌
        cachep->gfpflags |= GFP_DMA;
    //slab物件的大小
    cachep->buffer_size = size;
    //倒數
    cachep->reciprocal_buffer_size = reciprocal_value(size);


    //如果是外接slab,這裡要分配一個管理物件,儲存在slabp_cache中,如果是內建式的slab,此指標為空
    //array_cachine, cache_cache, 3list 這幾個肯定是內建式,不會進入這個
    if (flags & CFLGS_OFF_SLAB) {
        cachep->slabp_cache = kmem_find_general_cachep(slab_size, 0u);
        /*
         * This is a possibility for one of the malloc_sizes caches.
         * But since we go off slab only for object size greater than
         * PAGE_SIZE/8, and malloc_sizes gets created in ascending order,
         * this should not happen at all.
         * But leave a BUG_ON for some lucky dude.
         */
        BUG_ON(!cachep->slabp_cache);
    }

    //kmem_cach的名字和它管理的物件的建構函式
    cachep->ctor = ctor;
    cachep->name = name;

    //設定每個CPU上的local cache,配置local cache和slab 三鏈
    if (setup_cpu_cache(cachep)) {
        __kmem_cache_destroy(cachep);
        cachep = NULL;
        goto oops;
    }

    //將kmem_cache加入到cache_chain為頭的kmem_cache連結串列中
    /* cache setup completed, link it into the list */
    list_add(&cachep->next, &cache_chain);   //還是用了cache_chain
oops:
    if (!cachep && (flags & SLAB_PANIC))
        panic("kmem_cache_create(): failed to create slab `%s'\n",
              name);
    mutex_unlock(&cache_chain_mutex);  //mutex
    return cachep;   //返回該 kmem_cache
}

在這個函式中,首先要計算一些對齊的值。一是記憶體對齊,我們需要資料按照 CPU字長 進行對齊(比如結構體中間一個 char 型別資料 32 位下所佔位元組可能是 4 位元組),這樣才能提高 CPU 訪問資料的效率。其次如果設定了 SLAB_HWCACHE_ALIGN,那麼還要和快取行(cachine line)進行對齊。和快取行除此之外,除了需要DEBUG,可能還需要一些對齊,不過這都不是我們關注的重點。

總之,前面一大串就是計算出了 slab 的對齊值。

然後呼叫 kmem_cache_zalloc()函式為要建立的快取器申請記憶體,我們知道,先前我們定義了 cache_cache 這個快取器的快取器,此時就派上用場了,這裡就用它來做引數。

/**
 * kmem_cache_zalloc - Allocate an object. The memory is set to zero.
 * @cache: The cache to allocate from.
 * @flags: See kmalloc().
 *
 * Allocate an object from this cache and set the allocated memory to zero.
 * The flags are only relevant if the cache has no available objects.
 */
void *kmem_cache_zalloc(struct kmem_cache *cache, gfp_t flags)
{
    //底層呼叫__cache_alloc函式
    void *ret = __cache_alloc(cache, flags, __builtin_return_address(0));
    if (ret)
        memset(ret, 0, obj_size(cache));
    return ret;
}

它只是轉調了一下,負責將申請的記憶體清零,繼續看:

////不管是 kmalloc 還是 kmem_cache_alloc,kmem_cache_zalloc,最終都是呼叫__cache_alloc函式,這是給呼叫者分配slab的總介面
static __always_inline void *
__cache_alloc(struct kmem_cache *cachep, gfp_t flags, void *caller)
{
    unsigned long save_flags;
    void *objp;

    if (should_failslab(cachep, flags))
        return NULL;
    //和上面一樣都是引數檢測
    cache_alloc_debugcheck_before(cachep, flags);
    local_irq_save(save_flags);
    //底層呼叫__do_cache_alloc函式
    objp = __do_cache_alloc(cachep, flags);
    local_irq_restore(save_flags);
    objp = cache_alloc_debugcheck_after(cachep, flags, objp, caller);
    prefetchw(objp);

    return o

而這個函式,不管是 kmalloc() 還是 kmem_cache_create() 都會彙總到這個底層介面上來。kmalloc() 主要是靠提供一個 size 引數去 malloc_sizes[] 表中查到大小然後到達這裡的。而 kmem_cache_create() 是通過 cache_cache 快取器到達這裡的。它們兩個只是通過不同途徑獲知了要申請記憶體的大小,然後都交由 __cache_alloc() 函式處理,該函式又呼叫 __do_cache_alloc() 函式,如下:

static __always_inline void *
__do_cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
    //轉呼叫___cache_alloc
    return ____cache_alloc(cachep, flags);
}

轉呼叫下面的:

//分配的重點
static inline void *____cache_alloc(struct kmem_cache *cachep, gfp_t flags)
{
    void *objp;
    struct array_cache *ac;

    check_irq_off();

    //獲得快取的本地快取記憶體的描述符 array_cache
    ac = cpu_cache_get(cachep);   //如果是第一個cache_cache,在這裡就返回0
    //三部曲,第一步: 先看local cache中有沒有分配的物件,如果有就直接用,沒有就只能執行cache_alloc_refill,裡面有三部曲的另外兩步
    if (likely(ac->avail)) {
        STATS_INC_ALLOCHIT(cachep); //FIXME: 空函式, 定義是#define STATS_INC_ALLOCHIT(x)  do { } while (0), 這有什麼用?
        //將avail的值減1,這樣avail對應的空閒物件是最熱的,即最近釋放出來的,更有可能駐留在CPU快取記憶體中
        ac->touched = 1;   //slab分配物件是按塊分配,這裡肯定只要一塊,拿走就行了。
        //由於ac是記錄這至此struct array_cache結構體存放地址,通過ac->entry[]後,我們就得到一個地址,
        //這個地址可以看做是為 local cache 可用物件的首地址,從這裡可以看出,是從最後一個物件開始分配的,即LIFO結構。
        objp = ac->entry[--ac->avail];
    } else {
        STATS_INC_ALLOCMISS(cachep);  //空函式
        objp = cache_alloc_refill(cachep, flags);  //為快取記憶體記憶體空間增加新的記憶體物件,將會執行三部曲的二三步
    }
    return objp;
}

這個函式首先嚐試從本地快取分配一個物件出來,如果沒有那就呼叫 cache_alloc_refill() 函式嘗試從本地共享快取或者三鏈中分配物件,呼叫的 cache_alloc_refill() 如下:

static void *cache_alloc_refill(struct kmem_cache *cachep, gfp_t flags)
{
    int batchcount;
    struct kmem_list3 *l3;
    struct array_cache *ac;
    int node;

    node = numa_node_id();

    check_irq_off();
    //本低快取記憶體變數
    ac = cpu_cache_get(cachep);
retry:
    //準備填充本地快取記憶體,這裡先記錄填充物件個數,即batchcount成員(批量轉入轉出的個數)
    batchcount = ac->batchcount;
    if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {
        /*
         * If there was little recent activity on this cache, then
         * perform only a partial refill.  Otherwise we could generate
         * refill bouncing.
         */
        batchcount = BATCHREFILL_LIMIT;
    }

    //獲得本記憶體節點的kmem_cache的三鏈
    l3 = cachep->nodelists[node];

    BUG_ON(ac->avail > 0 || !l3);
    spin_lock(&l3->list_lock);

    /* See if we can refill from the shared array */
    //三步曲之第二步:
    //如果有本地共享快取記憶體,則從共享本地快取記憶體填充,僅用於多核,多個CPU共享的快取記憶體
    if (l3->shared && transfer_objects(ac, l3->shared, batchcount))
        goto alloc_done;  //一次性只會申請一個塊,分配成功就跳去alloc_down,ac->avail已經被修改

    //三步曲第三步:從本地的快取記憶體的三鏈中分配
    while (batchcount > 0) {
        struct list_head *entry;
        struct slab *slabp;
        /* Get slab alloc is to come from. */
        //首先訪問部分滿的slab連結串列,是entry指向第一個節點
        entry = l3->slabs_partial.next;
        //如果部分滿連結串列的都沒了,就找全空閒的
        if (entry == &l3->slabs_partial) {
            //在訪問全空閒slab連結串列前先做一個標記,表明全空閒slab連結串列被使用過了
            l3->free_touched = 1;
            //使entry指向全空閒連結串列第一個節點
            entry = l3->slabs_free.next;
            //全空閒的也沒了,也就是我們的三步曲都失敗了,則必須擴充了:)
            if (entry == &l3->slabs_free)
                goto must_grow;  
        }

        //上面出來的只有兩種情況就是部分滿的連結串列或者全空閒的連結串列有可分配物件,我們就去該鏈,準備調整
        slabp = list_entry(entry, struct slab, list);
        //底下是兩個空函式
        check_slabp(cachep, slabp);
        check_spinlock_acquired(cachep);

        /*
         * The slab was either on partial or free list so
         * there must be at least one object available for
         * allocation.
         */
        BUG_ON(slabp->inuse < 0 || slabp->inuse >= cachep->num);

        //如果三鏈中還存在物件未分配,就把它填充到ac中,最多batchcount個
        while (slabp->inuse < cachep->num && batchcount--) {
            //一般情況下下面是三個空函式
            STATS_INC_ALLOCED(cachep);
            STATS_INC_ACTIVE(cachep);
            STATS_SET_HIGH(cachep);

            //從slab中提取空閒物件,插入到ac中
            ac->entry[ac->avail++] = slab_get_obj(cachep, slabp,
                                node);
        }
        check_slabp(cachep, slabp);  //空函式

        /* move slabp to correct slabp list: */
        //由於三鏈在上面會分配出部分物件,所以在此處需要調整。如果slab中沒有空閒物件,新增到三鏈的full連結串列;
        //還有空閒物件,新增到三鏈的partial slab連結串列中
        list_del(&slabp->list);
        if (slabp->free == BUFCTL_END)
            list_add(&slabp->list, &l3->slabs_full);
        else
            list_add(&slabp->list, &l3->slabs_partial);
    }

must_grow:
    //前面從三鏈中新增了avail個空閒物件到ac中,此時需要更新三鏈的空閒物件數
    l3->free_objects -= ac->avail;
alloc_done:
    spin_unlock(&l3->list_lock);

    //此處有可能之前shared array 通過 transfer 給了 ac 一部分,也不會進入這裡了
    //三鏈轉移成功也不會進入了
    if (unlikely(!ac->avail)) {
        int x;
        //使用cache_grow為快取記憶體分配一個新的slab
        //引數分別是: cache指標、標誌、記憶體節點、頁虛擬地址(為空表示還未申請記憶體頁,不為空,說明已申請記憶體頁,可直接用來建立slab)
        //返回值: 1為成功,0為失敗
        x = cache_grow(cachep, flags | GFP_THISNODE, node, NULL);

        /* cache_grow can reenable interrupts, then ac could change. */
        //上面的操作使能了中斷,此期間local cache指標可能發生了變化,需要重新獲得
        ac = cpu_cache_get(cachep);

        //如果cache_grow失敗,local cache中也沒有空閒物件,說明我們無法分配記憶體了,那就掛了:)
        if (!x && ac->avail == 0)   /* no objects in sight? abort */
            return NULL;

        //如果ac中的可用物件為0,我們就把上面通過夥伴系統給三鏈的slab分給ac一部分,畢竟per-cpu優先嘛
        //不過,只要ac_>avail不為0,說明其他程式填充了ac,因為本程式只填充給連結串列,所以不許要再分配物件給ac了,不用執行retry
        if (!ac->avail)     /* objects refilled by interrupt? */
            goto retry;
    }
    //重填了local cache,即ac,設定近期訪問標誌 touch
    ac->touched = 1; 
    //我們需要返回ac中最後一個物件的地址,因為從local cache中分配物件總是最優的
    return ac->entry[--ac->avail];
}

該函式先視察本地共享快取,如果沒有,再看三鏈。三鏈也沒有,那就只能通過夥伴系統分配記憶體了,並建立新的可用的的 slab 。分配好後,轉到 ac 中,畢竟 ac 是本地快取,ac 的效率最高。然後返回 ac 的末尾就行了,記憶體就分配好了。

不過,我們還是看一下 cache_grow() 函式如何實現的:

/*
 * Grow (by 1) the number of slabs within a cache.  This is called by
 * kmem_cache_alloc() when there are no active objs left in a cache.
 */
static int cache_grow(struct kmem_cache *cachep,
        gfp_t flags, int nodeid, void *objp)
{
    struct slab *slabp;
    size_t offset;
    gfp_t local_flags;
    struct kmem_list3 *l3;

    /*
     * Be lazy and only check for valid flags here,  keeping it out of the
     * critical path in kmem_cache_alloc().
     */
    BUG_ON(flags & ~(GFP_DMA | GFP_LEVEL_MASK));

    local_flags = (flags & GFP_LEVEL_MASK);
    /* Take the l3 list lock to change the colour_next on this node */
    //確定關了中斷
    check_irq_off();
    //獲取本節點cache分配器的slab三鏈
    l3 = cachep->nodelists[nodeid];
    spin_lock(&l3->list_lock);

    /* Get colour for the slab, and cal the next value. */
    //獲取待建立slab的著色數目
    offset = l3->colour_next;
    //獲取完了要++,更新下一次要建立的slab的著色數目
    l3->colour_next++;
    if (l3->colour_next >= cachep->colour)
    //顏色編號必須小於顏色個數,如果超過了,重置為0,這就是著色迴圈問題。事實上,如果slab中浪費的空間很少,那麼很快就會迴圈一次
        l3->colour_next = 0;
    spin_unlock(&l3->list_lock);

    //該cache塊的著色偏移, 注意 *=
    offset *= cachep->colour_off;  //colour_off是單位

    //使用了_GFP_WAIT就會開啟中斷
    if (local_flags & __GFP_WAIT)
        local_irq_enable();

    /*
     * The test for missing atomic flag is performed here, rather than
     * the more obvious place, simply to reduce the critical path length
     * in kmem_cache_alloc(). If a caller is seriously mis-behaving they
     * will eventually be caught here (where it matters).
     */
     //檢查與夥伴系統互動的標記
    kmem_flagcheck(cachep, flags);

    /*
     * Get mem for the objs.  Attempt to allocate a physical page from
     * 'nodeid'.
     */
     //從buddy獲取物理頁,返回的是虛擬地址objp
    if (!objp)
        objp = kmem_getpages(cachep, flags, nodeid);
    if (!objp)  //失敗就failed
        goto failed;

    /* Get slab management. */
    //獲得一個新的slab描述符
    slabp = alloc_slabmgmt(cachep, objp, offset,
            local_flags & ~GFP_THISNODE, nodeid);
    if (!slabp)
        goto opps1;

    //設定節點id
    slabp->nodeid = nodeid;   
    //把slab描述符slabp賦給物理的prev欄位,把快取記憶體描述符 cachep 賦給物理頁的 LRU 欄位
    //本質是建立slab和cache到物理頁的對映,用於快速根據物理頁定位slab描述符和cache描述符
    slab_map_pages(cachep, slabp, objp);

    //初始化cache描述符合slab物件描述符
    cache_init_objs(cachep, slabp);

    if (local_flags & __GFP_WAIT)
        local_irq_disable();
    check_irq_off();
    spin_lock(&l3->list_lock);

    /* Make slab active. */
    //把上面初始化好的slab尾插法加入到三鏈的全空連結串列
    list_add_tail(&slabp->list, &(l3->slabs_free));
    STATS_INC_GROWN(cachep);
    //更新快取記憶體中空閒物件計數器
    l3->free_objects += cachep->num;
    spin_unlock(&l3->list_lock);
    return 1;
opps1:
    kmem_freepages(cachep, objp);
failed:
    if (local_flags & __GFP_WAIT)
        local_irq_disable();
    return 0;
}

該函式與夥伴系統互動的函式有:

/*
 * Interface to system's page allocator. No need to hold the cache-lock.
 *
 * If we requested dmaable memory, we will get it. Even if we
 * did not request dmaable memory, we might get it, but that
 * would be relatively rare and ignorable.
 */
static void *kmem_getpages(struct kmem_cache *cachep, gfp_t flags, int nodeid)
{
    struct page *page;
    int nr_pages;
    int i;

#ifndef CONFIG_MMU
    /*
     * Nommu uses slab's for process anonymous memory allocations, and thus
     * requires __GFP_COMP to properly refcount higher order allocations
     */
    flags |= __GFP_COMP;
#endif

    flags |= cachep->gfpflags;

    //從buddy獲取物理頁,大小由cachep->gfporder決定(2^cachep->gfporder)
    page = alloc_pages_node(nodeid, flags, cachep->gfporder);
    if (!page)
        return NULL;
    //計算出要獲取的物理頁個數(2^cachep->gfporder)
    nr_pages = (1 << cachep->gfporder);

    //設定頁的狀態(是否可回收),在vmstat中設定
    if (cachep->flags & SLAB_RECLAIM_ACCOUNT)
        add_zone_page_state(page_zone(page),
            NR_SLAB_RECLAIMABLE, nr_pages);
    else
        add_zone_page_state(page_zone(page),
            NR_SLAB_UNRECLAIMABLE, nr_pages);

    //把這些物理頁設定屬性為slab
    for (i = 0; i < nr_pages; i++)
        __SetPageSlab(page + i);
    return page_address(page);
}

上述這個是為 slab 分配頁面的,下面是處理 slab 管理者的函式:

/*
 * Get the memory for a slab management obj.
 * For a slab cache when the slab descriptor is off-slab, slab descriptors
 * always come from malloc_sizes caches.  The slab descriptor cannot
 * come from the same cache which is getting created because,
 * when we are searching for an appropriate cache for these
 * descriptors in kmem_cache_create, we search through the malloc_sizes array.  //通過malloc_sizes表
 * If we are creating a malloc_sizes cache here it would not be visible(明顯的) to
 * kmem_find_general_cachep till the initialization is complete.
 * Hence(因此,今後) we cannot have slabp_cache same as the original cache.
 */
static struct slab *alloc_slabmgmt(struct kmem_cache *cachep, void *objp,
                   int colour_off, gfp_t local_flags,
                   int nodeid)
{
    struct slab *slabp;

    //如果是外接slab
    if (OFF_SLAB(cachep)) {
        /* Slab management obj is off-slab. */
        //外接slab我們分配到slabp指標上,在外面建立一個slab:)
        slabp = kmem_cache_alloc_node(cachep->slabp_cache,
                          local_flags & ~GFP_THISNODE, nodeid);
        if (!slabp)
            return NULL;
    } else {
        //對於內建slab,slab描述符就在該slab所佔空間的起始,即所佔虛擬起始地址加上它的著色偏移
        //然後應該更新該著色偏移即加上管理物件的空間
        slabp = objp + colour_off;
        colour_off += cachep->slab_size;
    }

    //注意上面有兩種情況,如果slab管理者是外接的,在上面slabp的地址可能改變。不過下面不考慮這些,直接在slabp地址上設定slab管理者就行了

    slabp->inuse = 0;
    //對於第一個物件頁內偏移,由上面可知如果是內建式slab,colouroff成員不僅包括著色區,還包括管理物件佔用的空間
    //對於外接式slab,colouroff成員只包括著色區
    slabp->colouroff = colour_off;
    //第一個物件的虛擬地址,這時著色偏移對於內建slab已加上了管理物件的空間
    slabp->s_mem = objp + colour_off;
    //更新節點id
    slabp->nodeid = nodeid;
    return slabp;
}

沒錯,對於外接式 slab,slab 管理者或為自己通過快取器單獨分配一塊記憶體,只不過他的 s_mem 指標依舊指向了被管理的物件而已。

關於著色

由於 slab 都是整數頁大小,在硬體快取記憶體中,同一種 slab 的相同物件會被對映在相同的快取行。在交錯訪問多個同一種型別的 slab 時,就會造成快取頻繁衝突不命中。同一快取行會被頻繁的換入換出,產生快取顛簸(cache thrashing ),會極大影響系統效能。所以我們通過在每個 slab 前方著色,也就是採取一段偏移,一般情況下,大小為 i * L1_CACHE_BYTES(L1快取行大小),i 從零開始逐步增加,增加到一定程度回到零。這樣就可以將每個 slab 物件錯開一個。這樣即使頻繁訪問,也不會造成快取顛簸,有效利用了硬體快取記憶體。

不過由於 i 從零到一定程度再回到零,這就是著色迴圈,可見多多少少還是會有快取衝突的,只不過概率比不著色小了一些。

關於著色偏移計算的程式碼在 cache_grow() 中,因為該函式從夥伴系統申請並建立了新的 slab,如下:

/* Get colour for the slab, and cal the next value. */
    //獲取待建立slab的著色數目
    offset = l3->colour_next;
    //獲取完了要++,更新下一次要建立的slab的著色數目
    l3->colour_next++;
    if (l3->colour_next >= cachep->colour)
    //顏色編號必須小於顏色個數,如果超過了,重置為0,這就是著色迴圈問題。事實上,如果slab中浪費的空間很少,那麼很快就會迴圈一次
        l3->colour_next = 0;
    spin_unlock(&l3->list_lock);

    //該cache塊的著色偏移, 注意 *=
    offset *= cachep->colour_off;  //colour_off是單位

colour_next 是三鏈的成員,用來標誌下一個 slab 要偏移幾個單位的 coulour。而 colour 是快取器的成員,它是一個快取器所管理的所有 slab 著色單位大小。這是在申請快取器時,在 kmem_cache_create() 函式中已經決定過的值,不過特殊地方就在這裡,下面擷取了 kmem_cache_create() 函式中求 colour_off 的程式碼:

    if (flags & SLAB_HWCACHE_ALIGN) {
        ...
     //如果開啟了DEBUG,則按需要進行相應的對齊
    if (flags & SLAB_STORE_USER)
        ralign = BYTES_PER_WORD;
    if (flags & SLAB_RED_ZONE) {
        ...
    }

    //著色塊單位,為L1_CACHE_BYTES,即32位元組
    cachep->colour_off = cache_line_size();
    /* Offset must be a multiple of the alignment. */
    //著色單位必須是對齊單位的整數倍
    if (cachep->colour_off < align)
        cachep->colour_off = align;
    //計算碎片區域需要多少個著色塊
    cachep->colour = left_over / cachep->colour_off;

分析一下,首先 coulour_off 賦值為 L1 快取行大小。但是,我們的對齊單位不一定就是 L1 快取行大小,還要考慮記憶體對齊,DEBUG 對齊(如RED_ZONE)。kmem_cache_create() 函式中一開始就在檢測各種機制,計算對齊值 align,所以在此處,如果 L1 快取行大小小於計算出來的 align,我們選擇更大的也能解決快取行衝突,所以換更大的並不矛盾。不過,更重要的是下面一句,left_over / cachep->colour_off,這是用剩餘碎片長度除以對齊單位,這樣求得著色塊的數目甚至可能為0!也就是不會進行著色。

結論

一般情況下,slab 著色對齊值是 L1 快取行大小,由於記憶體對齊等其他因素可能更大,不過在 slab 中剩餘碎片大小不足時,那麼不會進行著色。**

其他輔助函式:

calculate_slab_order() 函式:

/**
 * calculate_slab_order - calculate size (page order) of slabs
 * @cachep: pointer to the cache that is being created
 * @size: size of objects to be created in this cache.
 * @align: required alignment for the objects.
 * @flags: slab allocation flags
 *
 * Also calculates the number of objects per slab.
 *
 * This could be made much more intelligent.  For now, try to avoid using
 * high order pages for slabs.  When the gfp() functions are more friendly
 * towards high-order requests, this should be changed.
 */
 //計算該slab的order,即該slab是由幾個頁面構成
static size_t calculate_slab_order(struct kmem_cache *cachep,
            size_t size, size_t align, unsigned long flags)
{
    unsigned long offslab_limit;
    size_t left_over = 0;
    int gfporder;

    //首先從order為0開始,知道最大order(KMALLOC_MAX_ORDER)為止
    for (gfporder = 0; gfporder <= KMALLOC_MAX_ORDER; gfporder++) {
        unsigned int num;
        size_t remainder;

        //通過cache計算函式,如果該order放不下一個size大小的物件,即num為0,表示order太小,需要調整。
        //引數: gfproder: slab大小為2^gfporder 個頁面
        //buffer_size: 物件大小
        //align: 物件的對齊方式
        //flags: 是外接slab還是內建slab
        //remainder: slab中浪費的空間碎片是多少
        //num: slab中物件的個數
        cache_estimate(gfporder, size, align, flags, &remainder, &num);
        if (!num)
            continue;

        //如果slab管理者沒有和物件放在一起,並且該order存放物件數量大於每個slab允許存放最大的物件數量,則返回
        if (flags & CFLGS_OFF_SLAB) {
            /*
             * Max number of objs-per-slab for caches which
             * use off-slab slabs. Needed to avoid a possible
             * looping condition in cache_grow().
             */
            offslab_limit = size - sizeof(struct slab);
            offslab_limit /= sizeof(kmem_bufctl_t);

            if (num > offslab_limit)
                break;
        }

        /* Found something acceptable - save it away */
        //找到了合適的order,將相關引數儲存
        cachep->num = num;   //slab中的物件數量
        cachep->gfporder = gfporder;   //order值
        left_over = remainder;   //slab中的碎片大小

        /*
         * A VFS-reclaimable slab tends to have most allocations
         * as GFP_NOFS and we really don't want to have to be allocating
         * higher-order pages when we are unable to shrink dcache.
         */
         //SLAB_RECLAIM_ACCOUNT表示此slab所佔頁面為可回收的,當核心檢測是否有足夠的頁面滿足使用者態的需求時,
         //此類頁面將被計算在內,通過呼叫kmem_freepages()函式可以將釋放分配給slab的頁框。
         //由於是可回收的,所以不用做後面的碎片檢測了。
        if (flags & SLAB_RECLAIM_ACCOUNT)
            break;

        /*
         * Large number of objects is good, but very large slabs are
         * currently bad for the gfp()s.
         */
         //slab_break_gfp_order初始化為1,即slab最多是2^1=2個頁
        if (gfporder >= slab_break_gfp_order)
            break;

        /*
         * Acceptable internal fragmentation?
         */
         //slab所佔頁面的大小是碎片大小的8倍以上,頁面利用率較高,可以接收這樣的order
        if (left_over * 8 <= (PAGE_SIZE << gfporder))
            break;
    }
    //返回碎片大小
    return left_over;
}

cache_estimate()函式:

/*
 * Calculate the number of objects and left-over bytes for a given buffer size.
 */
 //計算物件的數目以及碎片的大小,estimate 是估計的意思,第三個引數 align = cache_line_size()
static void cache_estimate(unsigned long gfporder, size_t buffer_size,
               size_t align, int flags, size_t *left_over,
               unsigned int *num)
{
    int nr_objs;
    size_t mgmt_size;

    //PAGE_SIZE代表一個頁面,slab_size記錄需要多少個頁面,下面這個式子等效於(1(頁面) * (2 ^ gfporder))
    size_t slab_size = PAGE_SIZE << gfporder;  //FIXME: #define PAGE_SIZE 0x400 (即1024),難道一個頁面是 1K ,怎麼可能?

    /*
     * The slab management structure can be either off the slab or  //有off-slab和on-slab兩種方式
     * on it. For the latter case, the memory allocated for a
     * slab is used for:   //這段記憶體被用來儲存:
     *
     * - The struct slab      //slab結構體
     * - One kmem_bufctl_t for each object    //每個物件的kmem_bufctl_t
     * - Padding to respect alignment of @align  //對齊的大小
     * - @buffer_size bytes for each object   //每個物件的大小
     *
     * If the slab management structure is off the slab, then the
     * alignment will already be calculated into the size. Because   //如果是off-slab,align早已被計算出來
     * the slabs are all pages aligned, the objects will be at the   //因為所有的頁面對齊過了,物件申請時會處在正確的位置
     * correct alignment when allocated.
     */
     //對於外接slab,沒有slab管理物件問題,直接用申請空間除以物件大小就是物件個數
    if (flags & CFLGS_OFF_SLAB) {
        //外接slab不存在管理物件,全部用於儲存slab物件
        mgmt_size = 0;
        //所以物件個數 = slab大小 / 物件大小
        nr_objs = slab_size / buffer_size;    //注意buffer_size已經和cache line對齊過了

        //物件個數不許超限
        if (nr_objs > SLAB_LIMIT)
            nr_objs = SLAB_LIMIT;
    } else {
        /*
         * Ignore padding for the initial guess. The padding
         * is at most @align-1 bytes, and @buffer_size is at
         * least @align. In the worst case, this result will
         * be one greater than the number of objects that fit
         * into the memory allocation when taking the padding
         * into account.
         */
         //內建式slab,slab管理物件與slab物件都在一片記憶體中,此時slab頁面包含:
         //一個struct slab 物件,一個kmem_bufctl_t 型別陣列(kmem_bufctl_t 陣列的項數和slab物件數目相同)
        //slab大小需要減去管理物件大小,所以物件個數為 剩餘大小 / (每個物件大小 + sizeof(kmem_bufctl_t)), 它們是一一匹配的關係
        nr_objs = (slab_size - sizeof(struct slab)) /
              (buffer_size + sizeof(kmem_bufctl_t));

        /*
         * This calculated number will be either the right
         * amount, or one greater than what we want.
         */
         //如果對齊後超過slab 總大小 ,需要減去一個物件
        if (slab_mgmt_size(nr_objs, align) + nr_objs*buffer_size
               > slab_size)
            nr_objs--;

        //物件個數不許超限
        if (nr_objs > SLAB_LIMIT)
            nr_objs = SLAB_LIMIT;

        //計算  管理物件以快取行  對齊後的總大小
        mgmt_size = slab_mgmt_size(nr_objs, align);
    }


    //得出slab最終物件個數
    *num = nr_objs;


    //前面已經得到了slab管理物件大小(外接為0,內建也已計算),這樣就可以最終的出slab最終浪費空間大小
    *left_over = slab_size - nr_objs*buffer_size - mgmt_size;
}

用於找某個大小對應的快取器的函式:

static struct kmem_cache *kmem_find_general_cachep(size_t size, gfp_t gfpflags)
{
    return __find_general_cachep(size, gfpflags);
}

呼叫:

static inline struct kmem_cache *__find_general_cachep(size_t size,
                            gfp_t gfpflags)
{
    struct cache_sizes *csizep = malloc_sizes;

#if 0
#if DEBUG
    /* This happens if someone tries to call
     * kmem_cache_create(), or __kmalloc(), before
     * the generic caches are initialized.
     */
    BUG_ON(malloc_sizes[INDEX_AC].cs_cachep == NULL);
#endif
#endif

    //這是本函式唯一有用的地方,計算出 cache 的最適大小
    //csizep 所指的就是 malloc_size 陣列,該陣列內部就是一大堆cache大小的值,最小為LI_CACHE_BYTES為32,最大為2^25=4096*8192(為什麼是25次方?)
    while (size > csizep->cs_size)   //反正就是各種物件的大小由小到大任你選,不過是選擇最合適的
        csizep++;

    /*
     * Really subtle: The last entry with cs->cs_size==ULONG_MAX
     * has cs_{dma,}cachep==NULL. Thus no special case
     * for large kmalloc calls required.
     */
#ifdef CONFIG_ZONE_DMA
    if (unlikely(gfpflags & GFP_DMA))
        return csizep->cs_dmacachep;
#endif

    return csizep->cs_cachep;   //返回對應cache_sizes的cs_cachep,也就是一個kmem_cache的描述符指標
}

kmem_cache_create() 函式還要負責設定本地快取,不過這個我下一篇部落格分析,就是任性 :)

相關文章