內存管理的目標是提供一種方法,爲實現各類目的而在各個用戶之間實現內存共享。內存管理方法應該實現如下兩個功能:linux
內存管理其實是一種關於權衡的零和遊戲。您能夠開發一種使用少許內存進行管理的算法,可是要花費更多時間來管理可用內存。也能夠開發一個算法來有效地管理內存,但卻要使用更多的內存。最終,特定應用程序的需求將促使對這種權衡做出選擇。算法
每一個內存管理器都使用了一種基於堆的分配策略。在這種方法中,大塊內存(稱爲 堆)用來爲用戶定義的目的提供內存。當用戶須要一塊內存時,就請求給本身分配必定大小的內存。堆管理器會查看可用內存的狀況(使用特定算法)並返回一塊內存。搜索過程當中使用的一些算法有first-fit(在堆中搜索到的第一個知足請求的內存塊 )和 best-fit(使用堆中知足請求的最合適的內存塊)。當用戶使用完內存後,就將內存返回給堆。api
這種基於堆的分配策略的根本問題是碎片(fragmentation)。當內存塊被分配後,它們會以不一樣的順序在不一樣的時間返回。這樣會在堆中留下一些洞,須要花一些時間纔能有效地管理空閒內存。這種算法一般具備較高的內存使用效率(分配須要的內存),可是卻須要花費更多時間來對堆進行管理。緩存
另一種方法稱爲 buddy memory allocation,是一種更快的內存分配技術,它將內存劃分爲 2 的冪次方個分區,並使用 best-fit 方法來分配內存請求。當用戶釋放內存時,就會檢查 buddy 塊,查看其相鄰的內存塊是否也已經被釋放。若是是的話,將合併內存塊以最小化內存碎片。這個算法的時間效率更高,可是因爲使用 best-fit 方法的緣故,會產生內存浪費。函數
本文將着重介紹 Linux 內核的內存管理,尤爲是 slab 分配提供的機制。性能
Linux 所使用的 slab 分配器的基礎是 Jeff Bonwick 爲 SunOS 操做系統首次引入的一種算法。Jeff 的分配器是圍繞對象緩存進行的。在內核中,會爲有限的對象集(例如文件描述符和其餘常見結構)分配大量內存。Jeff 發現對內核中普通對象進行初始化所需的時間超過了對其進行分配和釋放所需的時間。所以他的結論是不該該將內存釋放回一個全局的內存池,而是將內存保持爲針對特定目而初始化的狀態。例如,若是內存被分配給了一個互斥鎖,那麼只需在爲互斥鎖首次分配內存時執行一次互斥鎖初始化函數(mutex_init
)便可。後續的內存分配不須要執行這個初始化函數,由於從上次釋放和調用析構以後,它已經處於所需的狀態中了。spa
Linux slab 分配器使用了這種思想和其餘一些思想來構建一個在空間和時間上都具備高效性的內存分配器。操作系統
圖 1 給出了 slab 結構的高層組織結構。在最高層是 cache_chain
,這是一個 slab 緩存的連接列表。這對於 best-fit 算法很是有用,能夠用來查找最適合所須要的分配大小的緩存(遍歷列表)。cache_chain
的每一個元素都是一個 kmem_cache
結構的引用(稱爲一個 cache)。它定義了一個要管理的給定大小的對象池。code
每一個緩存都包含了一個 slabs 列表,這是一段連續的內存塊(一般都是頁面)。存在 3 種 slab:對象
slabs_full
slabs_partial
slabs_empty
注意 slabs_empty
列表中的 slab 是進行回收(reaping)的主要備選對象。正是經過此過程,slab 所使用的內存被返回給操做系統供其餘用戶使用。
slab 列表中的每一個 slab 都是一個連續的內存塊(一個或多個連續頁),它們被劃分紅一個個對象。這些對象是從特定緩存中進行分配和釋放的基本元素。注意 slab 是 slab 分配器進行操做的最小分配單位,所以若是須要對 slab 進行擴展,這也就是所擴展的最小值。一般來講,每一個 slab 被分配爲多個對象。
因爲對象是從 slab 中進行分配和釋放的,所以單個 slab 能夠在 slab 列表之間進行移動。例如,當一個 slab 中的全部對象都被使用完時,就從slabs_partial
列表中移動到 slabs_full
列表中。當一個 slab 徹底被分配而且有對象被釋放後,就從 slabs_full
列表中移動到slabs_partial
列表中。當全部對象都被釋放以後,就從 slabs_partial
列表移動到 slabs_empty
列表中。
與傳統的內存管理模式相比, slab 緩存分配器提供了不少優勢。首先,內核一般依賴於對小對象的分配,它們會在系統生命週期內進行無數次分配。slab 緩存分配器經過對相似大小的對象進行緩存而提供這種功能,從而避免了常見的碎片問題。slab 分配器還支持通用對象的初始化,從而避免了爲同一目而對一個對象重複進行初始化。最後,slab 分配器還能夠支持硬件緩存對齊和着色,這容許不一樣緩存中的對象佔用相同的緩存行,從而提升緩存的利用率並得到更好的性能。
如今來看一下可以建立新 slab 緩存、向緩存中增長內存、銷燬緩存的應用程序接口(API)以及 slab 中對對象進行分配和釋放操做的函數。
第一個步驟是建立 slab 緩存結構,您能夠將其靜態建立爲:
struct struct kmem_cache *my_cachep;
而後其餘 slab 緩存函數將使用該引用進行建立、刪除、分配等操做。kmem_cache
結構包含了每一箇中央處理器單元(CPU)的數據、一組可調整的(能夠經過 proc 文件系統訪問)參數、統計信息和管理 slab 緩存所必須的元素。
內核函數 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 文件系統(在 /proc/slabinfo 中)使用它標識這個緩存。 size
參數指定了爲這個緩存建立的對象的大小,align
參數定義了每一個對象必需的對齊。 flags
參數指定了爲緩存啓用的選項。這些標誌如表 1 所示。
選項 | 說明 |
---|---|
SLAB_RED_ZONE | 在對象頭、尾插入標誌,用來支持對緩衝區溢出的檢查。 |
SLAB_POISON | 使用一種己知模式填充 slab,容許對緩存中的對象進行監視(對象屬對象全部,不過能夠在外部進行修改)。 |
SLAB_HWCACHE_ALIGN | 指定緩存對象必須與硬件緩存行對齊。 |
ctor
和 dtor
參數定義了一個可選的對象構造器和析構器。構造器和析構器是用戶提供的回調函數。當從緩存中分配新對象時,能夠經過構造器進行初始化。
在建立緩存以後, kmem_cache_create
函數會返回對它的引用。注意這個函數並無向緩存分配任何內存。相反,在試圖從緩存(最初爲空)分配對象時,refill 操做將內存分配給它。當全部對象都被使用掉時,也能夠經過相同的操做向緩存添加內存。
內核函數 kmem_cache_destroy
用來銷燬緩存。這個調用是由內核模塊在被卸載時執行的。在調用這個函數時,緩存必須爲空。
void kmem_cache_destroy( struct kmem_cache *cachep );
要從一個命名的緩存中分配一個對象,可使用 kmem_cache_alloc
函數。調用者提供了從中分配對象的緩存以及一組標誌:
void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );
這個函數從緩存中返回一個對象。注意若是緩存目前爲空,那麼這個函數就會調用 cache_alloc_refill
向緩存中增長內存。kmem_cache_alloc
的 flags 選項與 kmalloc
的 flags 選項相同。表 2 給出了標誌選項的部分列表。
標誌 | 說明 |
---|---|
GFP_USER | 爲用戶分配內存(這個調用可能會睡眠)。 |
GFP_KERNEL | 從內核 RAM 中分配內存(這個調用可能會睡眠)。 |
GFP_ATOMIC | 使該調用強制處於非睡眠狀態(對中斷處理程序很是有用)。 |
GFP_HIGHUSER | 從高端內存中分配內存。 |
內核函數 kmem_cache_zalloc
與 kmem_cache_alloc
相似,只不過它對對象執行 memset
操做,用來在將對象返回調用者以前對其進行清除操做。
要將一個對象釋放回 slab,可使用 kmem_cache_free
。調用者提供了緩存引用和要釋放的對象。
void kmem_cache_free( struct kmem_cache *cachep, void *objp );
內核中最經常使用的內存管理函數是 kmalloc
和 kfree
函數。這兩個函數的原型以下:
void *kmalloc( size_t size, int flags ); void kfree( const void *objp );
注意在 kmalloc
中,唯一兩個參數是要分配的對象的大小和一組標誌(請參看 表 2 中的部分列表)。可是 kmalloc
和 kfree
使用了相似於前面定義的函數的 slab 緩存。kmalloc
沒有爲要從中分配對象的某個 slab 緩存命名,而是循環遍歷可用緩存來查找能夠知足大小限制的緩存。找到以後,就(使用 __kmem_cache_alloc
)分配一個對象。要使用 kfree
釋放對象,從中分配對象的緩存能夠經過調用virt_to_cache
肯定。這個函數會返回一個緩存引用,而後在 __cache_free
調用中使用該引用釋放對象。
slab 緩存 API 還提供了其餘一些很是有用的函數。 kmem_cache_size
函數會返回這個緩存所管理的對象的大小。您也能夠經過調用kmem_cache_name
來檢索給定緩存的名稱(在建立緩存時定義)。緩存能夠經過釋放其中的空閒 slab 進行收縮。這能夠經過調用kmem_cache_shrink
實現。注意這個操做(稱爲回收)是由內核按期自動執行的(經過 kswapd
)。
unsigned int kmem_cache_size( struct kmem_cache *cachep ); const char *kmem_cache_name( struct kmem_cache *cachep ); int kmem_cache_shrink( struct kmem_cache *cachep );
下面的代碼片段展現了建立新 slab 緩存、從緩存中分配和釋放對象而後銷燬緩存的過程。首先,必需要定義一個 kmem_cache
對象,而後對其進行初始化(請參看清單 1)。這個特定的緩存包含 32 字節的對象,而且是硬件緩存對齊的(由標誌參數 SLAB_HWCACHE_ALIGN
定義)。
static struct kmem_cache *my_cachep; static void init_my_cache( void ) { my_cachep = kmem_cache_create( "my_cache", /* Name */ 32, /* Object Size */ 0, /* Alignment */ SLAB_HWCACHE_ALIGN, /* Flags */ NULL, NULL ); /* Constructor/Deconstructor */ return; }
使用所分配的 slab 緩存,您如今能夠從中分配一個對象了。清單 2 給出了一個從緩存中分配和釋放對象的例子。它還展現了兩個其餘函數的用法。
int slab_test( void ) { void *object; printk( "Cache name is %s\n", kmem_cache_name( my_cachep ) ); printk( "Cache object size is %d\n", kmem_cache_size( my_cachep ) ); object = kmem_cache_alloc( my_cachep, GFP_KERNEL ); if (object) { kmem_cache_free( my_cachep, object ); } return 0; }
最後,清單 3 演示了 slab 緩存的銷燬。調用者必須確保在執行銷燬操做過程當中,不要從緩存中分配對象。
static void remove_my_cache( void ) { if (my_cachep) kmem_cache_destroy( my_cachep ); return; }
proc 文件系統提供了一種簡單的方法來監視系統中全部活動的 slab 緩存。這個文件稱爲 /proc/slabinfo,它除了提供一些能夠從用戶空間訪問的可調整參數以外,還提供了有關全部 slab 緩存的詳細信息。當前版本的 slabinfo 提供了一個標題,這樣輸出結果就更具可讀性。對於系統中的每一個 slab 緩存來講,這個文件提供了對象數量、活動對象數量以及對象大小的信息(除了每一個 slab 的對象和頁面以外)。另外還提供了一組可調整的參數和 slab 數據。
要調優特定的 slab 緩存,能夠簡單地向 /proc/slabinfo 文件中以字符串的形式迴轉 slab 緩存名稱和 3 個可調整的參數。下面的例子展現瞭如何增長 limit 和 batchcount 的值,而保留 shared factor 不變(格式爲 「cache name limit batchcount shared factor」):
# echo "my_cache 128 64 8" > /proc/slabinfo
limit
字段表示每一個 CPU 能夠緩存的對象的最大數量。 batchcount
字段是當緩存爲空時轉換到每一個 CPU 緩存中全局緩存對象的最大數量。 shared
參數說明了對稱多處理器(Symmetric MultiProcessing,SMP)系統的共享行爲。
注意您必須具備超級用戶的特權才能在 proc 文件系統中爲 slab 緩存調優參數。
[注]:本文轉自IBM Developer:http://www.ibm.com/developerworks/cn/linux/l-linux-slab-allocator/