1. 当系统卡在"Starting Kernel"时发生了什么?
每次看到屏幕上出现"Starting Kernel"后系统突然卡住,作为嵌入式开发者的血压都会瞬间升高。这个看似简单的提示背后,其实隐藏着Uboot、设备树和内核三者之间复杂的"三角关系"。我遇到过太多次这种情况,有时候甚至怀疑是不是开发板在故意和我作对。
让我们先理清这个启动过程的完整链条。当Uboot完成硬件初始化后,它会做三件关键事:加载内核镜像(通常是zImage)、加载设备树文件(dtb)、传递启动参数。这三者就像三个齿轮,必须严丝合缝才能正常运转。其中任何一个环节出错,系统就会在"Starting Kernel"这个节点卡住——因为这时Uboot已经完成了它的使命,正准备把控制权交给内核,但内核却因为各种原因无法继续执行。
常见的问题症状可能有:
- 完全卡死,没有任何输出
- 出现部分内核日志后停止
- 反复重启(如果是看门狗没配置好)
我在i.MX6ULL平台上就遇到过从网络启动切换到EMMC启动后卡在Starting Kernel的情况。当时第一反应是内核镜像损坏,但实际排查后发现是Uboot的环境变量中bootargs没有正确配置EMMC相关的参数。这种问题特别具有迷惑性,因为Uboot本身能正常启动,容易让人误以为Uboot没问题。
2. Uboot:被忽视的"幕后黑手"
很多人觉得Uboot只要能启动就说明它没问题,这种想法其实是个误区。Uboot在启动内核时要做很多准备工作,任何一个细节出错都可能导致后续失败。根据我的踩坑经验,Uboot方面需要重点检查以下内容:
2.1 启动介质配置
当切换启动方式(比如从网络启动改为EMMC启动)时,最容易忽略的就是Uboot的环境变量。以下是一个典型的EMMC启动配置示例:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw' setenv bootcmd 'mmc dev 1; ext4load mmc 1:1 0x80800000 zImage; ext4load mmc 1:1 0x83000000 imx6ull-14x14-evk.dtb; bootz 0x80800000 - 0x83000000' saveenv我曾经遇到过一个经典案例:开发板从网络启动切换回EMMC启动后卡在Starting Kernel。排查后发现是因为Uboot的bootcmd仍然指向网络加载,而bootargs却配置成了EMMC参数,导致内核拿到的是矛盾的启动参数。
2.2 内存加载地址
内核镜像和设备树的加载地址必须严格匹配硬件设计。以i.MX6ULL为例:
- zImage通常加载到0x80800000
- 设备树加载到0x83000000
如果地址设置错误,可能会出现以下症状:
- 完全黑屏(地址冲突导致数据损坏)
- 内核panic(错误地址导致代码执行异常)
- 卡在Starting Kernel(最常见)
我曾经帮同事排查过一个诡异问题:他的板子在QSPI Flash启动正常,但切换到SD卡就卡在Starting Kernel。最后发现是Uboot的include/configs/mx6ullevk.h文件中定义的加载地址与SD卡控制器存在冲突。
3. 设备树:硬件描述的"密码本"
设备树文件(.dtb)就像是给内核的硬件配置说明书。一个常见的误区是认为"同样的板子就应该用同样的设备树",实际上即使是同一型号的开发板,不同批次也可能有硬件差异。
3.1 设备树匹配检查
首先确认使用的设备树文件是否真的匹配你的硬件。我曾经遇到过这样的情况:
- 开发板型号:i.MX6ULL EVK
- 实际硬件版本:Rev B
- 使用的设备树:imx6ull-14x14-evk.dtb(对应Rev A)
结果就是卡在Starting Kernel,因为Rev B更换了PMIC芯片,而旧设备树没有相应配置。正确的做法是检查开发板丝印,使用匹配的设备树文件。
3.2 设备树关键节点
以下设备树节点最容易导致启动失败:
&usdhc2 { /* EMMC */ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_usdhc2>; bus-width = <8>; non-removable; status = "okay"; }; &iomuxc { pinctrl_usdhc2: usdhc2grp { fsl,pins = < MX6UL_PAD_NAND_RE_B__USDHC2_CLK 0x10069 MX6UL_PAD_NAND_WE_B__USDHC2_CMD 0x17059 /* 省略其他引脚配置 */ >; }; };如果EMMC控制器配置错误,内核在初始化存储设备时就会卡住。建议的检查步骤:
- 确认compatible字段与SoC型号匹配
- 检查pinctrl配置是否正确
- 验证时钟和电源配置
4. 内核镜像:看似简单实则暗藏玄机
很多人觉得内核镜像只要编译通过就没问题,其实编译选项和版本匹配同样重要。
4.1 内核配置检查
确保内核配置中包含必要的驱动支持:
# 检查EMMC驱动是否编译 zgrep MMC /proc/config.gz CONFIG_MMC=y CONFIG_MMC_SDHCI=y CONFIG_MMC_SDHCI_ESDHC_IMX=y # 检查文件系统支持 CONFIG_EXT4_FS=y CONFIG_VFAT_FS=y我曾经遇到过一个典型问题:内核配置中启用了设备树支持(CONFIG_OF=y),但实际使用的是非设备树的内核镜像,导致卡在Starting Kernel。这种问题在新老版本交替时特别常见。
4.2 内核版本兼容性
Uboot和内核版本存在兼容性要求。一般来说:
- 较新的Uboot可以启动较旧的内核
- 较旧的Uboot可能无法启动新内核(特别是设备树相关功能)
建议的版本匹配策略:
- 使用芯片厂商提供的BSP版本组合
- 如需升级,先升级Uboot再升级内核
- 避免混用不同来源的组件
5. 实战排查指南
当遇到Starting Kernel卡住时,可以按照以下步骤系统排查:
5.1 基础检查清单
- 确认Uboot版本和编译日期
=> version U-Boot 2020.04 (Jun 12 2022 - 15:30:25 +0800) - 检查内核加载是否完整
=> iminfo 0x80800000 ## Checking Image at 80800000 ... Legacy image found Image Name: Linux-4.19.35 Image Type: ARM Linux Kernel Image (uncompressed) Data Size: 3846216 Bytes = 3.7 MiB Load Address: 80008000 Entry Point: 80008000 - 验证设备树加载
=> fdt addr 0x83000000 => fdt print / chosen chosen { stdout-path = "serial0:115200n8"; linux,initrd-start = <0x83800000>; };
5.2 高级调试技巧
如果基础检查没问题,可以尝试:
- 启用Uboot的调试输出
=> setenv bootargs earlycon console=ttymxc0,115200 debug => saveenv - 检查内存映射
=> md 0x80800000 0x10 80800000: 56190527 00000000 60183b1a 00000000 '...V...;.`.... - 尝试最小化启动
setenv bootargs console=ttymxc0,115200 init=/bin/sh
记得有一次我遇到Starting Kernel卡住,最后发现是DDR初始化参数不对。通过在Uboot中手动调整DDR配置后问题解决:
=> mw 0x021b001c 0x00008000 => mw 0x021b0404 0x0000000f6. 预防胜于治疗:最佳实践建议
根据我的踩坑经验,遵循以下原则可以避免90%的Starting Kernel问题:
- 版本控制三件套:为每个项目维护明确的Uboot、内核、设备树版本对应表
- 变更记录:任何启动方式或配置变更都要记录,方便回滚排查
- 分步验证:
- 先验证官方镜像能否启动
- 然后逐个替换自定义组件
- 每次修改只变更一个变量
- 自动化构建:使用脚本确保每次构建的环境一致
#!/bin/bash make clean make imx_v7_defconfig make menuconfig make -j8
最近我在一个新项目上就严格执行了这套流程,当出现Starting Kernel卡住时,通过对比构建记录快速定位到是某个内核配置选项被意外修改导致的���这种系统化的方法比盲目试错效率高得多。