1. 核心記憶體配置
AArch64 Linux通常使用以下配置:
- 4KB頁面, 使用3級或4級轉換表,支援39位(512GB)或48位(256TB)的虛拟位址。
- 64KB頁面,使用2級轉換表,支援42位(4TB)虛拟位址。
他們的記憶體布局是一緻的。
以核心defconfig預設的4KB page + 4 levels配置為例,LINUX在arm架構上把虛拟位址空間劃分為2個空間, 虛拟位址和核心位址, 每個空間最大支援256TB.
Start End Size Use
-----------------------------------------------------------------------
0x0000000000000_0000 0x0000_ffff_ffff_ffff 256TB user
0xffff_0000_0000_0000 0xffff_ffff_ffff_ffff 256TB kernel
2. 核心記憶體布局的列印
在arm64 4.16的核心之前,核心基本完成記憶體初始化工作後會列印出核心的記憶體布局
qemu arm64列印如下:
這部分列印在mem_init()函數中實作(arch/arm64/mm/init.c)
start_kernel()->mm_init()->mem_init()
#define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLG(b, t) b, t, ((t) - (b)) >> 30
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K)
pr_notice("Virtual kernel memory layout:\n");
#ifdef CONFIG_KASAN
pr_notice(" kasan : 0x%16lx - 0x%16lx (%6ld GB)\n",
MLG(KASAN_SHADOW_START, KASAN_SHADOW_END));
#endif
pr_notice(" modules : 0x%16lx - 0x%16lx (%6ld MB)\n",
MLM(MODULES_VADDR, MODULES_END));
pr_notice(" vmalloc : 0x%16lx - 0x%16lx (%6ld GB)\n",
MLG(VMALLOC_START, VMALLOC_END));
pr_notice(" .text : 0x%p" " - 0x%p" " (%6ld KB)\n",
MLK_ROUNDUP(_text, _etext));
pr_notice(" .rodata : 0x%p" " - 0x%p" " (%6ld KB)\n",
MLK_ROUNDUP(__start_rodata, __init_begin));
pr_notice(" .init : 0x%p" " - 0x%p" " (%6ld KB)\n",
MLK_ROUNDUP(__init_begin, __init_end));
pr_notice(" .data : 0x%p" " - 0x%p" " (%6ld KB)\n",
MLK_ROUNDUP(_sdata, _edata));
pr_notice(" .bss : 0x%p" " - 0x%p" " (%6ld KB)\n",
MLK_ROUNDUP(__bss_start, __bss_stop));
pr_notice(" fixed : 0x%16lx - 0x%16lx (%6ld KB)\n",
MLK(FIXADDR_START, FIXADDR_TOP));
pr_notice(" PCI I/O : 0x%16lx - 0x%16lx (%6ld MB)\n",
MLM(PCI_IO_START, PCI_IO_END));
#ifdef CONFIG_SPARSEMEM_VMEMMAP
pr_notice(" vmemmap : 0x%16lx - 0x%16lx (%6ld GB maximum)\n",
MLG(VMEMMAP_START, VMEMMAP_START + VMEMMAP_SIZE));
pr_notice(" 0x%16lx - 0x%16lx (%6ld MB actual)\n",
MLM((unsigned long)phys_to_page(memblock_start_of_DRAM()),
(unsigned long)virt_to_page(high_memory)));
#endif
pr_notice(" memory : 0x%16lx - 0x%16lx (%6ld MB)\n",
MLM(__phys_to_virt(memblock_start_of_DRAM()),
(unsigned long)high_memory));
kasan: KASAN是一個動态檢測記憶體錯誤的工具, 原理是利用額外的記憶體标記可用記憶體的狀态. 這部分額外的記憶體被稱作shadow memory(影子區)。KASAN将1/8的記憶體用作shadow memory。
modules: 128MB的核心子產品區域,是核心子產品使用的虛拟位址空間
vmalloc: vmalloc函數使用的虛拟位址空間,kernel image也在vmalloc區域,核心鏡像的起始位址 = KIMAGE_ADDR + TEXT_OFFSET, TEXT_OFFSET是記憶體中的核心鏡像相對記憶體起始位置的偏移。
.text: 代碼段。 _text是代碼段的起始位址,_etext是結束位址, kernel image放在這段位置。
.rodata: read-only-data. 常量區,存放程式中定義為const的全局變量。
.init: 對應大部分子產品初始化的資料,初始化結束後就會釋放這部分記憶體。
.data: 資料段。 包含核心大部分已初始化的全局變量。
.bss: 靜态記憶體配置設定段。 包含所有未初始化或初始化為0的靜态全局變量。
fixed: 固定映射區。 在核心的啟動過程中,有些子產品需要使用虛拟記憶體并mapping到指定的實體位址上,而且,這些子產品也沒有辦法等待完整的記憶體管理子產品初始化之後再進行位址映射。是以,linux kernel固定配置設定了一些fixmap的虛拟位址,這些位址有固定的用途,使用該位址的子產品在初始化的時候,講這些固定配置設定的位址mapping到指定的實體位址上去。(Fix-Mapped Addresses)
PCI I/O: pci裝置的I/O位址空間
vmemmap: 記憶體的實體位址如果不連續的話,就會存在記憶體空洞(稀疏記憶體),vmemmap就用來存放稀疏記憶體的page結構體的資料的虛拟位址空間。
memory: 線性映射區,範圍是【0xffff_8000_0000_0000, 0xffff_ffff_ffff_ffff】, 一共有128TB, 但這裡代碼對應的是memblock_start_of_DRAM()和memblock_end_of_DRAM()函數。
memory根據實際實體記憶體大小做了限制,是以memroy顯示了實際能夠通路的記憶體區。
MLM(__phys_to_virt(memblock_start_of_DRAM()), (unsigned long)high_memory))
high_memory = __va(memblock_end_of_DRAM() - 1) + 1;
最終是通過dts或acpi中配置的memory節點确定的。
後面的核心版本删掉了這段列印,如果需要的話可以手動revert掉該更新檔。
3. 核心記憶體布局圖
根據arm64啟動的列印資訊, 确定arm64 核心記憶體布局圖:
4. 參考資料:
Memory Layout on AArch64 Linux
Fix-Mapped Addresses