《Linux 内核深度解析:基于 ARM64 架构的 Linux 4.x 内核》 第一章:内核引导与初始化
在面向嵌入式和服务器场景的 ARM64(AArch64)平台上,Linux 4.x 系列内核的启动流程与传统 x86 存在诸多差异。理解这一流程,对于驱动开发、系统调优以及内核移植都至关重要。本章以深入源码剖析为主线,结合常见板级引导方案,逐步拆解从硬件上电到 start_kernel() 的每一步。
1. 上电复位与固件阶段
-
上电复位(Power-On Reset)
ARM64 SoC 上电后,CPU 处于最高权限态 EL3(Secure Monitor),执行片上 ROM 中的 BootROM 代码。BootROM 的主要工作包括:- 初始化最基础的外设(串口、时钟、DDR 控制器初始化指针);
- 在预设介质(eMMC/SD、SPI-NOR、NAND Flash)中搜索并加载下一阶段固件。
-
ARM Trusted Firmware(ATF)
大多数 ARM64 平台使用 ATF(也称 BL2/BL31)完成 EL3 到 EL1/EL2 的过渡。典型流程:BootROM → BL1 → BL2 → BL31 (EL3 Runtime) → 转向 EL2/EL1 执行 U-Boot- BL1:BootROM 加载并验证 BL2(SCP 固件)。
- BL2:初始化更复杂的硬件(如 DDR PHY、PMIC),加载 BL31。
- BL31:设置 Exception Vector、Secure Monitor Call(SMC)接口并最终跳转至非安全态(EL2 或 EL1)。
2. Bootloader 阶段:U‑Boot 的角色
在大多数 ARM64 方案中,U‑Boot 是加载 Linux 内核的关键组件。其主要职责包括:
-
DRAM 初始化
调用 SoC 提供的 DRAM 初始化函数,确保后续可以安全地在内存中读写。 -
加载内核镜像与设备树
- 从存储介质读取压缩内核(
Image.gz/Image.xz)到内存某地址; - 读取设备树二进制文件(
*.dtb)到内存。
- 从存储介质读取压缩内核(
-
传递参数给内核
跳转前,约定将三个寄存器设置为:mov x0, <DTB_physical_addr> // 设备树地址 mov x1, #0 // 保留,通常为0 mov x2, #0 // machine type,已废弃 -
跳转到内核入口
bl arch_setup_and_jump // 通常封装了 cache/TLB 清理
3. 内核映像压缩与解压
Linux 4.x 在 ARM64 上支持多种压缩格式,常见的有 gzip、lzma、xz。对应的启动路径集中在:
arch/arm64/boot/compressed/
├── head.S // 入口汇编
├── decomp_head.o // 解压器前端
└── lib/*.o // CRC、zlib 等解压依赖
3.1 head.S 入口剖析
ENTRY(__sepc_address) // 入口符号
// 1. 设置初始堆栈
// 2. 清理 BSS 段
// 3. 调用 decompress_kernel
bl decompress_kernel
b __decompressed_entry // 解压后跳转
END(__sepc_address)
- 堆栈:在 DRAM 尚未完全初始化前,使用 CPU 内置 SRAM 或者早期 DRAM 区域;
- BSS 清零:确保解压阶段无未初始化数据干扰;
decompress_kernel:调用 zlib 解压,把压缩体写入目标物理地址后,再跳转到真正的_start。
4. 从汇编到 C:_start → start_kernel()
解压完成后,内核真正入口位于 arch/arm64/kernel/head.S:
ENTRY(_start)
// 设置寄存器、取消 prelude
bl secondary_start_kernel // SMP 次核入口
bl start_kernel // 主核进入 C 逻辑
END(_start)
4.1 主核 start_kernel() 流程
void __init start_kernel(void)
{
/* 1. 构建调试信息 */
setup_arch(&command_line);
mm_init(); // 内存子系统
trap_init(); // 异常向量、陷阱
time_init(); // 时钟与定时器
console_init(); // printk 与控制台
sched_clock_init(); // 调度时钟
cpuhp_setup_boot_thread();// 次核唤醒
rest_init(); // 创建 init 进程
}
各阶段之间通过 initcall 机制串联,保证子系统按优先级顺序初始化。
5. 架构相关初始化:setup_arch()
位于 arch/arm64/kernel/setup.c 的 setup_arch(),是整个启动流程的核心。
5.1 解析启动参数
parse_early_param(): 处理mem=,nohz=,earlyprintk=等早期命令行;- 将命令行保存于
saved_command_line,以供后续printk使用。
5.2 MMU 与页表
- 线性映射(Linear Mapping)
将整个物理内存区段,映射到内核虚拟地址空间(PAGE_OFFSET起始)。 - Kernel Text/C-RWX 映射
根据CONFIG_RANDOMIZE_BASE,可实现 KASLR; paging_init()→__early_init_mmu()完成三级(4 KB 页)或四级(16 KB 页)页表的建立。
5.3 设备树(Device Tree)解析
内核依赖 DTB 描述硬件拓扑。在 setup_arch() 中会调用:
early_init_dt_scan(); // 注册 /chosen, /memory, /cpus, /reserved-memory 节点
of_flat_dt_init(__dtb_start);
/memory:获取可用 RAM 区域,并调用memblock_add()注册到 memblock 管理器;/cpus:确定核数、拓扑;/reserved-memory:保留区段不用于一般分配。
6. 核心子系统初始化:init/main.c
6.1 Initcall 分级
Linux 将初始化函数按照级别划分,通过宏自动放入对应段:
| 级别 | 宏 | 用途 |
|---|---|---|
| early_initcall | __early_initcall | DDR、缓存等最早配置 |
| core_initcall | __core_initcall | IRQ、scheduler |
| postcore_initcall | __postcore_initcall | 架构相关 |
| arch_initcall | __arch_initcall | 架构设备驱动 |
| subsys_initcall | __subsys_initcall | 子系统(网络、文件系统) |
| fs_initcall | __fs_initcall | 文件系统 |
| device_initcall | __device_initcall | 平台设备 |
| late_initcall | __late_initcall | 延迟启动 |
6.2 典型流程
mm_init(); // zone、page allocator
trap_init(); // 异常向量表设置
time_init(); // timer、clockevents
console_init(); // tty、early printk
printk(...);
sched_init(); // CFS、runqueue
rootfs_init(); // initramfs
device_initialization(); // platform driver probe
rest_init(); // 启动 /sbin/init
rest_init():创建第一个用户态进程init(PID=1),并切换到用户空间。
7. 异常、中断与调度器简单介绍
- 异常向量:
vectors.S定义了 EL1/EL2 下的同步、IRQ、FIQ、SError 等处理入口 - 中断控制器:GICv3 驱动在 initcalls 中注册中断域,调用
irqchip_init() - 调度器:CFS(完全公平调度)在
sched_init()中注册时钟、调度钩子,完成基本就绪队列初始化。
8. 用户空间的启动
-
创建 init 进程
pid = kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); -
执行用户态
init函数最终调用do_execve(),加载/sbin/init或者内置linuxrc,设置好页表后,通过syscall_return_svc()跳转至用户态。
9. 总结与后续
本章从硬件上电、BootROM、ATF、U‑Boot 一直到内核 _start、setup_arch()、start_kernel()、rest_init(),全流程拆解 ARM64 Linux 4.x 内核的引导与初始化。下一章将深入探讨 内存管理子系统,包括 Page Allocation、Slab、Buddy 分配器及 KSM 等机制,并结合源码详细分析它们在启动阶段的交互。
关键代码索引
arch/arm64/boot/compressed/head.Sarch/arm64/kernel/head.Sarch/arm64/kernel/setup.c→setup_arch()init/main.c→start_kernel()、rest_init()



1245

被折叠的 条评论
为什么被折叠?



