Linux內存管理 (3)內核內存的布局圖


專題: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   (   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 - 0xc060a09c   (6153 kB)
      .init : 0xc060b000 - 0xc075c000   (1348 kB)
      .data : 0xc075c000 - 0xc07833c0   ( 157 kB)
       .bss : 0xc07833c0 - 0xc07acbf0   ( 167 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, -1, 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 >= 16384 && get_num_physpages() <= 128) {
        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 - (240 << 20) - VMALLOC_OFFSET);----------------------------vmalloc_min為VMALLOC_END向下偏移240MB。 void __init sanity_check_meminfo(void)
{
    phys_addr_t memblock_limit = 0;
    int highmem = 0;
    phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1;
    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 = 1;
        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 - 1) + 1;---------------------------------所以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 0 : 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中也可以看出:

00000000 t __vectors_start
00000024 A cpu_v7_suspend_size
0000002c A cpu_ca9mp_suspend_size
00001000 t __stubs_start
00001004 t vector_rst
00001020 t vector_irq
000010a0 t vector_dabt
00001120 t vector_pabt
000011a0 t vector_und
00001220 t vector_addrexcptn
00001240 t vector_fiq
00001240 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 * 2); 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-1    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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM