arm32linux内存分布,linux内存管理笔记(⼗六)----ARM32内
空间分配
Linux内核⼀般将处理器的虚拟地址空间分成两部分,在32系统上,地址空间在⽤户进程和内核之间划分的典型⽐例为3:1,在给出的4GB 的虚拟地址空间中,0 ~ 3GB将⽤于⽤户空间⽽3GB ~ 4GB将⽤于内核空间,内核提供了相关的配置项来修改该⽐例,也就是说Kernel最多寻址1GB的虚拟地址空间。
当CPU启动MMU后,CPU访问的时虚拟地址空间,然后由MMU根据页表转换成物理地址,页表是由Kernel维护的,所以Kernel可以决定1GB的虚拟地址空间具体映射到什么物理地址。但是不管Kernel怎么映射,最多也只能映射1G的物理内存,所以如果⼀个系统有超过1G的物理内存,在某⼀时刻,必然有⼀部分空间是内核⽆法访问到的,对于该问题内核借助于⾼端内存(highmem)⽅法来管理多余的内存,本章的主要讲解以下内容
什么是⾼端内存及其作⽤
ARM32 Linux的内存布局
1. 什么是⾼端内存
对于32系统,内核使⽤3G ~ 4G的虚拟地址空间,那么只有1G的地址空间可以⽤来映射物理空间。但是,如果我们使⽤的内存⼤于1G的情况,是不是超过1G的内存就⽆法使⽤了呢?为此内核引⼊了⼀个⾼端内存的概念,把1G的虚拟地址空间分成两部分
低端内存空间:⼩于high_memory的物理地址空间,这部分的内存物理地址和3G开始的线性地址是⼀⼀映射的,也就是说内核使⽤的线性地址空间3G ~ (3G + high_memory)和物
理地址空间0 ~ high_memory⼀⼀映射
⾼端内存空间:剩下的128M的线性空间⽤来映射剩下的⼤于high_memory的物理地址空间
对于以上,我们可以知道以下问题
对于⾼端内存high_memory,现在⼀般是896M,当我们只有512M的内存的时候,就没有⾼端内存⼀说了,因为512MB的物理内存已经被kernel直接映射
64位系统下不会有high memory,因为64位虚拟地址空间⾮常⼤(分给kernel的也很⼤),完全能够直接映射全部物理内存。
在32位系统上,没有任何进程能够有效地使⽤超过3GB的内存。这意味着购买超过4GB的从理论上是发挥不出其优势
对于我们使⽤的IMX6U,由于使⽤了CMA,所以其⾼端内存high_memory的地址空间范围为0xe000 0000 ~ 0xFFFF FFFF(512 MB ~ 1024MB)。那么内核如何借助512M⾼端内存地址空间实现访问访问所有4GB的物理内存。⽐如当内核项访问⾼于High_memory的物理地址时,可以从(0xc000 0000 + high_memory) ~ 0xFFFF FFFF地址空间范围内⼀段相应⼤⼩的空闲虚拟地址,建⽴映射到想访问的那段物理内存,⽤完后归还。这样就所有的⼈都可以借⽤这段地址空间访问物理地址,访问所有物理内容如下图所⽰:
我们可以知道⾼端内存的最基本思想:借⼀段地址空间,建⽴临时地址映射,⽤完后释放,达到这段地址空间可以循环使⽤,访问所有物理内存。万⼀有内核进程或模块⼀直占⽤某段逻辑地址空间不释放,怎么办?若真的出现的这种情况,则内核的⾼端内存地址空间越来越紧张,若都被占⽤不释放,则没有建⽴映射到物理内存都⽆法访问了。
2. Linux内核⾼端内存的划分
对于⾼端内存,⼀般划分如下:
动态内存映射区:虚拟内存中连续,但物理内存不连续的内存,可以在vmalloc区域分配。该机制通常⽤于⽤户空间,内核⾃⾝会试图尽⼒避免⾮连续的物理内存。
永久内存映射区:该区域可访问⾼端内存,访问⽅法是使⽤alloc_page(_GFP_HIGHMEM)分配⾼端内存页或使⽤kmap函数将分配到⾼端内存映射到该区域
固定映射区:固定映射是与物理内存空间中的固定页关联的虚拟地址空间项,该区域和4GB的顶端只有4K的隔离带,其每个地址项都服务于特定的⽤途。
对于⾼端内存的划分,其中fixed mapping主要⽤在boot阶段⽤来永久性映射⼀些物理地址固定的数据结构或者硬件地址(⽐如ACPI
表,APIC地址,等等)。kmap area是kernel⽤来临时建⽴映射来访问物理页⽤的,可⽤的地址空间也⽐较⼩。绝⼤部分reserve了给vmalloc area,vmalloc和ioremap返回的都是这个空间⾥的地址。
3. ARM32内存分布图
生活感悟句子了解完低端内存和⾼端内存的概念,我们来看看我们实际上内存布局是怎么样的?Linux内核在启动时,会打印出内核内存空间的布局图,下⾯是ARM IMX6平台打印出来的内存空间布局图
Virtual kernel memory layout:
vector : 0xffff0000 - 0xffff1000 ( 4 kB)
fixmap : 0xffc00000 - 0xfff00000 (3072 kB)
vmalloc : 0xe0800000 - 0xff800000 ( 496 MB)
lowmem : 0xc0000000 - 0xe0000000 ( 512 MB)
pkmap : 0xbfe00000 - 0xc0000000 ( 2 MB)
modules : 0xbf000000 - 0xbfe00000 ( 14 MB)
.text : 0xc0008000 - 0xc0a00000 (10208 kB)
.init : 0xc0e00000 - 0xc1000000 (2048 kB)
.data : 0xc1000000 - 0xc1074c00 ( 467 kB)
.bss : 0xc1076000 - 0xc10e8eec ( 460 kB)
这部分信息是的打印是在mem_init()函数中实现的
pr_notice("Virtual kernel memory layout:\n"
" vector : 0x%08lx - 0x%08lx (%4ld kB)\n"
#ifdef CONFIG_HAVE_TCM
" DTCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
" ITCM : 0x%08lx - 0x%08lx (%4ld kB)\n"
#endif
张馨予和吴卓羲" 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)),
#ifdef CONFIG_HAVE_TCM
MLK(DTCM_OFFSET, (unsigned long) dtcm_end),
MLK(ITCM_OFFSET, (unsigned long) itcm_end),
#endif
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));
内核image本⾝占据的内存空间从_text段到 _end段,并且分为如下⼏段
代码段:_text和 _etext为代码段的起始和结束地址,包含了编译后的内核代码
init 段:_init_begin 和 _init_end为init段的起始和结束地址,包含了⼤部分模块初始化的数据
数据段:_sdata和 _edata数据段的起始和结束地址,保存⼤部分内核的变量
BSS段:bss_start和 _bss_stop为BSS段的开始和结束地址,包含了初始化为0的所有的静态全局变量
那么⾼端内存的起始地址是如何确定的呢?在内核初始化内存时候,在adjust_lowmem_bounds函数中确定低端内存和⾼端内存的起始地址
vmalloc_limit = (u64)(uintptr_t)vmalloc_min - PAGE_OFFSET + PHYS_OFFSET;
小学竞选班长演讲稿arm_lowmem_limit = lowmem_limit;
五一去哪儿玩high_memory = __va(arm_lowmem_limit - 1) + 1;
计算出来内核线性映射512M地址空间,剩下的保留给vmalloc,fixmap和⾼端向量表使⽤,内核很多驱动使⽤vmalloc来分配连续虚拟地址内存,因为有的驱动不需要连续物理地址内存;除此之外,vmalloc还可以⽤于⾼端内存的临时映射。对于内核vmalloc空间配置如下
#define VMALLOC_OFFSET(8*1024*1024)
#define VMALLOC_START(((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))
#define VMALLOC_END0xff800000UL
vmalloc区域在ARM32内核中,从VMALLOC_START开始到VMALLOC_END结束,即从0xe0800000 - 0xff800000,⼤⼩为
512MB,在VMALLOC_START开始之前有⼀个8MB的空间,⽤于捕获越界访问。
4. 总结
对于⾼端内存,⾸先我们需要明确这是⼀个物理内存的概念,它仅仅是内核中的内存管理模块看待物理内存的时候的概念。在内核中,除了内存管理模块直接操作物理地址之外,其他的模块,都需要操作虚拟地址,⽽虚拟地址是需要内存管理模块分配和映射的。例如,我们有⼀个2G的内存,现在内核模块如果想要访问物理内存1.5G的地⽅,应该怎么办呢?
⾸先,我们不能使⽤物理地址,你需要使⽤内存管理模块给你分配虚拟地址,但是虚拟地址0 ~ 3G已经被⽤户态进程占⽤去了,作为内核不能使⽤。
其次,对于1.5G的地⽅就算映射了,也不是你真正要访问的物理内存地址,所以对于内核,能够使⽤虚拟内存地址,只剩下⾼端内存这块了。
内核通常把物理内存低于high_memory的地址成为线性映射地址,⽽⾼于high_memory以上的成为⾼端内存。由于32位系统寻址能⼒只有4GB,对于物理内存⾼于high_memory⽽低于4GB的情况,我们就需要从虚拟地址空间中画出⼀部分来⽤于动态映射⾼端内存,这样就可以访问到全部的4GB的内存。⽽对于映射⾼端内存的虚拟地址空间,可以划分位固定映射区和临时映射区,后⾯章节中单独来讲解每个的功能。对于我们现在使⽤的IMX6U的内核空间的内存分布图如下所⽰,对于途中的⾼端内存每个平台可能都不⼀样,主要是通过客户实际需要来配置