代码分析
/*
* Setup the initial page tables. We only setup the barest amount which is
* required to get the kernel running. The following sections are required:
* - identity mapping to enable the MMU (low address, TTBR0)
* - first few MB of the kernel linear mapping to jump to once the MMU has
* been enabled
*/
SYM_FUNC_START_LOCAL(__create_page_tables)
mov x28, lr
/*
* Invalidate the init page tables to avoid potential dirty cache lines
* being evicted. Other page tables are allocated in rodata as part of
* the kernel image, and thus are clean to the PoC per the boot
* protocol.
*/
adrp x0, init_pg_dir
adrp x1, init_pg_end
sub x1, x1, x0
bl __inval_dcache_area
/*
* Clear the init page tables.
*/
adrp x0, init_pg_dir
adrp x1, init_pg_end
sub x1, x1, x0
1: stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
stp xzr, xzr, [x0], #16
subs x1, x1, #64
b.ne 1b
mov x7, SWAPPER_MM_MMUFLAGS
接下来又到了一个可以媲美el2_setup的大函数,建立恒等映射。
第1行到第7行是注释
含义如下:建立初始化也表,只需要覆盖能让内核正常运行起来的引导部分即可。
引导部分包括 使用TTBR0使能MMU;
只需要映射内核的前面一部分即可。
细节我们可以分析代码。
第9行 将保存函数的返回地址的寄存器lr的值暂存在 x28里面,因为该函数可能还要调用别的函数,防止lr被覆盖。
第11行到16行是注释,含义是将初始页表也就是恒等页表对应的数据cache清理。
第17行到第19行涉及到2个变量 init_pg_dir 和 init_pg_end,它们在链接脚本 vmlinux.lds.S 里面定义
. = ALIGN(PAGE_SIZE);
init_pg_dir = .;
. += INIT_DIR_SIZE;
init_pg_end = .;
而 INIT_DIR_SIZE 的定义如下:
$ grep -rn INIT_DIR_SIZE arch/arm64/
arch/arm64/include/asm/kernel-pgtable.h:89:#define INIT_DIR_SIZE (PAGE_SIZE * EARLY_PAGES(KIMAGE_VADDR, _end))
根据名称的猜测,init_pg_dir 应该就是映射页表的起始部分,init_pg_end 就是结束部分,INIT_DIR_SIZE 应该就是页表的大小。
我们再看一下vmlinux.lds
_rodata = .;
idmap_pg_dir = .;
. += ((((((48)) - 4) / (12 - 3)) - 1) * (1 << 12));
idmap_pg_end = .;
说明该段属于只读数据段
第17行将 init_pg_dir 的物理地址加载x0寄存器
第18行将 init_pag_end 的物理地址加载到x1寄存器
第19行计算初始恒等页表的大小
然后第20行调用函数 __inval_dcache_area 清理数据cache,这个函数调用也对应了第9行保留lr到x28的原因,调用的时候lr寄存器的值会被覆盖
第22行到第24行是注释,表示清理初始恒等映射页表的内容
第25行到第27行与第17行到第19行完全相同,为什么又要再做一次呢?因为第20行调用函数 __inval_dcache_area 进入内部执行的时候,x0和x1的值并没有保存,而是被改变了
arm64 ABI规定了 x0~x7 寄存器用于传递参数和结果,在调用着和被调用者并不需要保存。
所以函数调用完毕返回后x0和x1已经不是初始化的值了,在第25行到第27行重新赋值也就不足为奇了。
第28行到第33行就是通过循环把页表的内容清零。
第35行涉及宏定义 ARM64_SWAPPER_USES_SECTION_MAPS,我们没有定义
#if ARM64_SWAPPER_USES_SECTION_MAPS
#define SWAPPER_MM_MMUFLAGS (PMD_ATTRINDX(MT_NORMAL) | SWAPPER_PMD_FLAGS)
#else
#define SWAPPER_MM_MMUFLAGS (PTE_ATTRINDX(MT_NORMAL) | SWAPPER_PTE_FLAGS)
#endif
该宏定义是否定义取决于内核的如下编译选项 CONFIG_ARM64_4K_PAGES
/*
* The linear mapping and the start of memory are both 2M aligned (per
* the arm64 booting.txt requirements). Hence we can use section mapping
* with 4K (section size = 2M) but not with 16K (section size = 32M) or
* 64K (section size = 512M).
*/
#ifdef CONFIG_ARM64_4K_PAGES
#define ARM64_SWAPPER_USES_SECTION_MAPS 1
#else
#define ARM64_SWAPPER_USES_SECTION_MAPS 0
#endif
而 CONFIG_ARM64_4K_PAGES 在我的内核使能了,所以 ARM64_SWAPPER_USES_SECTION_MAPS 的值为1
该寄存器主要保存内存的映射属性。