1.原理說明
Linux內核中採用了一種同時適用於32位和64位系統的內存分頁模型,對於32位系統來講,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表,如圖2-1所示。四級頁表分別爲:
l 頁全局目錄(Page Global Directory)
l 頁上級目錄(Page Upper Directory)
l 頁中間目錄(Page Middle Directory)
l 頁表(Page Table)
頁全局目錄包含若干頁上級目錄的地址,頁上級目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址,每個頁表項指向一個頁框。Linux中採用4KB大小的頁框做爲標準的內存分配單元。
多級分頁目錄結構
1.1.夥伴系統算法
在實際應用中,常常須要分配一組連續的頁框,而頻繁地申請和釋放不一樣大小的連續頁框,必然致使在已分配頁框的內存塊中分散了許多小塊的空閒頁框。這樣,即便這些頁框是空閒的,其餘須要分配連續頁框的應用也很可貴到知足。
爲了不出現這種狀況,Linux內核中引入了夥伴系統算法(buddy system)。把全部的空閒頁框分組爲11個塊鏈表,每一個塊鏈表分別包含大小爲1,2,4,8,16,32,64,128,256,512和1024個連續頁框的頁框塊。最大能夠申請1024個連續頁框,對應4MB大小的連續內存。每一個頁框塊的第一個頁框的物理地址是該塊大小的整數倍。
假設要申請一個256個頁框的塊,先從256個頁框的鏈表中查找空閒塊,若是沒有,就去512個頁框的鏈表中找,找到了則將頁框塊分爲2個256個頁框的塊,一個分配給應用,另一個移到256個頁框的鏈表中。若是512個頁框的鏈表中仍沒有空閒塊,繼續向1024個頁框的鏈表查找,若是仍然沒有,則返回錯誤。
頁框塊在釋放時,會主動將兩個連續的頁框塊合併爲一個較大的頁框塊。
1.2.slab分配器
slab分配器源於 Solaris 2.4 的分配算法,工做於物理內存頁框分配器之上,管理特定大小對象的緩存,進行快速而高效的內存分配。
slab分配器爲每種使用的內核對象創建單獨的緩衝區。Linux 內核已經採用了夥伴系統管理物理內存頁框,所以 slab分配器直接工做於夥伴系統之上。每種緩衝區由多個 slab 組成,每一個 slab就是一組連續的物理內存頁框,被劃分紅了固定數目的對象。根據對象大小的不一樣,缺省狀況下一個 slab 最多能夠由 1024個頁框構成。出於對齊等其它方面的要求,slab 中分配給對象的內存可能大於用戶要求的對象實際大小,這會形成必定的內存浪費。
2.經常使用內存分配函數
2.1.__get_free_pages
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
__get_free_pages函數是最原始的內存分配方式,直接從夥伴系統中獲取原始頁框,返回值爲第一個頁框的起始地址。__get_free_pages在實現上只是封裝了alloc_pages函數,從代碼分析,alloc_pages函數會分配長度爲1<
2.2.kmem_cache_alloc
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))
void *kmem_cache_alloc(struct kmem_cache *c, gfp_t flags)
kmem_cache_create/ kmem_cache_alloc是基於slab分配器的一種內存分配方式,適用於反覆分配釋放同一大小內存塊的場合(能夠小於頁大小)。首先用kmem_cache_create建立一個高速緩存區域,而後用kmem_cache_alloc從該高速緩存區域中獲取新的內存塊。 kmem_cache_alloc一次能分配的最大內存由mm/slab.c文件中的MAX_OBJ_ORDER宏定義,在默認的2.6.18內核版本中,該宏定義爲5,因而一次最多能申請1<<5 * 4KB也就是128KB的連續物理內存。分析內核源碼發現,kmem_cache_create函數的size參數大於128KB時會調用BUG()。測試結果驗證了分析結果,用kmem_cache_create分配超過128KB的內存時使內核崩潰。
2.3.mempool_alloc
void *mempool_alloc(mempool_t *pool,int gfp_mask)
爲了確保在內存分配不容許失敗狀況下成功分配內存,內核提供了稱爲內存池( "mempool" )的抽象,它實際上是某種後備高速緩存,mempool的底層一般使用slab。它爲了緊急狀況下的使用。因此使用時必須注意:mempool會分配一些內存塊,使其空閒而不真正使用,因此容易消耗大量內存。並且不要使用mempool處理可失敗的分配。應避免在驅動代碼中使用mempool。
2.4.kmalloc
void *kmalloc(size_t size, gfp_t flags)
kmalloc是內核中最經常使用的一種內存分配方式,它經過調用kmem_cache_alloc函數來實現。kmalloc一次最多能申請的內存大小由include/linux/Kmalloc_size.h的內容來決定,在默認的2.6.18內核版本中,kmalloc一次最多能申請大小爲131702B也就是128KB字節的連續物理內存。測試結果代表,若是試圖用kmalloc函數分配大於128KB的內存,編譯不能經過。
2.5.vmalloc
void *vmalloc(unsigned long size)
前面幾種內存分配方式都是物理連續的,能保證較低的平均訪問時間。可是在某些場合中,對內存區的請求不是很頻繁,較高的內存訪問時間也能夠接受,這是就能夠分配一段線性連續,物理不連續的地址,帶來的好處是一次能夠分配較大塊的內存。圖3-1表示的是vmalloc分配的內存使用的地址範圍。vmalloc對一次能分配的內存大小沒有明確限制。出於性能考慮,應謹慎使用vmalloc函數。在測試過程當中,最大能一次分配1GB的空間。
2.6.dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev, size_t size,
ma_addr_t *dma_handle, gfp_t gfp)
DMA是一種硬件機制,容許外圍設備和主存之間直接傳輸IO數據,而不須要CPU的參與,使用DMA機制能大幅提升與設備通訊的吞吐量。DMA操做中,涉及到CPU高速緩存和對應的內存數據一致性的問題,必須保證二者的數據一致,在x86_64體系結構中,硬件已經很好的解決了這個問題, dma_alloc_coherent和__get_free_pages函數實現差異不大,前者實際是調用__alloc_pages函數來分配內存,所以一次分配內存的大小限制和後者同樣。__get_free_pages分配的內存一樣能夠用於DMA操做。測試結果證實,dma_alloc_coherent函數一次能分配的最大內存也爲4M。
2.7.ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一種更直接的內存「分配」方式,使用時直接指定物理起始地址和須要分配內存的大小,而後將該段物理地址映射到內核地址空間。ioremap用到的物理地址空間都是事先肯定的,和上面的幾種內存分配方式並不太同樣,並非分配一段新的物理內存。ioremap多用於設備驅動,可讓CPU直接訪問外部設備的IO空間。ioremap能映射的內存由原有的物理內存空間決定,因此沒有進行測試。
2.8.Boot Memory
若是要分配大量的連續物理內存,上述的分配函數都不能知足,就只能用比較特殊的方式,在Linux內核引導階段來預留部份內存。
2.8.1. 在內核引導時分配內存
void* alloc_bootmem(unsigned long size)
能夠在Linux內核引導過程當中繞過夥伴系統來分配大塊內存。使用方法是在Linux內核引導時,調用mem_init函數以前用alloc_bootmem函數申請指定大小的內存。若是須要在其餘地方調用這塊內存,能夠將alloc_bootmem返回的內存首地址經過EXPORT_SYMBOL導出,而後就可使用這塊內存了。這種內存分配方式的缺點是,申請內存的代碼必須在連接到內核中的代碼裏才能使用,所以必須從新編譯內核,並且內存管理系統看不到這部份內存,須要用戶自行管理。測試結果代表,從新編譯內核後重啓,可以訪問引導時分配的內存塊。
2.8.2. 經過內核引導參數預留頂部內存
在Linux內核引導時,傳入參數「mem=size」保留頂部的內存區間。好比系統有256MB內存,參數「mem=248M」會預留頂部的8MB內存,進入系統後能夠調用ioremap(0xF800000,0x800000)來申請這段內存。linux