【Note】《Linux 内核深度解析:基于 ARM64 架构的 Linux 4.x 内核》 第一章:内核引导与初始化

《Linux 内核深度解析:基于 ARM64 架构的 Linux 4.x 内核》 第一章:内核引导与初始化

在面向嵌入式和服务器场景的 ARM64(AArch64)平台上,Linux 4.x 系列内核的启动流程与传统 x86 存在诸多差异。理解这一流程,对于驱动开发、系统调优以及内核移植都至关重要。本章以深入源码剖析为主线,结合常见板级引导方案,逐步拆解从硬件上电到 start_kernel() 的每一步。


1. 上电复位与固件阶段

  1. 上电复位(Power-On Reset)
    ARM64 SoC 上电后,CPU 处于最高权限态 EL3(Secure Monitor),执行片上 ROM 中的 BootROM 代码。BootROM 的主要工作包括:

    • 初始化最基础的外设(串口、时钟、DDR 控制器初始化指针);
    • 在预设介质(eMMC/SD、SPI-NOR、NAND Flash)中搜索并加载下一阶段固件。
  2. 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 内核的关键组件。其主要职责包括:

  1. DRAM 初始化
    调用 SoC 提供的 DRAM 初始化函数,确保后续可以安全地在内存中读写。

  2. 加载内核镜像与设备树

    • 从存储介质读取压缩内核(Image.gz/Image.xz)到内存某地址;
    • 读取设备树二进制文件(*.dtb)到内存。
  3. 传递参数给内核
    跳转前,约定将三个寄存器设置为:

    mov x0, <DTB_physical_addr>  // 设备树地址
    mov x1, #0                   // 保留,通常为0
    mov x2, #0                   // machine type,已废弃
    
  4. 跳转到内核入口

    bl arch_setup_and_jump       // 通常封装了 cache/TLB 清理
    

3. 内核映像压缩与解压

Linux 4.x 在 ARM64 上支持多种压缩格式,常见的有 gziplzmaxz。对应的启动路径集中在:

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:_startstart_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.csetup_arch(),是整个启动流程的核心。

5.1 解析启动参数

  • parse_early_param(): 处理 mem=, nohz=, earlyprintk= 等早期命令行;
  • 将命令行保存于 saved_command_line,以供后续 printk 使用。

5.2 MMU 与页表

  1. 线性映射(Linear Mapping)
    将整个物理内存区段,映射到内核虚拟地址空间(PAGE_OFFSET 起始)。
  2. Kernel Text/C-RWX 映射
    根据 CONFIG_RANDOMIZE_BASE,可实现 KASLR;
  3. 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_initcallDDR、缓存等最早配置
core_initcall__core_initcallIRQ、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. 用户空间的启动

  1. 创建 init 进程

    pid = kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
    
  2. 执行用户态
    init 函数最终调用 do_execve(),加载 /sbin/init 或者内置 linuxrc,设置好页表后,通过 syscall_return_svc() 跳转至用户态。


9. 总结与后续

本章从硬件上电、BootROM、ATF、U‑Boot 一直到内核 _startsetup_arch()start_kernel()rest_init(),全流程拆解 ARM64 Linux 4.x 内核的引导与初始化。下一章将深入探讨 内存管理子系统,包括 Page Allocation、Slab、Buddy 分配器及 KSM 等机制,并结合源码详细分析它们在启动阶段的交互。


关键代码索引

  • arch/arm64/boot/compressed/head.S
  • arch/arm64/kernel/head.S
  • arch/arm64/kernel/setup.csetup_arch()
  • init/main.cstart_kernel()rest_init()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值