1. 嵌入式Linux内核启动流程概述:从Bootloader到内核启动的完整链路

嵌入式Linux系统的启动是一个多阶段、高度可配置的过程,从硬件上电复位到用户空间init进程运行,涉及Bootloader、内核解压、硬件初始化、驱动加载等多个关键环节。理解这一完整链路是进行内核启动日志分析与调试的基础。

1.1 启动流程总览

典型的嵌入式Linux启动分为以下四个主要阶段:

  1. 第一阶段:Bootloader(引导加载程序) — 初始化硬件、加载内核映像到内存。
  2. 第二阶段:内核解压与早期初始化 — 解压压缩内核、设置页表、初始化CPU和内存。
  3. 第三阶段:内核核心初始化 — 执行start_kernel(),完成调度器、内存管理、中断等核心子系统初始化。
  4. 第四阶段:设备驱动与用户空间启动 — 枚举设备、加载驱动模块、挂载根文件系统、启动init进程。
嵌入式Linux启动流程总览 Bootloader U-Boot / Barebox 硬件初始化 + 加载内核 内核解压 & 早期初始化 head.S / decompress 页表、CPU、内存早期设置 核心初始化 start_kernel() 调度、内存、中断、定时器 驱动 & 用户空间 设备枚举 / init进程 根文件系统挂载 整个启动过程通常在几秒到几十秒内完成,具体时间取决于硬件性能和内核配置。 上电复位 ~1-2s ~3-5s ~5-10s 用户空间就绪

1.2 Bootloader阶段详解

Bootloader是系统上电后执行的第一个软件,负责从低级硬件初始化过渡到内核执行环境。嵌入式领域最常用的Bootloader是U-Boot(Das U-Boot)。

子阶段 主要任务 关键代码/函数
Stage 1 (SPL) 初始化CPU、时钟、DDR控制器;加载Stage 2到SRAM start.S、spl_init()
Stage 2 (U-Boot proper) 初始化串口、存储设备、网络;执行bootcmd加载内核 board_init_f()、board_init_r()
内核加载 从Flash/MMC/NFS读取内核映像到DDR指定地址 bootm、load命令
传递参数 设置ATAGS或设备树(DTB)传递给内核 setup_start_tag() / fdt_chosen()
调试提示: 在U-Boot命令行中使用 bdinfo 查看板级信息,使用 printenv 查看启动参数,使用 bootm 的详细输出可以定位内核加载失败的原因。

1.3 内核解压与早期汇编阶段

当Bootloader将控制权交给内核入口点(通常是 stext 或 _start)时,内核开始执行。对于压缩内核(zImage),首先执行解压缩代码。

// arch/arm/kernel/head.S (ARM架构示例)
ENTRY(stext)
    @ 设置SVC模式,关闭中断
    msr    cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE
    @ 检查CPU ID
    mrc    p15, 0, r9, c0, c0       @ 读取MIDR
    @ 创建初始页表
    bl     __create_page_tables
    @ 使能MMU
    ldr    r13, =__mmap_switched
    adr    lr, BSYM(1f)
    mov    r8, r4                   @ 保存DTB指针
    ldr    r12, [r10, #PROCINFO_INITFUNC]
    add    r12, r12, r10
    ret    r12
1:  b     start_kernel

早期汇编阶段的关键操作包括:

  • 设置异常向量表 — 确保中断和异常能正确路由。
  • 创建临时页表 — 使能MMU,从物理地址映射到虚拟地址。
  • 保存设备树指针 — 将Bootloader传递的DTB地址保存在寄存器中。
  • 跳转到start_kernel() — 进入C语言世界。
内核早期汇编阶段流程 Bootloader跳转 r0=0, r1=machine, r2=DTB 设置SVC模式 关闭IRQ/FIQ 创建页表 __create_page_tables 使能MMU 跳转到虚拟地址 跳转到start_kernel 进入C语言初始化 内核核心初始化 start_kernel()

1.4 start_kernel() 核心初始化

start_kernel() 是内核初始化C语言的入口点,位于 init/main.c。它按严格顺序调用各个子系统的初始化函数。

初始化顺序 子系统 关键函数 启动日志标记
1 锁与架构 lockdep_init()、setup_arch() Booting Linux on physical CPU 0x0
2 内存管理 mm_init()、kmem_cache_init() Memory: 1024MB available
3 调度器 sched_init() sched: registering cpufreq notifier
4 中断与定时器 init_IRQ()、time_init() NR_IRQS: 256
5 控制台 console_init() console [ttyS0] enabled
6 设备模型 do_basic_setup() → driver_init() Linux version 5.10.0
7 根文件系统 prepare_namespace() → mount_root() VFS: Mounted root (ext4 filesystem)
8 用户空间 run_init_process() init started: /sbin/init
关键观察: 启动日志中 console [ttyS0] enabled 之前的输出通常通过 early_printk 或 DEBUG_LL 实现。如果串口驱动初始化失败,你将看不到任何内核日志,这是调试中最常见的问题之一。

1.5 设备树与驱动加载

现代嵌入式Linux使用设备树(Device Tree, DTB)来描述硬件拓扑。内核在 setup_arch() 中解析DTB,并在 do_basic_setup() 中枚举设备并加载驱动。

驱动加载分为两种方式:

  • 内置驱动(built-in) — 编译进内核,在 do_initcalls() 中按优先级顺序执行。
  • 可加载模块(LKM) — 在用户空间通过 insmod 或 modprobe 动态加载。
// 驱动初始化调用顺序(initcall 优先级)
#define pure_initcall(fn)       __define_initcall(fn, 0)
#define core_initcall(fn)       __define_initcall(fn, 1)
#define postcore_initcall(fn)   __define_initcall(fn, 2)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define device_initcall(fn)     __define_initcall(fn, 6)
#define late_initcall(fn)       __define_initcall(fn, 7)
常见陷阱: 如果驱动依赖的资源(如时钟、GPIO)在更晚的initcall级别才初始化,驱动probe会失败并返回 -EPROBE_DEFER。内核会将该驱动加入延迟队列,稍后重试。在启动日志中搜索 probe deferred 可以定位此类问题。

1.6 根文件系统挂载与init启动

内核挂载根文件系统后,启动第一个用户空间进程 /sbin/init(通常是systemd或BusyBox init)。

根文件系统的挂载参数由Bootloader传递的 root= 和 rootfstype= 指定:

# U-Boot bootargs 示例
setenv bootargs 'console=ttyS0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 rw init=/sbin/init'

内核挂载根文件系统的典型日志输出:

[    3.456789] VFS: Mounted root (ext4 filesystem) readonly on device 179:2.
[    3.463210] devtmpfs: mounted
[    3.466789] Freeing unused kernel memory: 1024K
[    3.471234] Run /sbin/init as init process
根文件系统挂载与init启动流程 解析bootargs root= / rootfstype= 打开块设备 /dev/mmcblk0p2 挂载文件系统 ext4 / squashfs 启动init进程 /sbin/init 如果挂载失败,内核会触发 Kernel Panic,日志显示 "VFS: Unable to mount root fs on unknown-block(0,0)" 失败 → Kernel Panic

1.7 启动日志关键节点速查表

以下表格列出了启动日志中最重要的标记行及其含义,帮助快速定位启动阶段:

日志内容(示例) 所处阶段 调试意义
Booting Linux on physical CPU 0x0 start_kernel 入口 确认内核已获得控制权
Machine: Freescale i.MX6 Quad setup_arch 确认板级匹配
Memory: 1024MB available mm_init 确认内存大小正确
console [ttyS0] enabled console_init 串口输出就绪,后续日志可见
of: fdt: machine model: MY_BOARD DTB解析 确认设备树加载成功
mmc0: new high speed SDHC card 驱动probe 存储设备识别成功
VFS: Mounted root (ext4 filesystem) 根文件系统 根文件系统挂载成功
init started: /sbin/init 用户空间 系统启动完成
调试技巧: 使用 dmesg 命令可以查看内核环形缓冲区中的启动日志。如果系统无法启动,可以在Bootloader中设置 bootargs 添加 earlyprintk 或 debug 参数获取更多早期调试信息。