资讯动态

芯片启动链深度解析:从ROM code到kernel的接力机制

发布时间:2026/10/29 12:12:25 来源:尧图企业网站定制
简介这份文档面向嵌入式开发初学者与底层系统学习者系统梳理芯片从上电复位到操作系统启动的完整链路帮助读者建立对启动流程的整体认知。内容围绕ROM Code、SPL、U-Boot、Kernel四个阶段展开说明各阶段的任务分工与依赖关系并涉及UART、USB、SD卡、EMMC等多种boot模式及ptset测试模式对理解芯片工作原理与操作系统引导机制有实际参考价值。资源包共1个docx文件约1.41MB以文字讲解为主适合作为学习笔记或查阅资料使用。目前已有923人学习说明该主题在嵌入式方向具有一定关注度。读者可从中获得分阶段的启动流程说明、各模块初始化与加载顺序的梳理以及不同boot方式适用场景的对比思路便于后续结合具体芯片手册深入排查启动问题。1. 上电那一刻芯片其实只有几百字节的“本能”按下电源键CPU 从复位向量取第一条指令时它对外部世界几乎一无所知DDR 没初始化时钟还是最保守的振荡源存储控制器没配连串口都吐不出一个字符。可就在这种“白板”状态下它必须完成一件极难的事——把自己从一块外部存储介质里“捞”起来。这就是芯片 boot 过程最反直觉的地方真正启动系统的第一段代码不是 uboot也不是 kernel而是被永久烧在片内 ROM 里、改不了也删不掉的 ROM code。它像芯片的本能反射容量通常只有几十 KB却要负责 CPU 初始化、系统初始化、判断从 SD/eMMC/UART/USB 哪条路启动再把下一段程序搬进片内 SRAM。搞懂这条 ROM code → SPL → u-boot → kernel 的接力链你才能明白为什么换一块 DDR 就要改 SPL、为什么串口没输出往往卡在 ROM code、为什么 bootloader 要拆成两段。下面按机制层次把这根链条拆开。2. ROM code为什么第一段代码必须烧死在片内2.1 复位向量与 handler 模式CPU 醒来第一件事芯片上电复位后CPU 并不是从地址 0 随便取指而是跳到一个由芯片设计固定的复位向量进入 handler 模式异常/特权模式的一种开始执行 ROM code。这一步的动机很朴素此时内存子系统还没配好能保证可执行的只有片内 ROM所以第一段代码必须放在片内、地址固定、不可修改。ROM code 先做 CPU init——设置栈指针、关中断、把 CPU 从复位状态带到能跑 C 代码的状态然后才谈得上系统初始化。这里有个容易被忽略的细节ROM code 运行时的栈通常放在片内 SRAM 的一小块保留区而不是 DDR。因为 DDR 此刻还是一块“未训练的裸颗粒”时序参数、刷新周期都没配直接访问会读到垃圾甚至挂死。所以 ROM code 的整个执行环境是极度受限的几十 KB 代码空间、几 KB 栈、没有堆、没有文件系统。它做的每一件事都必须短小且确定。2.2 系统初始化与启动介质判定键盘、UART 先活过来CPU 能跑 C 之后ROM code 进入 system init主要完成键盘、UART 等模块的初始化配置。为什么先配这些因为它们是“人机可见”的最小通道UART 能打印启动日志键盘能触发进入下载/测试模式。常见的实现思路是ROM code 在启动早期采样某个 GPIO 或按键电平若检测到特定组合就停在下载模式等待主机通过 UART/USB 灌入程序否则继续走正常启动流程。接着是启动介质判定。ROM code 会按芯片预设的顺序或由 strapping 引脚决定依次探测 SD、eMMC、UART、USB 等通道。以 SD 卡启动为例ROM code 配置 SD 卡控制器发命令让卡进入传输状态读取卡上固定偏移处的内容到片内 aon SRAM 中运行。注意这里的目标是 aonalways-onSRAM不是 DDR——原因还是那个DDR 没初始化。这段被搬进 SRAM 运行的程序就是 SPL。提示ROM code 的启动顺序和探测逻辑是芯片流片时固化的改不了。调试时若串口完全无输出先怀疑 strapping 引脚选错了启动介质而不是 uboot 的问题。2.3 为什么 ROM code 不直接加载 kernel一个自然的问题既然 ROM code 能读 SD 卡为什么不直接把 kernel 读进 DDR 跑答案藏在两个约束里。第一DDR 初始化是一套相当复杂的流程——需要按颗粒手册配置时序、做写电平训练、读训练这段代码本身就有几十 KB塞不进 ROM。第二启动介质格式多样SD/eMMC/UART/USB把每种介质的完整驱动都放进 ROM 不现实。所以芯片设计者做了一个分层取舍ROM code 只负责“最小可启动”——把一小段可信的、能初始化 DDR 和加载完整 bootloader 的程序搬进 SRAM剩下的交给它。这就是 SPL 存在的根本理由。3. SPL70KB 的搬运工与 DDR 的第一次握手3.1 片内 SRAM 装不下 bootloader 时怎么办SPLSecondary Program Loader的职责非常聚焦在片内 SRAM 这个狭小空间里运行完成 DDR 初始化然后把完整的 bootloaderu-boot从启动介质搬到 DDR 并跳过去。问题在于片内 aon SRAM 往往只有几十 KB 到一百多 KB而完整 u-boot 动辄几百 KB根本装不下。于是就有了“分段搬运”的设计ROM code 先只把 SD 卡前 70KB 左右的内容读进 SRAM 运行这 70KB 就是 SPLSPL 跑起来、把 DDR 配好之后再把剩下的内容u-boot最终是 kernel搬到 DDR 中运行。这个 70KB 不是随便定的数字它对应的是片内 SRAM 可用容量减去栈和保留区后的安全上限。SPL 的代码必须被裁剪到极致去掉文件系统支持、去掉网络、只保留最少的 MMC 驱动和 DDR 初始化代码。很多项目里 SPL 编译出来要卡着这个尺寸超一点就链接失败这是嵌入式开发者常见的血泪经验。3.2 DDR 初始化SPL 里最不能出错的一段SPL 最核心也最脆弱的部分是 DDR 初始化。它要按颗粒手册写控制器寄存器、配置时序参数、执行训练流程。下面是一段示意性的伪代码展示 SPL 主流程的骨架不同芯片寄存器名各异逻辑相通/* spl.c —— SPL 主流程示意非真实寄存器地址 */ void board_init_f(void) { /* 1. 最小时钟与串口保证早期能打印 */ clock_early_init(); uart_early_init(); /* 2. DDR 控制器初始化时序、刷新、训练 */ ddr_controller_config(); if (ddr_training() ! 0) { /* 训练失败通常意味着颗粒型号/时序不匹配 */ panic(DDR training failed\n); } /* 3. DDR 可用后把 u-boot 从 SD 搬到 DDR */ mmc_init(); mmc_read(BOOTLOADER_OFFSET, (void *)UBOOT_LOAD_ADDR, UBOOT_SIZE); /* 4. 跳到 DDR 中的 u-boot 继续执行 */ jump_to(UBOOT_LOAD_ADDR); }这段代码里每一步的顺序都不能乱时钟没起来串口打不出字DDR 没训练完不能往 DDR 写数据mmc 没初始化读不到卡。ddr_training()的返回值是排查启动卡死的第一现场——训练失败往往不是代码 bug而是硬件上颗粒型号与 SPL 里写死的时序参数不匹配。参数怎么改通常要对照颗粒 datasheet 改ddr_controller_config()里的 tRCD、tRP、tRAS 等时序值或者调整训练算法的采样窗口。3.3 SPL 与 ROM code 的交接契约ROM code 和 SPL 之间有一份隐式契约ROM code 把 SPL 搬到 SRAM 的哪个地址、从哪个偏移读、读多长SPL 的链接脚本就必须把入口放在对应地址。这份契约通常体现在镜像头里——ROM code 读 SD 卡时会先解析一个头部结构校验魔数、长度、校验和通过后才搬运。头部结构大致长这样字段作用出错后果magic标识这是合法启动镜像魔数错则 ROM code 拒绝加载load_addrSPL 在 SRAM 的运行地址与链接脚本不符则跑飞length搬运长度超过 SRAM 容量则截断或溢出checksum完整性校验校验失败则回退到下载模式理解这份契约的价值在于当你换了一块容量不同的 SD 卡、或者用工具重新打包镜像时头部字段一旦写错现象就是“上电毫无反应”因为 ROM code 在搬运前就拒绝了。这类问题没法靠改 uboot 解决只能回到镜像打包环节查。4. u-boot 与 kernelDDR 里的接力与启动参数传递4.1 u-boot 为什么还要再分阶段进入 DDR 后u-boot 也不是一口气跑完。常见实现里 u-boot 分 SPL 之后的 proper 阶段甚至再拆出 TPL/SPL/u-boot proper 三级。动机是逐级扩大可用资源TPL 在更小的 SRAM 里只做最基础初始化SPL 配 DDRproper 阶段才拥有完整驱动、命令行、文件系统支持。这种“逐级解锁资源”的模式和 ROM code→SPL 是同一套设计哲学——每一级只做当前资源条件下必须做的事把重活留给资源更充裕的下一级。u-boot proper 阶段的核心任务是加载 kernel从 SD/eMMC 读内核镜像到 DDR准备好设备树device tree和启动参数然后跳转到内核入口。它还承担了多种 boot 模式的分发——UART/USB 下载模式、SD/eMMC boot 模式、以及 ptset 测试模式。ptset 模式是产线测试用的分支ROM code 检测到该模式就不走正常 system init而是停在测试流程里方便工厂验证硬件。4.2 启动参数与设备树内核拿到的“环境说明书”kernel 被跳转执行时它并不知道自己从哪块卡启动、内存多大、外设怎么接。这些信息由 u-boot 通过启动参数bootargs和设备树传给内核。设备树描述硬件拓扑bootargs 描述启动行为根文件系统位置、控制台等。这里的设计取舍是把硬件描述从内核代码里剥离出来同一份内核镜像配不同设备树就能跑不同板子。代价是设备树写错时内核会在早期挂死且报错往往很隐晦——这也是排查启动问题时经常要回头核对设备树的原因。4.3 四种 boot 模式的适用场景模式触发方式典型用途SD/eMMC bootstrapping 或默认顺序量产正常启动UART 下载按键/GPIO 采样板子无可用存储时灌程序USB 下载同上批量烧录、恢复变砖设备ptset 测试特定引脚组合产线硬件验证这张表解释了为什么 boot 流程做得“比较复杂”它不是为单一场景设计的而是要同时覆盖研发调试、量产烧录、产线测试三条路径。每多一种模式ROM code 里就多一段探测逻辑但换来的是设备变砖后还有救——UART/USB 下载模式就是那颗后悔药。5. 避坑启动卡死的排查清单5.1 串口一个字都不打印现象上电后串口无任何输出。原因多半卡在 ROM code 阶段可能是 strapping 引脚选错启动介质或镜像头部校验失败被拒绝加载。解决先用示波器确认启动介质引脚电平再用打包工具核对镜像头部的魔数和校验和确认 ROM code 确实在读你预期的介质。5.2 打印到 DDR 初始化就停住现象串口输出停在 DDR 训练相关日志。原因SPL 里的 DDR 时序参数与板上颗粒不匹配训练失败。解决对照颗粒 datasheet 核对 tRCD/tRP/tRAS 等参数或换用芯片厂商提供的 DDR 配置工具重新生成参数若换了颗粒批次务必重跑训练。5.3 SPL 编译链接报溢出现象链接阶段提示 SRAM 段超出容量。原因SPL 加了多余驱动或调试代码超过片内 SRAM 上限常见就是那 70KB 量级。解决裁剪 SPL 配置去掉文件系统、网络、多余日志只保留 MMC 与 DDR 初始化用 size 工具看各段占用优先砍 .rodata 里的字符串。5.4 跳到 u-boot 后跑飞现象SPL 日志正常跳转后无输出或异常复位。原因u-boot 的加载地址与链接地址不一致或搬运长度算错导致镜像不完整。解决核对 SPL 里UBOOT_LOAD_ADDR与 u-boot 链接脚本的入口地址确认搬运长度覆盖整个镜像必要时在跳转前做一次校验和比对。5.5 内核启动后找不到根文件系统现象内核日志正常但最后 panic 提示无法挂载 rootfs。原因bootargs 里的根文件系统位置写错或设备树里存储节点描述与实际介质不符。解决核对 bootargs 的 root 参数与分区表确认设备树中 MMC 节点与实际硬件一致。6. 设计取舍为什么启动链要拆成这么多级把 ROM code、SPL、u-boot、kernel 拆成四级本质是在“资源受限”和“功能完整”之间做渐进式妥协。ROM code 受限于片内 ROM 容量和未初始化的 DDR只能做最小可启动SPL 受限于片内 SRAM只能做 DDR 初始化和搬运u-boot 拥有 DDR 后才敢加载完整驱动和文件系统kernel 则在前三者铺好的环境里接管全部资源。每一级都在为下一级“解锁资源”同时把自身代码压到当前资源能容纳的极限。这个模式不是芯片启动独有的——很多分级加载系统都是同一思路先跑一个足够小、足够可信的引导者再由它验证并加载更大的下一级。验证这套机制是否按预期工作最直接的办法是逐级看日志ROM code 阶段看是否有下载模式提示SPL 阶段看 DDR 训练结果u-boot 阶段看加载地址与镜像校验kernel 阶段看 bootargs 与设备树解析。任何一级日志缺失问题就锁定在上一级的交接处。我一般会在每次改 DDR 参数或换存储介质后强制把这条日志链从头到尾走一遍而不是等系统起不来再回头猜——启动问题最怕的就是黑匣子式排查而这条链的每一环其实都留了可观测的痕迹。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑