本文kernel代码分析基于以下
1.linux-4.14.159
2.64bit代码处理逻辑
linux内存管理非常复杂及庞大,这节我们看下内核虚拟内存布局,理解内核内存布局对认识内存管理至关重要,我们从源码来进行解析,这样通过理解记忆的话比较牢固。
在linux系统中,内核的虚拟地址空间对所有进程共享的,然而每一个进程存在自己的用户虚拟地址空间和共享的内核虚拟地址空间,此节我们仅研究内核虚拟地址空间。
arm v8架构可以支持32~48位的物理寻址,最大可以寻找256TB的物理地址空间,64位支持64位寻址,为什么最大48位呢?因为48位对目前的应用已经够用,就无需进行64位的物理寻址,太大势必会增加硬件的设计复杂度。
针对arm64的硬件体系结构,如果page大小位4KB,使用3级或4级转换表,内核虚拟地址空间支持39位(512G)或48位(256TB)的寻址空间。
先看地址空间大小:
arch\arm64\include\asm\memory.h
#define VA_BITS (CONFIG_ARM64_VA_BITS)
//.config中查看CONFIG_ARM64_VA_BITS=39,
VA_BITS虚拟地址宽度,最大支持48位,不同系统看具体配置的值。如我这边config中配置的时39位,即64位系统配置为39位,值为0x7F FFFF FFFF,这个换算过来即位512G。
0x0000_0000_0000_0000~0x0000_007f_ffff_ffff 512GB user
0xffff_ff80_0000_0000~0xffff_ffff_ffff_ffff 512GB kernel
如之前阐述,linux在arm架构上虚拟地址分位用户虚拟地址空间和内核虚拟地址空间这两个空间,如上每个空间支持512GB寻址。
内核虚拟地址空间起始地址
#define VA_START (UL(0xffffffffffffffff) - \
(UL(1) << VA_BITS) + 1)
//展开0xFFFF_FFFF_FFFF_FFFF-0x0000_0080_0000_0000+1=0xFFFF_FF80_0000_ 0000
//也就是说kernel虚拟地址空间起始地址为0xFFFF_FF80_0000_ 0000
内核线性映射的起始地址
#define PAGE_OFFSET (UL(0xffffffffffffffff) - \
(UL(1) << (VA_BITS - 1)) + 1)
///计算下来为0xFFFF_FFC0_0000_ 0000
KASAN和MODULE区域
KASAN(KernelAddressSANitizer)是一个动态检测内存错误的工具,主要解决use-after-free和out-of-bounds问题,其原理是利用额外的内存标记可用内存的状态。这部分额外的内存被称作shadow memory(影子区),KASAN将1/8的内存用作shadow memory,详细的内容请百度了解。
MODULE:是内核模块使用的虚拟地址空间,其大小为128MB
/*KIMAGE_VADDR - the virtual address of the start of the kernel image*/
#define KIMAGE_VADDR (MODULES_END)
#define MODULES_END (MODULES_VADDR + MODULES_VSIZE)
#define MODULES_VADDR (VA_START + KASAN_SHADOW_SIZE)
// 0xFFFF_FF90 0000 0000
#define MODULES_VSIZE (SZ_128M)
#ifdef CONFIG_KASAN
#define KASAN_SHADOW_SIZE (UL(1) << (VA_BITS - 3))
// 0x10_0000_0000 :64G
#define KASAN_THREAD_SHIFT 1
#else
我们可以看到MODULES_VADDR ,等于VA_START + KASAN_SHADOW_SIZE,而KASAN_SHADOW_SIZE计算下来为64G,
因此MODULES_VADDR 地址为 0xFFFF_FF90 0000 0000
MODULES_END 地址为0xFFFF_FF90_0800_0000
///已确认的内存布局如下:
0xFFFF_FF80_0000_0000 ------ VA_START
Kasan大小64G
0xFFFF_FF90_0000_0000 ------ MODULES_VADDR
MODULE大小为128M
0xFFFF_FF90_0800_0000 ------ MODULES_END
64G为512G的1/8,因此符合kasan的定义。
再看VMALLOC区域
此区域为vmalloc函数使用的虚拟地址空间,注意因为linux kernel更新很快,笔者研究的代码kernel image映射区域已经从从原来的线性映射区域搬移到了VMALLOC区域
#define PUD_SIZE (_AC(1, UL) << PUD_SHIFT)
#if CONFIG_PGTABLE_LEVELS > 3
#define PUD_SHIFT ARM64_HW_PGTABLE_LEVEL_SHIFT(1)
#endif
page_SHIFT=12 //(4K)
#define SZ_64K 0x00010000
#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3)
//PUD_SHIFT=(12-3)*(4-1)+3=30
//PUD_SIZE=0x4000_0000
arch\arm64\include\asm\memory.h
#define STRUCT_PAGE_MAX_SHIFT 6
#define VMEMMAP_SIZE (UL(1) << (VA_BITS - PAGE_SHIFT - 1 + STRUCT_PAGE_MAX_SHIFT))
//VMEMMAP_SIZE =(UL(1) <<(39-12-1+6=32)=0x1_0000_0000
arch\arm64\include\asm\pgtable.h
#define VMALLOC_START (MODULES_END)
#define VMALLOC_END (PAGE_OFFSET - PUD_SIZE - VMEMMAP_SIZE - SZ_64K)
//VMALLOC_END=00xFFFF_FFC0_0000_ 0000- 0x4000_0000-0x1_0000_0000-0x10000=0xFFFF_FFBE_BFFF_0000
///已确认的内存布局如下:
0xFFFF_FF80_0000_0000 ------ VA_START
Kasan大小64G
0xFFFF_FF90_0000_0000 ------ MODULES_VADDR
MODULE大小为128M
0xFFFF_FF90_0800_0000 ------ MODULES_END /VMALLOC_START
vmalloc大小为186G
0xFFFF_FFBE_BFFF_0000 ------ VMALLOC_END
FIXADDR/PCI_IO/VMEMMAP
FIXADDR: 动态分配虚拟地址以及建立地址映射是一个复杂的过程,在内核完全启动之后,内存管理可以提供各种丰富的API让内核的其他模块可以完成虚拟地址分配和建立地址映射的功能,但是,在内核的启动过程中,有些模块需要使用虚拟内存并mapping到指定的物理地址上,而且,这些模块也没有办法等待完整的内存管理模块初始化之后再进行地址映射。因此,linux kernel固定分配了一些fixmap的虚拟地址,这些地址有固定的用途,使用该地址的模块在初始化的时候,将这些固定分配的地址mapping到指定的物理地址上去(参考1)
PCI_IO : PCI总线设备的地址空间
VMEMMAP:内存的物理地址空间如果不连续,存在很多空洞,称为稀疏内存。vmemmap区域是稀疏内存的page结构体数组的虚拟地址空间
arch\arm64\include\asm\fixmap.h
arch\arm64\include\asm\memory.h
#define VMEMMAP_START (PAGE_OFFSET - VMEMMAP_SIZE)
#define FIXADDR_SIZE (__end_of_permanent_fixed_addresses << PAGE_SHIFT)
#define FIXADDR_START (FIXADDR_TOP - FIXADDR_SIZE)
#define FIXADDR_TOP (PCI_IO_START - SZ_2M)
#define PCI_IO_START (PCI_IO_END - PCI_IO_SIZE)
#define PCI_IO_END (VMEMMAP_START - SZ_2M)
#define PCI_IO_SIZE SZ_16M
#define SZ_2M 0x200000
#define SZ_16M 0x01000000
//FIXADDR_TOP=PAGE_OFFSET - VMEMMAP_SIZE-SZ_2M-PCI_IO_SIZE-SZ_2M
=0xFFFF_FFC0_0000_ 0000-0x1_0000_0000-0x200000 - 0x01000000-0x200000
=0xFFFF_FFBE_FEC0_0000
//FIXADDR_START=这个我们从下面log反推(代码不能确定具体值)
//PCI_IO_START=PAGE_OFFSET - VMEMMAP_SIZE-SZ_2M-PCI_IO_SIZE
=0xFFFF_FFC0_0000_ 0000-0x1_0000_0000-0x200000 -0x01000000
=0xFFFF_FFBE_FEE0_0000
//PCI_IO_END= PAGE_OFFSET - VMEMMAP_SIZE- SZ_2M
= 0xFFFF_FFC0_0000_ 0000-0x1_0000_0000-0x200000
= 0xFFFF_FFBE_FFE0_0000
//VMEMMAP_START =(PAGE_OFFSET - VMEMMAP_SIZE)=0xFFFF_FFC0_0000_ 0000-0x1_0000_0000
= 0xFFFF_FFBF_0000_0000
///已确认的内存布局如下:
0xFFFF_FF80_0000_0000 ------ VA_START
KASAN大小64G
0xFFFF_FF90_0000_0000 ------ MODULES_VADDR
MODULES大小为128M
0xFFFF_FF90_0800_0000 ------ MODULES_END /VMALLOC_START
VMALLOC大小为186G
0xFFFF_FFBE_BFFF_0000 ------ VMALLOC_END
空隙
0xFFFF_FFBE_FE7F_B000 -------FIXADDR_START
FIXADDR_SIZE大小4116 KB
0xFFFF_FFBE_FEC0_0000 ------- FIXADDR_TOP
间隙 2M
0xFFFF_FFBE_FEE0_0000 ------- PCI_IO_START
PCI I/O 大小16M
0xFFFF_FFBE_FFE0_0000 ------- PCI_IO_END
间隙 2M
0xFFFF_FFBF_0000_0000 ------- VMEMMAP_START
VMEMMAP大小4G
0xFFFF_FFC0_0000_0000 ------- PAGE_OFFSET
256G linear mapping
0xFFFF_FFFF_FFFF_FFFF ------- OVER
上面就是我们特定设备 arm64位系统下的内核虚拟内存布局。
看是否和设备log匹配
user : 0x0000000000000000- 0x0000007fffffffff ( 512GB )
[ 0.000000] <6>-(0)[0:swapper]Virtual kernel memory layout:
[ 0.000000] <6>-(0)[0:swapper] modules : 0xffffff9000000000 - 0xffffff9008000000 ( 128 MB)
[ 0.000000] <6>-(0)[0:swapper] vmalloc : 0xffffff9008000000 - 0xffffffbebfff0000 ( 186 GB)
vmlinux : 0xffffff9008000000 - ... ...
[ 0.000000] <6>-(0)[0:swapper] .text : 0x (ptrval) - 0x (ptrval) ( 15488 KB)
[ 0.000000] <6>-(0)[0:swapper] .rodata : 0x (ptrval) - 0x (ptrval) ( 5184 KB)
[ 0.000000] <6>-(0)[0:swapper] .init : 0x (ptrval) - 0x (ptrval) ( 5312 KB)
[ 0.000000] <6>-(0)[0:swapper] .data : 0x (ptrval) - 0x (ptrval) ( 2406 KB)
[ 0.000000] <6>-(0)[0:swapper] .bss : 0x (ptrval) - 0x (ptrval) ( 11128 KB)
[ 0.000000] <6>-(0)[0:swapper] fixed : 0xffffffbefe7fb000 - 0xffffffbefec00000 ( 4116 KB)
[ 0.000000] <6>-(0)[0:swapper] PCI I/O : 0xffffffbefee00000 - 0xffffffbeffe00000 ( 16 MB)
[ 0.000000] <6>-(0)[0:swapper] vmemmap : 0xffffffbf00000000 - 0xffffffc000000000 ( 4 GB maximum)
[ 0.000000] <6>-(0)[0:swapper] 0xffffffbf49000000 - 0xffffffbf4afffec0 ( 31 MB actual)
[ 0.000000] <6>-(0)[0:swapper] memory : 0xffffffd240000000 - 0xffffffd2bfffb000 ( 2047 MB)
[ 0.000000] <6>-(0)[0:swapper]SLUB: HWalign=64, Order=0-3, MinObjects=0, CPUs=8, Nodes=1
[ 0.000000] <6>-(0)[0:swapper] 0xffffffc000000000 0xffffffffffffffff 256GB kernel logic
注意:1.memory项目大小是根据实际物理内存大小做了一定限制,因此memroy只是显示了实际能够访问的内存区的大小。
2.为了对比理解,上面user和vmlinux是笔者手动加上去的,也可以内核打印的代码上加上自己想看的项
从log结果看,设备实际布局和我们代码计算的结果是吻合的。
参考1:
https://www.cnblogs.com/alantu2018/p/8447570.html