专题:Linux内存管理专题

关键词:内核内存布局图、lowmem线性映射区、kernel image、ZONE_NORMAL、ZONE_HIGHMEM、swapper_pg_dir、fixmap、vector、pkmap。

内核内存布局图对于理解内存管理至关重要,有了布局图对于理解内存管理初始化,以及虚拟内存,各种内存分配都有辅助作用。

所以可以用一张图来总领,然后逐个介绍每一段的来历,作用等等。

内核内存布局图和内存管理框架图是不同视角的内存管理框图,还包括后面介绍的用户空间内存布局图。

1. 内核内存布局图框图

2. 内核内存布局打印

在内核基本完成内存初始化工作,整体布局稳定之后,start_kernel-->mm_init-->mem_init打印了一段内存layout。

Vexpress平台打印如下:

Memory: 1031428K/1048576K available (4787K kernel code, 156K rwdata, 1364K rodata, 1348K init, 166K bss, 17148K reserved, 0K cma-reserved, 270336K highmem)
Virtual kernel memory layout:
vector : 0xffff0000 - 0xffff1000 ( kB)
fixmap : 0xffc00000 - 0xfff00000 ( kB)
vmalloc : 0xf0000000 - 0xff000000 ( MB)
lowmem : 0xc0000000 - 0xef800000 ( MB)
pkmap : 0xbfe00000 - 0xc0000000 ( MB)
modules : 0xbf000000 - 0xbfe00000 ( MB)
.text : 0xc0008000 - 0xc060a09c ( kB)
.init : 0xc060b000 - 0xc075c000 ( kB)
.data : 0xc075c000 - 0xc07833c0 ( kB)
.bss : 0xc07833c0 - 0xc07acbf0 ( kB)

这里面的地址都是虚拟地址,其中lowmem是线性映射区。

线性映射区的意思是0xc0000000 - 0xef800000这段虚拟地址,和0x60000000 - 0x8f800000这段物理地址是一一对应的。

.text、.init、.data、.bss都属于lowmem区域,也即ZONE_NORMAL;vector、fixmap、vmalloc属于ZONE_HIGHMEM区域。

pkmap、modules属于用户空间。

void __init mem_init(void)
{
#ifdef CONFIG_HAVE_TCM
/* These pointers are filled in on TCM detection */
extern u32 dtcm_end;
extern u32 itcm_end;
#endif set_max_mapnr(pfn_to_page(max_pfn) - mem_map); /* this will put all unused low memory onto the freelists */
free_unused_memmap();
free_all_bootmem(); #ifdef CONFIG_SA1111
/* now that our DMA memory is actually so designated, we can free it */
free_reserved_area(__va(PHYS_OFFSET), swapper_pg_dir, -, NULL);
#endif free_highpages(); mem_init_print_info(NULL); #define MLK(b, t) b, t, ((t) - (b)) >> 10
#define MLM(b, t) b, t, ((t) - (b)) >> 20
#define MLK_ROUNDUP(b, t) b, t, DIV_ROUND_UP(((t) - (b)), SZ_1K) 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)); #undef MLK
#undef MLM
#undef MLK_ROUNDUP /*
* Check boundaries twice: Some fundamental inconsistencies can
* be detected at build time already.
*/
#ifdef CONFIG_MMU
BUILD_BUG_ON(TASK_SIZE > MODULES_VADDR);
BUG_ON(TASK_SIZE > MODULES_VADDR);
#endif #ifdef CONFIG_HIGHMEM
BUILD_BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
BUG_ON(PKMAP_BASE + LAST_PKMAP * PAGE_SIZE > PAGE_OFFSET);
#endif if (PAGE_SIZE >= && get_num_physpages() <= ) {
extern int sysctl_overcommit_memory;
/*
* On a machine this small we won't get
* anywhere without overcommit, so turn
* it on by default.
*/
sysctl_overcommit_memory = OVERCOMMIT_ALWAYS;
}
}

3. 各部分框图详解

3.1 内核空间用户空间划分

内核和用户空间的分界点是由PAGE_OFFSET决定的,即内核image的起始地址为0xc0000000。

/* PAGE_OFFSET - the virtual address of the start of the kernel image */
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET) #define CONFIG_PAGE_OFFSET 0xC0000000

3.2 kernel image空间

为什么kernel image空间从0xc0008000开始?

0xc0008000是由两部分组成的PAGE_OFFSET + TEXT_OFFSET。在arch/arm/kernel/vmlinux.lds.S中进行了明确定义。

生成的文件vmlinux.lds中,可以看出.text即从0xc000800开始。

OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
/*
* XXX: The linker does not define how output sections are
* assigned to input sections when there are multiple statements
* matching the same input section name. There is no documented
* order of matching.
*
* unwind exit sections must be discarded before the rest of the
* unwind sections get included.
*/
...
. = 0xC0000000 + 0x00008000;
.head.text : {
_text = .;
*(.head.text)
}
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */...
}

在了解.text其实地址来历之后,通过查看System.map就可以确定其余段的地址了。

从mem_init中打印的log,可以看出:_text - _etext,0xc060a09c - 0xc0008000=6153KB,采用1K对齐。

kernel image空间从_text开始,到_end结束。

c0008000 T _text
c0008000 T stext
...
c060a09c T _etext
c060b000 T __init_begin
...
c075c000 D __init_end
c075c000 D _data
c075c000 D _sdata
c075c000 D init_thread_union
c075e000 D __nosave_begin
...
c07833c0 B __bss_start
c07833c0 D _edata
..
c07acbf0 B __bss_stop
c07acbf0 B _end

3.3 vmalloc空间

vmalloc的区域用于给vmalloc/ioremap动态分配内存。

vmalloc的空间确定较简单,首先确定vmalloc终点0xff000000。

#define VMALLOC_OFFSET        (8*1024*1024)
#define VMALLOC_START (((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))
#define VMALLOC_END 0xff000000UL

然后是确定vmalloc区域的起点,其中vmalloc和lowmem之间需要一个8MB空间的Gap。

static void * __initdata vmalloc_min =
(void *)(VMALLOC_END - ( << ) - VMALLOC_OFFSET);----------------------------vmalloc_min为VMALLOC_END向下偏移240MB。 void __init sanity_check_meminfo(void)
{
phys_addr_t memblock_limit = ;
int highmem = ;
phys_addr_t vmalloc_limit = __pa(vmalloc_min - ) + ;
struct memblock_region *reg; for_each_memblock(memory, reg) {
phys_addr_t block_start = reg->base;
phys_addr_t block_end = reg->base + reg->size;
phys_addr_t size_limit = reg->size; if (reg->base >= vmalloc_limit)
highmem = ;
else
size_limit = vmalloc_limit - reg->base;
...
if (!highmem) {
if (block_end > arm_lowmem_limit) {
if (reg->size > size_limit)
arm_lowmem_limit = vmalloc_limit;------------------------------此种情况arm_lowmem_limit等于vmalloc_min
else
arm_lowmem_limit = block_end;
}
...
}
} high_memory = __va(arm_lowmem_limit - ) + ;---------------------------------所以high_memory也即vmalloc_min。
...
memblock_set_current_limit(memblock_limit);-----------------------------------根据arm_lowmem_limit来作为ZONE_NORMAL的终点。
}

最终VMALLOC_START即为VMALLOC_END向下偏移240MB,即VMALLOC_START=0xf0000000

关于vmalloc 8MB hole

vmalloc区域和lowmem区域之间有一个8MB的hole。

lowmem区域是线性映射,可以虚拟地址和物理地址是1:1对应的。

这个8MB由于捕获虚拟地址的越界访问。

3.4 ZONE_NORMAL和ZONE_HIGHMEM划分

zone的划分在bootmem_init-->find_limits中确定。其中memblock.current_limit是在sanity_check_meminfo中确定。

max_low即作为ZONE_NORMAL和ZONE_HIGHMEM分界点,即0xef800000。

ZONE_NORMAL大小760MB,从0xc0000000 - 0xef800000,ZONE_HIGHMEM大小264MB,从0xef800000 - 0xffffffff。

ZONE_HIGHMEM并不等同于vmalloc,还有8MB hole和末尾16MB空间。所以vmalloc=264-8-16=240MB。

static void __init find_limits(unsigned long *min, unsigned long *max_low,
unsigned long *max_high)
{
*max_low = PFN_DOWN(memblock_get_current_limit());
*min = PFN_UP(memblock_start_of_DRAM());
*max_high = PFN_DOWN(memblock_end_of_DRAM());
} void __init_memblock memblock_set_current_limit(phys_addr_t limit)
{
memblock.current_limit = limit;
} phys_addr_t __init_memblock memblock_get_current_limit(void)
{
return memblock.current_limit;
}

对于ZONE_NORMAL线性映射区域,虚拟地址和物理地址转换比较简单。由于Vexpress的RAM起始地址映射在0x60000000。

所以计算的时候需要去除这部分偏移PHYS_OFFSET。

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;
}

3.5 swapper_pg_dir

swapper_pg_dir用于存放内核PGD页表的地方,赋给init_mm.pgd。

swapper_pg_dir被定义了绝对地址,在arch/arm/kernel/head.S中有如下定义。

swapper_pd_dir的大小为16KB,对应的虚拟地址空间是从0xc0004000 - 0xc0008000,物理地址空间是0x6000400~0x60008000。

arch/arm/kernel/head.S:
/*
* swapper_pg_dir is the virtual address of the initial page table.
* We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we must
* make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
* the least significant 16 bits to be 0x8000, but we could probably
* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
*/
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000--------------------------------------KERNEL_RAM_VADDR也确实是0xc0008000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif #ifdef CONFIG_ARM_LPAE
/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE 0x5000
#define PMD_ORDER 3
#else
#define PG_DIR_SIZE 0x4000
#define PMD_ORDER 2
#endif .globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE-------------------.equ定义swapper_pg_dir的绝对地址,所以swapper_pg_dir=0xc0008000-0x4000=0xc0004000 mm/init-mm.c:
struct mm_struct init_mm = {
.mm_rb = RB_ROOT,
.pgd = swapper_pg_dir,
...
INIT_MM_CONTEXT(init_mm)
};

3.6 fixmap

fixmap是固定映射的意思,固定指的是固定虚拟地址。

那么这些固定的虚拟地址谁在用?都对应哪些物理地址?

fixmap区域很固定,大小为3MB,从0xffc00000 - 0xfff00000。

#define FIXADDR_START        0xffc00000UL
#define FIXADDR_END 0xfff00000UL
#define FIXADDR_TOP (FIXADDR_END - PAGE_SIZE)-------------保留一页Hole

延伸阅读:《Fix-Mapped Addresses

3.7 vector

vector区域用于映射CPU vector page,大小一页4KB,从0xffff0000 - 0xffff1000。

#define CONFIG_VECTORS_BASE 0xffff0000

在系统编译的时候,arch/arm/kernel/vmlinux.ld.S决定__vectors_start、__stubs_start起始地址。

    __vectors_start = .;
.vectors : AT(__vectors_start) {
*(.vectors)
}
. = __vectors_start + SIZEOF(.vectors);
__vectors_end = .; __stubs_start = .;
.stubs 0x1000 : AT(__stubs_start) {
*(.stubs)
}
. = __stubs_start + SIZEOF(.stubs);
__stubs_end = .;

这两部分的生成结果在System.map中也可以看出:

 t __vectors_start
A cpu_v7_suspend_size
0000002c A cpu_ca9mp_suspend_size
t __stubs_start
t vector_rst
t vector_irq
000010a0 t vector_dabt
t vector_pabt
000011a0 t vector_und
t vector_addrexcptn
t vector_fiq
T vector_fiq_offset
...

这两部分在early_trap_init中拷贝,拷贝到了0xffff0000。__vextors_start占据一页,__stubs_start占据一页。

void __init early_trap_init(void *vectors_base)
{
...
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x1000, __stubs_start, __stubs_end - __stubs_start); kuser_init(vectors_base); flush_icache_range(vectors, vectors + PAGE_SIZE * );
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
...
}

3.8 pkmap

pkmap的意思是Permanent Kernel MAPping,永久内核内存映射。

是一种将Highmem页面映射到内核空间的技术。所以就需要定义CONFIG_HIGHMEM,才会存在这个区域。

所以pkmap区域是2MB大小,从0xbfe00000 - 0xc0000000。

#define PKMAP_BASE        (PAGE_OFFSET - PMD_SIZE)---------------------0xc0000000-0x200000=0xbfe00000
#define LAST_PKMAP PTRS_PER_PTE
#define PTRS_PER_PTE 512 #define PMD_SHIFT 21
#define PGDIR_SHIFT 21 #define PMD_SIZE (1UL << PMD_SHIFT) Documentation/arm/memory.txt: PKMAP_BASE PAGE_OFFSET- Permanent kernel mappings One way of mapping HIGHMEM pages into kernel
space.

延伸阅读:《高端内存永久映射分析

3.9 modules

如果定义了CONFIG_MODULES功能,则需要在用户空间开辟一段空间给insmod插入的模块。

这部分空间是动态映射的,在定义CONFIG_HIGHMEM情况下为16MB-2MB=14MB,从0xbf00000 - 0xbfe00000。

/*
* The module space lives between the addresses given by TASK_SIZE
* and PAGE_OFFSET - it must be within 32MB of the kernel text.
*/
#ifndef CONFIG_THUMB2_KERNEL
#define MODULES_VADDR (PAGE_OFFSET - SZ_16M)------------------0xc0000000-0x1000000=0xbf000000
#else
/* smaller range for Thumb-2 symbols relocation (2^24)*/
#define MODULES_VADDR (PAGE_OFFSET - SZ_8M)
#endif #if TASK_SIZE > MODULES_VADDR
#error Top of user space clashes with start of module space
#endif /*
* The highmem pkmap virtual space shares the end of the module area.
*/
#ifdef CONFIG_HIGHMEM
#define MODULES_END (PAGE_OFFSET - PMD_SIZE)----------------0xc0000000-0x200000=0xbfe00000
#else
#define MODULES_END (PAGE_OFFSET)
#endif

Linux内存管理 (3)内核内存的布局图的更多相关文章

  1. Linux中的Buffer Cache和Page Cache echo 3 > /proc/sys/vm/drop_caches Slab内存管理机制 SLUB内存管理机制

    Linux中的Buffer Cache和Page Cache echo 3 > /proc/sys/vm/drop_caches   Slab内存管理机制 SLUB内存管理机制 http://w ...

  2. Win3内存管理之私有内存跟共享内存的申请与释放

    Win3内存管理之私有内存跟共享内存的申请与释放 一丶内存简介私有内存申请 通过上一篇文章.我们理解了虚拟内存与物理内存的区别. 那么我们有API事专门申请虚拟内存与物理内存的. 有私有内存跟共享内存 ...

  3. 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配

    垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  4. JVM自动内存管理机制——Java内存区域(下)

    一.虚拟机参数配置 在上一篇<Java自动内存管理机制——Java内存区域(上)>中介绍了有关的基础知识,这一篇主要是通过一些示例来了解有关虚拟机参数的配置. 1.Java堆参数设置 a) ...

  5. 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法

    垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...

  6. linux内存管理-内核用户空间 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4491362.html 1,linux内存管理中几个重要的结构体和数组 page unsigned long ...

  7. Linux内存管理 (19)总结内存管理数据结构和API

    专题:Linux内存管理专题 关键词:mm.vaddr.VMA.page.pfn.pte.paddr.pg_data.zone.mem_map[]. 1. 内存管理数据结构的关系图 在大部分Linux ...

  8. linux内存管理--伙伴系统和内存分配器

    3.1页框的管理 所有的页框描述符都存放在mem_map数组中. 3.1.1page数据结构 struct page { page_flags_t flags; //标志 atomic_t _coun ...

  9. Linux内存管理 (23)一个内存Oops解析

    专题:Linux内存管理专题 关键词:DataAbort.fsr.pte.backtrace.stack.   在内存相关实际应用中,内存异常访问是一种常见的问题. 本文结合异常T32栈回溯.Oops ...

随机推荐

  1. scrapy爬虫学习系列三:scrapy部署到scrapyhub上

    系列文章列表: scrapy爬虫学习系列一:scrapy爬虫环境的准备:      http://www.cnblogs.com/zhaojiedi1992/p/zhaojiedi_python_00 ...

  2. Java开发知识之Java的枚举

    Java开发知识之Java的枚举 一丶什么是枚举 枚举可以理解为就是常量,在Java中我们定义常量.都是用 final语句. C++中都是用const关键字. 枚举跟C++概念都是一样的.就是特定的常 ...

  3. 克拉克拉(KilaKila):大规模实时计算平台架构实战

    克拉克拉(KilaKila):大规模实时计算平台架构实战 一.产品背景:克拉克拉(KilaKila)是国内专注二次元.主打年轻用户的娱乐互动内容社区软件.KilaKila推出互动语音直播.短视频配音. ...

  4. [十七]JavaIO之CharArrayReader 和 CharArrayWriter

    功能简介 CharArrayReader  和 CharArrayWriter, 字符数组作为数据源的字符读写  CharArrayReader  CharArrayWriter  只需要记住他们的根 ...

  5. #12 Python函数

    前言 矩形的面积 S = ab,只要知道任一矩形的的长和宽,就可以带入上式求得面积.这样有什么好处呢?一个公式,适用于全部矩形,一个公式,重复利用,减少了大脑的记忆负担.像这类用变量代替不变量的思想在 ...

  6. ajax跨域请求,亲测有效

    跨域请求域有两种常用解决方案,jsonp和cors, 因为jsonp只能解决get请求问题,我这里用的是cors方法. js前端ajax请求: $.ajax({ url: "http://1 ...

  7. Scala(四) —— 集合

    一.List var x = List(1,2,3,4) //x:List[Int] = List(1, 2, 3, 4) var y = List("x","y&quo ...

  8. 第17章 社区快速入门和模板 - Identity Server 4 中文文档(v1.0.0)

    IdentityServer组织不维护这些示例.IdentityServer组织愉快地链接到社区模板,但不能对模板做出任何保证.请直接与作者联系. 17.1 各种ASP.NET核心安全样本 https ...

  9. (摘)使用 .NET Core 实现依赖关系注入

    为什么使用依赖关系注入? 使用 .NET,通过 new 运算符(即,new MyService 或任何想要实例化的对象类型)调用构造函数即可轻松实现对象实例化.遗憾的是,此类调用会强制实施客户端(或应 ...

  10. RESTful api风格介绍

    RESTful 接口是目前来说比较流行的一种接口,平常在开发中会非常常见. 有过和后端人员对接接口的小伙伴都应该知道,我们所做的大多数操作都是对数据库的四格操作 “增删改查” 对应到我们的接口操作分别 ...