slab機制

1.內部碎片和外部碎片

外部碎片
什麼是外部碎片呢?咱們經過一個圖來解釋:
node

假設這是一段連續的頁框,陰影部分表示已經被使用的頁框,如今須要申請一個連續的5個頁框。這個時候,在這段內存上不能找到連續的5個空閒的頁框,就會去另外一段內存上去尋找5個連續的頁框,這樣子,長此以往就造成了頁框的浪費。稱爲外部碎片。
內核中使用夥伴算法的遷移機制很好的解決了這種外部碎片。
內部碎片
當咱們申請幾十個字節的時候,內核也是給咱們分配一個頁,這樣在每一個頁中就造成了很大的浪費。稱之爲內部碎片。
內核中引入了slab機制去盡力的減小這種內部碎片。linux

2.slab分配機制

slab分配器是基於對象進行管理的,所謂的對象就是內核中的數據結構(例如:task_struct,file_struct 等)。相同類型的對象歸爲一類,每當要申請這樣一個對象時,slab分配器就從一個slab列表中分配一個這樣大小的單元出去,而當要釋放時,將其從新保存在該列表中,而不是直接返回給夥伴系統,從而避免內部碎片。slab分配器並不丟棄已經分配的對象,而是釋放並把它們保存在內存中。slab分配對象時,會使用最近釋放的對象的內存塊,所以其駐留在cpu高速緩存中的機率會大大提升。算法

3.內核中slab的主要數據結構

簡要分析下這個圖:kmem_cache是一個cache_chain的鏈表,描述了一個高速緩存,每一個高速緩存包含了一個slabs的列表,這一般是一段連續的內存塊。存在3種slab:slabs_full(徹底分配的slab),slabs_partial(部分分配的slab),slabs_empty(空slab,或者沒有對象被分配)。slab是slab分配器的最小單位,在實現上一個slab有一個貨多個連續的物理頁組成(一般只有一頁)。單個slab能夠在slab鏈表之間移動,例如若是一個半滿slab被分配了對象後變滿了,就要從slabs_partial中被刪除,同時插入到slabs_full中去。
舉例說明:若是有一個名叫inode_cachep的struct kmem_cache節點,它存放了一些inode對象。當內核請求分配一個新的inode對象時,slab分配器就開始工做了:api

  • 首先要查看inode_cachep的slabs_partial鏈表,若是slabs_partial非空,就從中選中一個slab,返回一個指向已分配但未使用的inode結構的指針。完事以後,若是這個slab滿了,就把它從slabs_partial中刪除,插入到slabs_full中去,結束;
  • 若是slabs_partial爲空,也就是沒有半滿的slab,就會到slabs_empty中尋找。若是slabs_empty非空,就選中一個slab,返回一個指向已分配但未使用的inode結構的指針,而後將這個slab從slabs_empty中刪除,插入到slabs_partial(或者slab_full)中去,結束;
  • 若是slabs_empty也爲空,那麼沒辦法,cache內存已經不足,只能新建立一個slab了。

接下來咱們來分析下slab在內核中數據結構的組織,首先要從kmem_cache這個結構體提及了數組

struct kmem_cache {
    struct array_cache *array[NR_CPUS];//per_cpu數據,記錄了本地高速緩存的信息,也是用於跟蹤最近釋放的對象,每次分配和釋放都要直接訪問它。
    unsigned int batchcount;//本地高速緩存轉入和轉出的大批數據數量
    unsigned int limit;//本地高速緩存中空閒對象的最大數目
    unsigned int shared;

    unsigned int buffer_size;/*buffer的大小,就是對象的大小*/
    u32 reciprocal_buffer_size;

    unsigned int flags;     /* constant flags */
    unsigned int num;       /* # of objs per slab *//*slab中有多少個對象*/

    /* order of pgs per slab (2^n) */
    unsigned int gfporder;/*每一個slab中有多少個頁*/

    gfp_t gfpflags;       /*與夥伴系統交互時所提供的分配標識*/  

    size_t colour;          /* cache colouring range *//*slab中的着色*/
    unsigned int colour_off;    /* colour offset */着色的偏移量
    struct kmem_cache *slabp_cache;
    unsigned int slab_size;              //slab管理區的大小
    unsigned int dflags;        /* dynamic flags */

    /* constructor func */
    void (*ctor)(void *obj);    /*構造函數*/

/* 5) cache creation/removal */
    const char *name;/*slab上的名字*/
    struct list_head next;              //用於將高速緩存連入cache chain

/* 6) statistics */ //一些用於調試用的變量
#ifdef CONFIG_DEBUG_SLAB
    unsigned long num_active;
    unsigned long num_allocations;
    unsigned long high_mark;
    unsigned long grown;
    unsigned long reaped;
    unsigned long errors;
    unsigned long max_freeable;
    unsigned long node_allocs;
    unsigned long node_frees;
    unsigned long node_overflow;
    atomic_t allochit;
    atomic_t allocmiss;
    atomic_t freehit;
    atomic_t freemiss;

    int obj_offset;
    int obj_size;
#endif /* CONFIG_DEBUG_SLAB */
    //用於組織該高速緩存中的slab
    struct kmem_list3 *nodelists[MAX_NUMNODES];/*最大的內存節點*/

};

/* Size description struct for general caches. */
struct cache_sizes {
    size_t          cs_size;
    struct kmem_cache   *cs_cachep;
#ifdef CONFIG_ZONE_DMA
    struct kmem_cache   *cs_dmacachep;
#endif
};

由上面的總圖可知,一個核心的數據結構就是kmem_list3,它描述了slab描述符的狀態。緩存

struct kmem_list3 {
/*三個鏈表中存的是一個高速緩存slab*/
/*在這三個鏈表中存放的是cache*/
    struct list_head slabs_partial; //包含空閒對象和已經分配對象的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 *//*即將要着色的下一個*/
    spinlock_t list_lock;
    struct array_cache *shared; /* shared per node */
    struct array_cache **alien; /* on other nodes */
    unsigned long next_reap;    /* updated without locking *//**/
    int free_touched;       /* updated without locking */
};

接下來介紹描述單個slab的結構struct slab數據結構

struct slab {
    struct list_head list;   //用於將slab連入keme_list3的鏈表
    unsigned long colouroff;   //該slab的着色偏移
    void *s_mem;        /* 指向slab中的第一個對象*/
    unsigned int inuse; /* num of objs active in slab */已經分配出去的對象
    kmem_bufctl_t free;       //下一個空閒對象的下標
    unsigned short nodeid;   //節點標識符
};

在kmem_cache中還有一個重要的數據結構struct array_cache.這是一個指針數組,數組的元素是系統的cpu的個數。該結構用來描述每一個cpu的高速緩存,它的主要做用是減小smp系統中對於自旋鎖的競爭。函數

  • 實際上,每次分配內存都是直接與本地cpu高速緩存進行交互,只有當其空閒內存不足時,纔會從keme_list中的slab中引入一部分對象到本地高速緩存中,而keme_list中的空閒對象也不足時,那麼就要從夥伴系統中引入新的頁來創建新的slab了。
struct array_cache {
    unsigned int avail;/*當前cpu上有多少個可用的對象*/
    unsigned int limit;/*per_cpu裏面最大的對象的個數,當超過這個值時,將對象返回給夥伴系統*/
    unsigned int batchcount;/*一次轉入和轉出的對象數量*/
    unsigned int touched;/*標示本地cpu最近是否被使用*/
    spinlock_t lock;/*自旋鎖*/
    void *entry[];  /*
             * Must have this definition in here for the proper
             * alignment of array_cache. Also simplifies accessing
             * the entries.
             */
};

對上面提到的各個數據結構作一個總結,用下圖來描述:this

4.關於slab分配器的API

下面看一下slab分配器的接口——看看slab緩存是如何建立、撤銷以及如何從緩存中分配一個對象的。一個新的kmem_cache經過kmem_cache_create()函數來建立:atom

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

*name是一個字符串,存放kmem_cache緩存的名字;size是緩存所存放的對象的大小;align是slab內第一個對象的偏移;flag是可選的配置項,用來控制緩存的行爲。最後一個參數ctor是對象的構造函數,通常是不須要的,以NULL來代替。kmem_cache_create()成功執行以後會返回一個指向所建立的緩存的指針,不然返回NULL。kmem_cache_create()可能會引發阻塞(睡眠),所以不能在中斷上下文中使用。

撤銷一個kmem_cache則是經過kmem_cache_destroy()函數:

int kmem_cache_destroy( struct kmem_cache *cachep);

該函數成功則返回0,失敗返回非零值。調用kmem_cache_destroy()以前應該知足下面幾個條件:首先,cachep所指向的緩存中全部slab都爲空閒,不然的話是不能夠撤銷的;其次在調用kmem_cache_destroy()過程當中以及調用以後,調用者須要確保不會再訪問這個緩存;最後,該函數也可能會引發阻塞,所以不能在中斷上下文中使用。
能夠經過下面函數來從kmem_cache中分配一個對象:

void* kmem_cache_alloc(struct kmem_cache* cachep, gfp_t flags);

這個函數從cachep指定的緩存中返回一個指向對象的指針。若是緩存中全部slab都是滿的,那麼slab分配器會經過調用kmem_getpages()建立一個新的slab。

釋放一個對象的函數以下:

void kmem_cache_free(struct kmem_cache* cachep,  void* objp);

這個函數是將被釋放的對象返還給先前的slab,其實就是將cachep中的對象objp標記爲空閒而已

5.使用以上的API寫內核模塊,生成本身的slab高速緩存。

其實到了這裏,應該去分析以上函數的源碼,可是幾回奮起分析,都被打趴在地。因此就寫個內核模塊,鼓勵下本身吧。

#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/slab.h>

MODULE_AUTHOR("wangzhangjun");
MODULE_DESCRIPTION("slab test module");

static struct kmem_cache  *test_cachep = NULL;
struct slab_test
{
    int val;
};
void fun_ctor(struct slab_test *object , struct kmem_cache  *cachep , unsigned long flags )
{
    printk(KERN_INFO "ctor fuction ...\n");
    object->val = 1;
}

static int __init slab_init(void)
{
    struct slab_test *object = NULL;//slab的一個對象
    printk(KERN_INFO "slab_init\n");
    test_cachep = kmem_cache_create("test_cachep",sizeof(struct slab_test)*3,0,SLAB_HWCACHE_ALIGN,fun_ctor);
    if(NULL == test_cachep) 
                return  -ENOMEM ;
    printk(KERN_INFO "Cache name is %s\n",kmem_cache_name(test_cachep));//獲取高速緩存的名稱
    printk(KERN_INFO "Cache object size  is %d\n",kmem_cache_size(test_cachep));//獲取高速緩存的大小
    object = kmem_cache_alloc(test_cachep,GFP_KERNEL);//從高速緩存中分配一個對象
    if(object)
    {
        printk(KERN_INFO "alloc one val = %d\n",object->val);
        kmem_cache_free( test_cachep, object );//歸還對象到高速緩存
        //這句話的意思是雖然對象歸還到了高速緩存中,可是高速緩存中的值沒有作修改
        //只是修改了一些它的狀態。
        printk(KERN_INFO "alloc three val = %d\n",object->val);
            object = NULL;
        }else
            return -ENOMEM;
    return 0;
}

static void  __exit slab_clean(void)
{
    printk(KERN_INFO "slab_clean\n");
    if(test_cachep)
                kmem_cache_destroy(test_cachep);//調用這個函數時test_cachep所指向的緩存中全部的slab都要爲空

}

module_init(slab_init);
module_exit(slab_clean);
MODULE_LICENSE("GPL");

咱們結合結果來分析下這個內核模塊:

這是dmesg的結果,能夠發現咱們本身建立的高速緩存的名字test_cachep,還有每一個對象的大小。

還有構造函數修改了對象裏面的值,至於爲何構造函數會出現這麼屢次,多是由於,這個函數被註冊了以後,系統的其餘地方也會調用這個函數。在這裏能夠分析源碼,當調用keme_cache_create()的時候是沒有調用對象的構造函數的,調用kmem_cache_create()並無分配slab,而是在建立對象的時候發現沒有空閒對象,在分配對象的時候,會調用構造函數初始化對象。
另外結合上面的代碼能夠發現,alloc three val是在kmem_cache_free以後打印的,可是它的值依然能夠被打印出來,這充分說明了,slab這種機制是在將某個對象使用完以後,就其緩存起來,它仍是切切實實的存在於內存中。
再結合/proc/slabinfo的信息看咱們本身建立的slab高速緩存

能夠發現名字爲test_cachep的高速緩存,每一個對象的大小(objsize)是16,和上面dmesg看到的值相同,objperslab(每一個slab中的對象時202),pagesperslab(每一個slab中包含的頁數),能夠知道objsize * objperslab < pagesperslab

6.總結

目前只是對slab機制的原理有了一個感性的認識,對於這部分相關的源碼涉及到着色以及內存對齊等細節。看的不是很清楚,後面還須要仔細研究。

相關文章
相關標籤/搜索