內(nèi)核把物理頁(yè)作為內(nèi)存管理的基本單位;內(nèi)存管理單元(MMU)把虛擬地址轉(zhuǎn)換為物理
地址,通常以頁(yè)為單位進(jìn)行處理。MMU以頁(yè)大小為單位來(lái)管理系統(tǒng)中的也表。
32位系統(tǒng):頁(yè)大小4KB
64位系統(tǒng):頁(yè)大小8KB
內(nèi)核用相應(yīng)的數(shù)據(jù)結(jié)構(gòu)表示系統(tǒng)中的每個(gè)物理頁(yè):
<linux/mm_types.h>
struct page {}
內(nèi)核通過(guò)這樣的數(shù)據(jù)結(jié)構(gòu)管理系統(tǒng)中所有的頁(yè),因此內(nèi)核判斷一個(gè)頁(yè)是否空閑,誰(shuí)有擁有這個(gè)頁(yè)
,擁有者可能是:用戶空間進(jìn)程、動(dòng)態(tài)分配的內(nèi)核數(shù)據(jù)、靜態(tài)內(nèi)核代碼、頁(yè)高速緩存……
系統(tǒng)中每一個(gè)物理頁(yè)都要分配這樣一個(gè)結(jié)構(gòu)體,進(jìn)行內(nèi)存管理。
Linux內(nèi)存尋址存在問(wèn)題:
一些硬件只能用某些特定的內(nèi)存來(lái)執(zhí)行DMA(直接內(nèi)存訪問(wèn))
一些體系結(jié)構(gòu)其內(nèi)存的物理尋址范圍必須你尋址范圍大得多。這樣導(dǎo)致一些內(nèi)存不能永久映射到內(nèi)核空間上。
通常32位Linux內(nèi)核地址空間劃分0~3G為用戶空間,3~4G為內(nèi)核空間。當(dāng)內(nèi)核模塊代碼或線程訪問(wèn)內(nèi)存時(shí),
代碼中的內(nèi)存地址都為邏輯地址,而對(duì)應(yīng)到真正的物理內(nèi)存地址,需要地址一對(duì)一的映射。因此內(nèi)核空間地址為3~4G,
最多只能映射到1G空間的內(nèi)存,超出1G大小的內(nèi)存將如何去問(wèn)呢!
由于存在上述條件的限制。Linux將內(nèi)核空間地址劃分為三個(gè)區(qū):
ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。
ZONE_HIGHMEM即為高端內(nèi)存,這就是內(nèi)存高端內(nèi)存概念的由來(lái)。
在x86結(jié)構(gòu)中,三種類(lèi)型的區(qū)域如下:
ZONE_DMA 內(nèi)存開(kāi)始的16MB
ZONE_NORMAL 16MB~896MB
ZONE_HIGHMEM 896MB ~ 結(jié)束
同樣每個(gè)區(qū)包含眾多頁(yè),形成不同內(nèi)存池,按照用途進(jìn)行內(nèi)存分配。
用相應(yīng)的數(shù)據(jù)結(jié)構(gòu)來(lái)表示區(qū):
<linux/mmzone.h>
struct zone {}
static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
該函數(shù)分配2的order次方個(gè)連續(xù)的物理頁(yè),返回指向第一個(gè)頁(yè)的page結(jié)構(gòu)體指針。
void *page_address(const struct page *page)
返回指向給定物理頁(yè)當(dāng)前所在的邏輯地址
extern unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
extern unsigned long get_zeroed_page(gfp_t gfp_mask);
釋放:
extern void __free_pages(struct page *page, unsigned int order);
extern void free_pages(unsigned long addr, unsigned int order);
內(nèi)存的分配可能失敗,內(nèi)存的釋放要準(zhǔn)確!
1 kmalloc
kmalloc()函數(shù)與用戶空間malloc一組函數(shù)類(lèi)似,獲得以字節(jié)為單位的一塊內(nèi)核內(nèi)存。
void *kmalloc(size_t size, gfp_t flags)
void kfree(const void *objp)
分配內(nèi)存物理上連續(xù)。
gfp_t標(biāo)志:表明分配內(nèi)存的方式。如:
GFP_ATOMIC:分配內(nèi)存優(yōu)先級(jí)高,不會(huì)睡眠
GFP_KERNEL:常用的方式,可能會(huì)阻塞。
2 vmalloc
void *vmalloc(unsigned long size)
void vfree(const void *addr)
vmalloc()與kmalloc方式類(lèi)似,vmalloc分配的內(nèi)存虛擬地址是連續(xù)的,而物理地址則無(wú)需連續(xù),與用戶空間分配函數(shù)一致。
vmalloc通過(guò)分配非連續(xù)的物理內(nèi)存塊,在修正頁(yè)表,把內(nèi)存映射到邏輯地址空間的連續(xù)區(qū)域中,虛擬地址是連續(xù)的。
是否必須要連續(xù)的物理地址和具體使用場(chǎng)景有關(guān)。在不理解虛擬地址的硬件設(shè)備中,內(nèi)存區(qū)都必須是連續(xù)的。
通過(guò)建立頁(yè)表轉(zhuǎn)換成虛擬地址空間上連續(xù),肯定存在一些消耗,帶來(lái)性能上影響。
所以通常內(nèi)核使用kmalloc來(lái)申請(qǐng)內(nèi)存,在需要大塊內(nèi)存時(shí)使用vmalloc來(lái)分配。
內(nèi)核中經(jīng)常進(jìn)行內(nèi)存的分配和釋放。為了便于數(shù)據(jù)的頻繁分配和回收,通常建立一個(gè)空
閑鏈表——內(nèi)存池。當(dāng)不使用的已分配的內(nèi)存時(shí),將其放入內(nèi)存池中,而不是直接釋放掉。
Linux內(nèi)核提供了slab層來(lái)管理內(nèi)存的分配和釋放。
頻繁分配和回收必然導(dǎo)致內(nèi)存碎片,緩存他們.
slab層得設(shè)計(jì)實(shí)現(xiàn)
slab層把不同的對(duì)象劃分為所謂的高速緩存組。每個(gè)高速緩存組存放不同類(lèi)型的對(duì)象。高速緩存劃分為slab,
slab由一個(gè)或多個(gè)物理上連續(xù)的頁(yè)組成。每個(gè)slab處于三種狀態(tài)之一:滿,部分滿,空。
高速緩存,slab,對(duì)象之間的關(guān)系:
與傳統(tǒng)的內(nèi)存管理模式相比, slab 緩存分配器提供了很多優(yōu)點(diǎn)。首先,內(nèi)核通常依賴(lài)于對(duì)小對(duì)象的分配,
它們會(huì)在系統(tǒng)生命周期內(nèi)進(jìn)行無(wú)數(shù)次分配。slab 緩存分配器通過(guò)對(duì)類(lèi)似大小的對(duì)象進(jìn)行緩存而提供這種功能,
從而避免了常見(jiàn)的碎片問(wèn)題。slab 分配器還支持通用對(duì)象的初始化,從而避免了為同一目而對(duì)一個(gè)對(duì)象重復(fù)
進(jìn)行初始化。最后,slab 分配器還可以支持硬件緩存對(duì)齊和著色,這允許不同緩存中的對(duì)象占用相同的緩存行,
從而提高緩存的利用率并獲得更好的性能。
slab數(shù)據(jù)結(jié)構(gòu)和接口:
每個(gè)高速緩存用kmem_cache結(jié)構(gòu)來(lái)表示:
struct kmem_cache {
struct kmem_list3 **nodelists;
……
}
緩存區(qū)包含三種slab:滿,未滿,空閑
struct kmem_list3 {
struct list_head slabs_partial; /* partial list first, better asm code */
struct list_head slabs_full;
struct list_head slabs_free;
……
};
每一個(gè)slab包含多個(gè)對(duì)象:
struct slab {
struct list_head list;
unsigned long colouroff;
void *s_mem; /* including colour offset */
unsigned int inuse; /* num of objs active in slab */
kmem_bufctl_t free;
unsigned short nodeid;
};
相關(guān)接口:mm/slab.c
內(nèi)核函數(shù) kmem_cache_create 用來(lái)創(chuàng)建一個(gè)新緩存。這通常是在內(nèi)核初始化時(shí)執(zhí)行的,或者在首次加載內(nèi)核模塊時(shí)執(zhí)行。
struct kmem_cache *kmem_cache_create (
const char *name,
size_t size,
size_t align,
unsigned long flags,
void (*ctor)(void *))
name 參數(shù)定義了緩存名稱(chēng),proc 文件系統(tǒng)(在 /proc/slabinfo 中)使用它標(biāo)識(shí)這個(gè)緩存。
size 參數(shù)指定了為這個(gè)緩存創(chuàng)建的對(duì)象的大小,
align 參數(shù)定義了每個(gè)對(duì)象必需的對(duì)齊。
flags 參數(shù)指定了為緩存啟用的選項(xiàng):
kmem_cache_create 的部分選項(xiàng)(在 flags 參數(shù)中指定)
SLAB_RED_ZONE 在對(duì)象頭、尾插入標(biāo)志,用來(lái)支持對(duì)緩沖區(qū)溢出的檢查。
SLAB_POISON 使用一種己知模式填充 slab,允許對(duì)緩存中的對(duì)象進(jìn)行監(jiān)視(對(duì)象屬對(duì)象所有,不過(guò)可以在外部進(jìn)行修改)。
SLAB_HWCACHE_ALIGN 指定緩存對(duì)象必須與硬件緩存行對(duì)齊。
ctor 和 dtor 參數(shù)定義了一個(gè)可選的對(duì)象構(gòu)造器和析構(gòu)器。構(gòu)造器和析構(gòu)器是用戶提供的回調(diào)函數(shù)。當(dāng)從緩存中分配新對(duì)象時(shí),可以通過(guò)構(gòu)造器進(jìn)行初始化。
要從一個(gè)命名的緩存中分配一個(gè)對(duì)象,可以使用 kmem_cache_alloc 函數(shù)。
void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t flags );
這個(gè)函數(shù)從緩存中返回一個(gè)對(duì)象。注意如果緩存目前為空,那么這個(gè)函數(shù)就會(huì)調(diào)用 cache_alloc_refill 向緩存中增加內(nèi)存。
kmem_cache_alloc 的 flags 選項(xiàng)與 kmalloc 的
cachep:所建立的緩存區(qū)
flags參數(shù):
GFP_USER 為用戶分配內(nèi)存(這個(gè)調(diào)用可能會(huì)睡眠)。
GFP_KERNEL 從內(nèi)核 RAM 中分配內(nèi)存(這個(gè)調(diào)用可能會(huì)睡眠)。
GFP_ATOMIC 使該調(diào)用強(qiáng)制處于非睡眠狀態(tài)(對(duì)中斷處理程序非常有用)。
GFP_HIGHUSER 從高端內(nèi)存中分配內(nèi)存。
永久映射:可能會(huì)阻塞
映射一個(gè)給定的page結(jié)構(gòu)到內(nèi)核地址空間:
void *kmap(struct page *page)
解除映射:
void kunmap(struct page *page)
臨時(shí)映射:不會(huì)阻塞
void *kmap_atomic(struct page *page)
l 連續(xù)的物理頁(yè):kmalloc或者低級(jí)頁(yè)分配器
l 高端內(nèi)存分配:alloc_pages 指向page結(jié)構(gòu)指針,不是邏輯地址指針。再通過(guò)kmap()把高端地址內(nèi)存映射到內(nèi)核的邏輯地址空間。
l 無(wú)需連續(xù)物理地址:vmalloc 虛擬地址連續(xù)物理地址可能不連續(xù),相對(duì)存在性能損失
l 頻繁創(chuàng)建和銷(xiāo)毀很多較大數(shù)據(jù)結(jié)構(gòu):建立slab緩存區(qū),提高對(duì)象分配和回收性能。
聯(lián)系客服