文章代码分析基于linux-5.19.13,架构基于aarch64(ARM64)。
涉及页表代码分析部分:
(1)假设页表映射层级是4,即配置CONFIG_ARM64_PGTABLE_LEVELS=4;
(2)虚拟地址宽度是48,即配置CONFIG_ARM64_VA_BITS=48;
(3)物理地址宽度是48,即配置CONFIG_ARM64_PA_BITS=48;
1. 入口分析
1.1 链接脚本arch/arm64/kernel/vmlinux.lds.S
??这里只列举与内存初始化相关的定义,其它的采用“......”省略。
......
OUTPUT_ARCH(aarch64) '指定一个特定的输出机器架构为aarch64'
ENTRY(_text) '设置入口地址,实现在arch/arm64/kernel/head.S'
......
SECTIONS
{
......
'在5.8内核版本发现TEXT_OFFSET没有任何作用,因此,被重新定义为0x0'
. = KIMAGE_VADDR; '内核映像虚拟的起始地址(在5.8内核之前这里为KIMAGE_VADDR + TEXT_OFFSET)'
.head.text : { '早期汇编代码的text段'
_text = .; '入口地址'
HEAD_TEXT 定义在include/asm-generic/vmlinux.lds.h'#define HEAD_TEXT KEEP(*(.head.text))'
}
.text : ALIGN(SEGMENT_ALIGN) { /* Real text segment */
_stext = .; /* Text and read-only data */ 'text段起始'
......
}
......
. = ALIGN(SEGMENT_ALIGN);
_etext = .; /* End of text section */ 'text段结束'
/* everything from this point to __init_begin will be marked RO NX */
RO_DATA(PAGE_SIZE) '只读数据段'
......
idmap_pg_dir = .; '恒等映射一级页表地址'
. += IDMAP_DIR_SIZE;
idmap_pg_end = .;
#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
tramp_pg_dir = .; '熔断(安全漏洞引入)'
. += PAGE_SIZE;
#endif
reserved_pg_dir = .;
. += PAGE_SIZE;
swapper_pg_dir = .;
. += PAGE_SIZE;
. = ALIGN(SEGMENT_ALIGN);
__init_begin = .; 'init段起始'
__inittext_begin = .;
......
. = ALIGN(SEGMENT_ALIGN);
__initdata_end = .;
__init_end = .; 'init段结束'
_data = .;
_sdata = .; '数据段起始'
RW_DATA(L1_CACHE_BYTES, PAGE_SIZE, THREAD_ALIGN)
_edata = .; '数据段结束'
BSS_SECTION(SBSS_ALIGN, 0, 0) --- 'BSS段'
. = ALIGN(PAGE_SIZE);
init_pg_dir = .;
. += INIT_DIR_SIZE;
init_pg_end = .;
......
}
1.2 入口
#arch/arm64/kernel/head.S
/*
* Kernel startup entry point.
* ---------------------------
*
* The requirements are:
* MMU = off, D-cache = off, I-cache = on or off,
* x0 = physical address to the FDT blob.
*
* This code is mostly position independent so you call this at
* __pa(PAGE_OFFSET).
*
* Note that the callee-saved registers are used for storing variables
* that are useful before the MMU is enabled. The allocations are described
* in the entry routines.
*/
__HEAD --- 定义在include/linux/init.h中'#define __HEAD .section ".head.text","ax"',紧接着_text
/*
* DO NOT MODIFY. Image header expected by Linux boot-loaders.
*/
efi_signature_nop // special NOP to identity as PE/COFF executable
b primary_entry // branch to kernel start, magic '要重点关注分析的启动汇编代码'
......
1.3 启动 AArch64 Linux的调用约定
??内核从上电开始到执行到内核入口"_text",中间要经过bootloader或者bios的引导。引导程序会做一些初始化内存,设置device tree,解压内核,跳转到内核等等。在跳转到内核之前,有一些标准的约定,参见Documentation/translations/zh_CN/arm64/booting.txt。这里仅列出在跳转入内核前,必须符合以下章节的状态:
在跳转入内核前,必须符合以下状态:
- 停止所有 DMA 设备,这样内存数据就不会因为虚假网络包或磁盘数据而 被破坏。这可能可以节省你许多的调试时间。
- 主 CPU 通用寄存器设置 x0 = 系统 RAM 中设备树数据块(dtb)的物理地址。
x1 = 0 (保留,将来可能使用)
x2 = 0 (保留,将来可能使用)
x3 = 0 (保留,将来可能使用)
- CPU 模式 所有形式的中断必须在 PSTATE.DAIF 中被屏蔽(Debug、SError、IRQ 和 FIQ)。
CPU 必须处于 EL2(推荐,可访问虚拟化扩展)或非安全 EL1 模式下。'bootloader来切'
- 高速缓存、MMU MMU 必须关闭。 'mmu关闭,指令高速缓存一般可以打开,数据高速缓存必须关闭'
指令缓存开启或关闭皆可。 已载入的内核映像的相应内存区必须被清理,以达到缓存一致性点(PoC)。 当存在系统缓存或其他使能缓存的一致性主控器时,通常需使用虚拟地址 维护其缓存,而非 set/way 操作。 遵从通过虚拟地址操作维护构架缓存的系统缓存必须被配置,并可以被使能。 而不通过虚拟地址操作维护构架缓存的系统缓存(不推荐),必须被配置且 禁用。
*译者注:对于 PoC 以及缓存相关内容,请参考 ARMv8 构架参考手册 ARM DDI 0487A
- 架构计时器 CNTFRQ 必须设定为计时器的频率,且 CNTVOFF 必须设定为对