Linux ARM的存儲分布那些事
linux arm 內(nèi)存分布總覽
地址范圍大小,虛擬轉(zhuǎn)物理的接口函數(shù),各個區(qū)域?qū)?yīng)的分配函數(shù),該區(qū)域有什么作用,使用場合等等。
首先開始第一個區(qū)域:CPUvector page null pointer trap
該區(qū)域的大小是一個page頁的大小,對于那些不支持中斷向量重映射的cpu,該區(qū)域用來存儲對應(yīng)的中斷向量表;
對于那些支持中斷向量重映射的cpu,該區(qū)域用來撲獲0地址的非法訪問,即null指針。針對arm體系,他是支持中斷向量重映射,該區(qū)域一般保留不用,用來撲獲null指針。
上圖是linux的arm的虛擬地址分布總覽,我們按從低地址到高地址的順序逐個描述,每項的描述包括如下的內(nèi)容的組和:
第二個區(qū)域:應(yīng)用程序地址空間
地址大小范圍屬于[0x1000, 0xbf000000],我知道每個應(yīng)用進(jìn)程都有如下幾個段:text段即存儲代碼段,data段即存儲初始化的數(shù)據(jù)段,bss段即存儲未初始化的數(shù)據(jù)段,堆(malloc,free),棧(往下生長)。他們的地址分布如下:
圖1
在應(yīng)用程序加載到內(nèi)存后,會為每個段,分一個vma的內(nèi)核結(jié)構(gòu)體,并且為每個段都分配了虛擬地址(虛擬地址和大小都存儲在vma結(jié)構(gòu)體中),當(dāng)可執(zhí)行程序的各個段在加載的時候,就會給其分配虛擬地址,每個段對應(yīng)內(nèi)核的一個vma結(jié)構(gòu),程序所有段對應(yīng)的vma,都掛在程序?qū)?yīng)的進(jìn)程的struct mm結(jié)構(gòu)中,但并未給他分配實際的物理地址,待cpu實際去訪問它時,才會去實際建立物理到vma指定的虛擬地址映射,并且將對應(yīng)的段內(nèi)容從elf文件中拷貝到相應(yīng)的物理內(nèi)存中。
譬如當(dāng)cpu要訪問text段時,這個時候并未建立相應(yīng)的映射表,所以會產(chǎn)生page fault異常,從而在異常處理中,linux的內(nèi)存管理系統(tǒng)會為其分配物理內(nèi)存, 并從二進(jìn)制可執(zhí)行程序的elf文件讀取text段到物理內(nèi)存,并且為該進(jìn)程對應(yīng)的頁表建立該物理頁到虛擬地址的映射,這樣cpu就可以訪問該進(jìn)程的text段,并且執(zhí)行對應(yīng)的指令了。
stack跟heap都一樣,在cpu有實際的訪問時,才會分配物理內(nèi)存,并建立物理到對應(yīng)的虛擬地址(在程序加載時,vma中就已經(jīng)分配了虛擬地址)映射。這樣做,就可以節(jié)省程序運(yùn)行時實際物理內(nèi)存的使用。而不是程序一開始就建立了所有物理到虛擬的映射,從而導(dǎo)致物理內(nèi)存被大量不必要的消耗。
第三個區(qū)域:模塊地址
該區(qū)域用來為內(nèi)核模塊分配地址,譬如在insmod一個驅(qū)動模塊時,會通過如下的流程:sysinit_module-->load_module-->layout_and_allocate-->move_module-->module_alloc_update_bounds-->module_alloc來為模塊的各個段分配虛擬地址
圖2
line42可見:就指定了模塊的虛擬地址范圍為:[MODULES_VADDR,MODULES_END] = [0xbf000000,0xbfe00000],總計14MB。注意此時__vmalloc_node_range進(jìn)行了實際的物理內(nèi)存分配,并且建立了物理到虛擬地址的映射。
第四個區(qū)域:PKMAP地址段
該區(qū)域跟fixmap區(qū)域都是用來將高端物理內(nèi)存頁映射到內(nèi)核的線性地址范圍,以使內(nèi)核能夠訪問他。但為什么還要分兩個區(qū)域呢?他們有什么異同?
kmap和fixmap驅(qū)動的地址范圍都是有限的,所以不能長久持有,最好使用完后,就盡快的釋放。
其中kmap區(qū)域的API函數(shù)為:kmap/kunmap,該函數(shù)可以休眠,在地址資源緊張的時候就會發(fā)生休眠。
fixmap區(qū)域的api函數(shù)為:kmap_atomic/__kunmap_atomic,該函數(shù)為每個cpu都保留一個地址槽,并且該函數(shù)是原子的,不會休眠。使用kmap_atomic影射高端物理內(nèi)存頁,處理完后(并且該處理不應(yīng)該休眠,同時kmap_atomic還會禁止搶占),就應(yīng)該盡快調(diào)用__kunmap_atomic進(jìn)行釋放。所以該函數(shù)可以在中斷上下文中使用
kmap地址段的開始虛擬地址和大小在trunk/arch/arm/mm/mmu.c中的kmap_init函數(shù)就指定了。
關(guān)于kmap的詳細(xì)分析,見我的另一篇blog文章。
第五個區(qū)域:內(nèi)核地址空間的直接映射區(qū),即linux內(nèi)核的低端內(nèi)存區(qū)
該區(qū)域也稱為內(nèi)核邏輯地址空間 是指從PAGE_OFFSET(3G)到high_memory之間的線性地址空間,是系統(tǒng)物理內(nèi)存映射區(qū),它映射了全部或部分(如果系統(tǒng)包含高端內(nèi)存)物理內(nèi)存。內(nèi)核邏輯地址空間與系統(tǒng)RAM內(nèi)存物理地址空間是一一對應(yīng)的,內(nèi)核邏輯地址空間中的地址與RAM內(nèi)存物理地址空間中對應(yīng)的地址只差一個固定偏移量(3G),如果RAM內(nèi)存物理地址空間從0x00000000地址編址,那么這個偏移量就是PAGE_OFFSET(0xc0000000)。
系統(tǒng)初始化過程中將低端內(nèi)存永久映射到了內(nèi)核邏輯地址空間,為低端內(nèi)存建立了虛擬映射頁表。低端內(nèi)存內(nèi)物理內(nèi)存的物理地址與線性地址之間的轉(zhuǎn)換可以通過__pa(x)和__va(x)兩個宏來進(jìn)行:
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET) __pa(x)將內(nèi)核邏輯地址空間的地址x轉(zhuǎn)換成對應(yīng)的物理地址,相當(dāng)于__virt_to_phys((unsigned long)(x)),
__va(x)則相反,把低端物理內(nèi)存空間的地址轉(zhuǎn)換成對應(yīng)的內(nèi)核邏輯地址,相當(dāng)于((void *)__phys_to_virt((unsigned long)(x)))
該區(qū)域的內(nèi)存分配函數(shù):kmalloc/kfree和__get_free_page都是從低端內(nèi)存來分配內(nèi)存
第六個區(qū)域:高端內(nèi)存vmalloc區(qū)
該區(qū)域是屬于linux內(nèi)核的高端內(nèi)存地址,該區(qū)域分配的虛擬地址是連續(xù)的,但對應(yīng)的物理地址則可能是不連續(xù)的。該區(qū)域的內(nèi)存分配api函數(shù)為:vmalloc/vfree, 該區(qū)域的api可以用來分配大片內(nèi)存,但對應(yīng)的物理內(nèi)存可能是不連續(xù)的。該函數(shù)會修改頁目錄映射表,因為要為對應(yīng)的虛擬地址和物理地址建立映射關(guān)系。
另外vmalloc區(qū)域跟高端內(nèi)核(high_memory)有一個8MB的保留區(qū)域。端內(nèi)存的物理地址與線性地址之間的轉(zhuǎn)換不能使用上面的__pa(x)和__va(x)宏,關(guān)于該區(qū)域linux內(nèi)核的文檔:arm/memmory.txt有如下的描述:
- vmalloc() / ioremap() space.
- Memory returned by vmalloc/ioremap will
- be dynamically placed in this region.
- Machine specific static mappings are also
- located here through iotable_init().
- VMALLOC_START is based upon the value
- of the high_memory variable, and VMALLOC_END
- is equal to 0xff000000.
第七個區(qū)域:DMA內(nèi)存映射區(qū)
該區(qū)域是為DMA分配內(nèi)存的,該段區(qū)域的開始地址和大小在
trunk/arch/arm/mm/dma-mapping.c中已經(jīng)指定了。
分別由consistent_base,DEFAULT_CONSISTENT_DMA_SIZE,
CONSISTENT_END指定該區(qū)域的開始地址,大小,結(jié)束地址。
該區(qū)域的內(nèi)存分配api函數(shù)為:dma_alloc_coherent/dma_free_coherent,
該分配函數(shù)會建立映射表,并且分配出來的物理地址是連續(xù)的。
dma_alloc_coherent的核心函數(shù)為:__dma_alloc。具體詳細(xì)的流程,
請見我的另外一篇blog。在調(diào)用這個api進(jìn)行dma內(nèi)存分配時,
虛擬地址是從CONSISTENT_END高地址往consistent_base低地址方向分配的,
即第一次dma_alloc_coherent調(diào)用的返回值>第二次dma_alloc_coherent
調(diào)用的返回值。請看圖3一個實際的系統(tǒng)dma分配的內(nèi)存情況
另外dma分配函數(shù)分配的物理頁是屬于低端內(nèi)存,但他會通過__dma_alloc_remap函數(shù),將該物理頁重新映射到dma所屬的地址范圍。所以同一個物理頁存在兩個虛擬地址映射,因為該物理頁對應(yīng)的低端內(nèi)存地址在內(nèi)核初始化的時候,就已經(jīng)映射建立好了。
第八個區(qū)域:Fixmap映射區(qū)
該區(qū)域的開始地址和大小在trunk/arch/arm/include/asm/fixmap.h文件中指定了,
該區(qū)域的地址范圍:[0xfff00000,0xfffe0000],該區(qū)域是屬于最頂部的pte頁表中
(set_top_pte),他為系統(tǒng)中的每個cpu都保留了16個page頁的虛擬地址。
該區(qū)域有兩個特殊函數(shù):
- fix_to_virt/virt_to_fix
- #define __virt_to_fix(x)(((x) - FIXADDR_START) >> PAGE_SHIFT)
表示虛擬地址相對FIXADDR_START偏移的頁框數(shù),該返回值應(yīng)該屬于
[0,15]之間。
第九個區(qū)域:CPUvector page
該區(qū)域是用來映射cpu的中斷向量表,因為linux arm使用的高端向量,即cpu中斷產(chǎn)生時,pc指針會自動跳轉(zhuǎn)到0xffff0000+4*vector_num的地方。
圖4
line1107分配一個低端的物理內(nèi)存頁框,line1109 early_trap_init將中斷向量表的內(nèi)容拷貝到這個新分配的物理頁框中。
圖5
line1149-1153:將line1107行分配的物理頁映射到虛擬地址0xffff0000,為cpu中斷產(chǎn)生時,做好準(zhǔn)備(對應(yīng)的地址有各自的跳轉(zhuǎn)代碼,來處理各自的中斷異常)。在這里這個物理頁同樣是存在兩個虛擬地址的映射,一個是低端虛擬地址的影射,一個是高端虛擬地址的映射
最后附一個我們實際使用中的contexA9雙核,ram為1GB大小的系統(tǒng)的linux內(nèi)存分布情況圖:
圖6
可以結(jié)合圖1和圖6一起分析來加深對linux的內(nèi)存分布情況的理解,至于圖1是怎么來的,就需要看上面每個段的具體分析。