kernel:5.10
Arch:arm64
1.前言
本专题主要基于《arm64_linux head.S的执行流程》系列文章,前者是基于3.18,本专题针对的是内核5.10。主要分析head.S的执行过程。本文将概括性的说明head.S的总体执行流程。
2. head.S执行前
bootloader在跳转到kernel前,需要确保如下设置:
MMU = off, D-cache = off, I-cache = on or off
x0 = physical address to the FDT blob
1.为何要保持MMU关闭?
因为此时还没有创建页表
2.为何要关闭D-cache?
因为D-cache中可能存在bootloader中带过来的数据,对于kernel阶段是无效的,因此要关闭D-cache.
3.为何I-cache可以开?因为bootloader与kernel位于不同的内存区间,不可能映射到I-cache的同一个set
在head.S执行前链接地址和物理内存布局,但是由于页表没有创建,MMU处于关闭状态,虚拟地址并未与物理地址建立映射关系
3. primary_entry执行总体流程
b primary_entry// 跳转到primary_entry
kernel的入口在arch\arm64\kernel\head.S,它会跳转到primary_entry,primary_entry在哪里定义的呢?
SYM_CODE_START(primary_entry)
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
SYM_CODE_END(primary_entry)
关于SYM_CODE_START宏定义如下:
#define SYM_CODE_START(name) \
SYM_START(name, SYM_L_GLOBAL, SYM_A_ALIGN)
#define SYM_L_GLOBAL(name) .globl name
#define SYM_A_ALIGN ALIGN
/* SYM_START -- use only if you have to */
#define SYM_START(name, linkage, align...) \
SYM_ENTRY(name, linkage, align)
/* SYM_ENTRY -- use only if you have to for non-paired symbols */
#define SYM_ENTRY(name, linkage, align...) \
linkage(name) ASM_NL \
align ASM_NL \
name:
#define ASM_NL ;
因此SYM_CODE_START(primary_entry)可以转换为:
.globl primary_entry; ALIGN ;primary_entry:
下面我们来看下primary_entry的执行流程:
1. preserve_boot_args:将bootloader传递的x0, x1, x2, x3保存到boot_args数组中,其中x0保存了FDT的地址,x1, x2, x3为0
2. el2_setup:根据当前CPU处于EL1还是EL2对CPU进行设置,主要设置了端模式,VHE设置,GIC设置,定时器开启等
3 .__PHYS_OFFSET:
#arch/arm64/kernel/head.S
#define __PHYS_OFFSET KERNEL_START
#arch/arm64/include/asm/memory.h
#define KERNEL_START _text
/*
* arm64 requires the kernel image to placed at a 2 MB aligned base address
*/
#define MIN_KIMG_ALIGN SZ_2M
booting.rst:
The Image must be placed text_offset bytes from a 2MB aligned base address anywhere in usable system RAM and called there. The region between the 2 MB aligned base address and the start of the image has no special significance to the kernel, and may be used for other purposes.At least image_size bytes from the start of the image must be free for use by the kernel.
(1)将__PHYS_OFFSET也就是kernel的入口链接地址_text保存到x23中
(2)由于kernel需2M对齐,通过and x23, x23, MIN_KIMG_ALIGN - 1,计算出对齐到2M需要的偏移量,保存到x23中,后面可以看到在kernle重定位时会通过x23进行2M对齐
4. set_cpu_boot_mode_flag:将cpu启动的模式保存到全局变量__boot_cpu_mode中
5. __create_page_tables:在idmap_pg区域为kernel创建恒等映射,在init_pg区域为kernel创建映射,这两块区域都位于vmlinux的data段,执行完__create_page_tables后得到地址映射关系如下:
注意:
(1)将.idmap.text段进行恒等映射,其物理地址等于虚拟地址:
arch/arm64/kernel/head.S:
/*Create the identity mapping*/
adrp x0, idmap_pg_dir //恒等映射区域的页表基地址
adrp x3, __idmap_text_start //开始映射的虚拟地址,直接就是__idmap_text_start物理地址
....
adrp x6, vabits_actual //结束映射的虚拟地址,直接等于__idmap_text_end的物理地址
adrp x5, _idmap_text_end //idmap段结束地址
/*
* .macro map_memory tbl, rtbl, vstart, vend, flags, phys, pgds, ....
*/
//可以看出映射内存时,使用的虚拟起始地址和物理起始地址都是x3
map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14
(2)映射kernel镜像(从HEAD_TEXT段开始,一直到.got.plt段结束,参考vmlinux.lds.S)
//Map the kernel image(starting with PHYS_OFFSET)
adrp x0, init_pg_dir //kernel镜像映射的页表基地址
//x5:映射的起始虚拟地址,为链接的虚拟地址(0xffff800000000000+BPF_JIT_REGION_SIZE+MODULES_VSIZE)
mov_q x5, KIMAGE_VADDR
...
adrp x3, _text //映射的起始物理地址
map_memory x0, x1, x5, x6, x7, x7 ....
之所以.idmap段可以进行恒等映射,原因是使用adrp 获取__idmap_text_start,此时获取的是相对pc的物理地址,而此时MMU未开启,故pc中的地址就是物理地址。如上述mov_q x5, KIMAGE_VADDR获取的就不是物理地址,而是符号链接接地址
id_map.text:为需要创建恒等映射的物理内存区域
id_map_pg:为恒等区域存放的页表区域
init_pg:为内核镜像存放的页表区域
7. __cpu_setup:为开启mmu, 对cpu进行设置,包括设置memory attribute等
8. __primary_switch:使能MMU,使能之前会分别用idmap_pg_dir和init_pag_dir设置TTBR0和TTBR1, 他们分别是kernel image的一致性页表起始虚拟地址和kernel image页表的起始虚拟地址;将kernel image的.rela.dyn段实现重定位;
为何用idmap_pg_dir初始化TTBR0?因为一致性映射意味着物理地址与虚拟地址相同,由于kernel image的idmap.text段位于物理地址0x48000000地址以下,对应虚拟地址空间处于用户空间,因此需要用idmap_pg_dir初始化TTBR0,这样打开MMU时就可以通过TTBR0来访问一致性页表了
// 由于ttbr0保存用户空间页表基地址, ttbr1保存内核空间页表基地址。0x48000000明显位于用户空间(48bit: 0x0 - 0xffffffffffff), 补充一点cpu更加bit63是否为0,选择使用ttbr0(bit63=0)还是ttbr1 (bit63 != 0)作为页表基地址。
9. __primary_switched会设置init进程栈及异常向量表,保存FDT地址,最终跳转到start_kernel执行
初始化init进程的地址映射关系如下:
参考文档
http://www.wowotech.net/armv8a_arch/arm64_initialize_1.html ARM64的启动过程之(一):内核第一个脚印
http://www.wowotech.net/armv8a_arch/create_page_tables.html ARM64的启动过程之(二):创建启动阶段的页表
http://www.wowotech.net/armv8a_arch/__cpu_setup.html ARM64的启动过程之(三):为打开MMU而进行的CPU初始化
http://www.wowotech.net/armv8a_arch/turn-on-mmu.html ARM64的启动过程之(四):打开MMU
https://blog.csdn.net/jkzzxQQQ/article/details/109880584
http://www.wowotech.net/memory_management/436.html
ARM64 Kernel Image Mapping的变化
Documents/arm64/booting.rst