当前位置:首页>Linux>Linux启动流程

Linux启动流程

  • 2026-09-08 15:57:19
Linux启动流程

一块新板子上电后没有任何输出,或者卡在某个阶段不动,是嵌入式Linux开发中会遇到的问题。

启动链路全景

嵌入式Linux的启动是一条层层传递的链:硬件上电后执行片内BootROM,加载SPL/U-Boot,U-Boot加载内核和设备树,内核解压并启动,最后挂载根文件系统并执行init进程。

加载

初始化DDR

加载 zImage + DTB

解压/启动

挂载

执行

上电/复位
BootROM 片内固件
SPL 二级加载器
U-Boot 主引导
Linux内核
内核初始化
根文件系统
init 进程
用户空间服务

每一环都依赖上一环的正确输出。排查启动问题时,关键是从最靠近"有输出"的地方往前推——串口打印在哪里停了,问题就在那一环。

BootROM与SPL

片上ROM中的BootROM是芯片出厂固化的代码,负责最基础的初始化:检查启动介质选择(SD卡、eMMC、NAND、USB、串口等),从选定介质读取前几KB到内部SRAM,跳转到该代码执行。

这段初始代码就是SPL(Secondary Program Loader),也叫MLO(在AM335x等TI芯片上)或FSBL(Zynq上的First Stage Boot Loader)。SPL的任务很专一:初始化外部DDR内存,因为后续U-Boot和内核都太大,塞不进SRAM。

# 典型的SPL启动日志U-Boot SPL 2023.04 (Jul 15 2026 - 10:00:00)Trying to boot from MMC1spl: mmc boot mode: raw...

如果串口完全没有任何输出,问题在BootROM或之前:电源、时钟、复位电路、启动模式引脚配置。BootROM本身不打印,它的失败表现为"板子彻底没反应"。此时要量电源电压、检查晶振是否起振、确认BOOT引脚电平符合预期启动介质。

如果只看到SPL开头几行就停了,通常是DDR初始化失败——SPL里DDR时序参数和板子上的内存颗粒不匹配。这需要用芯片厂商提供的DDR压力测试工具(如i.MX的DDR Stress Test)来校准RPA(Register Programming Aid)参数。

U-Boot阶段

SPL把完整的U-Boot加载到DDR后跳转执行。U-Boot负责更完整的硬件初始化,并提供命令行环境,最终加载内核镜像和设备树到内存,把控制权交给内核。

# U-Boot启动日志(节选)U-Boot 2023.04 (Jul 15 2026 - 10:00:00)CPU:   Freescale i.MX6UL rev1.2 at 396MHzDRAM:  512 MiBMMC:   FSL_SDHC: 0Loading Environment from MMC... OKIn:    serialOut:   serialErr:   serial# 倒计时结束后启动内核Hit any key to stop autoboot: 0

U-Boot最常见的启动方式是bootz(加载zImage+DTB)或bootm(加载uImage)。bootcmd环境变量定义了自动启动命令:

# 典型的bootcmdsetenv bootcmd 'mmc dev 0; fatload mmc 0:1 ${kernel_addr_r} zImage; \                 fatload mmc 0:1 ${fdt_addr_r} myboard.dtb; \                 bootz ${kernel_addr_r} - ${fdt_addr_r}'# 启动参数(传递给内核)setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw'

常见卡点:

  • • Loading Environment from MMC... *** Warning - bad CRC:环境变量区未初始化,不影响启动但每次都会打印,用saveenv写入一次即可消除。
  • • 卡在Hit any key to stop autoboot之后无输出:内核没加载成功(fatload路径错)或加载了但无法启动(镜像格式错、地址重叠)。
  • • Wrong Image Format for bootz:用bootz加载了uImage格式,或反之。确认镜像类型和boot命令匹配。

内核解压与启动

U-Boot执行bootz后,内核镜像已被搬运到内存。ARM Linux内核的自解压代码(如果使用的是压缩的zImage)先自解压,再跳转到真正的内核入口。

# 内核启动早期日志Booting Linux on physical CPU 0x0Linux version 6.1.22 (gcc 12.2.0)CPU: ARMv7 Processor [410fc075] revision 5OF: fdt: Machine model: My Custom BoardMemory policy: Data cache writeallocKernel command line: console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw

内核启动阶段的排查重点是console=参数。console=ttymxc0,115200中的ttymxc0是串口设备名,不同SoC不同(i.MX是ttymxc,三星是ttySAC,全志是ttyS)。如果内核启动后串口没输出了,先确认console参数和设备树中串口节点aliases是否匹配。

内核解压失败的典型现象是U-Boot打印Starting kernel ...后完全没有内核输出。原因可能是:DTB地址和内核地址重叠、DTB格式损坏、内核编译时未选对应SoC的CONFIG_选项。

是

否

否

是

否

是

Starting kernel...
DTB地址与内核重叠?
内核被破坏, 无输出
console参数正确?
内核运行但无串口输出
看到内核解压日志
内存/时钟初始化成功?
早期panic/卡死
继续启动

根文件系统挂载

内核完成自身初始化后,会尝试挂载根文件系统。根文件系统的位置和类型由bootargs中的root=指定。

# 常见root=配置root=/dev/mmcblk0p2          # eMMC/SD卡第2分区root=/dev/nfs                # NFS网络根文件系统root=PARTUUID=xxxx-02        # 按分区UUID挂载root=/dev/ram0               # initramfs/initrd

挂载失败的内核会panic:

VFS: Unable to mount root fs on unknown-block(179,2)Please append a correct "root=" boot optionKernel panic - not syncing: VFS: Unable to mount root fs

排查思路:

  • • unknown-block(179,2):179是SD/MMC主设备号,2是分区号。确认SD卡确实有第2个分区,且分区格式是内核支持的(ext4需要CONFIG_EXT4_FS)。
  • • 用了rootwait但分区识别慢:加上rootwait让内核等待设备就绪,否则可能分区还没枚举完就panic。
  • • 根文件系统本身不完整:用root=/dev/nfs先通过NFS启动验证内核,排除根文件系统制作问题。NFS启动成功说明内核和bootargs没问题,问题在根文件系统镜像。

initramfs的处理有所不同。它是内核镜像内置的cpio归档,内核启动时解压到内存作为初始根文件系统,由其中的init脚本负责挂载真正的根文件系统(pivot_root)。很多发行版用initramfs来加载存储控制器驱动模块,因为根文件系统所在的磁盘驱动未必编进内核。

init进程与用户空间

内核挂载根文件系统后,执行/sbin/init(或bootargs中init=指定的程序)。init进程PID=1,是所有用户空间进程的祖先。

# 系统进入用户空间的标志[    3.123456] Run /sbin/init as init process[    3.145678] EXT4-fs (mmcblk0p2): mounted filesystemStarting logging: OKStarting network: OKWelcome to My Embedded Linuxmyboard login:

init进程的种类决定了后续的启动行为:

  • • BusyBox init:最轻量,读取/etc/inittab,适合资源紧张的嵌入式设备。
  • • systemd:功能全面但资源占用大,常用于需要服务管理的设备。
  • • OpenRC / SysVinit:介于两者之间。

卡在Run /sbin/init之后没有继续,通常是init程序或它的依赖库缺失/不兼容。用init=/bin/sh作为bootargs可以跳过init直接进shell,验证根文件系统基本可用。

# 绕过init直接进shell,排查根文件系统setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk0p2 rootwait rw init=/bin/sh'

进入shell后手动执行init的脚步,观察哪一步卡住。BusyBox init会解析/etc/inittab:

# 典型的inittab::sysinit:/etc/init.d/rcS       # 系统初始化脚本::respawn:-/bin/sh              # 串口终端(respawn表示退出后重启)::ctrlaltdel:/sbin/reboot::shutdown:/sbin/swapoff -a::shutdown:/bin/umount -a -r

rcS脚本负责挂载proc/sysfs、配置网络、启动守护进程。如果rcS中某条命令卡死(比如等待一个不存在的网络接口),系统就会停在那里。把rcS里命令逐条注释掉排查,能定位到具体哪一步出问题。

调试手段

早期串口输出

内核启动早期(console初始化之前)的日志看不到,因为串口驱动还没加载。需要开启CONFIG_EARLY_PRINTK并配置earlycon参数:

# 在bootargs中加入earlyconsetenv bootargs 'console=ttymxc0,115200 earlycon root=/dev/mmcblk0p2 rootwait rw'# 或指定具体earlycon(i.MX6)earlycon=ec_imx6q,0x02020000

earlycon能在console初始化前就输出内核日志,对排查"Starting kernel后无输出"这类问题很有用。

用NFS根文件系统隔离问题

# bootargs使用NFS根文件系统setenv bootargs 'console=ttymxc0,115200 \                 root=/dev/nfs \                 nfsroot=192.168.1.100:/nfs/rootfs,v3,tcp \                 ip=192.168.1.200:192.168.1.100:192.168.1.1:255.255.255.0::eth0:off \                 rw'

NFS根文件系统让根文件系统在开发主机上,修改不需要重新烧写SD卡。当怀疑根文件系统有问题时,先NFS启动验证,能大幅缩短调试周期。

内核调试选项

# 有用的内核调试配置CONFIG_DEBUG_KERNEL=y           # 总开关CONFIG_DEBUG_INFO=y             # 保留调试符号,配合gdbCONFIG_EARLY_PRINTK=y           # 早期打印CONFIG_PANIC_ON_OOPS=y          # oops时直接panic,便于捕获CONFIG_KALLSYMS=y               # 显示函数名而非地址

CONFIG_KALLSYMS让内核panic/oops时打印函数名而非十六进制地址,配合addr2line工具可以把地址转成源码行号,定位崩溃位置。

# 把panic地址转成源码位置arm-linux-gnueabihf-addr2line -e vmlinux 0xc0123456# 输出: /path/to/kernel/source/mm/slab.c:1234

最新文章

随机文章