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