1. 程式人生 > >詳解slab機制(1) slab是什麼

詳解slab機制(1) slab是什麼

目前有很多講slab的文章,要麼是純講原理畫一堆圖結合原始碼不深導致理解困難,要麼是純程式碼註釋導致理解更困難,我在猛攻了一週時間後,細緻總結一下slab,爭取從原理到原始碼都能細緻的理解到並立刻達到清楚的使用。

一、slab分配器概述:

有了夥伴系統buddy,我們可以以頁為單位獲取連續的實體記憶體了,即4K為單位的獲取,但如果需要頻繁的獲取/釋放並不大的連續實體記憶體怎麼辦,如幾十位元組幾百位元組的獲取/釋放,這樣的話用buddy就不太合適了,這就引出了slab

比如我需要一個100位元組的連續實體記憶體,那麼核心slab分配器會給我提供一個相應大小的連續實體記憶體單元,為128位元組大小(不會是整好100

位元組,而是這個檔的一個對齊值,如100位元組對應128位元組,30位元組對應32位元組,60位元組對應64位元組),這個實體記憶體實際上還是從夥伴系統獲取的物理頁;當我不再需要這個記憶體時應該釋放它,釋放它並非把它歸還給夥伴系統,而是歸還給slab分配器,這樣等再需要獲取時無需再從夥伴系統申請,這也就是為什麼slab分配器往往會把最近釋放的記憶體(即所謂“熱”)分配給申請者,這樣效率是比較高的。

二、建立一個slab

2.1、什麼叫建立slab

上面舉了申請100位元組連續實體記憶體的例子,還提到了實際分配的是128位元組記憶體,也就是實際上核心中slab分配器對不同長度記憶體是分檔的,其實這就是slab

分配器的一個基本原則,按申請的記憶體的大小分配相應長度的記憶體。

同時也說明一個事實,核心中一定應該有這樣的按不同長度slab記憶體單元,也就是說已經建立過這樣的記憶體塊,否則申請時怎能根據大小識別應該分配給怎樣大小的記憶體,這可以先參加kmalloc的實現,kmalloc->__do_kmalloc__do_kmalloc函式中的如下:

static __always_inline void *__do_kmalloc(size_t size, gfp_t flags,  void *caller)

{

         struct kmem_cache *cachep;

         void *ret;

         /*找一個合適大小的快取記憶體*/

         cachep = __find_general_cachep(size, flags);

         if (unlikely(ZERO_OR_NULL_PTR(cachep)))

                   return cachep;

         ret = __cache_alloc(cachep, flags, caller);

         trace_kmalloc((unsigned long) caller, ret,

                         size, cachep->buffer_size, flags);

         return ret;

}

加深的部分就是說,kmalloc申請的實體記憶體長度為引數size,它需要先根據這個長度找到相應的長度的快取,這個快取的概念是什麼馬上就要引出先彆著急,先看函式__find_general_cachep

static inline struct kmem_cache *__find_general_cachep(size_t size,  gfp_t gfpflags)

{

         struct cache_sizes *csizep = malloc_sizes;

#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

         if (!size)

                   return ZERO_SIZE_PTR;

    /*這是本函式唯一有用的地方: 尋找合適大小的cache_sizes*/

         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;

}

如上面加深的部分所示,這個函式唯一有用的部分就是這裡,csizep初始化成全域性變數malloc_sizes,根據全域性變數malloc_sizescs_size成員和size的大小比較,不斷後移malloc_sizes,現在就要看看malloc_sizes是怎麼回事:

struct cache_sizes malloc_sizes[] = {

#define CACHE(x) { .cs_size = (x) },

#include <linux/kmalloc_sizes.h>

         CACHE(ULONG_MAX)

#undef CACHE

};

觀察檔案linux/kmalloc_sizes.h的情況,篇幅太大這個檔案內容就不列了,裡面都是一堆的CACHE(X)的巨集宣告,根據裡邊的定製巨集情況(L1_CACHE_BYTES值為32KMALLOC_MAX_SIZE值為4194304),一共聲明瞭CACHE(32)CACHE(64)CACHE(96)CACHE(128)CACHE(192)CACHE(256)CACHE(512)CACHE(1024)CACHE(2048)CACHE(4096)CACHE(8192)CACHE(16384)CACHE(32768)CACHE(65536)CACHE(131072)CACHE(262144)CACHE(524288)CACHE(1048576)CACHE(2097152)CACHE(4194304)和最後的CACHE(0xffffffff)共計21CACHE(X)的巨集宣告,結合結構型別struct cache_sizes,對於arm它實際上有兩個成員:

struct cache_sizes {

         size_t                        cs_size;

         struct kmem_cache         *cs_cachep;

#ifdef CONFIG_ZONE_DMA

         struct kmem_cache         *cs_dmacachep;

#endif

};

X86以外基本都沒有DMA必須在實體記憶體前16MB的限制,所以包括arm的很多體系結構都沒有CONFIG_ZONE_DMA,所以本結構實際上是兩個成員cs_sizecs_cachep,那麼這裡就比較清晰了,全域性變數malloc_sizes共有21個成員,每個成員都定義了cs_size值,從324194304加上0xffffffffcs_cachep都為NULL;其實這些值就是slab分配器的一個個按長度的分檔;

回到函式__find_general_cachep,已經很清晰了,全域性變數malloc_sizes的第0個成員開始,當申請的記憶體長度比該成員的檔次值cs_size大,就換下一個成員,直到比它小為止,仍然如申請100位元組的例子,在96位元組的分檔時還比申請長度小,在128位元組的分檔時就可以滿足了,這就是為什麼說申請100位元組實際獲取到的是128位元組的記憶體單元的原因。

回到函式__do_kmalloc,接下來呼叫的是__cache_alloc,將按照前面確定的記憶體分檔值給申請者分配一個相應值的記憶體,這說明,核心有能力給分配這樣的記憶體單元;

核心為什麼有能力建立這樣的記憶體單元?slab分配器並非一開始就能智慧的根據記憶體分檔值分配相應長度的記憶體的,它需要先建立一個這樣的“規則”式的東西,之後才可以根據這個“規則”分配相應長度的記憶體,看看前面的結構struct cache_sizes的定義,裡邊的成員cs_cachep,它的結構型別是struct kmem_cache      *,這個結構也是同樣是剛才提到的快取的概念,每種長度的slab分配都得通過它對應的cache分配,換句話說就是每種cache對應一種長度的slab分配,這裡順便能看看slab分配介面,一個是函式kmalloc一個是函式kmem_cache_allockmalloc的引數比較輕鬆,直接輸入自己想要的記憶體長度即可,由slab分配器去找應該是屬於哪個長度分檔的,然後由那個分檔的kmem_cache結構指標去分配相應長度記憶體,而kmem_cache_alloc就顯得比較“專業”,它不是輸入我要多少長度記憶體,而是直接以kmem_cache結構指標作為引數,直接指定我要這樣長度分檔的記憶體,稍微看看這兩個函式的呼叫情況就可以發現它們很快都是呼叫函式__cache_alloc,只是前面的這些不同而已。

比如現在有一個核心模組想要申請一種它自創的結構,這個結構是111位元組,並且它不想獲取128位元組記憶體就想獲取111位元組長度記憶體,那麼它需要在slab分配器中建立一個這樣的“規則”,這個規則規定slab分配器當按這種“規則”分配時要給我111位元組的記憶體,這個“規則”的建立方法就是呼叫函式kmem_cache_create

同樣,核心slab分配器之所以能夠預設的提供32-4194304共20種記憶體長度分檔,肯定也是需要建立這樣20個“規則”的,這是在初始化時建立的,由函式kmem_cache_init,先不要糾結kmem_cache_init,它裡邊有一些道理需要在理解slab分配器原理後才能更好的理解,先看kmem_cache_create: