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进行最后一项物理地址的映射
当执行完上面的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的映射属于永久映射,这段映射的虚拟地址是在启动后一直有效的。
既然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);
}
在完成了上面的内存映射之后,我们可以访问到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);
什么样的内存应该被设置为保留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