虚拟地址空间与物理地址空间完整映射
- RISC-V
- 2024-07-07
- 202热度
- 0评论
setup bootmem
物理内存都添加到系统之后,会调用setup_bootmem对整个物理内存进行整理,主要的工作就是remove掉一些no-map区域(不归内核管理),同时保留一些关键区域,如内核镜像区,dtb中reserved的内存节点。
上图中,浅绿色的就是reserved部分,不能被分配使用,而剩下的部分就可以通过调用上小章节中的函数去使用内存了。
void __init setup_bootmem(void)
{
struct memblock_region *reg;
phys_addr_t mem_size = 0;
phys_addr_t vmlinux_end = __pa(&_end);
phys_addr_t vmlinux_start = __pa(&_start);
/* Find the memory region containing the kernel */
for_each_memblock(memory, reg) {
phys_addr_t end = reg->base + reg->size;
if (reg->base <= vmlinux_end && vmlinux_end <= end) {
mem_size = min(reg->size, (phys_addr_t)-PAGE_OFFSET);
/*
* Remove memblock from the end of usable area to the
* end of region
*/
if (reg->base + mem_size < end)
memblock_remove(reg->base + mem_size,
end - reg->base - mem_size);
}
}
BUG_ON(mem_size == 0);
/* Reserve from the start of the kernel to the end of the kernel */
memblock_reserve(vmlinux_start, vmlinux_end - vmlinux_start);
①将内核镜像地址空间添加到&memblock.reserved。
set_max_mapnr(PFN_DOWN(mem_size));
max_low_pfn = PFN_DOWN(memblock_end_of_DRAM());
#ifdef CONFIG_BLK_DEV_INITRD
setup_initrd();
#endif /* CONFIG_BLK_DEV_INITRD */
/*
* Avoid using early_init_fdt_reserve_self() since __pa() does
* not work for DTB pointers that are fixmap addresses
*/
memblock_reserve(dtb_early_pa, fdt_totalsize(dtb_early_va));
②将dtb的空间添加到&memblock.reserved
early_init_fdt_scan_reserved_mem();
③遍历设备树的节点,将/memreserve/,reserved-memory节点添加到&memblock.reserved
memblock_allow_resize();
memblock_dump_all();
for_each_memblock(memory, reg) {
unsigned long start_pfn = memblock_region_memory_base_pfn(reg);
unsigned long end_pfn = memblock_region_memory_end_pfn(reg);
memblock_set_node(PFN_PHYS(start_pfn),
PFN_PHYS(end_pfn - start_pfn),
&memblock.memory, 0);
}
}
下面是调用memblock_dump_all的信息:
MEMBLOCK configuration:
memory size = 0x000000000fe00000 reserved size = 0x000000000091288b
memory.cnt = 0x1
memory[0x0] [0x0000000080200000-0x000000008fffffff], 0x000000000fe00000 bytes flags: 0x0
reserved.cnt = 0x3
reserved[0x0] [0x0000000080000000-0x000000008003ffff], 0x0000000000040000 bytes flags: 0x0
reserved[0x1] [0x0000000080200000-0x0000000080ad133b], 0x00000000008d133c bytes flags: 0x0
reserved[0x2] [0x0000000082200000-0x000000008220154e], 0x000000000000154f bytes flags: 0x0
小结:
(1)系统通过memblock以数组memory type的方式记录物理内存空间,数组中每一个内存区域描述了一段内存信息,包括base,size,node id等。
(2)在memblock信息中,已经被使用或者被内核定义需要保留的区域,会存储在reserved 数组中。
(3)memory type数组中并不是代表整个内核系统的内存空间,因为部分驱动会保留一段内存区域供自己单独使用,其在dts中具有no-map属性的reserved-memory节点,不会由内核创建地址映射。
(4)可以通过内核调试节点/sys/kernel/debug/memblock进行查询相关信息
完整映射
在前面部分只完成了kernel、dtb,部分临时页表的临时映射,也就是说对于内核来说使能了MMU后也只能访问kernel,dtb,部分临时页表。在前面阶段,内核也只知道kernel,dtb的物理位置,对于其他的内存位置内核是不清楚的。通过memblock机制后,内核系统已经对物理内存布局信息比较清楚了,那么就需要对内存完成最后的映射。主要的工作是,对此前的kernel、dtb等映射重新做一次调整,统一使用swapper_pg_dir根目录页表,同时对于新纳入memblock中的内存建立映射,填充好对应的页表。
static void __init setup_vm_final(void)
{
uintptr_t va, map_size;
phys_addr_t pa, start, end;
struct memblock_region *reg;
/* Set mmu_enabled flag */
mmu_enabled = true;
/* Setup swapper PGD for fixmap */
create_pgd_mapping(swapper_pg_dir, FIXADDR_START,
__pa(fixmap_pgd_next),
PGDIR_SIZE, PAGE_TABLE);
① 将fixmap的映射的页表从early_pg_dir切换为swapper_pg_dir。
/* Map all memory banks */
for_each_memblock(memory, reg) {
start = reg->base;
end = start + reg->size;
if (start >= end)
break;
if (memblock_is_nomap(reg))
continue;
if (start <= __pa(PAGE_OFFSET) &&
__pa(PAGE_OFFSET) < end)
start = __pa(PAGE_OFFSET);
map_size = best_map_size(start, end - start);
for (pa = start; pa < end; pa += map_size) {
va = (uintptr_t)__va(pa);
create_pgd_mapping(swapper_pg_dir, va, pa,
map_size, PAGE_KERNEL_EXEC);
}
}
②遍历memblock中的内存,对所有的内存进行映射,根目录页表为swapper_pg_dir,二级页表和PTE页表会使用memblock_phys_alloc进行分配内存,对已经分配的内存访问会先反向映射到fixmap地址空间,通过fixmap进行访问页表。
/* Clear fixmap PTE and PMD mappings */
clear_fixmap(FIX_PTE);
clear_fixmap(FIX_PMD);
/* Move to swapper page table */
csr_write(CSR_SATP, PFN_DOWN(__pa(swapper_pg_dir)) | SATP_MODE);
local_flush_tlb_all();
③将swapper_pg_dir的根目录写到satp寄存器,这样就完成了整个物理内存到虚拟地址空间的映射,所有的物理内存空间都可以进行访问了。
}
到此,内核通过memblock知道物理内存的布局信息,虚拟地址翻译到物理内存所需要的页表都填充好了,所有要访问的物理内存都映射虚拟地址空间,所有的物理内存都可以通过虚拟地址正常访问了。内存下一阶段的初始化会涉及到Node、Zone以及伙伴系统等,这里就先不阐述。