Linux內(nèi)核中采 用了一種同時(shí)適用于32位和64位系統(tǒng)的內(nèi) 存分頁模型,對(duì)于32位系統(tǒng)來說,兩級(jí)頁表足夠用了,而在x86_64系 統(tǒng)中,用到了四級(jí)頁表,
1.原理說明
Linux內(nèi)核中采 用了一種同時(shí)適用于32位和64位系統(tǒng)的內(nèi) 存分頁模型,對(duì)于32位系統(tǒng)來說,兩級(jí)頁表足夠用了,而在x86_64系 統(tǒng)中,用到了四級(jí)頁表,如圖2-1所示。四級(jí)頁表分別為:
* 頁全局目錄(Page Global Directory)
* 頁上級(jí)目錄(Page Upper Directory)
* 頁中間目錄(Page Middle Directory)
* 頁表(Page Table)
頁全局目錄包含若干頁上級(jí)目錄的地址,頁上級(jí)目錄又依次包含若干頁中間目錄的地址,而頁中間目錄又包含若干頁表的地址,每一個(gè)頁表項(xiàng)指 向一個(gè)頁框。Linux中采用4KB大小的 頁框作為標(biāo)準(zhǔn)的內(nèi)存分配單元。
多級(jí)分頁目錄結(jié)構(gòu)
1.1.伙伴系統(tǒng)算法
在實(shí)際應(yīng)用中,經(jīng)常需要分配一組連續(xù)的頁框,而頻繁地申請(qǐng)和釋放不同大小的連續(xù)頁框,必然導(dǎo)致在已分配頁框的內(nèi)存塊中分散了許多小塊的 空閑頁框。這樣,即使這些頁框是空閑的,其他需要分配連續(xù)頁框的應(yīng)用也很難得到滿足。
為了避免出現(xiàn)這種情況,Linux內(nèi)核中引入了伙伴系統(tǒng)算法(buddy system)。把所有的空閑頁框分組為11個(gè) 塊鏈表,每個(gè)塊鏈表分別包含大小為1,2,4,8,16,32,64,128,256,512和1024個(gè)連續(xù)頁框的頁框塊。可以申請(qǐng)1024個(gè)連 續(xù)頁框,對(duì)應(yīng)4MB大小的連續(xù)內(nèi)存。每個(gè)頁框塊的個(gè)頁框的物理地址是該塊大小的整數(shù)倍。
假設(shè)要申請(qǐng)一個(gè)256個(gè)頁框的塊,先從256個(gè)頁框的鏈表中查找空閑塊,如果沒有,就去512個(gè) 頁框的鏈表中找,找到了則將頁框塊分為2個(gè)256個(gè) 頁框的塊,一個(gè)分配給應(yīng)用,另外一個(gè)移到256個(gè)頁框的鏈表中。如果512個(gè)頁框的鏈表中仍沒有空閑塊,繼續(xù)向1024個(gè)頁 框的鏈表查找,如果仍然沒有,則返回錯(cuò)誤。
頁框塊在釋放時(shí),會(huì)主動(dòng)將兩個(gè)連續(xù)的頁框塊合并為一個(gè)較大的頁框塊。
1.2.slab分 配器
slab分配器源于 Solaris 2.4 的 分配算法,工作于物理內(nèi)存頁框分配器之上,管理特定大小對(duì)象的緩存,進(jìn)行快速而高效的內(nèi)存分配。
slab分配器為每種使用的內(nèi)核對(duì)象建立單獨(dú)的緩沖區(qū)。Linux 內(nèi)核已經(jīng)采用了伙伴系統(tǒng)管理物理內(nèi)存頁框,因此 slab分配器直接工作于伙伴系 統(tǒng)之上。每種緩沖區(qū)由多個(gè) slab 組成,每個(gè) slab就是一組連續(xù)的物理內(nèi)存頁框,被劃分成了固定數(shù)目的對(duì)象。根據(jù)對(duì)象大小的不同,缺省情況下一個(gè) slab 多可以由 1024個(gè)頁框構(gòu)成。出于對(duì)齊 等其它方面的要求,slab 中分配給對(duì)象的內(nèi)存可能大于用戶要求的對(duì)象實(shí)際大小,這會(huì)造成一定的 內(nèi)存浪費(fèi)。
2.常用內(nèi)存分配函數(shù)
2.1.__get_free_pages
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
__get_free_pages函數(shù)是原始的內(nèi)存分配方式,直接從伙伴系統(tǒng)中獲取原始頁框,返回值為個(gè)頁框的起始地址。__get_free_pages在實(shí)現(xiàn)上只是封裝了alloc_pages函 數(shù),從代碼分析,alloc_pages函數(shù)會(huì)分配長度為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分配器的一種內(nèi)存分配方式,適用于反復(fù)分配釋放同一大小內(nèi)存塊的場(chǎng)合。首先用kmem_cache_create創(chuàng)建一個(gè)高速緩存區(qū)域,然后用kmem_cache_alloc從 該高速緩存區(qū)域中獲取新的內(nèi)存塊。 kmem_cache_alloc一次能分配的內(nèi)存由mm/slab.c文件中的MAX_OBJ_ORDER宏 定義,在默認(rèn)的2.6.18內(nèi)核版本中,該宏定義為5, 于是一次多能申請(qǐng)1<<5 * 4KB也就是128KB的 連續(xù)物理內(nèi)存。分析內(nèi)核源碼發(fā)現(xiàn),kmem_cache_create函數(shù)的size參數(shù)大于128KB時(shí)會(huì)調(diào)用BUG()。測(cè)試結(jié)果驗(yàn)證了分析結(jié)果,用kmem_cache_create分 配超過128KB的內(nèi)存時(shí)使內(nèi)核崩潰。
2.3.kmalloc
void *kmalloc(size_t size, gfp_t flags)
kmalloc是內(nèi)核中常用的一種內(nèi)存分配方式,它通過調(diào)用kmem_cache_alloc函 數(shù)來實(shí)現(xiàn)。kmalloc一次多能申請(qǐng)的內(nèi)存大小由include/Linux/Kmalloc_size.h的 內(nèi)容來決定,在默認(rèn)的2.6.18內(nèi)核版本中,kmalloc一 次多能申請(qǐng)大小為131702B也就是128KB字 節(jié)的連續(xù)物理內(nèi)存。測(cè)試結(jié)果表明,如果試圖用kmalloc函數(shù)分配大于128KB的內(nèi)存,編譯不能通過。
2.4.vmalloc
void *vmalloc(unsigned long size)
前面幾種內(nèi)存分配方式都是物理連續(xù)的,能保證較低的平均訪問時(shí)間。但是在某些場(chǎng)合中,對(duì)內(nèi)存區(qū)的請(qǐng)求不是很頻繁,較高的內(nèi)存訪問時(shí)間也 可以接受,這是就可以分配一段線性連續(xù),物理不連續(xù)的地址,帶來的好處是一次可以分配較大塊的內(nèi)存。圖3-1表 示的是vmalloc分配的內(nèi)存使用的地址范圍。vmalloc對(duì) 一次能分配的內(nèi)存大小沒有明確限制。出于性能考慮,應(yīng)謹(jǐn)慎使用vmalloc函數(shù)。在測(cè)試過程中, 能一次分配1GB的空間。
Linux內(nèi)核部分內(nèi)存分布
2.5.dma_alloc_coherent
void *dma_alloc_coherent(struct device *dev, size_t size,
ma_addr_t *dma_handle, gfp_t gfp)
DMA是一種硬件機(jī)制,允許外圍設(shè)備和主存之間直接傳輸IO數(shù)據(jù),而不需要CPU的參與,使用DMA機(jī)制能大幅提高與設(shè)備通信的 吞吐量。DMA操作中,涉及到CPU高速緩 存和對(duì)應(yīng)的內(nèi)存數(shù)據(jù)一致性的問題,必須保證兩者的數(shù)據(jù)一致,在x86_64體系結(jié)構(gòu)中,硬件已經(jīng)很 好的解決了這個(gè)問題, dma_alloc_coherent和__get_free_pages函數(shù)實(shí)現(xiàn)差別不大,前者實(shí)際是調(diào)用__alloc_pages函 數(shù)來分配內(nèi)存,因此一次分配內(nèi)存的大小限制和后者一樣。__get_free_pages分配的內(nèi) 存同樣可以用于DMA操作。測(cè)試結(jié)果證明,dma_alloc_coherent函 數(shù)一次能分配的內(nèi)存也為4M。
2.6.ioremap
void * ioremap (unsigned long offset, unsigned long size)
ioremap是一種更直接的內(nèi)存“分配”方式,使用時(shí)直接指定物理起始地址和需要分配內(nèi)存的大小,然后將該段 物理地址映射到內(nèi)核地址空間。ioremap用到的物理地址空間都是事先確定的,和上面的幾種內(nèi)存 分配方式并不太一樣,并不是分配一段新的物理內(nèi)存。ioremap多用于設(shè)備驅(qū)動(dòng),可以讓CPU直接訪問外部設(shè)備的IO空間。ioremap能映射的內(nèi)存由原有的物理內(nèi)存空間決定,所以沒有進(jìn)行測(cè)試。
2.7.Boot Memory
如果要分配大量的連續(xù)物理內(nèi)存,上述的分配函數(shù)都不能滿足,就只能用比較特殊的方式,在Linux內(nèi) 核引導(dǎo)階段來預(yù)留部分內(nèi)存。
2.7.1.在內(nèi)核引導(dǎo)時(shí)分配內(nèi)存
void* alloc_bootmem(unsigned long size)
可以在Linux內(nèi)核引導(dǎo)過程中繞過伙伴系統(tǒng)來分配大塊內(nèi)存。使用方法是在Linux內(nèi)核引導(dǎo)時(shí),調(diào)用mem_init函數(shù)之前 用alloc_bootmem函數(shù)申請(qǐng)指定大小的內(nèi)存。如果需要在其他地方調(diào)用這塊內(nèi)存,可以將alloc_bootmem返回的內(nèi)存首地址通過EXPORT_SYMBOL導(dǎo) 出,然后就可以使用這塊內(nèi)存了。這種內(nèi)存分配方式的缺點(diǎn)是,申請(qǐng)內(nèi)存的代碼必須在鏈接到內(nèi)核中的代碼里才能使用,因此必須重新編譯內(nèi)核,而且內(nèi)存管理系統(tǒng) 看不到這部分內(nèi)存,需要用戶自行管理。測(cè)試結(jié)果表明,重新編譯內(nèi)核后重啟,能夠訪問引導(dǎo)時(shí)分配的內(nèi)存塊。
2.7.2.通過內(nèi)核引導(dǎo)參數(shù)預(yù)留頂部?jī)?nèi)存
在Linux內(nèi)核引導(dǎo)時(shí),傳入?yún)?shù)“mem=size”保留頂部的內(nèi)存區(qū)間。比如系統(tǒng)有256MB內(nèi) 存,參數(shù)“mem=248M”會(huì)預(yù)留頂部的8MB內(nèi)存,進(jìn)入系統(tǒng)后可以調(diào)用ioremap(0xF800000,0x800000)來申請(qǐng)這段內(nèi)存。
3.幾種分配函數(shù)的比較
分配原理內(nèi)存其他
__get_free_pages直接對(duì)頁框進(jìn)行操作4MB適用于分配較大量的連續(xù)物理內(nèi)存
kmem_cache_alloc基于slab機(jī)制實(shí)現(xiàn)128KB適合需要頻繁申請(qǐng)釋放相同大小內(nèi)存塊時(shí)使用
kmalloc基于kmem_cache_alloc實(shí)現(xiàn)128KB常見的分配方式,需要小于頁框大小的內(nèi)存時(shí)可以使用
vmalloc建立非連續(xù)物理內(nèi)存到虛擬地址的映射物理不連續(xù),適合需要大內(nèi)存,但是對(duì)地址連續(xù)性沒有要求的場(chǎng)合
dma_alloc_coherent基于__alloc_pages實(shí)現(xiàn)4MB適用于DMA操 作
ioremap實(shí)現(xiàn)已知物理地址到虛擬地址的映射適用于物理地址已知的場(chǎng)合,如設(shè)備驅(qū)動(dòng)
alloc_bootmem在啟動(dòng)kernel時(shí),預(yù)留一段內(nèi)存,內(nèi)核看不見小于物理內(nèi)存大小,內(nèi)存管理要求較高