1. Question
- 内核启动入口在哪,怎么从代码中找到入口?
- 启动内核前,要有哪些准备工作?
- 进程调度是何时开始的?
- 多核启动是何时完成的?
2. 程序入口
基于linux-5.15
ARM64
分析。
2.1. _text
从链接脚本arch/arm64/kernel/vmlinux.lds
可以查到,程序的入口为_text
,镜像起始位置存放的是.head.text
段生成的指令码。搜索.head.text
,可以找到include/linux/init.h
对__HEAD
定义.section ".head.text","ax"
。
ENTRY(_text)
SECTIONS
{
. = ((((((-(((1)) << ((((48))) - 1)))) + (0x08000000))) + (0x08000000)));
.head.text : {
_text = .;
KEEP(*(.head.text))
}
/* For assembly routines */
#define __HEAD .section ".head.text","ax"
#define __INIT .section ".init.text","ax"
#define __FINIT .previous
再看一下arch/arm64/kernel/vmlinux.lds
是怎么生成的,编译日志中,会有LDS arch/arm64/kernel/vmlinux.lds
,scripts/Makefile.build
中可以看到,也就是通过对arch/arm64/kernel/vmlinux.lds.S
进行预处理得到了最终的链接脚本。
# Linker scripts preprocessor (.lds.S -> .lds)
# ---------------------------------------------------------------------------
quiet_cmd_cpp_lds_S = LDS $@
cmd_cpp_lds_S = $(CPP) $(cpp_flags) -P -U$(ARCH) \
-D__ASSEMBLY__ -DLINKER_SCRIPT -o $@ $<
$(obj)/%.lds: $(src)/%.lds.S FORCE
$(call if_changed_dep,cpp_lds_S)
2.2. head.S
再搜索__HEAD
,可以看到程序起始代码位于arch/arm64/kernel/head.S
。通过代码梳理出内核启动基本流程。
__HEAD
/*
* 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
.quad 0 // Image load offset from start of RAM, little-endian
le64sym _kernel_size_le // Effective size of kernel image, little-endian
le64sym _kernel_flags_le // Informative flags, little-endian
.quad 0 // reserved
.quad 0 // reserved
.quad 0 // reserved
.ascii ARM64_IMAGE_MAGIC // Magic number
.long .Lpe_header_offset // Offset to the PE header.
3. 内核启动基本流程
3.1. head.S
- 建立页表
- 开启MMU
- 跳转到start_kernel
3.2. start_kernel
start_kernel执行setup_arch和一些子系统的初始化,最后通过rest_init创建kernel_init和kthreadd两个线程,分别为1号和2号线程。kernel_init的工作主要通过kernel_init_freeable完成
- 执行early_initcall指定的函数
- 拉起从CPU,并启动多核调度
- 通过do_initcalls执行built-in到内核的各个initcall
- 挂载根文件系统
- 执行根文件系统中的init程序,kernel_init演变为init进程,内核也随之切换到用户态