Linux内核ARM32内核内存布局图,Linux内核在启动时会打印出内核内存空间的布局图,下面是ARM Vexpress平台打印出来的内存空间布局图:
Virtual kernel memory layout:
vector : 0xffff0000 - 0xffff1000 ( 4 kB)
fixmap : 0xffc00000 - 0xfff00000 (3072 kB)
vmalloc : 0xf0000000 - 0xff000000 ( 240 MB)
lowmem : 0xc0000000 - 0xef800000 ( 760 MB)
pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
.text : 0xc0008000 - 0xc0658750 (6466 kB)
.init : 0xc0659000 - 0xc0782000 (1188 kB)
.data : 0xc0782000 - 0xc07b1920 ( 191 kB)
.bss : 0xc07b1920 - 0xc07db378 ( 167 kB)
这部分信息的打印是在mem_init()函数中实现的。
[start_kernel->mm_init->mem_init]
pr_notice("Virtual kernel memory layout:\n"
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
" fixmap : 0x%08lx - 0x%08lx (%4ld kB)\n"
" vmalloc : 0x%08lx - 0x%08lx (%4ld MB)\n"
" lowmem : 0x%08lx - 0x%08lx (%4ld MB)\n"
#ifdef CONFIG_HIGHMEM
" pkmap : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
#ifdef CONFIG_MODULES
" modules : 0x%08lx - 0x%08lx (%4ld MB)\n"
#endif
" .text : 0x%p" " - 0x%p" " (%4td kB)\n"
" .init : 0x%p" " - 0x%p" " (%4td kB)\n"
" .data : 0x%p" " - 0x%p" " (%4td kB)\n"
" .bss : 0x%p" " - 0x%p" " (%4td kB)\n",
MLK(UL(CONFIG_VECTORS_BASE), UL(CONFIG_VECTORS_BASE) +
(PAGE_SIZE)),
MLK(FIXADDR_START, FIXADDR_END),
MLM(VMALLOC_START, VMALLOC_END),
MLM(PAGE_OFFSET, (unsigned long)high_memory),
#ifdef CONFIG_HIGHMEM
MLM(PKMAP_BASE, (PKMAP_BASE) + (LAST_PKMAP) *
(PAGE_SIZE)),
#endif
#ifdef CONFIG_MODULES
MLM(MODULES_VADDR, MODULES_END),
#endif
MLK_ROUNDUP(_text, _etext),
MLK_ROUNDUP(__init_begin, __init_end),
MLK_ROUNDUP(_sdata, _edata),
MLK ROUNDUP( bss start, bss stop))
编译器在编译目标文件并且链接完成之后,就可以知道内核映像文件最终的大小,接下来打包成二进制文件,该操作由arch/arm/kernel/vmlinux.ld.S
控制,其中也划定了内核的内存布局。
内核image本身占据的内存空间从_text段到_end段,并且分为如下几个段。
- 代码段:_text和_etext为代码段的起始和结束地址,包含了编译后的内核代码。
- init段:__init_begin和__init_end为init段的起始和结束地址,包含了大部分模块初始化的数据。
- 数据段:_sdata和_edata为数据段的起始和结束地址,保存大部分内核的变量。
- BSS段:__bss_start和__bss_stop为BSS段的开始和结束地址,包含初始化为0的所有静态全局变量。
上述几个段的大小在编译链接时根据内核配置来确定,因为每种配置的代码段和数据段长度都不相同,这取决于要编译哪些内核模块,但是起始地址_text总是相同的。内核编译完成之后,会生成一个System.map文件,查询这个文件可以找到这些地址的具体数值。
figo# cat System.map
...
c0008000 T _text
...
c0658750 A _etext
c0659000 A __init_begin
...
c0782000 A __init_end
c0782000 D _sdata
...
c07b1920 D _edata
c07b1920 A __bss_start
...
c07db378 A __bss_stop
c07db378 A _end
...
内核模块使用虚拟地址从MODULES_VADDR到MODULES_END的这段14MB大小的内存区域。
#define MODULES_VADDR (PAGE_OFFSET - SZ_16M)
/*
* The highmem pkmap virtual space shares the end of the module area.
*/
#ifdef CONFIG_HIGHMEM
#define MODULES_END (PAGE_OFFSET - PMD_SIZE)
#else
#define MODULES_END (PAGE_OFFSET)
#endif
用户空间和内核空间使用3:1
的划分方法时,内核空间只有1GB大小。这1GB的映射空间,其中有一部分用于直接映射物理地址,这个区域称为线性映射区。在\2RM32
平台上,物理地址[0:760MB]
的这一部分内存被线性映射到[3GB:3GB+ 760MB]
的虚拟地址上。线性映射区的虚拟地址和物理地址相差PAGE_OFFSET,即3GB。内核中有相关的宏来实现线性映射区虚拟地址到物理地址的查找过程,例如__pa(x)
和__va(x)
。
[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)。
那高端内存的起始地址(760MB)是如何确定的呢?
在内核初始化内存时,在sanity_check_meminfo()函数中确定高端内存的起始地址,全局变量high_memory来存放高端内存的起始地址。
[arch/arm/mm/mmu.c]
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的虚拟地址空间用来做什么呢?
那是保留给vmalloc、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指向的就是固定映射区。如下图所示是ARM Vexpress平台上画出内核空间的内存布局图,详细可以参考内核中文档documentation/arm/memory.txt
文件。
酷客网相关文章:
评论前必须登录!
注册