4. 内核启动阶段
Bootloader 把内核镜像与设备树交给 CPU 之后,控制权正式移交给 Linux 内核。从这一刻起,系统进入内核启动阶段:先自解压并完成早期体系结构初始化,再解析设备树获得硬件描述,随后按依赖顺序初始化各子系统与驱动,最后通过 initramfs 过渡并挂载真正的根文件系统,启动第一个用户态进程。理解这一阶段,是排查“卡在开机 Logo”“找不到根文件系统”“驱动没加载”等问题的关键。
一、核心原理:内核启动的四段式推进
内核启动并不是一条直线,而是由若干阶段串联而成,每个阶段都有明确的职责边界:
- 解压与自引导:内核镜像通常是压缩格式(zImage、Image.gz、Image.lz4 等)。解压头(decompressor)先把内核解压到内存,再跳转到解压后的内核入口。此阶段还没有完整的 C 运行环境,依赖汇编与极简 C 代码。
- 早期初始化:建立临时页表、初始化内存管理、识别 CPU 与 SMP、解析 cmdline 与设备树,为后续驱动初始化准备运行环境。
- 驱动与子系统初始化:按 initcall 等级依次执行,从核心子系统(时钟、中断、GPIO、电源)到总线(platform、I2C、SPI)再到具体外设驱动。
- 根文件系统挂载与 init 启动:先挂载 initramfs 作为临时根,加载必要模块、等待存储设备就绪,再切换(switch_root)到真实根文件系统,执行 /sbin/init 或 /init。
二、设备树加载:硬件描述的来源
在 ARM/ARM64 等平台上,内核不再硬编码板级信息,而是通过设备树(Device Tree)获得硬件描述。Bootloader 把 DTB 的物理地址通过寄存器或启动协议传给内核,内核在早期解析 DTB,得到 CPU 数量、内存布局、中断控制器、时钟、串口、存储控制器等关键信息。
设备树中的 chosen 节点尤其重要,它承载 bootargs 与 stdout-path:
/ {
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rootwait rw";
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x0 0x80000000 0x0 0x40000000>;
};
};
如果 DTB 与内核版本不匹配、节点缺失或 bootargs 写错,常见现象是串口无输出、内存识别异常或根文件系统找不到。因此设备树是内核启动阶段最需要先确认的输入之一。
三、驱动初始化:initcall 的分级执行
内核使用 initcall 机制组织驱动初始化顺序,不同等级对应不同的宏,执行顺序由等级决定:
| 等级 | 宏 | 典型用途 |
|---|---|---|
| early | early_initcall | 极早期平台代码、调试设施 |
| core | core_initcall | 核心子系统、总线框架 |
| postcore | postcore_initcall | 依赖核心但早于普通设备 |
| arch | arch_initcall | 体系结构相关初始化 |
| subsys | subsys_initcall | 子系统级初始化 |
| device | device_initcall | 绝大多数外设驱动 |
| late | late_initcall | 依赖其他驱动的收尾工作 |
驱动与设备通过总线匹配:设备树节点被转换为 platform_device,驱动注册后由总线进行 match,匹配成功则调用 probe。若驱动未编入内核、模块未放入 initramfs,或 compatible 字符串不匹配,设备就不会被初始化。
四、initramfs 与根文件系统挂载
initramfs 是一个被内核直接解压到内存的 cpio 归档,作为临时根文件系统。它的作用是“过渡”:在真实存储驱动可能尚未就绪时,先提供一个可运行的用户态环境,完成模块加载、设备等待、文件系统检查,再切换到真实根。
- 内核解压 initramfs 到 rootfs(ramfs/tmpfs),并执行其中的
/init。 /init加载存储、文件系统、加密等模块,等待设备节点出现。- 挂载真实根文件系统到
/root或类似目录。 - 调用
switch_root切换根目录,释放 initramfs 占用,执行真实根的/sbin/init。
# 典型内核日志片段
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 5.10.110
[ 0.000000] Machine model: MyBoard
[ 0.000000] Memory: 1024MB available
[ 0.512000] Unpacking initramfs...
[ 0.980000] Freeing initrd memory
[ 1.230000] mmc0: new HS200 MMC card
[ 1.560000] EXT4-fs (mmcblk0p2): mounted filesystem
[ 1.570000] Run /sbin/init as init process
/init 是否可执行、以及根文件系统分区是否存在。
五、案例与排障场景
场景一:设备卡在开机 Logo,串口无输出。此时问题多在内核解压或早期初始化,需确认内核镜像格式、加载地址、DTB 地址与串口配置。
场景二:日志显示 “VFS: Cannot open root device”。说明内核已运行到挂载根文件系统阶段,但 root= 参数指向的设备不存在或驱动未就绪。可检查分区名、rootwait、存储驱动是否编入内核或 initramfs。
场景三:系统能启动但某外设不可用。通常是驱动 probe 失败,可通过 dmesg | grep -i probe 与 /sys/bus/platform/devices 查看匹配情况,重点核对 compatible 与时钟、电源、GPIO 依赖。
六、阶段关系总览
下图把内核启动阶段的关键对象、顺序与依赖关系集中呈现,便于对照日志定位问题:
把这张图与串口日志对照,可以快速判断当前处于哪个阶段:有解压日志说明镜像与加载地址正确;有设备树解析与内存信息说明 DTB 正常;有 initcall 与 probe 日志说明驱动在推进;出现 initramfs 与 switch_root 日志则说明已接近用户态。掌握这条链路,后续分析 Android init 与属性服务时就有了清晰的起点。