内核启动分析
- BIOS/UEFI阶段:计算机开机后首先会执行BIOS或UEFI程序,进行一些硬件初始化操作,如检查硬件配置信息、启动自检程序、加载操作系统引导程序等。
- Bootloader阶段:BIOS/UEFI会加载引导程序,比如GRUB等,这个引导程序会在屏幕上显示一个菜单,供用户选择要启动的操作系统,如果只有一个操作系统,那么该引导程序将自动启动内核。引导程序会根据用户选择或默认设置找到内核映像文件,加载到内存中。
- 内核启动自解压阶段:内核启动时,它首先会解压缩自身,然后进行一系列的初始化工作,如初始化CPU、内存管理、设备管理、文件系统等。其中,内存管理是最重要的一步,因为内核需要将系统中的所有可用内存映射到自己的地址空间中。
- 内核引导阶段:
- init进程启动阶段:当内核初始化完毕后,会启动init进程。在Linux系统中,init进程是用户空间中的第一个进程,它负责初始化系统环境,包括加载配置文件、启动系统服务等。
- 过渡到rootfs
跟踪线索
arch/arm64/kernel/head.S 入口
init/main.c start_kernel
kernel中敲make后会生成System.map,相关的函数名和地址都放里面了
通过地址追踪到函数所在的文件,使用交叉编译工具链的某个工具
//arch/arm64/kernel/vmlinux.lds.S 连接脚本:指定程序在代码段数据段中如何分配
通过它知道arm64的第一条就是_text
//第一条指令位置
{
//System.map 函数 列表
ffffff8008080000 T _text //第一条指令
ffffff8008081800 T vectors //异常向量表
ffffff80096007f4 T start_kernel //内核初始化
}
//通过地址追踪到函数所在的文件,使用交叉编译工具链的addr2line工具
//vmlinux是所有调试信息还在的内核镜像,也在kernel-4.9目录下
$ aarch64-linux-gnu-addr2line ffffff8008080000 -e vmlinux -f /*查找地址对应的文件位置
/home/yhai/kernel-4.9/arch/arm64/kernel/head.S:83 //这个文件的83行
$ aarch64-linux-gnu-addr2line ffffff8008081800 -e vmlinux -f
vectors
/home/yhai/kernel-4.9/arch/arm64/kernel/entry.S:393
$aarch64-linux-gnu-addr2line ffffff80096007f4 -e vmlinux -f
start_kernel
/home/yhai/kernel-4.9/init/main.c:482
*/
{
//arch/arm64/kernel/vmlinux.lds.S 连接脚本:指定程序在代码段数据段中如何分配
21 OUTPUT_ARCH(aarch64)
22 ENTRY(_text) //入口地址
110 SECTIONS
111 {
130 .head.text : {
131 _text = .; //代码段连接的时,第一条指令的位置
132 HEAD_TEXT
133 }
134 .text : {
/* Real text segment */
135 _stext = .; /* Text and read-only data */
136 __exception_text_start = .;
137 *(.exception.text)
138 __exception_text_end = .;
139 IRQENTRY_TEXT
140 SOFTIRQENTRY_TEXT
141 ENTRY_TEXT
}
}
}
入口(汇编部分)
//arch/arm64/kernel/head.S 入口
__HEAD
_head:
//对应前面 ffffff8008080000 T _text
83 b stext // branch to kernel start, magic
ENTRY(stext)
bl preserve_boot_args //保存u-boot传入的启动参数
bl el2_setup //异常级别(权限)的设置:建议EL2级别,如不则下降到EL1
//Drop to EL1, w0=cpu_boot_mode
bl set_cpu_boot_mode_flag // 按 ctrl+] 会跳到定义处, 按ctrl+o 会返回原位置
bl __create_page_tables //创建内存映射表
bl __cpu_setup //初始化CPU(配置访问权限,内存地址划分)
//查找源码: vscode -> arch/arm64 -> 右键 在文件夹中查找 __cpu_setup -> mm/proc.S
b __primary_switch //主切换
ENDPROC(stext)
1.保存传入的启动参数
/*
* Preserve the arguments passed by the bootloader in x0 .. x3
*/
preserve_boot_args:
mov x21, x0 // x21=FDT (X0里存放的是u-boot传入的设备树dtb的首地址)
adr_l x0, boot_args // record the contents of
stp x21, x1, [x0] // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16]
dmb sy // needed before dc ivac with
// MMU off
add x1, x0, #0x20 // 4 x 8 bytes
b __inval_cache_range // tail call
ENDPROC(preserve_boot_args)
2.异常级别(特权)设置
//el2级别下的初始设置(el2_setup): 是el2,则进行虚拟化设置,处理完后用w0保存cpu的异常级别(el1或推荐的el2)
ENTRY(el2_setup)
msr SPsel, #1 // We want to use SP_EL{1,2}
mrs x0, CurrentEL //读取当前异常级别
cmp x0, #CurrentEL_EL2 //判断是EL2否
b.ne 1f //不是则跳到1f标号处
mrs x0, sctlr_el2
msr sctlr_el2, x0
b 2f
1: mrs x0, sctlr_el1
msr sctlr_el1, x0
mov w0, #BOOT_CPU_MODE_EL1 // This cpu booted in EL1
isb
ret
2:
#ifdef CONFIG_ARM64_VHE
/*
* Check for VHE being present. For the rest of the EL2 setup,
* x2 being non-zero indicates that we do have VHE, and that the
* kernel is intended to run at EL2.
*/
mrs x2, id_aa64mmfr1_el1
ubfx x2, x2, #8, #4
#else
mov x2, xzr
#endif
/* Hyp configuration. 虚拟化的配置*/
mov_q x0, HCR_HOST_NVHE_FLAGS
cbz x2, set_hcr
mov_q x0, HCR_HOST_VHE_FLAGS
set_hcr:
msr hcr_el2, x0
isb
/* Generic timers. 通用定时器设置*/
mrs x0, cnthctl_el2
orr x0, x0, #3 // Enable EL1 physical timers
msr cnthctl_el2, x0
msr cntvoff_el2, xzr // Clear virtual offset
#ifdef CONFIG_ARM_GIC_V3
/* GICv3 system register access 中断系统系统寄存器访问*/
mrs x0, id_aa64pfr0_el1
ubfx x0, x0, #24, #4
cbz x0, 3f
mrs_s x0, ICC_SRE_EL2
orr x0, x0, #ICC_SRE_EL2_SRE // Set ICC_SRE_EL2.SRE==1
orr x0, x0, #ICC_SRE_EL2_ENABLE // Set ICC_SRE_EL2.Enable==1
msr_s ICC_SRE_EL2, x0
isb // Make sure SRE is now set
mrs_s x0, ICC_SRE_EL2 // Read SRE back,
tbz x0, #0, 3f // and check that it sticks
msr_s ICH_HCR_EL2, xzr // Reset ICC_HCR_EL2 to defaults
3:
#endif
/* Populate ID registers. */
mrs x0, midr_el1
mrs x1, mpidr_el1
msr vpidr_el2, x0
msr vmpidr_el2, x1
/*
* When VHE is not in use, early init of EL2 and EL1 needs to be
* done here.
* When VHE _is_ in use, EL1 will not be used in the host and
* requires no configuration, and all non-hyp-specific EL2 setup
* will be done via the _EL1 system register aliases in __cpu_setup.
*/
cbnz x2, 1f
/* sctlr_el1 */
mov x0, #0x0800 // Set/clear RES{1,0} bits
msr sctlr_el1, x0
/* Coprocessor traps. */
mov x0, #0x33ff
msr cptr_el2, x0 // Disable copro. traps to EL2
1:
/* EL2 debug */
mrs x0, id_aa64dfr0_el1 // Check ID_AA64DFR0_EL1 PMUVer
sbfx x0, x0, #8, #4
cmp x0, #1
b.lt 4f // Skip if no PMU present
mrs x0, pmcr_el0 // Disable debug access traps
ubfx x0, x0, #11, #5 // to EL2 and allow access to
4:
csel x0, xzr, x0, lt // all PMU counters from EL1
msr mdcr_el2, x0 // (if they exist)
/* Stage-2 translation */
msr vttbr_el2, xzr
cbz x2, install_el2_stub
mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2
isb
ret
install_el2_stub:
/* Hypervisor stub */
adrp x0, __hyp_stub_vectors
add x0, x0, #:lo12:__hyp_stub_vectors
msr vbar_el2, x0 //设置异常向量表 基地址
/* spsr */
mov x0, #(PSR_F_BIT | PSR_I_BIT | PSR_A_BIT | PSR_D_BIT |\
PSR_MODE_EL1h)
msr spsr_el2, x0
msr elr_el2, lr
mov w0, #BOOT_CPU_MODE_EL2 // This CPU booted in EL2
eret
ENDPROC(el2_setup)