Linux內核在啓動時會打印出內核內存空間的佈局圖,下面是ARM Vexpress平臺打印出來的內存空間佈局圖:linux
這部分信息打印是在mem_init()函數中實現的。express
[start_kernel->mm_init->mem_init] pr_notice("Virtual kernel memory layout:\n" " vmalloc : 0x%16lx - 0x%16lx (%6ld GB)\n" #ifdef CONFIG_SPARSEMEM_VMEMMAP " vmemmap : 0x%16lx - 0x%16lx (%6ld GB maximum)\n" " 0x%16lx - 0x%16lx (%6ld MB actual)\n" #endif " fixed : 0x%16lx - 0x%16lx (%6ld KB)\n" " PCI I/O : 0x%16lx - 0x%16lx (%6ld MB)\n" " modules : 0x%16lx - 0x%16lx (%6ld MB)\n" " memory : 0x%16lx - 0x%16lx (%6ld MB)\n" " .init : 0x%p" " - 0x%p" " (%6ld KB)\n" " .text : 0x%p" " - 0x%p" " (%6ld KB)\n" " .data : 0x%p" " - 0x%p" " (%6ld KB)\n", MLG(VMALLOC_START, VMALLOC_END), #ifdef CONFIG_SPARSEMEM_VMEMMAP MLG((unsigned long)vmemmap, (unsigned long)vmemmap + VMEMMAP_SIZE), MLM((unsigned long)virt_to_page(PAGE_OFFSET), (unsigned long)virt_to_page(high_memory)), #endif MLK(FIXADDR_START, FIXADDR_TOP), MLM(PCI_IO_START, PCI_IO_END), MLM(MODULES_VADDR, MODULES_END), MLM(PAGE_OFFSET, (unsigned long)high_memory), MLK_ROUNDUP(__init_begin, __init_end), MLK_ROUNDUP(_text, _etext), MLK_ROUNDUP(_sdata, _edata));
編譯器在編譯目標文件而且連接完成以後,就能夠知道內核映像文件最終的大小,接下來打包成二進制文件,該操做由arch/arm/kernel/vmlinux.ld.S
控制,其中也劃定了內核的內存佈局。架構
內核image自己佔據的內存空間從_text段到 _end段,而且分爲以下幾個段:函數
__init_begin
和 __init_end
爲init段的起始和結束地址,包含了大部分的模塊初始化的數據。_sdata
和_edata
爲數據段的起始和結束地址,包含了大部份內核的變量;__bss_start
和__bss_stop
爲BSS段的開始和結束地址,包含初始化爲0的全部靜態全局變量。上述幾個段的大小在編譯連接時根據內核配置來肯定,由於每種配置代碼段和數據段長度都不相同,這取決與要編譯哪些內核模塊,可是起始地址__text
老是相同的。內核編譯完成後,會生成一個System.map文件,查詢這個文件能夠找到這些地址的具體數值。佈局
內核使用虛擬地址從MODULES_VADDR到MODULES_END這段14MB大小的內存區域。3d
#define MODULES_VADDR (PAGE_OFFSET - SZ_16M) #ifdef CONFIG_HIGHMEM #define MODULES_END (PAGE_OFFSET - PMD_SIZE) #else #define MODULES_END (PAGE_OFFSET) #endif
用戶空間和內核空間使用3:1的劃分方法時,內核空間只有1GB大小。這1GB的映射空間,其中有一部分用於直接映射物理地址。這個區域稱爲線性映射區。在ARM32平臺上,物理地址[0:760MB]的這一部份內存被線性映射到[3GB:3GB+768MB]的虛擬地址上。線性映射區的虛擬地址和物理地址相差PAGE_OFFSET,即3GB。內核中有相關的宏來實現線性映射區虛擬地址與物理地址的查找過程,例如__pa(x)
和__va(x)
code
[arch/arm/include/asm/memory.h] #define __pa(x) __virt_to_phys((unsigned long)(x)) #define __va(x) ((void *)__phys_to_virt(phys_addr_t)(x)) static inline phys_addr_t __virt_to_phys(unsigned long x) { return (phys_addr_t)x - PAGE_OFFSET + PHYS_OFFSET; } static inline unsigned long __phys_to_virt(phys_addr_t x) { return x - PHYS_OFFSET + PAGE_OFFSET; }
其中,__pa()把線性映射區的虛擬地址轉換爲物理地址,轉換公式很簡單,即用虛擬地址減去PAGE_OFFSET(3GB),而後再加上PHYS_OFFSET(這個值在有的ARM平臺上爲0,在ARM Vexpress平臺該值爲0x6000_0000)。orm
那麼高端內存的起始地址(760MB)如何肯定呢?blog
在內核初始化內存時,在santiy_check_meminfo()
函數中肯定高端內存的起始地址,全局變量high_memory來存放高端內存的起始地址。內存
static void * __initdata vmalloc_min = (void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET); void __init sanity_check_meminfo(void) { phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1; arm_lowmem_limit = vmalloc_limit; high_memory = __va(arm_lowmem_limit - 1) + 1; }
vmalloc_min計算出來的結果是0x2F80_0000,即760MB;
爲何內核只線性映射760MB呢?剩下的264MB的虛擬地址空間用來作什麼呢?
那是保留給vmallc,fixmap和高端向量等使用的。內核許多驅動使用vmalloc來分配連續的虛擬地址的內存,由於有的驅動不須要連續的物理地址的內存;除此以外,vmalloc還能夠用於高端內存的臨時映射。一個32bit系統中實際支持的內存數量會超過內核線性映射的長度,可是內核具備對全部內存的尋找能力。
/* * Just any arbitrary offset to the start of the vmalloc VM area: the * current 8MB value just means that there will be a 8MB "hole" after the * physical memory until the kernel virtual memory starts. That means that * any out-of-bounds memory accesses will hopefully be caught. * The vmalloc() routines leaves a hole of 4kB between each vmalloced * area for the same reason. ;) */ #define VMALLOC_OFFSET (8*1024*1024) #define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1)) #define VMALLOC_END 0xff000000UL
vmalloc區域在ARM32內核中,從VMALLOC_START開始到VMALLOC_END結束,即從0xf000_0000到0xff00_0000,大小爲240MB。從VMALLOC_START開始以前有一個8MB的洞,用於捕捉越界訪問。
內核一般把物理內存低於760MB的稱爲線性映射內存(Normal Memory),而高於760MB以上的稱爲高端內存(High Memory)。因爲32位系統的尋址能力只有4GB,對於物理內存高於760MB而低於4GB的狀況,咱們能夠從保留240MB的虛擬地址劃出一部分用於動態映射高端內存,這樣內核就能夠訪問到所有的4GB的內存了。若是物理內存高於4GB,那麼在ARMv7-A架構中就要使用LPE機制來擴展物理內存訪問了。用於映射高端內存的虛擬地址空間有限,因此又能夠劃分爲兩部分,一部分是臨時映射區,另外一部分爲固定映射區。PKMAP指向的就是固定映射區。如圖2.6所示是ARM Vexpress平臺上畫出內核空間的內存佈局圖,詳細能夠參考文檔documentation/arm/memory.txt文件。