目前有很多講slab的文章,要麼是純講原理畫一堆圖結合原始碼不深導致理解困難,要麼是純**注釋導致理解更困難,我在猛攻了一周時間後,細緻總結一下slab,爭取從原理到原始碼都能細緻的理解到並立刻達到清楚的使用。
有了夥伴系統buddy,我們可以以頁為單位獲取連續的物理記憶體了,即4k為單位的獲取,但如果需要頻繁的獲取/釋放並不大的連續物理記憶體怎麼辦,如幾十位元組幾百位元組的獲取/釋放,這樣的話用buddy就不太合適了,這就引出了slab。
比如我需要乙個100位元組的連續物理記憶體,那麼核心slab分配器會給我提供乙個相應大小的連續物理記憶體單元,為128位元組大小(不會是整好100位元組,而是這個檔的乙個對齊值,如100位元組對應128位元組,30位元組對應32位元組,60位元組對應64位元組),這個物理記憶體實際上還是從夥伴系統獲取的物理頁;當我不再需要這個記憶體時應該釋放它,釋放它並非把它歸還給夥伴系統,而是歸還給slab分配器,這樣等再需要獲取時無需再從夥伴系統申請,這也就是為什麼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)
加深的部分就是說,kmalloc申請的物理記憶體長度為引數size,它需要先根據這個長度找到相應的長度的快取,這個快取的概念是什麼馬上就要引出先別著急,先看函式__find_general_cachep:
static inline struct kmem_cache *__find_general_cachep(size_t size, gfp_t gfpflags)
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_sizes的cs_size成員和size的大小比較,不斷後移malloc_sizes,現在就要看看malloc_sizes是怎麼回事:
struct cache_sizes malloc_sizes = ,
#include
cache(ulong_max)
#undef cache };
觀察檔案linux/kmalloc_sizes.h的情況,篇幅太大這個檔案內容就不列了,裡面都是一堆的cache(x)的巨集宣告,根據裡邊的定製巨集情況(l1_cache_bytes值為32,kmalloc_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)共計21個cache(x)的巨集宣告,結合結構型別struct cache_sizes,對於arm它實際上有兩個成員:
struct cache_sizes ;
除x86以外基本都沒有dma必須在物理記憶體前16mb的限制,所以包括arm的很多體系結構都沒有config_zone_dma,所以本結構實際上是兩個成員cs_size和cs_cachep,那麼這裡就比較清晰了,全域性變數malloc_sizes共有21個成員,每個成員都定義了cs_size值,從32到4194304加上0xffffffff,cs_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_alloc,kmalloc的引數比較輕鬆,直接輸入自己想要的記憶體長度即可,由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:
slab機制解析
2 原始碼解讀 slab是linux在夥伴系統之上的一種記憶體管理機制。夥伴系統最小的記憶體處理大小為4k 即一頁的記憶體大小 然而,實際使用記憶體過程中,很多都是小記憶體,為了提高記憶體申請釋放效率 防止記憶體碎片的產生 防止記憶體連續分配導致cpu快取命中率低,linux在夥伴系統之上設計出sl...
夥伴系統和slab機制
夥伴系統 linux核心中採用了一種同時適用於32位和64位系統的記憶體分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86 64系統中,用到了四級頁表。四級頁表分別為 頁全域性目錄 page global directory 頁上級目錄 page upper directory 頁中間目錄 ...
Linux頁框 夥伴演算法以及slab機制
無論是上層應用還是作業系統核心開發,記憶體問題都是我們所需要關注的。我們來看看linux核心管理和分配機制。夥伴演算法 夥伴演算法從物理連續的大小固定的段上進行分配。從這個段上分配記憶體,採用 2 的冪分配器來滿足請求分配單元的大小為 2 的冪 4kb 8kb 16kb 等 請求單元的大小如不適當,...