Linux_aarch64_head.S到main.c的环境建立

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

前言


最开始,我仅仅是对linux比较感兴趣,觉得其很神奇的,能够做到很多事情。后面了解到其源码也是开源的,于是抱着学习的态度,简要的看了看相关的代码,在那个时候,我还看的比较粗略,仅仅是简单的会点编译,执行linux命令等等。这期间还有一个有印象的有趣的事儿就是那个pdf《Linux 那些事儿之USB》,大概就是讲述了作者因为要看pian儿,但是U盘识别不到,所以去细读USB相关linux 内核内容的资料。这精神,虽然我看不懂,但是我大为震撼!!!

在我工作的近几年来,逐渐的和linux打上了交道,从最开始的hisi 3520a^1^系列的流媒体处理开始,为其搭建了文件系统,编译内核,同时为其适配EC20 4G模块,这期间,我基本都是照着别人的教程或者说文档,渐渐的熟悉了一些linux内核的一些事务。同时这期间,我做过一些简单的字符驱动玩耍模块,只能说玩玩可以的。

在前几年中的某段时间,我接到一个任务,要在android进程之间大量传输数据^2^。这个时候我调研到了一个叫做android 匿名共享内存的东西,我发现了一个binder的驱动程序和linux unix socket的功能可以在android 和 linux 里面实现进程间的文件描述符的共享,注意这个方法是通用的,不像某些功能在linux里面能够使用,在android里面不能够使用。在这个时候,我天马行空实现了一个类似 binder的驱动demo^3^。这可以说是我第一个为了自己写的内核及的相关代码,而且具有实际应用意义。

在这些工作过程中,我逐渐的觉得自己学习的《操作系统原理》与现实的差别,特别想把书中知识和实际系统结合起来,经过查询,如果想要大概了解linux 内核,最好从其远古的版本读起来,因为大概的脉络没有变,新内核只是更加的结构化,多了很多现代的功能。于是乎,有了《Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)》一文^4^。经过了《Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)》一文的学习之后,我基本了解了linux kernel 0.12版本内核的基本工作原理,例如其调度,内存管理等。其次是对于x86架构下,linux kernel 0.12的启动流程有了一个简要的认知。

在最近这段时间,我的工作有部分和ai相关,有部分和android和linux的差异相关,需要我对linux内核有更深的印象和见解。于是在以前的基础上,这次,我要实际分析我们工作中所用的最新版本的内核,再一次的去验证一个内核从上电开始,到系统完整起来的过程。由于现代linux内核非常的巨大,所以我只关注我喜欢的部分。

本文主要是分析aarch64架构的arch/arm64/kernel/head.S 到 init/main.c 中的start_kernel的过程。这可能也是我短时间内最后一次分析这种启动的过程,因为其实道理都是相同的,大部分都是cpu初始化,虚拟内存启用,由实地址切换为虚拟地址,创建init_task,设置sp,进入start_kernel。其实这里很多都是和特定的CPU有关系,内容是固定的。但是虚拟内存启用,初始task创建,初始sp指针初始化这些和《操作系统原理》有关联,可以印证我们所学。

本文分为两大部分,一部分是head.S到main.c的调试环境建立, 二是从上电开始到进入start_kernel的代码注释分析和部分解释。

准备


  1. AARCH64 异常等级要简单了解一下^5^
  2. AARCH64 内存布局要简单了解一下^6^
  3. 看长文警告,一定要有耐心,否则看不下去。

汇编部分的调试环境搭建


本文的测试环境为qemu-system-aarch64 raspi3b 模拟板卡。linux内核为树莓派内核 rpi-5.15.y, 下载地址为:https://github.com/raspberrypi/linux.git

生成带调试符号的linux kernel 镜像

在make menu的时候勾选: Kernel hacking > Compile-time checks and compiler options > Compile the kernel with debug info

通过如下命令生成镜像:

shell 复制代码
cd rpi-linux-kernel-dir
cp arch/arm64/configs/bcm2711_defconfig .config
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- menuconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs
生成rootfs.img镜像

下载ubuntu-base-18.04.5-base-arm64.tar.gz的基础文件系统。

shell 复制代码
# 解压文件系统到指定目录
tar -xzf ubuntu-base-18.04.5-base-arm64.tar.gz -C temp/*

# 制作裸文件镜像
dd if=/dev/zero of=linuxroot.img bs=1M count=2048
sudo mkfs.ext4 linuxroot.img
mkdir  rootfs
sudo mount linuxroot.img rootfs/
sudo cp -rfp temp/*  rootfs/
sudo umount rootfs/
e2fsck -p -f linuxroot.img
resize2fs  -M linuxroot.img
编译生成最新版qemu

只有新版的qemu才支持raspi3b模拟板卡

shell 复制代码
# 下载qemu代码
git clone https://github.com/qemu/qemu.git
cd qemu
mkdir build
cd build
../configure --prefix=/home/sky/LinuxKernel/qemu_install --target-list=arm-softmmu,arm-linux-user,armeb-linux-user,aarch64-softmmu,aarch64-linux-user,aarch64_be-linux-user 
make
执行qemu加载镜像

这里的linux目录是内核目录,qemu_install是qemu生成的最新可执行文件目录,当前目录有rootfs镜像linuxroot.img。

shell 复制代码
# -S              freeze CPU at startup (use 'c' to start execution)
# -s              shorthand for -gdb tcp::1234
#  注意下面命令如果要直接运行,而不是等待gdb调试,请去掉最后的-s 和 -S。
./qemu_install/bin/qemu-system-aarch64 \
	-M raspi3b \
	-kernel ./linux/arch/arm64/boot/Image \
	-dtb ./linux/arch/arm64/boot/dts/broadcom/bcm2710-rpi-3-b.dtb \
	-drive id=hd-root,format=raw,file=./linuxroot.img \
	-m 1024M \
	-serial stdio \
	-smp 4 \
	-device usb-kbd \
	-device usb-tablet \
	-device usb-net,netdev=net0 \
	-netdev user,id=net0,hostfwd=tcp::5555-:22 \
	-append "rw earlycon=pl011,0x3f201000 console=ttyAMA0 loglevel=8 root=/dev/mmcblk0 rootwait" -S -s
gdb 连接调试

注意请在qemu启动后面加上-s 和 -S。当连接成功时,这个时候cpu还未执行,输入ni执行到第一条指令。

shell 复制代码
# 没有gdb-multiarch自行安装
# 这里的vmlinux就是编译生成的最终镜像
gdb-multiarch  linux/vmlinux

# 在gdb cli中执行
target remote localhost:1234
汇编代码调试

我这里把整个head.S里面重要的部分都dump下来了。跟着这个部分然后参考head.S去阅读,会有奇效。长文注释警告。

首先是上电部分,当板卡上电后,会执行bootloader,bootloader会将内核和dtb放到特定的位置,然后按照Linux arm64 boot protocal去初始化对应的寄存器,最后进入head.S的第一条指令。

asm 复制代码
@ boot start ... ...
//Linux arm64 boot protocal
@ https://www.kernel.org/doc/Documentation/arm64/booting.txt
@ 0x08000000 FDT
/*
- 主 CPU 通用寄存器设置
  x0 = 系统 RAM 中设备树 blob (dtb) 的物理地址。
  x1 = 0(留作将来使用)
  x2 = 0(留作将来使用)
  x3 = 0(留作将来使用)
*/
//注意这里的0x18地址存放的是fdt的地址,地址为0x08000000

0x0000000000000000:	ldr	x0, 0x18
0x0000000000000004:	mov	x1, xzr
0x0000000000000008:	mov	x2, xzr
0x000000000000000c:	mov	x3, xzr
//注意这里的0x20是存放的kernel地址,地址为0x00200000
0x0000000000000010:	ldr	x4, 0x20
0x0000000000000014:	br	x4
@ 0x00200000 head.S start ... ...
@ =======> now, go to 0x00200000

注意,当我们进入head.S的最开始的地方的时候,有一个标准得到头如下。其中code1的部分将会跳转到真正执行的地方。

asm 复制代码
@ The decompressed kernel image contains a 64-byte header as follows:
@   u32 code0;			/* Executable code */ 
@   u32 code1;			/* Executable code */
@   u64 text_offset;		/* Image load offset, little endian */
@   u64 image_size;		/* Effective Image size, little endian */
@   u64 flags;			/* kernel flags, little endian */
@   u64 res2	= 0;		/* reserved */
@   u64 res3	= 0;		/* reserved */
@   u64 res4	= 0;		/* reserved */
@   u32 magic	= 0x644d5241;	/* Magic number, little endian, "ARM\x64" */
@   u32 res5;			/* reserved (used for PE COFF offset) */

@ 0x200000  code0
@ 0x200004  code1                            
@ 0x200008  text_offset 
@ 0x20000c                           
@ 0x200010  image_size                    
@ 0x200014                    
@ 0x200018  flags                       
@ 0x20001c                          
@ 0x200020  res2                    
@ 0x200024                          
@ 0x200028  res3                       
@ 0x20002c                      
@ 0x200030  res4                       
@ 0x200034                    
@ 0x200038  magic       
@ 0x20003c  res5
//注意,这里的code0,code1就是地址0x200000和0x200004的指令。整个0x200000到0x200040就是内核镜像的64字节头。
0x0000000000200000:	ccmp	x18, #0x0, #0xd, pl  // special NOP to identity as PE/COFF executable
0x0000000000200004:	b	0x1190000 @ =======> now, go to 0x1190000(primary_entry)

这是整个内核启动部分最重要的函数,所有的东西都在这里做完,然后跳转到start_kernel。下面我们会来重点分析这个部分的内容。详细请看注释。

asm 复制代码
//注意这里的几个bl指令,覆盖了进入kernel_start前的所有操作
@ SYM_CODE_START(primary_entry)

//跳转过去保存boot参数
//保存x0(fdt),x1,x2,x3到符号boot_args的变量中,靠dcache_inval_poc中的ret返回到下一行指令
0x0000000001190000:	bl	0x1190020 //preserve_boot_args

//跳转过去执行不同异常等级的初始化,这里比较复杂,从开始的el2异常级别跳转到el1级别。
0x0000000001190004:	bl	0xd5d000 //init_kernel_el

//将内核镜像开始地址给x23,也就是0x200000
0x0000000001190008:	adrp	x23, 0x200000
// KASLR offset, defaults to 0
0x000000000119000c:	and	x23, x23, #0x1fffff

//跳转过去根据w0的值,保存相关的cpu boot mode,注意当前我们的cpu已经处于el1等级,w0 存的是 el2 的标识符
0x0000000001190010:	bl	0xd5d1f8

//创建页表,这里面的创建只填充了相关的页表项,并没有开启mmu
//idmap = 0x117e00, 0x117e0 = 0x117f03, 0x117f30 = 0x00c00701, 
//    注意,这时页表项表示2MB的区域。idmap区域包含了__cpu_setup和__primary_switch
//这里执行完,有两个重要的数据结构:idmap_pg_dir 和 init_pg_dir
//我们将物理地址__idmap_text_start映射到虚拟地址[__idmap_text_start, __idmap_text_end],注意观察,物理地址和虚拟地址基本是一致的。
//还将物理地址_text映射到虚拟地址[KIMAGE_VADDR + KASLR, _end]
0x0000000001190014:	bl	0x1190040

//The following calls CPU setup code, see arch/arm64/mm/proc.S
//注意,这里已经准备好了SCTLR在x0中,下面就是相关的初始化,然后准备打开mmu的参数
0x0000000001190018:	bl	0xd5d6f4

//最终的初始化,开启mmu,并跳转到kernel_start
0x000000000119001c:	b	0xd5d3d8

@ SYM_CODE_END(primary_entry)

此部分对应保存启动参数,主要还是保存启动时,x0~x3。

asm 复制代码
@ SYM_CODE_START_LOCAL(preserve_boot_args)
//x21保存dtb物理地址
0x0000000001190020:	mov	x21, x0
//将dtb,x1,x2,x3物理地址存放到变量arch/arm64/kernel/setup.c:u64 __cacheline_aligned boot_args[4];
0x0000000001190024:	adrp	x0, 0x1545000
0x0000000001190028:	add	x0, x0, #0x0
//存放dtb,x1
0x000000000119002c:	stp	x21, x1, [x0]
//存放x2,x3
0x0000000001190030:	stp	x2, x3, [x0, #16]

@ 刷新cache
0x0000000001190034:	dmb	sy
0x0000000001190038:	add	x1, x0, #0x20
0x000000000119003c:	b	0x2346a8

@ SYM_CODE_END(preserve_boot_args)

此部分很长,其实主要是汇编代码稍微复杂,其基本的作用就是创建页表,这里面的创建只填充了相关的页表项。分别创建了这里执行完,有两个重要的数据结构:idmap_pg_dir和init_pg_dir的数据结构。这里用的是两级映射,第一级是全局映射,第二级是每个项2MB的映射。这里的idmap_pg_dir映射的是__cpu_setup和__primary_switch部分的内容,这部分主要涉及到mmu开启的过程,需要将物理地址和虚拟地址对应起来。init_pg_dir主要是映射的是kernel虚拟地址和kernel镜像地址。

asm 复制代码
@ SYM_FUNC_START_LOCAL(__create_page_tables)
//保存返回值到x28
0x0000000001190040:	mov	x28, x30
//加载init_pg_dir 到x0
0x0000000001190044:	adrp	x0, 0x181d000
//加载init_pg_end 到x1
0x0000000001190048:	adrp	x1, 0x1820000
@ 刷新cache
0x000000000119004c:	bl	0x2346a8

//加载init_pg_dir 到x0
0x0000000001190050:	adrp	x0, 0x181d000
//加载init_pg_end 到x1
0x0000000001190054:	adrp	x1, 0x1820000
//求出init_pg的大小放入x1中count
0x0000000001190058:	sub	x1, x1, x0
//向x0中写入0,然后x1 -= 64,当x1等于0时候,所有pg清理完毕。
0x000000000119005c:	stp	xzr, xzr, [x0], #16
0x0000000001190060:	stp	xzr, xzr, [x0], #16
0x0000000001190064:	stp	xzr, xzr, [x0], #16
0x0000000001190068:	stp	xzr, xzr, [x0], #16
0x000000000119006c:	subs	x1, x1, #0x40
0x0000000001190070:	b.ne	0x119005c  // b.any

//根据配置加载不同的flag, SWAPPER_MM_MMUFLAGS
0x0000000001190074:	mov	x7, #0x701                 	// #1793

// 获取idmap的页表基地址,idmap_pg_dir
0x0000000001190078:	adrp	x0, 0x117e000
@ 获取idmap的代码段虚地址,__idmap_text_start
0x000000000119007c:	adrp	x3, 0xd5d000
@ 将系统地址线位数给x5, VA_BITS_MIN
0x0000000001190080:	mov	x5, #0x27                  	// #39
//获取变量地址到x6, vabits_actual
0x0000000001190084:	adrp	x6, 0x1728000
0x0000000001190088:	add	x6, x6, #0x10
//将39写入变量vabits_actual
0x000000000119008c:	str	x5, [x6]
0x0000000001190090:	dmb	sy
0x0000000001190094:	dc	ivac, x6

@ 判断虚拟地址空间是否够IDmap来映射
0x0000000001190098:	adrp	x5, 0xd5d000
0x000000000119009c:	clz	x5, x5
0x00000000011900a0:	cmp	x5, #0x19
@ 这里要跳转,不需要扩展虚拟地址
0x00000000011900a4:	b.ge	0x11900e0  // b.tcont

@ 这部分是虚拟地址扩展的相关操作,这里不做详解
0x00000000011900a8:	adrp	x6, 0x1555000
0x00000000011900ac:	add	x6, x6, #0xcc8
0x00000000011900b0:	str	x5, [x6]
0x00000000011900b4:	dmb	sy
0x00000000011900b8:	dc	ivac, x6
0x00000000011900bc:	mov	x4, #0x200                 	// #512
0x00000000011900c0:	add	x5, x0, #0x1, lsl #12
0x00000000011900c4:	mov	x6, x5
0x00000000011900c8:	orr	x6, x6, #0x3
0x00000000011900cc:	lsr	x5, x3, #39
0x00000000011900d0:	sub	x4, x4, #0x1
0x00000000011900d4:	and	x5, x5, x4
0x00000000011900d8:	str	x6, [x0, x5, lsl #3]
0x00000000011900dc:	add	x0, x0, #0x1, lsl #12

@ 从上面不需要扩展虚拟地址跳转而来,0x00000000011900a4
@ 将512 个 pgd entry存入x4
0x00000000011900e0:	adrp	x4, 0x1555000
0x00000000011900e4:	ldr	x4, [x4, #3280]

@ 将__idmap_text_end放入x6
0x00000000011900e8:	adrp	x6, 0xd5d000
0x00000000011900ec:	add	x6, x6, #0x7e0

@ 这里开始映射[__idmap_text_start, __idmap_text_end] 到 idmap_pg_dir中,
@	且,这部分内容就是cpu_setup部分的内容,恰好对应开启mmu的代码。
//tbl:    x0 = idmap_pg_dir = 0x117e000
//rtbl:   x1 = 0 
//vstart: x3 = __idmap_text_start = 0xd5d000
//vend:   x6 = __idmap_text_end = 0xd5d7e0
//flags:  x7 = SWAPPER_MM_MMUFLAGS
//phys:   x3 = __idmap_text_start = 0xd5d000
//pgds:   x4 = idmap_ptrs_per_pgd = 512
//tmp regs: x10, x11, x12, x13, x14
@ macro map_memory start ...
@ __idmap_text_end - 1 是idmap映射结束的地方
0x00000000011900f0:	sub	x6, x6, #0x1
@ x1 = idmap的页表基地址(idmap_pg_dir) + 2^12 ,并指向了下一个page entry
0x00000000011900f4:	add	x1, x0, #0x1, lsl #12
@ 将x1 保存到 x14
0x00000000011900f8:	mov	x14, x1
@ 将count赋值为0
0x00000000011900fc:	mov	x13, #0x0                   	// #0

// vstart:	x3 = __idmap_text_start = 0xd5d000
// vend:	x6 = __idmap_text_end = 0xd5d7e0
// shift:	30
// ptrs:	x4 = idmap_ptrs_per_pgd = 512
// istart:	x10
// iend:	
// count:	x13
@ compute_indices  start ...
//将vend右逻辑偏移shift(30)位
0x0000000001190100:	lsr	x11, x6, #30
//将ptrs(number of entries in page table)存放到istart, 每个表512项
0x0000000001190104:	mov	x10, x4
//pte 数量减一
0x0000000001190108:	sub	x10, x10, #0x1

//此时算出来vend的pgd index,根据虚拟地址右移30位,还剩9位,恰好表示512个项的id。
// iend = (vend >> shift) & (ptrs - 1)
0x000000000119010c:	and	x11, x11, x10
0x0000000001190110:	mov	x10, x4
0x0000000001190114:	mul	x10, x10, x13
// iend += count * ptrs
0x0000000001190118:	add	x11, x11, x10

//将vstart右逻辑偏移shift位
0x000000000119011c:	lsr	x10, x3, #30
0x0000000001190120:	mov	x13, x4
0x0000000001190124:	sub	x13, x13, #0x1
// istart = (vstart >> shift) & (ptrs - 1), 此时算出来vstart的pgd index
0x0000000001190128:	and	x10, x10, x13
//计算出多少项page entry
0x000000000119012c:	sub	x13, x11, x10
@ compute_indices  end ...



@ populate_entries start ... 
0x0000000001190130:	mov	x12, x1
//给当前entry设置内存属性
0x0000000001190134:	orr	x12, x12, #0x3
@ 向一级页表idmap_pg_dir(0x117e000)存入二级页表地址(0x117f000)
0x0000000001190138:	str	x12, [x0, x10, lsl #3]
0x000000000119013c:	add	x1, x1, #0x1, lsl #12
0x0000000001190140:	add	x10, x10, #0x1
0x0000000001190144:	cmp	x10, x11
0x0000000001190148:	b.ls	0x1190130  // b.plast
@ populate_entries end ... 

//注意,这里相当于tbl=tbl+PAGE_SIZE,主要还是指向了二级页表
//相当于现在一级页表为:idmap_pg_dir(0x117e000),里面存放的是0x0117f003
//这里的x14是二级页表的地址,为0x0117f000
0x000000000119014c:	mov	x0, x14
//sv = rtbl = tbl+PAGE_SIZE+PAGE_SIZE,相当于指向了三级页表
0x0000000001190150:	mov	x14, x1

@ compute_indices  start ...
//将vend右逻辑偏移shift位
0x0000000001190154:	lsr	x11, x6, #21
//将ptrs(number of entries in page table)存放到istart, 每个表512项
0x0000000001190158:	mov	x10, #0x200                 	// #512
//pte 数量减一
0x000000000119015c:	sub	x10, x10, #0x1

//此时算出来vend的pgd index
// iend = (vend >> shift) & (ptrs - 1)
0x0000000001190160:	and	x11, x11, x10
0x0000000001190164:	mov	x10, #0x200                 	// #512
0x0000000001190168:	mul	x10, x10, x13
// iend += count * ptrs
0x000000000119016c:	add	x11, x11, x10

//将vstart右逻辑偏移shift位
0x0000000001190170:	lsr	x10, x3, #21
0x0000000001190174:	mov	x13, #0x200                 	// #512
0x0000000001190178:	sub	x13, x13, #0x1
// istart = (vstart >> shift) & (ptrs - 1), 此时算出来vstart的pgd index
0x000000000119017c:	and	x10, x10, x13
//计算出多少项page entry
0x0000000001190180:	sub	x13, x11, x10
@ compute_indices  end ...

@ x13 = 0xc00000, x3 = 0xd5d000
0x0000000001190184:	and	x13, x3, #0xffffffffffe00000

@ populate_entries start ... 
@ x12 = 0xc00000
0x0000000001190188:	mov	x12, x13
//给当前entry设置内存属性
0x000000000119018c:	orr	x12, x12, x7
@ 向二级页表(0x117f000 + 6*8 = 0x117f030)存入地址0xc00701
0x0000000001190190:	str	x12, [x0, x10, lsl #3]
0x0000000001190194:	add	x13, x13, #0x200, lsl #12
0x0000000001190198:	add	x10, x10, #0x1
0x000000000119019c:	cmp	x10, x11
0x00000000011901a0:	b.ls	0x1190188  // b.plast
@ populate_entries end ... 

@ init_pg_dir  给x0
0x00000000011901a4:	adrp	x0, 0x181d000
@ KIMAGE_VADDR 给x5
0x00000000011901a8:	mov	x5, #0xffffffc0ffffffff    	// #-270582939649
0x00000000011901ac:	movk	x5, #0x800, lsl #16
0x00000000011901b0:	movk	x5, #0x0
// add KASLR displacement
0x00000000011901b4:	add	x5, x5, x23
@ 将页表项数目给x4
0x00000000011901b8:	mov	x4, #0x200                 	// #512
@ _end 给x6
0x00000000011901bc:	adrp	x6, 0x1820000
@ _start 给x3
0x00000000011901c0:	adrp	x3, 0x200000
@ 求出_end-_start
0x00000000011901c4:	sub	x6, x6, x3
@ 算出基于KIMAGE_VADDR和KASLR的偏移
0x00000000011901c8:	add	x6, x6, x5

//tbl:    x0 = init_pg_dir
//rtbl:   x1 = 0
//vstart: x5 = KIMAGE_VADDR + KASLR
//vend:   x6 = _end
//flags:  x7 = SWAPPER_MM_MMUFLAGS
//phys:   x3 = _text
//pgds:   x4 = PTRS_PER_PGD
//tmp regs: x10, x11, x12, x13, x14
@ macro map_memory start ...
@ 开始填充页表init_pg_dir
0x00000000011901cc:	sub	x6, x6, #0x1
0x00000000011901d0:	add	x1, x0, #0x1, lsl #12
0x00000000011901d4:	mov	x14, x1
0x00000000011901d8:	mov	x13, #0x0                   	// #0
0x00000000011901dc:	lsr	x11, x6, #30
0x00000000011901e0:	mov	x10, x4
0x00000000011901e4:	sub	x10, x10, #0x1
0x00000000011901e8:	and	x11, x11, x10
0x00000000011901ec:	mov	x10, x4
0x00000000011901f0:	mul	x10, x10, x13
0x00000000011901f4:	add	x11, x11, x10
0x00000000011901f8:	lsr	x10, x5, #30
0x00000000011901fc:	mov	x13, x4
0x0000000001190200:	sub	x13, x13, #0x1
0x0000000001190204:	and	x10, x10, x13
0x0000000001190208:	sub	x13, x11, x10
0x000000000119020c:	mov	x12, x1
0x0000000001190210:	orr	x12, x12, #0x3
0x0000000001190214:	str	x12, [x0, x10, lsl #3]
0x0000000001190218:	add	x1, x1, #0x1, lsl #12
0x000000000119021c:	add	x10, x10, #0x1
0x0000000001190220:	cmp	x10, x11
0x0000000001190224:	b.ls	0x119020c  // b.plast
0x0000000001190228:	mov	x0, x14
0x000000000119022c:	mov	x14, x1
0x0000000001190230:	lsr	x11, x6, #21
0x0000000001190234:	mov	x10, #0x200                 	// #512
0x0000000001190238:	sub	x10, x10, #0x1
0x000000000119023c:	and	x11, x11, x10
0x0000000001190240:	mov	x10, #0x200                 	// #512
0x0000000001190244:	mul	x10, x10, x13
0x0000000001190248:	add	x11, x11, x10
0x000000000119024c:	lsr	x10, x5, #21
0x0000000001190250:	mov	x13, #0x200                 	// #512
0x0000000001190254:	sub	x13, x13, #0x1
0x0000000001190258:	and	x10, x10, x13
0x000000000119025c:	sub	x13, x11, x10
0x0000000001190260:	and	x13, x3, #0xffffffffffe00000
0x0000000001190264:	mov	x12, x13
0x0000000001190268:	orr	x12, x12, x7
0x000000000119026c:	str	x12, [x0, x10, lsl #3]
0x0000000001190270:	add	x13, x13, #0x200, lsl #12
0x0000000001190274:	add	x10, x10, #0x1
0x0000000001190278:	cmp	x10, x11
0x000000000119027c:	b.ls	0x1190264  // b.plast
@ macro map_memory end ...


//内存屏障
0x0000000001190280:	dmb	sy
@ 刷新cache
0x0000000001190284:	adrp	x0, 0x117e000
0x0000000001190288:	adrp	x1, 0x1181000
0x000000000119028c:	bl	0x2346a8
@ 刷新cache
0x0000000001190290:	adrp	x0, 0x181d000
0x0000000001190294:	adrp	x1, 0x1820000
0x0000000001190298:	bl	0x2346a8
@ 返回到bl	__cpu_setup
0x000000000119029c:	ret	x28


@ SYM_FUNC_END(__create_page_tables)

这部分是刷新i/d cache

asm 复制代码
@ dcache_inval_poc start ...
0x00000000002346a8:	mrs	x3, ctr_el0
0x00000000002346ac:	nop
0x00000000002346b0:	ubfx	x3, x3, #16, #4
0x00000000002346b4:	mov	x2, #0x4                   	// #4
0x00000000002346b8:	lsl	x2, x2, x3
0x00000000002346bc:	sub	x3, x2, #0x1
0x00000000002346c0:	tst	x1, x3
0x00000000002346c4:	bic	x1, x1, x3
0x00000000002346c8:	b.eq	0x2346d0  // b.none
0x00000000002346cc:	dc	civac, x1
0x00000000002346d0:	tst	x0, x3
0x00000000002346d4:	bic	x0, x0, x3
0x00000000002346d8:	b.eq	0x2346e4  // b.none
0x00000000002346dc:	dc	civac, x0
0x00000000002346e0:	b	0x2346e8
0x00000000002346e4:	dc	ivac, x0
0x00000000002346e8:	add	x0, x0, x2
0x00000000002346ec:	cmp	x0, x1
0x00000000002346f0:	b.cc	0x2346e4  // b.lo, b.ul, b.last
0x00000000002346f4:	dsb	sy
0x00000000002346f8:	ret
@ dcache_inval_poc end ...

这部分就是对应的是开始的在el2模式下初始化,并返回到el1,并保存启动参数。

asm 复制代码
@ SYM_FUNC_START(init_kernel_el)
//读取当前的异常等级
0x0000000000d5d000:	mrs	x0, currentel
//判断是否为异常等级2
0x0000000000d5d004:	cmp	x0, #0x8
//跳转到el2(qemu 模拟机器执行路径), init_el2
0x0000000000d5d008:	b.eq	0xd5d034  // b.none
0x0000000000d5d00c:	mov	x0, #0x30500000            	// #810549248
0x0000000000d5d010:	movk	x0, #0x800
0x0000000000d5d014:	msr	sctlr_el1, x0
0x0000000000d5d018:	isb
0x0000000000d5d01c:	movz	x0, #0x0, lsl #16
0x0000000000d5d020:	movk	x0, #0x3c5
0x0000000000d5d024:	msr	spsr_el1, x0
0x0000000000d5d028:	msr	elr_el1, x30
0x0000000000d5d02c:	mov	w0, #0xe11                 	// #3601
0x0000000000d5d030:	eret

@ init_el2 start ... ...
//配置hcr_el2寄存器,HCR(Hypervisor Configuration Register)
0x0000000000d5d034:	mov	x0, #0x100000000000000     	// #72057594037927936
0x0000000000d5d038:	movk	x0, #0x300, lsl #32
0x0000000000d5d03c:	movk	x0, #0x8000, lsl #16
0x0000000000d5d040:	movk	x0, #0x0
0x0000000000d5d044:	msr	hcr_el2, x0
//ISB. 指令同步屏障
0x0000000000d5d048:	isb

//初始化el2下的各种状态
/*
.macro init_el2_state
	__init_el2_sctlr
	__init_el2_timers
	__init_el2_debug
	__init_el2_lor
	__init_el2_stage2
	__init_el2_gicv3
	__init_el2_hstr
	__init_el2_nvhe_idregs
	__init_el2_nvhe_cptr
	__init_el2_nvhe_sve
	__init_el2_fgt
	__init_el2_nvhe_prepare_eret
.endm
*/

//__init_el2_sctlr
0x0000000000d5d04c:	mov	x0, #0x30c50000            	// #818216960
0x0000000000d5d050:	movk	x0, #0x830
0x0000000000d5d054:	msr	sctlr_el2, x0
0x0000000000d5d058:	isb

//__init_el2_timers
0x0000000000d5d05c:	mov	x0, #0x3                   	// #3
0x0000000000d5d060:	msr	cnthctl_el2, x0
0x0000000000d5d064:	msr	cntvoff_el2, xzr

//__init_el2_debug
0x0000000000d5d068:	mrs	x1, id_aa64dfr0_el1
0x0000000000d5d06c:	sbfx	x0, x1, #8, #4
0x0000000000d5d070:	cmp	x0, #0x1
0x0000000000d5d074:	b.lt	0xd5d080  // b.tstop
0x0000000000d5d078:	mrs	x0, pmcr_el0
0x0000000000d5d07c:	ubfx	x0, x0, #11, #5
0x0000000000d5d080:	csel	x2, xzr, x0, lt  // lt = tstop
0x0000000000d5d084:	ubfx	x0, x1, #32, #4
0x0000000000d5d088:	cbz	x0, 0xd5d0a8
0x0000000000d5d08c:	mrs	x0, pmbidr_el1
0x0000000000d5d090:	and	x0, x0, #0x10
0x0000000000d5d094:	cbnz	x0, 0xd5d0a0
0x0000000000d5d098:	mov	x0, #0x50                  	// #80
0x0000000000d5d09c:	msr	pmscr_el2, x0
0x0000000000d5d0a0:	mov	x0, #0x3000                	// #12288
0x0000000000d5d0a4:	orr	x2, x2, x0
0x0000000000d5d0a8:	ubfx	x0, x1, #44, #4
0x0000000000d5d0ac:	cbz	x0, 0xd5d0c4
0x0000000000d5d0b0:	mrs	x0, s3_0_c9_c11_7
0x0000000000d5d0b4:	and	x0, x0, #0x10
0x0000000000d5d0b8:	cbnz	x0, 0xd5d0c4
0x0000000000d5d0bc:	mov	x0, #0x3000000             	// #50331648
0x0000000000d5d0c0:	orr	x2, x2, x0
0x0000000000d5d0c4:	msr	mdcr_el2, x2

//__init_el2_lor
0x0000000000d5d0c8:	mrs	x1, id_aa64mmfr1_el1
0x0000000000d5d0cc:	ubfx	x0, x1, #16, #4
0x0000000000d5d0d0:	cbz	x0, 0xd5d0d8
0x0000000000d5d0d4:	msr	s3_0_c10_c4_3, xzr

//__init_el2_stage2
0x0000000000d5d0d8:	msr	vttbr_el2, xzr

//__init_el2_gicv3
0x0000000000d5d0dc:	mrs	x0, id_aa64pfr0_el1
0x0000000000d5d0e0:	ubfx	x0, x0, #24, #4
0x0000000000d5d0e4:	cbz	x0, 0xd5d108
0x0000000000d5d0e8:	mrs	x0, s3_4_c12_c9_5
0x0000000000d5d0ec:	orr	x0, x0, #0x1
0x0000000000d5d0f0:	orr	x0, x0, #0x8
0x0000000000d5d0f4:	msr	s3_4_c12_c9_5, x0
0x0000000000d5d0f8:	isb
0x0000000000d5d0fc:	mrs	x0, s3_4_c12_c9_5
0x0000000000d5d100:	tbz	w0, #0, 0xd5d108
0x0000000000d5d104:	msr	s3_4_c12_c11_0, xzr

//__init_el2_hstr
0x0000000000d5d108:	msr	hstr_el2, xzr

//__init_el2_nvhe_idregs
0x0000000000d5d10c:	mrs	x0, midr_el1
0x0000000000d5d110:	mrs	x1, mpidr_el1
0x0000000000d5d114:	msr	vpidr_el2, x0
0x0000000000d5d118:	msr	vmpidr_el2, x1

//__init_el2_nvhe_cptr
0x0000000000d5d11c:	mov	x0, #0x33ff                	// #13311
0x0000000000d5d120:	msr	cptr_el2, x0

//__init_el2_nvhe_sve
0x0000000000d5d124:	mrs	x1, id_aa64pfr0_el1
0x0000000000d5d128:	ubfx	x1, x1, #32, #4
0x0000000000d5d12c:	cbz	x1, 0xd5d144
0x0000000000d5d130:	and	x0, x0, #0xfffffffffffffeff
0x0000000000d5d134:	msr	cptr_el2, x0
0x0000000000d5d138:	isb
0x0000000000d5d13c:	mov	x1, #0x1ff                 	// #511
0x0000000000d5d140:	msr	zcr_el2, x1

//__init_el2_fgt
0x0000000000d5d144:	mrs	x1, id_aa64mmfr0_el1
0x0000000000d5d148:	ubfx	x1, x1, #56, #4
0x0000000000d5d14c:	cbz	x1, 0xd5d18c
0x0000000000d5d150:	mov	x0, xzr
0x0000000000d5d154:	mrs	x1, id_aa64dfr0_el1
0x0000000000d5d158:	ubfx	x1, x1, #32, #4
0x0000000000d5d15c:	cmp	x1, #0x3
0x0000000000d5d160:	b.lt	0xd5d168  // b.tstop
0x0000000000d5d164:	orr	x0, x0, #0x4000000000000000
0x0000000000d5d168:	msr	s3_4_c3_c1_4, x0
0x0000000000d5d16c:	msr	s3_4_c3_c1_5, x0
0x0000000000d5d170:	msr	s3_4_c1_c1_4, xzr
0x0000000000d5d174:	msr	s3_4_c1_c1_5, xzr
0x0000000000d5d178:	msr	s3_4_c1_c1_6, xzr
0x0000000000d5d17c:	mrs	x1, id_aa64pfr0_el1
0x0000000000d5d180:	ubfx	x1, x1, #44, #4
0x0000000000d5d184:	cbz	x1, 0xd5d18c
0x0000000000d5d188:	msr	s3_4_c3_c1_6, xzr

// __init_el2_nvhe_prepare_eret
0x0000000000d5d18c:	mov	x0, #0x3c5                 	// #965
0x0000000000d5d190:	msr	spsr_el2, x0


//加载el2的中断向量表
0x0000000000d5d194:	adrp	x0, 0xd4f000
0x0000000000d5d198:	add	x0, x0, #0x0
0x0000000000d5d19c:	msr	vbar_el2, x0
0x0000000000d5d1a0:	isb

/*
	* Fruity CPUs seem to have HCR_EL2.E2H set to RES1,
	* making it impossible to start in nVHE mode. Is that
	* compliant with the architecture? Absolutely not!
*/
0x0000000000d5d1a4:	mrs	x0, hcr_el2
0x0000000000d5d1a8:	and	x0, x0, #0x400000000
//跳转到1f(0xd5d1d0)位置继续执行
0x0000000000d5d1ac:	cbz	x0, 0xd5d1d0

0x0000000000d5d1b0:	mov	x0, #0x30500000            	// #810549248
0x0000000000d5d1b4:	movk	x0, #0x800
0x0000000000d5d1b8:	msr	sctlr_el12, x0
0x0000000000d5d1bc:	mov	x0, #0x3c9                 	// #969
0x0000000000d5d1c0:	msr	spsr_el1, x0
0x0000000000d5d1c4:	adr	x0, 0xd5d1e8
0x0000000000d5d1c8:	msr	elr_el1, x0
0x0000000000d5d1cc:	eret

//将x0写入sctlr_el1
0x0000000000d5d1d0:	mov	x0, #0x30500000            	// #810549248
0x0000000000d5d1d4:	movk	x0, #0x800
0x0000000000d5d1d8:	msr	sctlr_el1, x0

//将当前的lr(x30)地址放到elr_el2中,后续eret到el1时,跳转到此地址执行
0x0000000000d5d1dc:	msr	elr_el2, x30
//将flag写入w0中
0x0000000000d5d1e0:	mov	w0, #0xe12                 	// #3602
//注意这里的返回值,这里返回到elr_el2指向的地方,
//也就是adrp	x23, __PHYS_OFFSET, 0x0000000001190008
0x0000000000d5d1e4:	eret


0x0000000000d5d1e8:	mov	x0, #0x3                   	// #3
0x0000000000d5d1ec:	hvc	#0x0
0x0000000000d5d1f0:	mov	x0, #0xe12                 	// #3602
0x0000000000d5d1f4:	ret

@ SYM_FUNC_END(init_kernel_el)


@ SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
//加载__boot_cpu_mode符号地址,此地址存放
0x0000000000d5d1f8:	adrp	x1, 0x1728000
0x0000000000d5d1fc:	add	x1, x1, #0x0
0x0000000000d5d200:	cmp	w0, #0xe12
0x0000000000d5d204:	b.ne	0xd5d20c  // b.any
0x0000000000d5d208:	add	x1, x1, #0x4
0x0000000000d5d20c:	str	w0, [x1]
0x0000000000d5d210:	dmb	sy
0x0000000000d5d214:	dc	ivac, x1
@ 返回到0x0000000001190014
0x0000000000d5d218:	ret

@ SYM_FUNC_END(set_cpu_boot_mode_flag)

这里包含3个步骤:

  1. 这里其实包含了开启mmu,mmu开启时,tbbr0_el1是idmap_pg_dir,tbbr1_el1是init_pg_dir,还是使用的物理地址,所以这个时候的地址翻译由页表idmap_pg_dir来支持。
  2. 当完成mmu开启后,就会将.rela.dyn段实现重定位,因为这部分的符号的实际地址为0,需要我们填写为实际的符号地址。
  3. 当重定位完成后,会加载__primary_switched的虚拟地址,其地址在内核空间,当执行blr 跳转到__primary_switched的时候,这个时候的地址返回由页表init_pg_dir来支持。
asm 复制代码
@ SYM_FUNC_START(__enable_mmu)
/*
符合ARMv8的PE最大支持的物理地址宽度也是48个bit,当然,具体的实现可以自己定义
(不能超过48个bit),具体的配置可以通过ID_AA64MMFR0_EL1 
(AArch64 Memory Model Feature Register 0)这个RO寄存器获取
*/
0xd5d308        mrs    x2, id_aa64mmfr0_el1     
@ 判断物理地址宽度是否满足最小和最大的要求。
0xd5d30c        ubfx   x2, x2, #28, #4                                        
0xd5d310        cmp    x2, #0x0                                               
0xd5d314        b.lt   0xd5d36c  // b.tstop                                   
0xd5d318        cmp    x2, #0x7                                               
0xd5d31c        b.gt   0xd5d36c     

//update_early_cpu_boot_status
0xd5d320        mov    x3, #0x0                        // #0                  
0xd5d324        adrp   x2, 0x1728000                                          
0xd5d328        add    x2, x2, #0x8                                           
0xd5d32c        str    x3, [x2]                                               
0xd5d330        dmb    sy                                                     
0xd5d334        dc     ivac, x2   

//加载idmap_pg_dir到ttbr0
//注意,跳转过来时,x1是init_gdir
0xd5d338        adrp   x2, 0x117e000                                          
0xd5d33c        mov    x1, x1                                                 
0xd5d340        mov    x2, x2           

//加载两个映射表到tbbr0和1,在el1模式下面
0xd5d344        msr    ttbr0_el1, x2                                          
0xd5d348        msr    ttbr1_el1, x1                                          
0xd5d34c        isb     

//打开mmu
0xd5d350        msr    sctlr_el1, x0                                          
0xd5d354        isb                                                           
0xd5d358        ic     iallu                                                  
0xd5d35c        dsb    nsh                                                    
0xd5d360        isb                                                           
0xd5d364        ret  
@ SYM_FUNC_END(__enable_mmu)

//此部分作用为解决符号重定位问题
//readelf -r vmlinux 读取重定位表
//readelf -S vmlinux 读取所有section头
//hexdump -s 0x24000 -n 16 vmlinux 读取重定位表中的第一项的目的地址内容,
//       可以发现为8个0,需要我们来手动填写符号重定位地址
@ SYM_FUNC_START_LOCAL(__relocate_kernel)
0xd5d390        ldr    w9, 0xd5d438   // offset to reloc table                                                                                                                                    │
0xd5d394        ldr    w10, 0xd5d43c  // size of reloc table  
// default virtual offset                                                                                                                                  │
0xd5d398        mov    x11, #0xffffffc0ffffffff        // #-270582939649                                                                                                  │
0xd5d39c        movk   x11, #0x800, lsl #16                                                                                                                               │
0xd5d3a0        movk   x11, #0x0     
// actual virtual offset                                                                                                                                     │
0xd5d3a4        add    x11, x11, x23   
// x9保存了.rela.dyn区域的链接地址
// x10保存了.rela.dyn区域的结束地址                                                                                                                                   │
0xd5d3a8        add    x9, x9, x11                                                                                                                                        │
0xd5d3ac        add    x10, x9, x10                                                                                                                                       │
0xd5d3b0        cmp    x9, x10                                                                                                                                            │
0xd5d3b4        b.cs   0xd5d3d4  // b.hs, b.nlast 
//获取一项offset和info+flag                                                                                                                        │
0xd5d3b8        ldp    x12, x13, [x9], #24      
//获取Addend值                                                                                                                          │
0xd5d3bc        ldur   x14, [x9, #-8]                                                                                                                                     │
0xd5d3c0        cmp    w13, #0x403                                                                                                                                        │
0xd5d3c4        b.ne   0xd5d3b0  // b.any    
//// relocate,这里主要是修正[offset + KASLR offset] = KASLR offset + append的值,也就是符号重定位了                                                                                                                             │
0xd5d3c8        add    x14, x14, x23                                                                                                                                      │
0xd5d3cc        str    x14, [x12, x23]                                                                                                                                    │
0xd5d3d0        b      0xd5d3b0                                                                                                                                           │
0xd5d3d4        ret    
@ SYM_FUNC_END(__relocate_kernel)

@ SYM_FUNC_START_LOCAL(__primary_switch)
0xd5d3d8        mov    x19, x0         // preserve new SCTLR_EL1 value         
0xd5d3dc        mrs    x20, sctlr_el1  // preserve old SCTLR_EL1 value         
0xd5d3e0        adrp   x1, 0x181d000   // 将init_pg_dir 给x1  
//跳转过去初始化mmu                                                            
0xd5d3e4        bl     0xd5d308        //__enable_mmu        
//将kernel image的.rela.dyn段实现重定位                  
0xd5d3e8        bl     0xd5d390        //__relocate_kernel       

//注意ldr指令加载的是__primary_switched的链接地址,
//    注意这里的链接地址已经是虚拟地址了(例子值为:0xffffffc008f902a0)              
0xd5d3ec        ldr    x8, 0xd5d448    //__primary_switched给x8                
0xd5d3f0        adrp   x0, 0x200000    //__PHYS_OFFSET给x0   
//注意这里的跳转指令,这个时候mmu已经生效了,由于目标地址为0xffffffc008f902a0,
//      所以这个时候查询的页表为init_pg_dir                  
0xd5d3f4        blr    x8  //跳转到__primary_switched                          
0xd5d3f8        isb                                                           
0xd5d3fc        msr    sctlr_el1, x20                                         
0xd5d400        isb                                                           
0xd5d404        bl     0x1190040                                              
0xd5d408        tlbi   vmalle1                                                
0xd5d40c        dsb    nsh                                                    
0xd5d410        isb                                                           
0xd5d414        msr    sctlr_el1, x19                                         
0xd5d418        isb                                                           
0xd5d41c        ic     iallu                                                  
0xd5d420        dsb    nsh                                                    
0xd5d424        isb                                                           
0xd5d428        bl     0xd5d390                                               
0xd5d42c        ldr    x8, 0xd5d448                                           
0xd5d430        adrp   x0, 0x200000                                           
0xd5d434        br     x8        
@ SYM_FUNC_END(__primary_switch)

这部分主要是在页表初始化好后,进行el1的一些cpu设置,并准备好mmu开启参数。

asm 复制代码
@ 	.pushsection ".idmap.text", "awx"
@ SYM_FUNC_START(__cpu_setup)
// Invalidate local TLB
0x0000000000d5d6f4:	tlbi	vmalle1
0x0000000000d5d6f8:	dsb	nsh

0x0000000000d5d6fc:	mov	x1, #0x300000              	// #3145728
// Enable FP/ASIMD
0x0000000000d5d700:	msr	cpacr_el1, x1
// Reset mdscr_el1 and disable
0x0000000000d5d704:	mov	x1, #0x1000                	// #4096
// access to the DCC from EL0
0x0000000000d5d708:	msr	mdscr_el1, x1
// Unmask debug exceptions now,
0x0000000000d5d70c:	isb


0x0000000000d5d710:	msr	daifclr, #0x8
0x0000000000d5d714:	mrs	x1, id_aa64dfr0_el1
0x0000000000d5d718:	sbfx	x1, x1, #8, #4
0x0000000000d5d71c:	cmp	x1, #0x1
0x0000000000d5d720:	b.lt	0xd5d728  // b.tstop
0x0000000000d5d724:	msr	pmuserenr_el0, xzr
0x0000000000d5d728:	mrs	x1, id_aa64pfr0_el1
0x0000000000d5d72c:	ubfx	x1, x1, #44, #4
0x0000000000d5d730:	cbz	x1, 0xd5d738
0x0000000000d5d734:	msr	s3_3_c13_c2_3, xzr
0x0000000000d5d738:	mov	x17, #0x400000000           	// #17179869184
0x0000000000d5d73c:	movk	x17, #0x44, lsl #16
0x0000000000d5d740:	movk	x17, #0xffff
0x0000000000d5d744:	mov	x16, #0x40000000000000      	// #18014398509481984
0x0000000000d5d748:	movk	x16, #0x30, lsl #32
0x0000000000d5d74c:	movk	x16, #0xb559, lsl #16
0x0000000000d5d750:	movk	x16, #0x3519
0x0000000000d5d754:	mrs	x9, midr_el1
0x0000000000d5d758:	mov	x5, #0xffffffffffefffff    	// #-1048577
0x0000000000d5d75c:	movk	x5, #0xffff
0x0000000000d5d760:	and	x9, x9, x5
0x0000000000d5d764:	mov	x5, #0x460f0000            	// #1175388160
0x0000000000d5d768:	movk	x5, #0x10
0x0000000000d5d76c:	cmp	x9, x5
0x0000000000d5d770:	b.ne	0xd5d788  // b.any
0x0000000000d5d774:	mov	x5, #0x60000000000000      	// #27021597764222976
0x0000000000d5d778:	movk	x5, #0x0, lsl #32
0x0000000000d5d77c:	movk	x5, #0x0, lsl #16
0x0000000000d5d780:	movk	x5, #0x0
0x0000000000d5d784:	bic	x16, x16, x5
0x0000000000d5d788:	adrp	x9, 0x1555000
0x0000000000d5d78c:	ldr	x9, [x9, #3272]
0x0000000000d5d790:	bfxil	x16, x9, #0, #6
0x0000000000d5d794:	mrs	x5, id_aa64mmfr0_el1
0x0000000000d5d798:	ubfx	x5, x5, #0, #3
0x0000000000d5d79c:	mov	x6, #0x5                   	// #5
0x0000000000d5d7a0:	cmp	x5, x6
0x0000000000d5d7a4:	csel	x5, x6, x5, hi  // hi = pmore
0x0000000000d5d7a8:	bfi	x16, x5, #32, #3


0x0000000000d5d7ac:	mrs	x9, id_aa64mmfr1_el1
0x0000000000d5d7b0:	and	x9, x9, #0xf
0x0000000000d5d7b4:	cbz	x9, 0xd5d7bc
0x0000000000d5d7b8:	orr	x16, x16, #0x8000000000


0x0000000000d5d7bc:	msr	mair_el1, x17
0x0000000000d5d7c0:	msr	tcr_el1, x16

/*
  * Prepare SCTLR, INIT_SCTLR_EL1_MMU_ON 给 x0
  */
0x0000000000d5d7c4:	mov	x0, #0x200000000000000     	// #144115188075855872
0x0000000000d5d7c8:	movk	x0, #0x20, lsl #32
0x0000000000d5d7cc:	movk	x0, #0x34f4, lsl #16
0x0000000000d5d7d0:	movk	x0, #0xd91d

// return to head.S
0x0000000000d5d7d4:	ret
0x0000000000d5d7d8:	msr	tpidr_el2, x13
0x0000000000d5d7dc:	msr	disr_el1, xzr
@ SYM_FUNC_END(__cpu_setup)

当我们进入__primary_switched的时候,这个时候用的是内核地址。同时地址翻译是由init_pg_dir来完成。此过程初始化init_task,将

asm 复制代码
@ SYM_FUNC_START_LOCAL(__primary_switched)

//将init_task给x4
0xffffffc008f902a0      adrp   x4, 0xffffffc00934f000 <inet6_offloads+1376>                        
0xffffffc008f902a4      add    x4, x4, #0xb80   

//init_cpu_task                                                   
0xffffffc008f902a8      msr    sp_el0, x4                                                          
0xffffffc008f902ac      ldr    x5, [x4, #24]                                                       
0xffffffc008f902b0      add    sp, x5, #0x4, lsl #12                                               
0xffffffc008f902b4      sub    sp, sp, #0x150                                                      
0xffffffc008f902b8      stp    xzr, xzr, [sp, #304]                                                
0xffffffc008f902bc      add    x29, sp, #0x130                                                     
0xffffffc008f902c0      adrp   x5, 0xffffffc009349000 <event_hash+616>                             
0xffffffc008f902c4      add    x5, x5, #0x7f8                                                      
0xffffffc008f902c8      ldr    w6, [x4, #64]                                                       
0xffffffc008f902cc      ldr    x5, [x5, x6, lsl #3]                                                
0xffffffc008f902d0      msr    tpidr_el1, x5          

//加载异常向量表地址                                                                             
0xffffffc008f902d4      adrp   x8, 0xffffffc008010000 <bcm2835_handle_irq>                         
0xffffffc008f902d8      add    x8, x8, #0x800                                                      
0xffffffc008f902dc      msr    vbar_el1, x8                                                        
0xffffffc008f902e0      isb 

//将x29,x30放入到sp
0xffffffc008f902e4      stp    x29, x30, [sp, #-16]!                                               
0xffffffc008f902e8      mov    x29, sp  

// Save FDT pointer                                                                                                        
0xffffffc008f902ec      adrp   x5, 0xffffffc009001000 <tmp_cmdline.73085+2040>                     
0xffffffc008f902f0      str    x21, [x5, #920]                      

// Save the offset between                                                                              
0xffffffc008f902f4      adrp   x4, 0xffffffc008b70000 <kimage_vaddr>                                                                              
0xffffffc008f902f8      ldr    x4, [x4]       
// the kernel virtual and                                                                                                    
0xffffffc008f902fc      sub    x4, x4, x0                                    
// physical mappings                                                                     
0xffffffc008f90300      adrp   x5, 0xffffffc008ebb000 <rt_sched_class+192>                                                                        
0xffffffc008f90304      str    x4, [x5, #3392]       

//Clear BSS                                                                                             
0xffffffc008f90308      adrp   x0, 0xffffffc009529000 <__kvm_nvhe_cur>                                                                            
0xffffffc008f9030c      add    x0, x0, #0x0                                                                                                       
0xffffffc008f90310      mov    x1, xzr                                                                                                            
0xffffffc008f90314      adrp   x2, 0xffffffc00961c000 <gssp_stats+24>                                                                             
0xffffffc008f90318      add    x2, x2, #0xf8                                                                                                      
0xffffffc008f9031c      sub    x2, x2, x0                                                                                                         
0xffffffc008f90320      bl     0xffffffc008664200 <memset>                                                                                        
0xffffffc008f90324      dsb    ishst    

// pass FDT address in x0                                                                                                          
0xffffffc008f90328      mov    x0, x21    
// Try mapping the FDT early                                                                                                        
0xffffffc008f9032c      bl     0xffffffc008f946c8 <early_fdt_map>    
// Parse cpu feature overrides                                                                    
0xffffffc008f90330      bl     0xffffffc008f96354 <init_feature_override>    
//already running randomized?                                                                     
0xffffffc008f90334      tst    x23, #0xffffffffffe00000                                                                                           
0xffffffc008f90338      b.ne   0xffffffc008f90350 <__primary_switched+176>  // b.any
// parse FDT for KASLR options                                                              
0xffffffc008f9033c      bl     0xffffffc008f96b80 <kaslr_early_init>  
// KASLR disabled? just proceed          这里直接跳转到 0xffffffc008f90350                                                              
0xffffffc008f90340      cbz    x0, 0xffffffc008f90350 <__primary_switched+176>  
// record KASLR offset                                                                  
0xffffffc008f90344      orr    x23, x23, x0  
// we must enable KASLR, return                                                                                                     
0xffffffc008f90348      ldp    x29, x30, [sp], #16      
// to __primary_switch()                                                                                         
0xffffffc008f9034c      ret  

// Prefer VHE if possible
0xffffffc008f90350      bl     0xffffffc008021e6c <switch_to_vhe>                                                                                 
0xffffffc008f90354      ldp    x29, x30, [sp], #16                  
//跳转到kernel_start                                                                              
0xffffffc008f90358      bl     0xffffffc008f90c40 <start_kernel>                                                                                  
0xffffffc008f9035c      brk    #0x800                                                                                                             
0xffffffc008f90360      msr    tpidr_el2, x5  
@ SYM_FUNC_END(__primary_switched)

注意,阅读本文这部分时,需要对照着head.S来看,这样才有一个基本的认识。

后记


本文得到的基本流程为,上电,保存上电后的基础寄存器,在el2的模式下初始化cpu,保存启动标志,初始化两个页表(注意,这部分是精华,我这部分注释也最多),然后初始化el1模式下的cpu,并准备好开启mmu,最后在__primary_switch里面开启mmu,当mmu开启后,这个时候的地址还是物理地址,所以我们需要一个映射物理地址和虚拟地址相等的页表(idmap_pg_dir),重定位符号表,最后加载__primary_switched的虚拟地址(其虚拟地址在kernel logical memory map中),跳转到执行(此时查表init_pg_dir),然后初始化init_task,最后进入start_kernel。

本文也没有尝试去完全注释每句代码,太繁杂了,对于我来说也没有意义,特别是一些特别的初始化,只有以后遇到了才去查询。Linux现代内核太大了,后面可能就要去看个人喜欢的模块,这样才有收获,不要尝试去阅读全部内容,那样太难了。

每个人对于这部分的关注可能都是不一样的,如果本文没有写的地方,可以尝试搜索对应的关键字,可以得到更多的信息。

参考文献

[1]https://blog.csdn.net/u011728480/article/details/79498816
[2]https://blog.csdn.net/u011728480/article/details/88420467
[3]https://blog.csdn.net/u011728480/article/details/88553602
[4]https://blog.csdn.net/u011728480/article/details/114491022
[5]https://developer.arm.com/documentation/100933/0100
[6]https://www.kernel.org/doc/html/latest/arm64/memory.html


打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。