3.3 启动链路排错实录:从上电到内核入口


3.3 启动链路排错实录:从上电到内核入口

本节摘要:特权架构的条文只有在启动失败时才显出分量。本节先拆解从上电到内核入口的标准接力流程——复位向量、引导加载、固件交接、内核初始化四棒——再完整复盘一次真实的启动卡死排错:症状、假设、验证、修复、复盘,五步走完,第三章的所有寄存器都会在这条链路上找到自己的岗位。

一块不点灯的板子

排错实录从一个故障现场开始:新回来的测试板,上电后串口吐出前两行启动横幅就再无声息,调试器接上去单步,程序计数器停在一个看似随机的地址。没有日志、没有异常跳转、外设全静默——启动阶段的故障大多长这个样子,因为日志系统本身也是启动链路的产物。要按图索骥,得先把"标准答案"画出来:一块正常的 RISC-V 板子从通电到应用代码接管,硬件与软件要完成一棒接一棒的权限与状态交接,任何一棒掉链子,症状都会被下一棒放大成"莫名卡死"。

标准接力:四棒交接的全景

第一棒是芯片内部固化的引导代码(业内惯称第一级引导),住在只读存储里,上电即执行。它做的事少而关键:配置最基础的时钟,把下一级程序从闪存搬进片上静态内存,然后跳过去。它通常顺带完成多 hart 的抢跑协调——读出 mhartid,只放一个 hart 继续跑,其余的设好等待点原地驻扎。

第二棒在静态内存里展开,负责把主存(通常是 DRAM)初始化出来。这棒代码空间紧张,常常没有完整栈可用,写法极度克制;它结束后,主存可用,足够大的运行时环境成立,接力进入第三棒。

第三棒是固件层,通常由开源固件项目承担,运行在机器特权级。它的职责清单最能体现本章前两节的价值:初始化所有 hart 的陷入向量、配置委托寄存器把系统调用与时钟中断下放给监督级、设置物理内存保护条款、为每个 hart 建好独立的栈,最后按交接契约装载监督级程序的入口。这个契约值得背下来:跳转前,a0 放 hart 编号,a1 放设备树描述块的物理地址,a2 在新版规范里放交接扩展的编号,特权级按前级字段安排好,一条 mret 交棒。

第四棒起,舞台交给监督级:引导加载器或内核接管页表、调度器、驱动模型,应用世界的大门就此打开。四棒各自的失败模式差异很大,排错时第一件事就是判断"卡在第几棒"——串口横幅是现成的里程碑标记,横幅打到哪里,哪棒之前的环节就都清白。

图 3-3 启动接力与典型故障落点

图 3-3 启动接力与典型故障落点

排错实录:横幅之后的沉默

背景:回到开头那块板子。串口输出停在固件横幅之后——按里程碑规则,第一、二棒清白,故障锁定在固件层或交接契约的执行段。板子所用固件是团队基于开源工程裁剪的定制版本,此前在参考板上工作正常,这次换成了自研 SoC 的首轮流片。

操作:按三板斧走。第一问,横幅停在哪:固件在打印交接前的最后一条日志后卡死,说明崩溃点在交接装填或最初几条监督级指令。第二问,卡在哪:调试器 halt 后读 pc,落点既不在向量表也不在任何已知符号附近,像是指到了未映射区域。第三问,读现场寄存器:mcause 为值十五,即访存页错误类故障;mepc 指向一条从内存加载的指令;再读 a0 到 a2,a1 的值与设备树应放置的物理地址对不上——差了一个大偏移,恰好等于链接脚本里一段保留内存的大小。

顺着 a1 的线索往上追:定制固件里,交接前把设备树复制到了自研 SoC 的一段新保留区,复制用的目标地址是写死的新常量,但传给 a1 的仍是参考板的旧地址宏——两个宏改名时漏改了一处。

结果:把传给 a1 的宏与实际复制目标统一到同一个定义,重新编译烧写,横幅之后串口继续吐出内核初始化日志,系统完整起来。复测数次,启动稳定。

解读:复盘这场排错,三处方法论值得沉淀。其一,里程碑优先于猜测:横幅先于一切假设划定了嫌疑范围,避免了对硬件质量的徒劳怀疑。其二,异常读数是第一现场:mcause 的页错误编号、mepc 的落点、a1 的偏差值,三组读数互相印证才锁定了"地址装填不一致"这个根因——单看任何一个都可能引向错误结论(比如只看 pc 会怀疑镜像没烧对)。其三,跨板移植的故障多数不是逻辑错误而是常量漂移:写死在两处的地址、只改一处的宏、按参考板参数训练的内存时序,都是惯犯。

变式:另有一类高发故障值得自测:把固件里初始化 gp 寄存器的几行注释掉重新启动,症状是程序能走到进入监督级前后,随后在第一次经由链接器松弛访问全局变量的位置行为错乱——pc 落点毫无规律,mcause 时而是非法指令时而像访存故障。这个实验验证了第二章埋下的伏笔:gp 是链接器松弛的前提,启动代码漏装它,故障点会漂移到任何使用短编码取全局变量的地方,极难与随机内存错误区分。启动代码清单里,gp 与 tp 的初始化应当享有与栈指针同级的重要性。

启动排错的通用检查单

把本节经验收拢成一份可以直接抄用的检查单。上电无输出:查晶振与时钟、查复位极性、确认第一棒是否执行(调试器在复位后立即 halt 看 pc 是否落在复位向量)。横幅中断:按里程碑定位当前棒,用调试器读 mcause、mepc、mtval 三件套。进入内核即崩:核对 a0、a1、a2 交接三寄存器与特权级前级字段,确认委托寄存器已按内核预期配置,确认物理内存保护没有挡住监督级的访存。多 hart 现象诡异:检查是否所有 hart 都闯过了抢跑协调点,查 mhartid 读数是否与软件预期一致。每一条都对应本章前两节的一个具体机制——启动排错本质上不是玄学,是特权架构条文的现场应用。

检查单之外再补一条元规则:每一次排错结束后,把当时的症状、读数与根因写回团队的知识库,并同步修订检查单——启动故障的形态有限,积累两三轮之后,新板子的多数问题都能在十分钟内对号入座。检查单是活的文档,它的价值随每次复盘递增。

本节要点回顾

  • 四棒接力:片上引导、主存初始化、固件、内核,每棒失败的症状形态不同,串口横幅是现成里程碑;
  • 交接契约三寄存器:a0 装 hart 编号、a1 装设备树地址、一条 mret 降级交棒,移植期故障的高发区;
  • 三板斧顺序:横幅定位、pc 落点、异常三件套读数,先框架后细节;
  • 常量漂移是移植头号病:写死两处的地址、漏改的宏,逻辑全对而数值错位;
  • gp 与 tp 是隐性地基:漏初始化的症状随机漂移,启动清单里要与栈指针同级对待。

至此,一条程序终于能在板上稳定跑起来。但它访问的每个地址还都是"裸奔"的物理地址——下一章给它铺上虚拟内存、内存保护与多核一致性三块真正的地基。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U