本文主要簡介在X86體系結構下和在ARM體系結構下,Linux內存布局的概況,力求簡單明了,不過多深入概念,多以圖示的方式來記憶理解,一圖勝萬言。
X86體系結構
在X86體系結構下,物理內存地址一般從0x0000_0000開始,而Linux內核主要按照在物理地址0x0010_0000開始的地方,即物理地址1M以上的空間。那最開始的1M空間是用來干什么的呢?
考慮到通用的IBM-PC體系結構,最開始的1M空間由BIOS例程和映射ISA圖形卡的內存,這塊區域為了所有IBM兼容PC從640K到1M的物理地址,始終存在,但無法被操作系統使用。
主要內存布局如上圖所示:
mmap映射區向下擴展,堆向上擴展,兩者相對擴展,直到耗盡虛擬地址空間中的剩余區域。
BSS段用來存放程序中未初始化的全局變量,該段內容只記錄數據所需空間大小,並不分配真實空間。
DATA段用來存放程序中已初始化的全局變量,為數據分配空間,數據具體值保持在目標文件中。
CODE段用來存放程序中執行代碼的內存區域,通常為大小確定的只讀段,包括只讀常量、只讀代碼等。
ARM體系結構
以S3C2410為例子,假設物理內存為64M,映射到ARM的起始物理地址為【0x3000_0000~0x3200_0000】,這個由硬件接線決定。我們可以通過查看內核編譯輸出的System.map文件來了解內核虛擬地址空間布局,結果類似如下:
可以看出內核鏡像大小為3.7M,虛擬地址空間起始地址為0xc000_0000(這是開啟MMU之后的虛擬地址空間),在內核head.S文件中,有內核線性地址和物理地址的描述,見下圖:
PAGE_OFFSET為0xC000_0000,為內核虛擬地址相對偏移(相對於0地址的偏移),PHYS_OFFSET為內核載入實際物理地址相對偏移,不同的硬件板子,ARM訪問的內存物理地址不一樣,這里以0x3000_0000(這由硬件接線決定)為假設。TEXT_OFFSET為0x0000_8000,為編譯時指定的代碼段偏移,所以,uboot最后啟動內核的地址為內核代碼指定的KERNEL_RAM_PADDR(0x3000_8000),這樣才能正常運行,而內核的入口地址和載入地址,最好設置成一樣。而uboot加載kernel的實際地址設置為0x3000_7fc0,比KERNEL_RAM_PADDR少64個字節,這可以避免拷貝內核,64個字節為uImage內核鏡像針對uboot添加的特定頭部信息。
從上面的檢查宏可以看出,內核開始的物理地址,必須開始在0xXXXX_8000的地址空間。
swapper_pg_dir 為內核全局頁表的起始地址,stext為內核的入口虛擬地址,因此,可以看出,全局頁表占據16K的空間。
head.S文件的功能,主要獲取處理器類型和機器類型信息,創建臨時頁表,然后開啟MMU,並進入第一個C語言函數start_kernel。
更加詳細的可以參見:
http://www.arm.linux.org.uk/developer/memory.txt
內核提供的內存訪問接口
內核提供的所有接口都是以頁為單位分配內存的,其中,最核心的函數為alloc_pages,其原型如下:
struct page * alloc_pages(unsigned int gfp_mask,unsigned int order)
該函數分配2^order個連續的物理頁,並返回指向第一個頁的指針。如果分配出錯,返回NULL。其他的一些頁分配接口。
1. kmalloc/kfree: 基於slab分配器的內存分配函數,支持分配大小32byte-128KB,分配的為物理地址連續的一段內存,使用GFP_KERNEL時,函數可能睡眠。使用GFP_ATOMIC時,函數不睡眠。
kzalloc:基於kmallc的,分配一段內核內存並且清零。
2.vmalloc/vfree:工作在內核虛擬空間的VMALLOC_START和VMALLOC_END所代表的vmalloc區,支持分配大內容,分配為邏輯地址連續的一段內存,速度慢,效率低,可能睡眠,映射的地址優先從ZONE_HIGHMEM分配.
3.利用slab分配器的高速緩存,使用kmem_cache_create和keme_cache_alloc這兩個函數。/proc/slabinfo查看所有的kmem_cache緩存。 keme_cache_alloc 用於需要頻繁分配和釋放同一類型的數據結構對象,充分利用硬件緩存,提升系統性能。
相反的處理函數有kmem_cache_destory和kmem_cache_free
4.用於多處理器的per-CPU變量,核心思想是,通過為系統中每個處理器都分配一個CPU特定的變量副本,減少多處理器並發訪問時的鎖定操作,提高系統性能。
強烈推薦優秀參考鏈接:arm-linux啟動過程