PS:要转载请注明出处,本人版权所有。
PS: 这个只是基于《我自己》的理解,
如果和你的原则及想法相冲突,请谅解,勿喷。
环境说明
无
前言
最开始,我仅仅是对linux比较感兴趣,觉得其很神奇的,能够做到很多事情。后面了解到其源码也是开源的,于是抱着学习的态度,简要的看了看相关的代码,在那个时候,我还看的比较粗略,仅仅是简单的会点编译,执行linux命令等等。这期间还有一个有印象的有趣的事儿就是那个pdf《Linux 那些事儿之USB》,大概就是讲述了作者因为要看pian儿,但是U盘识别不到,所以去细读USB相关linux 内核内容的资料。这精神,虽然我看不懂,但是我大为震撼!!!
在我工作的近几年来,逐渐的和linux打上了交道,从最开始的hisi 3520a1系列的流媒体处理开始,为其搭建了文件系统,编译内核,同时为其适配EC20 4G模块,这期间,我基本都是照着别人的教程或者说文档,渐渐的熟悉了一些linux内核的一些事务。同时这期间,我做过一些简单的字符驱动玩耍模块,只能说玩玩可以的。
在前几年中的某段时间,我接到一个任务,要在android进程之间大量传输数据2。这个时候我调研到了一个叫做android 匿名共享内存的东西,我发现了一个binder的驱动程序和linux unix socket的功能可以在android 和 linux 里面实现进程间的文件描述符的共享,注意这个方法是通用的,不像某些功能在linux里面能够使用,在android里面不能够使用。在这个时候,我天马行空实现了一个类似 binder的驱动demo3。这可以说是我第一个为了自己写的内核及的相关代码,而且具有实际应用意义。
在这些工作过程中,我逐渐的觉得自己学习的《操作系统原理》与现实的差别,特别想把书中知识和实际系统结合起来,经过查询,如果想要大概了解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的代码注释分析和部分解释。
准备
汇编部分的调试环境搭建
本文的测试环境为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
通过如下命令生成镜像:
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的基础文件系统。
# 解压文件系统到指定目录
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模拟板卡
# 下载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。
# -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执行到第一条指令。
# 没有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的第一条指令。
@ 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的部分将会跳转到真正执行的地方。
@ 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。下面我们会来重点分析这个部分的内容。详细请看注释。
//注意这里的几个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。
@ 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镜像地址。
@ 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
@ 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,并保存启动参数。
@ 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个步骤:
- 这里其实包含了开启mmu,mmu开启时,tbbr0_el1是idmap_pg_dir,tbbr1_el1是init_pg_dir,还是使用的物理地址,所以这个时候的地址翻译由页表idmap_pg_dir来支持。
- 当完成mmu开启后,就会将.rela.dyn段实现重定位,因为这部分的符号的实际地址为0,需要我们填写为实际的符号地址。
- 当重定位完成后,会加载__primary_switched的虚拟地址,其地址在内核空间,当执行blr 跳转到__primary_switched的时候,这个时候的地址返回由页表init_pg_dir来支持。
@ 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开启参数。
@ .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,将
@ 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.htmlPS: 请尊重原创,不喜勿喷。
PS: 要转载请注明出处,本人版权所有。
PS: 有问题请留言,看到后我会第一时间回复。