3. Linux内核启动

Android 系统启动的第一段真正意义上的“软件旅程”,是从 BootROM 把控制权交给引导程序、引导程序再把 Linux 内核装载进内存开始的。内核启动阶段决定了后续 Android 能否顺利挂载根文件系统、能否找到硬件、能否拉起第一个用户态进程。理解这一段,等于拿到了整条启动链路的“地基图纸”。本节围绕内核加载、设备树传递、init 进程诞生以及内核启动日志分析四个关键点展开。

一、核心原理:从引导程序到内核入口

在 ARM64 平台上,典型的启动顺序是:BootROM → Preloader/SPL → U-Boot/LK → Linux Kernel → init。引导程序完成 DRAM 初始化、存储介质读取后,会把内核镜像(如 Image 或 Image.gz)和设备树二进制文件(dtb)加载到内存指定地址,然后跳转到内核入口。内核入口会依次完成解压(若为压缩镜像)、建立临时页表、初始化内存管理、解析设备树、初始化驱动子系统,最后启动第一个用户态进程。

内核启动的本质是“从无到有地建立运行环境”:先让 CPU 能访问内存,再让内核能识别硬件,最后让用户态能运行。这三步分别对应内核解压与内存初始化、设备树解析与驱动探测、以及 init 进程的创建。

二、设备树:内核与硬件的“契约”

在 ARM 架构中,硬件信息不再像 x86 那样大量依赖 BIOS 探测,而是通过设备树(Device Tree)以数据形式描述。设备树由 .dts 源文件编译为 .dtb,由引导程序传递给内核。内核解析设备树后,才能知道串口寄存器地址、中断控制器、GPIO、I2C 总线、存储控制器等硬件信息。

设备树的核心结构包括根节点、CPU 节点、内存节点、总线节点和外设节点。每个节点通过 compatible 属性与内核驱动匹配,通过 reg 属性描述寄存器地址,通过 interrupts 描述中断号。若设备树与硬件不匹配,常见现象是串口无输出、存储无法识别或系统卡在早期启动。

/* 简化示例:一个 I2C 设备节点 */
&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    touchscreen@38 {
        compatible = "vendor,ts-ft5x06";
        reg = <0x38>;
        interrupt-parent = <&gpio1>;
        interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
    };
};

三、init 进程的诞生:内核态到用户态的交接

内核完成初始化后,会尝试挂载根文件系统并执行第一个用户态程序。在 Android 中,这个程序通常是 /init。内核通过 init= 启动参数或默认路径查找 init,若成功执行,内核态启动阶段结束,用户态启动阶段开始。

init 进程的诞生标志着内核已经具备“运行用户程序”的能力。它会继续解析 init.rc、挂载分区、启动服务、设置属性,最终拉起 Zygote 和 SystemServer。因此,内核启动日志中是否出现 init 相关输出,是判断内核是否成功交接的重要标志。

关键交接点:内核启动的最后一步是 run_init_process(),它尝试执行 /init。若该步骤失败,内核会打印 Failed to execute /init 并触发 panic,系统无法进入 Android 用户态。

四、内核启动日志分析:定位问题的第一现场

内核启动日志通常通过串口输出,是排查启动问题的第一手资料。日志按时间顺序记录了内核版本、命令行参数、内存信息、设备树解析、驱动探测、根文件系统挂载以及 init 启动结果。分析日志时,应重点关注以下节点:

  • 内核命令行:确认 console=、init=、root= 等参数是否正确。
  • 设备树解析:查看是否有 OF: fdt 相关输出,确认 dtb 是否被正确识别。
  • 驱动探测:关注 probe 失败、timeout 或 failed to request 等关键字。
  • 根文件系统:确认 VFS: Mounted root 是否出现,以及挂载类型是否为 ext4、f2fs 或 ramdisk。
  • init 启动:查看是否出现 Run /init as init process,这是内核成功交接的标志。

下面是一段典型的内核启动日志片段,可用于对照分析:

[    0.000000] Booting Linux on physical CPU 0x0
[    0.000000] Linux version 5.10.66 (build@host)
[    0.000000] Machine model: Vendor SoC Board
[    0.000000] earlycon: uart0 at MMIO 0x1c28000
[    0.000000] Memory: 2048MB available
[    0.000000] OF: fdt: Machine model: Vendor SoC Board
[    0.210000] i2c i2c-1: IMX I2C adapter registered
[    0.350000] mmc0: SDHCI controller on 0x1c0f000
[    0.520000] VFS: Mounted root (ext4 filesystem)
[    0.530000] Run /init as init process

五、案例与场景:一次“卡在串口无输出”的复盘

某项目在更换 DDR 颗粒后,系统上电后串口无任何输出。排查思路如下:首先确认 BootROM 是否正常,若 BootROM 有输出但内核无输出,说明问题在引导程序到内核之间;其次检查内核镜像加载地址与设备树地址是否冲突;最后核对设备树中的 chosen 节点是否包含正确的 stdout-path。最终发现是设备树中串口节点被误设为 disabled,导致内核无法输出日志。修复后日志恢复正常。

提示:内核启动日志的“第一行”往往包含 CPU 和内核版本信息,若这一行都没有,说明问题在引导程序或硬件层面,而不是内核内部。

六、常见误区与检查清单

误区现象正确做法
设备树与内核版本不匹配驱动 probe 失败或硬件不识别确保 dtb 与内核源码同步编译
init 路径错误内核 panic,提示无法执行 init检查 init= 参数和根文件系统
根文件系统挂载失败卡在 VFS: Cannot open root device核对 root= 与分区表
串口未配置无日志输出,误判为死机检查设备树 stdout-path
忽略早期日志只关注 Android 层日志从内核第一行开始逐段分析

内核启动阶段是 Android 启动链路中最接近硬件的一环,也是问题最容易“沉默”的一环。掌握内核加载流程、设备树传递机制、init 进程诞生条件以及日志分析方法,就能在系统无法启动时快速缩小范围,判断问题出在引导程序、内核、设备树还是根文件系统。后续章节将在此基础上,进入 init 进程与 Android 用户态启动的详细分析。

Linux 内核启动与 init 诞生流程 BootROM 上电自检 / 启动介质 引导程序 U-Boot / LK 内核镜像加载 Image / Image.gz 设备树传递 .dtb → 内核解析 内核初始化 内存管理 / 中断 / 驱动 probe 根文件系统挂载 执行 /init 内核态 → 用户态交接 Android 用户态启动 init.rc → Zygote → SystemServer 内核启动日志关键节点 [0.000] Booting Linux on CPU 0x0 [0.000] Linux version 5.10.66 [0.000] OF: fdt: Machine model [0.210] i2c adapter registered [0.520] VFS: Mounted root [0.530] Run /init as init process 内核加载 内核初始化 init 交接