内存映射过程之paging_init
导读
之前已经逐步整理完成了idmap、fixup、memblock等内容,在此阶段已经可以:
- 访问kernel image的地址空间;
- 访问FDT地址空间,即已经获取device tree相关数据;
- 可以通过memblock提供的接口申请内存;
则接下来需要做的处理,是要把从fdt中获取的mem layout关系建立实际的映射;
所谓建立映射,实质很简单,即根据页表大小和地址空间大小,填充PUD PMD PTE等数据;
1. 页表转换关系
这部分之前已经整理过了,在这里制作简单说明:
1.1 涉及页表层级相关宏
宏(Config) | 转换后 | 值 | 描述 |
---|---|---|---|
CONFIG_ARM64_VA_BITS_39=y | NA | NA | 决策逻辑地址的范围,以40~63位标记kernel or user,user + kernel共 1T |
CONFIG_ARM64_VA_BITS=39 | NA | 39 | 同上 |
CONFIG_ARM64_4K_PAGES=y | NA | NA | 决策页表的大小,当前被配置为4K |
CONFIG_ARM64_PAGE_SHIFT=12 | NA | 12 | 同上 |
CONFIG_PGTABLE_LEVELS=3 | NA | 3 | 决策页表层级,即当前平台支持3级页表:PGD PMD PTE |
PTRS_PER_PTE | 1 << (PAGE_SHIFT - 3) | 1<<9 | PTE/PMD/PUD中表项的多少 |
PGDIR_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS) | 30 | PGD 的偏移位 |
PUD_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(1) | NA | PUD的偏移位,level = 3 无PUD |
PMD_SHIFT | ARM64_HW_PGTABLE_LEVEL_SHIFT(2) | 21 | PMD的偏移位 |
ARM64_HW_PGTABLE_LEVEL_SHIFT(n) | (PAGE_SHIFT - 3) * (4 - (n)) + 3 | NA | 根据level决策该层的shift |
1.2 页表转换
一次地址的翻译过程基本如上图所示,相关的level、page size、VA size等根据平台不同,上述Config和宏都已经配置完成,即上述转换过程是固定的
则我们本部分要介绍的paging_init实质上就是在构造上述转换关系:
- 确认PGD的访问入口(实质为swapper的地址)
- 根据PGD和 offset 计算到PMD的页表地址,并建立映射:
- 申请物理页表;
- 与上述计算出来的地址建立映射;
- 使用同样方法得到PTE的页表地址并建立映射关系;
- 使用同样方法得到PAGE的页表地址并建立映射关系;
则上述过程中:
- 页表入口地址PGD,在此时已经可以访问到了;
- 核心功能为建立映射关系,即可以通过该线性地址读取到实际的物理地址;
1.3 页表构建过程
上述部分的核心目录:
目录 | 描述 |
---|---|
./arch/arm64/mm/mmu.c | MMU地址转换相关函数的实现 |
./kernel-4.9/arch/arm64/include/asm/pgtable.h | 页表实现相关接口 |
./kernel-4.9/arch/arm64/include/asm/pgtable-hwdef.h | 页表各个level中shift、size等实现 |
2. 功能描述
直接上图:
3. code 阅读
3.1 Paging_init
paging_init这个函数并不长,注释已经说明的比较清晰了:
- set up page table;
- 初始化zone memory映射;
- 建立第一个页面;
/*
* paging_init() sets up the page tables, initialises the zone memory
* maps and sets up the zero page.
*/
void __init paging_init(void)
{
phys_addr_t pgd_phys = early_pgtable_alloc(); //分配一个phy 页面
pgd_t *pgd = pgd_set_fixmap(pgd_phys); //将其放在fixmap中,建立映射关系,方便访问
map_kernel(pgd); //映射内核中各个段的关系,即我们之前看的vmlinux.lds.S中的内容;
map_mem(pgd); //这里是映射之前memblock中add的部分
//这里复用swapper_pg_dir的空间,将当前新建的页表放进去,转换为VA哦;
cpu_replace_ttbr1(__va(pgd_phys));
memcpy(swapper_pg_dir, pgd, PAGE_SIZE);
cpu_replace_ttbr1(swapper_pg_dir);
pgd_clear_fixmap();//上述已经映射完成,由于PGD复用了swapper的空间的PGD,事实上整个系统就只用一个PGD,在这里释放掉pgd
memblock_free(pgd_phys, PAGE_SIZE);
//释放掉swapper_pg_dir中的pud pmd,这里之前是在kernel 汇编汇中填充的,目的是为了访问kernel空间,
memblock_free(__pa(swapper_pg_dir) + PAGE_SIZE, SWAPPER_DIR_SIZE - PAGE_SIZE);
}
逐步分析就是做了这几件事情:
- 获取pgd_phys地址,即页表地址
- 在fixmap中建立映射关系,方便访问
- 映射kernel中各个段
- 映射memblock中添加的部分
- 复用swapper_pg_dir的地址,将pgd添加过去
- 释放掉没有使用的资源
3.2 申请物理页面
early_pgtable_alloc 申请一个page:
static phys_addr_t __init early_pgtable_alloc(void)
{
phys_addr_t phys;
void *ptr;
phys = memblock_alloc(PAGE_SIZE, PAGE_SIZE); // 通过memblock 申请一个page大小的地址,注意这里是物理地址
ptr = pte_set_fixmap(phys);//这里就是将申请的物理页面放到fixmap地址中的pte部分,这里还没用过,则现在可以通过fixmap中PTE的虚拟地址访问当前申请的这块物理地址;