linux核心中常見記憶體分配函式
原理說明
linux核心中採用了一種同時適用於32位和64位系統的記憶體分頁模型,對於32位系統來說,兩級頁表足夠用了,而在x86_64系統中,用到了四級頁表,如圖2-1所示。四級頁表分別為:
頁全域性目錄(page global directory)
頁上級目錄(page upper directory)
頁中間目錄(page middle directory)
頁表(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.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. 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.4. vmalloc
void *vmalloc(unsigned long size)
前面幾種記憶體分配方式都是物理連續的,能保證較低的平均訪問時間。但是在某些場合中,對記憶體區的請求不是很頻繁,較高的記憶體訪問時間也可以接受,這是就可以分配一段線性連續,物理不連續的位址,帶來的好處是一次可以分配較大塊的記憶體。圖3-1表示的是vmalloc分配的記憶體使用的位址範圍。vmalloc對一次能分配的記憶體大小沒有明確限制。出於效能考慮,應謹慎使用vmalloc函式。在測試過程中,最大能一次分配1gb的空間。
linux核心部分記憶體分布
2.5. 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.6. ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一種更直接的記憶體「分配」方式,使用時直接指定物理起始位址和需要分配記憶體的大小,然後將該段物理位址對映到核心位址空間。ioremap用到的實體地址空間都是事先確定的,和上面的幾種記憶體分配方式並不太一樣,並不是分配一段新的物理記憶體。ioremap多用於裝置驅動,可以讓cpu直接訪問外部裝置的io空間。ioremap能對映的記憶體由原有的物理記憶體空間決定,所以沒有進行測試。
2.7. boot memory
如果要分配大量的連續物理記憶體,上述的分配函式都不能滿足,就只能用比較特殊的方式,在linux核心引導階段來預留部分記憶體。
2.7.1. 在核心引導時分配記憶體
void* alloc_bootmem(unsigned long size)
可以在linux核心引導過程中繞過夥伴系統來分配大塊記憶體。使用方法是在linux核心引導時,呼叫mem_init函式之前用alloc_bootmem函式申請指定大小的記憶體。如果需要在其他地方呼叫這塊記憶體,可以將alloc_bootmem返回的記憶體首位址通過export_symbol匯出,然後就可以使用這塊記憶體了。這種記憶體分配方式的缺點是,申請記憶體的**必須在鏈結到核心中的**裡才能使用,因此必須重新編譯核心,而且記憶體管理系統看不到這部分記憶體,需要使用者自行管理。測試結果表明,重新編譯核心後重啟,能夠訪問引導時分配的記憶體塊。
2.7.2. 通過核心引導引數預留頂部記憶體
在linux核心引導時,傳入引數「mem=size」保留頂部的記憶體區間。比如系統有256mb記憶體,引數「mem=248m」會預留頂部的8mb記憶體,進入系統後可以呼叫ioremap(0xf800000,0x800000)來申請這段記憶體。
幾種分配函式的比較
分配原理
最大記憶體
其他__get_free_pages
直接對頁框進行操作
4mb適用於分配較大量的連續物理記憶體
kmem_cache_alloc
基於slab機制實現
128kb
適合需要頻繁申請釋放相同大小記憶體塊時使用
kmalloc
基於kmem_cache_alloc實現
128kb
最常見的分配方式,需要小於頁框大小的記憶體時可以使用
vmalloc
建立非連續物理記憶體到虛擬位址的對映
物理不連續,適合需要大記憶體,但是對位址連續性沒有要求的場合
dma_alloc_coherent
基於__alloc_pages實現
4mb適用於dma操作
ioremap
實現已知實體地址到虛擬位址的對映
適用於實體地址已知的場合,如裝置驅動
alloc_bootmem
在啟動kernel時,預留一段記憶體,核心看不見
小於物理記憶體大小,記憶體管理要求較高
注:表中提到的最大記憶體資料來自centos5.3 x86_64系統,其他系統和體系結構會有不同
記憶體分配方式
記憶體分配方式有三種 1 從靜態儲存區域分配。內存在程式編譯的時候就已經分配好,這塊內存在程式的 整個執行期間都存在。例如全域性變數,static 變數。2 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函 數執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指...
記憶體分配方式
記憶體分配方式有三種 1 從靜態儲存區域分配。內存在程式編譯的時候就已經分配好,這塊內存在程式的整個 執行期間都存在。例如全域性變數,static變數。2 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都可以在棧上建立,函式執 行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令...
記憶體分配方式
乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其 操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os回 收 注意它與資料結構中的堆是兩回事,分配方...