在遇到類似於越界溢出、迷途指針這些漏洞時,一般會對內存進行覆蓋(比如噴堆),無論哪種重寫方式,無疑都需要了解內存分配器的工作方式。在進行了閱讀,源碼的分析,包括很多博文的幫助下大概摸清了SLAB分配器的脈絡。
SLAB分配器主要的作用是針對常用的對象,以鏈表的方式緩存它們,緩存的鏈表會連續的存放,這樣頻繁地分配和回收就不會導致內存碎片,此外對於很多初始化的對象(如互斥鎖)內存回收後仍保持初始化的狀態,再次分配的時候只需要將數據寫入就可以了,不需要再次初始化。
在SLAB層的設計中,將不同的對象劃分成高速緩存組kmem_cache,每個kmem_cache存放不同類型的對象,kmem_cache之間用鏈表連接。這些高速緩存保存SLAB的鏈表,每個SLAB結構由一個或多個頁page組成,通常爲一個。在頁裏用來保存對象的實例。
結構圖如下:
每個緩存都有三種SLAB:
slabs_full:完全分配的
slab
slabs_partial:部分分配的 slab
slabs_free:空 slab,或者沒有對象被分配
結合圖片和源碼,看看SLAB關鍵結構的實現
struct kmem_cache{
/* 1) per-cpu data, touched during every alloc/free */
struct array_cache *array[NR_CPUS];//array是一個指向數組的指針,每個數組項都對應於系統中的一個CPU。每個數組項都包含了另一個指針,指向下文討論的array_cache結構的實例
/* 2) Cache tunables. Protected by cache_chain_mutex */
unsigned int batchcount;//指定了在每CPU列表爲空的情況下,從緩存的slab中獲取對象的數目。它還表示在緩存增長時分配的對象數目
unsigned int limit;//limit指定了每CPU列表中保存的對象的最大數目,如果超出該值,內核會將batchcount個對象返回到slab
unsigned int shared;
unsigned int buffer_size;//指定了緩存中管理的對象的長度
u32 reciprocal_buffer_size;//buffer_size的倒數值,爲了克服出發運算對性能的影響
/* 3) touched by every alloc & free from the backend */
unsigned int flags;//是一個標誌寄存器,定義緩存的全局性質,當前只有一個標誌位,用於標記slab頭得管理數據是在slab內還是外
unsigned int num;//保存了可以放入slab的對象的最大數目
/* 4) cache_grow/shrink */
/* order of pgs per slab (2^n) */
unsigned int gfporder;//指定了slab包含的頁數目以2爲底得對數
/* force GFP flags, e.g. GFP_DMA */
gfp_t gfpflags;//與夥伴系統交互時所提供的分配標識
size_t colour;//指定了顏色的最大數目
unsigned int colour_off;//是基本偏移量乘以顏色值獲得的絕對偏移量
struct kmem_cache *slabp_cache;//如果slab頭部的管理數據存儲在slab外部,則slabp_cache指向分配所需內存的一般性緩存;如果slab頭部在slab上,則其爲NULL
unsigned int slab_size;//slab管理區的大小
unsigned int dflags;//另一個標誌集合,描述slab的“動態性質”,但目前還沒有定義標誌
/* constructor func */
void (*ctor)(struct kmem_cache *, void *);//創建高速緩存時的構造函數指針
/* 5) cache creation/removal */
const char *name;//緩存的名稱
struct list_head next;//用於將kmem_cache的所有實例保存在全局鏈表cache_chain上
/* 6) statistics */
#if STATS//統計數據字段
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;
#endif
#if DEBUG
/*
* If debugging is enabled, then the allocator can add additional
* fields and/or padding to every object. buffer_size contains the total
* object size including these internal fields, the following two
* variables contain the offset to the user object and its size.
*/
int obj_offset;
int obj_size;
#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.
*/
struct kmem_list3 *nodelists[MAX_NUMNODES];//nodelists是一個數組,每個數組對應於系統中一個可能的內存結點。每個數組項都包含kmem_list3的一個實例
/*
* Do not add fields after nodelists[]
*/
}
</pre><pre name="code" class="cpp">struct kmem_list3 {
struct list_head slabs_partial;//部分空閒的slab鏈表
struct list_head slabs_full;//非空閒的slab鏈表
struct list_head slabs_free;//完全空閒的slab鏈表
unsigned long free_objects;//部分空閒的slab鏈表和完全空閒的slab鏈表中空閒對象的總數
unsigned int free_limit;//指定了所有slab上容許未使用對象的最大數目
unsigned int colour_next;//內核建立的下一個slab的顏色
spinlock_t list_lock;
struct array_cache *shared;//結點內共享
struct array_cache **alien;//在其他結點上
unsigned long next_reap;//定義了內核在兩次嘗試收縮緩存之間,必須經過的時間間隔
int free_touched;//表示緩存是否是活動的
};
struct slab {
struct list_head list;
unsigned long colouroff;//該Slab上着色區的大小
void *s_mem;//指向對象區的起點
unsigned int inuse;//Slab中所分配對象的個數
kmem_bufctl_t free;//指明瞭空閒對象鏈中的第一個對象,kmem_bufctl_t其實是一個整數
unsigned short nodeid;//結點標識號
};
struct array_cache {
unsigned int avail;//本地高速緩存中可用的空閒對象數
unsigned int limit;//空閒對象的上限
unsigned int batchcount;//一次轉入和轉出的對象數量
unsigned int touched;//標識本地CPU最近是否被使用
spinlock_t lock;
void *entry[];//這是一個僞數組,便於對後面用於跟蹤空閒對象的指針數組的訪問
};
還要介紹的一個數據結構就是struct array_cache。struct kmem_cache中定義了一個struct array_cache指針數組,數組的元素個數對應了系統的CPU數,和夥伴系統中的每CPU頁框高速緩存類似,該結構用來描述每個CPU的本地高速緩存,它可以減少SMP系統中對於自旋鎖的競爭。在每個array_cache的末端都用一個指針數組記錄了slab中的空閒對象,分配對象時,採用LIFO方式,也就是將該數組中的最後一個索引對應的對象分配出去,以保證該對象還駐留在高速緩存中的可能性。實際上,每次分配內存都是直接與本地CPU高速緩存進行交互,只有當其空閒內存不足時,纔會從kmem_list中的slab中引入一部分對象到本地高速緩存中,而kmem_list中的空閒對象也不足了,那麼就要從夥伴系統中引入新的頁來建立新的slab了,這一點也和夥伴系統的每CPU頁框高速緩存很類似。
上述內容來自http://blog.csdn.net/vanbreaker/article/details/7664296在SLAB結構中,還存在一種叫做着色區的結構。由於對象實例保存在頁中,爲了對象起始地址與緩衝行對齊,設置着色區對對象的位置進行調整和補充