slab原始碼分析--主要資料結構分析

FreeeLinux發表於2017-01-13

基本原理

Linux 在保護模式下,由夥伴系統負責記憶體分配,分配記憶體的最小單位是頁。在夥伴系統之上,Linux 又增加了 slab 機制,其工作是針對一些經常分配並釋放的物件,如 task_struct 結構體等,這種物件的大小一般比較小,如果直接採用夥伴系統來進行分配和釋放,不僅會造成大量的內部碎片,而且處理速度也太慢。而 slab 機制是基於物件進行管理的,相同型別的物件歸為一類(如 tast_struct 就是一類),每次要申請這樣一個物件,slab 快取器就從一個 slab 列表中分配一個這樣大小的單元出去,而當要釋放時,將其重新歸還給 slab 快取器,而不是直接歸還給夥伴系統。slab 分配物件時,會使用最近釋放的物件記憶體塊,因此其駐留在 CPU 快取記憶體的概率較高。並且 slab 機制針對 SMP 和 NUMA 架構進行了處理,還考慮到了硬體 cache 方面的優化。

記憶體管理這一部分的處理框架就是下圖:
這裡寫圖片描述

管理機構圖示

slab 機制管理機構如圖:
這裡寫圖片描述

主要資料結構

kmem_cache快取器結構體

slab 機制為每種物件建立單獨的 kmem_cache(下文統稱快取器),每個 快取器實際上就是指定了一種規則,符合該快取器描述規則的物件都將從該快取器分配。快取器自身被組織成了一個雙向連結串列,它的頭節點是 cache_chain。

實際上,有兩種快取器,general cache(通用快取器) 和 specific cache(專用快取器),通用快取器包括:

  1. 快取 kmem_cache 的快取器(呵呵,有點玄乎,kmem_cache 即快取器,它自己也是一個小記憶體,也需要使用 slab 機制來分配,所以這就是一個雞與蛋的問題,我們需要“手工”製造一個快取器,名曰 cache_cache 來快取 kmem_cache 這個通用快取器,完了再取而代之就行)。
  2. kmalloc 使用的物件按照 malloc_sizes[] 表的大小分屬不同的快取器,32、64、128…,每種大小對應兩個兩個快取器,一個對應DMA,一個用於普通分配,這也是通用快取器。

通用快取器是按照大小進行劃分,所以大小就是通用快取器的主要規則。

專用快取器為系統特定結構建立的物件,比如 struct file,此類快取器物件來源於同一個結構。

下面來看快取器的結構體:

//快取器
struct kmem_cache {
/* 1) per-cpu data, touched during every alloc/free */
    //per-cpu資料,本地快取,記錄了本地快取記憶體的資訊,也用於跟蹤最近釋放的物件,每次分配和釋放都先訪問它
    struct array_cache *array[NR_CPUS];
/* 2) Cache tunables. Protected by cache_chain_mutex */
    unsigned int batchcount;  //本地快取轉入或轉出的大批物件數目
    unsigned int limit;            //本地快取空閒物件的最大數目
    unsigned int shared;         //是否支援本節點共享一部分cache的標誌,如果支援,那就存在本地共享快取

    unsigned int buffer_size;   //管理的物件大小
    u32 reciprocal_buffer_size;  //上面這個大小的倒數,貌似利用這個可用牛頓迭代法求什麼:)
/* 3) touched by every alloc & free from the backend */

    unsigned int flags;     //cache 的永久標誌
    unsigned int num;       //一個 slab 所包含的物件數目!!! 也就是說,kmem_cache 控制了它所管轄的所有物件大小數目及其他屬性

/* 4) cache_grow/shrink */
    /* order of pgs per slab (2^n) */
    unsigned int gfporder;  //一個slab所包含的 page 的對數,也就是一個slab分配 2^gfporder 個 page

    /* force GFP flags, e.g. GFP_DMA */
    gfp_t gfpflags;     //與夥伴系統互動時所提供的分配標識

    size_t colour;          /* cache colouring range */ //著色的範圍吧
    unsigned int colour_off;    /* colour offset */  //著色的偏移量
    struct kmem_cache *slabp_cache;//如果將slab描述符儲存在外部,該指標指向slab描述符的 cache,否則為 NULL
    unsigned int slab_size;    // slab 的大小
    unsigned int dflags;        /* dynamic flags */  //FIXME: 動態標誌

    /* constructor func */
    void (*ctor) (void *, struct kmem_cache *, unsigned long);

/* 5) cache creation/removal */
    const char *name;     //名字:)
    struct list_head next;    //構造連結串列所用

/* 6) statistics */
#if STATS
    //都是除錯資訊,略去
#endif
    /*
     * We put nodelists[] at the end of kmem_cache, because we want to size
     * this array to nr_node_ids slots instead of MAX_NUMNODES
     * (see kmem_cache_init())
     * We still use [MAX_NUMNODES] and not [1] or [0] because cache_cache
     * is statically defined, so we reserve the max number of nodes.
     */
     //nodelists 用於組織所有節點的 slab,每個節點尋找自己擁有的 cache 將自己作為 nodelists 的下標就可以訪問了
     //不過從這裡訪問的只是每個節點的 slab 管理的 cache 以及每個節點的共享 cache ,per-cpu cache 是上面的array陣列管理的
     //當然,針對同一個快取器kmem_cache,它管理的是同一種物件,所以通過本 kmem_cache 結構體的 nodelists 成員訪問的也就只是同種物件的 cache
    struct kmem_list3 *nodelists[MAX_NUMNODES];  
    /*
     * Do not add fields after nodelists[]?
     */
};

始終記住一點:快取器的職責就是為它負責快取的物件指定規則! 符合某個規則的物件就會從相應的快取器去申請記憶體。

從上面的結構體可以看出,同一個快取器中所有物件大小是相同的(buffer_size),並且同一快取器中所有 slab 大小也是相同的(gfporder、num)。

快取器最重要有三個成員:

  1. array[] 陣列。它是每 CPU(per_cpu) 資料,也就是我們的本地快取。這是為了減小 SMP 架構下自旋鎖而設定的成員,無論是物件的分配還是回收都優先考慮本地快取。
  2. shared 標誌。該標誌如果使能的話,快取器成員 nodelist 對應的三鏈就會啟用 struct array_cache* 型別的成員 shared,它用來串接共享的快取物件。這就是所謂的本地共享快取,用於 CPU 之間的物件共享。
  3. nodelist[] 陣列。該陣列的 index 是 NUMP 架構下的記憶體結點 node,陣列成員都是三鏈。針對每個記憶體結點,快取器都為它維持一個三鏈。三鏈是指 struct kmem_list3 結構體,該結構體內部有滿(full)、部分滿(partial)、空(free)三種以 slab 內部成員分配情況為依據建立的連結串列,所有的 slab 快取都維繫在三鏈上。

為什麼要引入本地共享快取?

考慮到下面的場景:
CPU1 收到大量的網路報文,分配 struct sk_buff 物件,報文處理完成後,由 CPU2 發出並釋放,這樣物件就被 CPU2 正好回收。這會造成 CPU1 的本地快取耗盡,需要從三鏈中分配物件。而 CPU2 的本地快取過多需要釋放物件到三鏈中。此時本地共享快取相當於給它們架了一座橋樑,有了它,上述情形執行由本地共享快取負責互動即可,無需再訪問三鏈。

array_cache結構體

本地快取其實就是一個 array_cache 結構體。不過由於處於多 CPU 環境,因此就組建了一個該型別的 array 陣列,將各個 CPU 的本地快取組織在一起,作為快取器的成員,統一起來方便管理。

/*
 * struct array_cache
 *
 * Purpose:
 * - LIFO ordering, to hand out cache-warm objects from _alloc
 * - reduce the number of linked list operations
 * - reduce spinlock operations
 *
 * The limit is stored in the per-cpu structure to reduce the data cache
 * footprint.
 *
 */  //array_cache中都是per-cpu資料,不會共享,這可以減少NUMA架構中多CPU的自旋鎖競爭
struct array_cache {  
    unsigned int avail;     //本地快取中可用的空閒物件數
    unsigned int limit;     //本地快取空閒物件數目上限
    unsigned int batchcount;   //本地快取一次性轉入和轉出的物件數量
    unsigned int touched;     //標識本地物件是否最近被使用
    spinlock_t lock;       //自旋鎖
    void *entry[0]; /*    //這是一個柔性陣列,便於對後面用於跟蹤空閒物件的指標陣列的訪問
             * Must have this definition in here for the proper
             * alignment of array_cache. Also simplifies accessing
             * the entries.
             * [0] is for gcc 2.95. It should really be [].
             */
};

注意該結構體的最後一個元素 void* entry[0],這就是它能串接快取物件的原因。

kmem_list3三鏈結構體

三鏈結構體如下:

/*
 * The slab lists for all objects.
 */
struct kmem_list3 {
    struct list_head slabs_partial;//部分滿的slab連結串列,也就是部分物件唄分配出去的slab
    struct list_head slabs_full;    //滿slab連結串列
    struct list_head slabs_free;  //空slab連結串列
    unsigned long free_objects;  //空閒物件的個數
    unsigned int free_limit;        //空閒物件的上限數目
    unsigned int colour_next;   /* Per-node cache coloring */   //每個節點下一個slab使用的顏色
    spinlock_t list_lock;      
    struct array_cache *shared; /* shared per node */    //每個節點共享出去的快取
    struct array_cache **alien; /* on other nodes */    //FIXME: 其他節點的快取,應該是共享的
    unsigned long next_reap;    /* updated without locking */  
    int free_touched;       /* updated without locking */
};

申請物件三部曲,先從本地快取申請,本地快取沒有就找本地共享快取,如果還沒有,就來找三鏈了。最差的情況是,三鏈也沒有,那就只能去夥伴系統要點 page,新建 slab 了。

當空閒物件比較富餘時,free 連結串列的部分 slab 可能被定期回收。

slab結構體

struct slab 結構體如下,它的另外一個名字是 slab 描述符(與kmem_bufclt陣列組成 slab 管理者(manager)。

/*
 * struct slab
 *
 * Manages the objs in a slab. Placed either at the beginning of mem allocated
 * for a slab, or allocated from an general cache.     //該管理者可以在slab頭部申請記憶體,也可以從general cache處申請記憶體。
 * Slabs are chained into three list: fully used, partial, fully free slabs.
 */
struct slab {
    struct list_head list;       //用於將slab納入三鏈之中
    unsigned long colouroff;    //該slab的著色偏移,是一大塊,不是單位
    void *s_mem;            //指向slab中的第一個物件
    unsigned int inuse;     //slab中已分配出去的物件數目
    kmem_bufctl_t free;     //下一個空閒物件的下標
    unsigned short nodeid;  //NUMA架構節點標識號
};

這個結構體負責描述一個 slab 的情況。slab 機制還採用 kmem_bufctl_t (unsigned int) 型別陣列來儲存物件的下標(在這個結構體內沒出現該陣列,是在外部和它組合的),它們的儲存位置是相連的。將它們兩個統稱為“slab管理者”。

slab 管理者的自身儲存位置有兩種,一種是 on-slab(內建式),一種是 off-slab (外接式)。內建式需要佔據 slab 內部空間,所以計算偏移量什麼的要加上。而外接式是重新申請一塊 slab 專門用來存放 slab 管理者(總共兩塊 slab)。該陣列使用指標強制轉化方式訪問,所以沒有設為結構體成員。

通常物件小於 512 的小物件採用內建式 slab,大於等於 512 的大物件採用外接式 slab。

內建式:
pic

外接式:
pic

參考:

後記

先默哀一秒鐘,從 9 號晚上開始看 slab,這幾天暈暈乎乎的,尤其是所謂的快取器的快取 cache_cache,我至少想了一天才想明白套路。核心確實不好剖析,主要還是沒有人出相應的書吧,期待有一天有相關的書,那就可以節約學習核心的人大量時間了。

相關文章