AARCH64 启动过程中的内存映射与管理(基于kernel-4.9)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/79958577

1.汇编阶段会进行identity map和kernel image map

identity map:是指把idmap_text区域的物理地址映射到相等的虚拟地址上,这种映射完成后,其虚拟地址等于物理地址。idmap_text区域都是一些打开MMU相关的代码,这中映射就保证了打开MMU的前后,代码的执行寻址不会出错,从而可以无缝切换的。

kernel image map:这个映射操作是将KERNLE IMAGE对应的物理地址区间映射到物理地址+PAGE_OFFSET的虚拟地址上。

备注:
idmap_text:这段代码是包含在kernel image中的,所以它会经历上述两种map,也就是说text区域会被map两次,一次被映射到和物理地址相等的虚拟地址上,另一次被映射到物理地址+PAGE_OFFSET对应的虚拟地址上。所以这段代码是可能在两个地址上运行,那么它的实现必须要PIC的,也就是地址无关的。

arch/arm64/kernel/head.S:

 ENTRY(stext)
     bl  preserve_boot_args
     bl  el2_setup           // Drop to EL1, w0=cpu_boot_mode
     adrp    x23, __PHYS_OFFSET
     and x23, x23, MIN_KIMG_ALIGN - 1    // KASLR offset, defaults to 0
     bl  set_cpu_boot_mode_flag
     bl  __create_page_tables
     /*
      * The following calls CPU setup code, see arch/arm64/mm/proc.S for
      * details.
      * On return, the CPU will be ready for the MMU to be turned on and
      * the TCR will have been set.
      */
     bl  __cpu_setup         // initialise processor
     b   __primary_switch
 ENDPROC(stext)

__create_page_tables主要执行的就是identity map和kernel image map:

  __create_page_tables:
......
     create_pgd_entry x0, x3, x5, x6
     mov x5, x3              // __pa(__idmap_text_start)
     adr_l   x6, __idmap_text_end        // __pa(__idmap_text_end)
     create_block_map x0, x7, x3, x5, x6

     /*
      * Map the kernel image (starting with PHYS_OFFSET).
      */
     adrp    x0, swapper_pg_dir
     mov_q   x5, KIMAGE_VADDR + TEXT_OFFSET  // compile time __va(_text)
     add x5, x5, x23         // add KASLR displacement
     create_pgd_entry x0, x5, x3, x6
     adrp    x6, _end            // runtime __pa(_end)
     adrp    x3, _text           // runtime __pa(_text)
     sub x6, x6, x3          // _end - _text
     add x6, x6, x5          // runtime __va(_end)
     create_block_map x0, x7, x3, x5, x6
 ......

其中调用到 create_pgd_entry进行页表项的创建,调用create_block_map进行最后一项物理地址的映射

2.dtb map

当执行完上面的map之后,MMU就已经打开了并且开始进入C代码运行阶段,那么下一步就要对dtb进行映射了。

 void __init setup_arch(char **cmdline_p)
 {
     pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());

     sprintf(init_utsname()->machine, UTS_MACHINE);
     init_mm.start_code = (unsigned long) _text;
     init_mm.end_code   = (unsigned long) _etext;
     init_mm.end_data   = (unsigned long) _edata;
     init_mm.brk    = (unsigned long) _end;

     *cmdline_p = boot_command_line;

     early_fixmap_init();  //创建了dtb对应地址中间level级别的页表entry
     early_ioremap_init(); //执行各个模块的早期ioremap(此时内存管理系统还没有加载)

     setup_machine_fdt(__fdt_pointer);//其中会调用到fixmap_remap_fdt来创建最后一个level的页表entry,完成dtb最终的映射

     parse_early_param();

     /*
      *  Unmask asynchronous aborts after bringing up possible earlycon.
      * (Report possible System Errors once we can report this occurred)
      */
     local_async_enable();

     /*
      * TTBR0 is only used for the identity mapping at this stage. Make it
      * point to zero page to avoid speculatively fetching new entries.
      */
     cpu_uninstall_idmap();

     xen_early_init();
     efi_init();
     arm64_memblock_init();

     paging_init();

......

在执行setup_arch中,会最先进行early_fixmap_init(),这个函数就是用来map dtb的,但是它只会建立DTB对应的这段物理地址中间level的页表entry,而最后一个level的页表映射则留到后面通过fixmap_remap_fdt来创建。这个函数是在setup_machine_fdt中被调用到的。

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
    void *dt_virt = fixmap_remap_fdt(dt_phys);//创建DTB地址对应的最后一个level的页表entry,完成dtb最终的map

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n"
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    machine_name = arch_read_machine_name();
    if (machine_name) {
        dump_stack_set_arch_desc("%s (DT)", machine_name);
        pr_info("Machine: %s\n", machine_name);
    }
}

这里的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里可能还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射,而dtb的映射属于永久映射,这段映射的虚拟地址是在启动后一直有效的。

3.early ioremap

既然fixmap机制可以完成对dtb的映射,自然也可以用于其他模块,对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。上面已经介绍过fixmap可以支持临时映射,这个临时映射就是用来执行early ioremap使用的。

各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。

/*
 * Must be called after early_fixmap_init
 */
void __init early_ioremap_init(void)
{
    early_ioremap_setup();
}

从这个函数的注释描述可以看出,它的实现是依赖与fixmap的,所以它必须要在early_fixmap_init之后才能运行。

void __init early_ioremap_setup(void)
{
    int i;
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
        if (WARN_ON(prev_map[i]))
            break;
    for (i = 0; i < FIX_BTMAPS_SLOTS; i++)
        slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
}

4.系统内存的加载

(1)memory type内存加载:

在完成了上面的内存映射之后,我们可以访问到kernel和dtb了,那么我们就可以通过dtb来窥探到系统内存布局了,接下来就可以进一步去把系统内存都加入进来并管理起来了。这一步主要在setup_machine_fdt中完成。
我们需要通过这一步告诉系统,具体是物理内存布局是怎样的,这里memory type是包含了所有的memory的,也包含了保留内存。也就是说memory type中其实是包含了保留内存的,从它定义的名称可以看出,就是代表所有内存。

 void __init setup_arch(char **cmdline_p)
 {
     pr_info("Boot CPU: AArch64 Processor [%08x]\n", read_cpuid_id());

     sprintf(init_utsname()->machine, UTS_MACHINE);
     init_mm.start_code = (unsigned long) _text;
     init_mm.end_code   = (unsigned long) _etext;
     init_mm.end_data   = (unsigned long) _edata;
     init_mm.brk    = (unsigned long) _end;

     *cmdline_p = boot_command_line;

     early_fixmap_init();
     early_ioremap_init();

     setup_machine_fdt(__fdt_pointer); //dtb映射和解析

     parse_early_param();

     /*      
      *  Unmask asynchronous aborts after bringing up possible earlycon.
      * (Report possible System Errors once we can report this occurred)
      */ 
     local_async_enable();

     /*
      * TTBR0 is only used for the identity mapping at this stage. Make it
      * point to zero page to avoid speculatively fetching new entries.
      */ 
     cpu_uninstall_idmap();

     xen_early_init();
     efi_init();
     arm64_memblock_init();
static void __init setup_machine_fdt(phys_addr_t dt_phys)
{       
    void *dt_virt = fixmap_remap_fdt(dt_phys);

    if (!dt_virt || !early_init_dt_scan(dt_virt)) {
        pr_crit("\n"
            "Error: invalid device tree blob at physical address %pa (virtual address 0x%p)\n
            "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
            "\nPlease check your bootloader.",
            &dt_phys, dt_virt);

        while (true)
            cpu_relax();
    }

    machine_name = arch_read_machine_name();
    if (machine_name) {
        dump_stack_set_arch_desc("%s (DT)", machine_name);
        pr_info("Machine: %s\n", machine_name);
    }
}
bool __init early_init_dt_scan(void *params)
{      
    bool status; 

    status = early_init_dt_verify(params);
    if (!status)
        return false;

    early_init_dt_scan_nodes();
    return true;
}  
void __init early_init_dt_scan_nodes(void)
{
    /* Retrieve various information from the /chosen node */
    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);

    /* Initialize {size,address}-cells info */
    of_scan_flat_dt(early_init_dt_scan_root, NULL);
    /* Setup memory, calling early_init_dt_add_memory_arch */
    of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}  

上面的代码追溯,最终会执行到 of_scan_flat_dt(early_init_dt_scan_memory, NULL);这一步会对dtb中的内存信息通过memblock_add加入到memblock.memory对应的memblock_type链表中进行管理。

memory type region加载过程的代码流:

setup_arch>>setup_machine_fdt>>early_init_dt_scan>>early_init_dt_scan_nodes>>
early_init_dt_scan_memory>>memblock_add>>memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
(2)reserved type内存加载

什么样的内存应该被设置为保留reserved?

当这部分物理内存区域不能够被内存管理系统所管理,也就是说有特定用途,并且不能够被释放和再次分配的,我们把这块内存设置为保留区,比如dtb/kernel image/initrd等等。
注意事项:memory type region中是包含了reserved type region区域的。也就是说reserved type region 是memory region的一个子集。

dtb保留区:

通过前文我们知道,dtb fixed map是在fixmap_remap_fdt函数中来创建最后一个level的页表的,也就是在此函数中,我们找到对应物理内存区域并把它设置为保留的。


 void *__init fixmap_remap_fdt(phys_addr_t dt_phys)
 {
     void *dt_virt;
     int size;

     dt_virt = __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
     if (!dt_virt)
         return NULL;

     memblock_reserve(dt_phys, size);//加入到保留区域
     return dt_virt;
 }

kernel image和initrd保留区:


 void __init arm64_memblock_init(void)
 {
......
     memblock_reserve(__pa_symbol(_text), _end - _text);  //kernel image保留区
 #ifdef CONFIG_BLK_DEV_INITRD
     if (initrd_start) {
         memblock_reserve(initrd_start, initrd_end - initrd_start); //initrd保留区
         /* the generic initrd code expects virtual addresses */
         initrd_start = __phys_to_virt(initrd_start);
         initrd_end = __phys_to_virt(initrd_end);
     }
 #endif
     early_init_fdt_scan_reserved_mem();  //dts中配置为保留的区域
......
}

dts中申请保留的区域:

如上面的代码中所写,除了上面特殊用途的区域会保留以外,在arm64_memblock_init中也会检索fdt中用户的配置,如果用户有在dts中配置reserve memory,那么系统也会执行保留动作。

 void __init early_init_fdt_scan_reserved_mem(void) 
 {
     int n;
     u64 base, size; 

     if (!initial_boot_params)
         return;

     /* Process header /memreserve/ fields */
     for (n = 0; ; n++) {
         fdt_get_mem_rsv(initial_boot_params, n, &base, &size);
         if (!size)
             break;
         early_init_dt_reserve_memory_arch(base, size, 0);
     }

     of_scan_flat_dt(__fdt_scan_reserved_mem, NULL);
     fdt_init_reserved_mem();
 }  

总结:通过上面的一系列操作,内存已经被放到了memory type和reserved type这两个region中了,现在内存已经被memblock模块所管理了,这只是启动后的第一步,后续内存才会加入到伙伴系统去管理。

参考文章:蜗窝科技http://www.wowotech.net/memory_management/memory-layout.html

阅读更多
想对作者说点什么? 我来说一句
相关热词

没有更多推荐了,返回首页