AArch64 Linux通常使用以下配置:
他们的内存布局是一致的。
以内核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
在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掉该补丁。
根据arm64启动的打印信息, 确定arm64 内核内存布局图:
Memory Layout on AArch64 Linux
Fix-Mapped Addresses