消失的服务处理器(2025)
摘要
Oxide Computer Company 描述了调试一款在下一代 Cosmo 滑轨中从管理网络消失的服务处理器。调查过程涉及检查任务饥饿、堆栈溢出,并最终使用调试头标识根因。
暂无内容
查看缓存全文
缓存时间: 2026/05/30 22:29
# 正在消失的服务处理器 | Oxide Computer Company
来源:https://oxide.computer/blog/cosmo-sp
在设计 Oxide 机架时,其中一个考量因素是确定哪些部件应该可访问,以及通过什么方式访问。Oxide 机架设计用于数据中心,只能通过网络独占访问。工程师唯一需要亲临机架物理访问的原因,是更换故障部件(如硬盘)。我们的[服务处理器 (SP)](https://docs.oxide.computer/guides/architecture/service-processors) 可通过[管理网络](https://docs.oxide.computer/guides/architecture/networking#_management_network)访问。
在将下一代 Cosmo 滑轨装入 Oxide 机架的早期尝试中,我们曾遇到服务处理器从网络断开的情况。这是一个棘手的调试场景,因为失去网络访问后,我们对 SP 自身状态几乎一无所知。调试从系统其余部分的状态入手(最初的 [Hubris 问题](https://github.com/oxidecomputer/hubris/issues/2157) 可能包含本文的剧透!):
- AMD 主机 CPU 仍然存活,意味着整个系统仍然通电
- SP 本身没有通过管理网络广播其存活状态
- SP 的网络数据计数器没有增加
- 风扇以恒定升高的转速运转。服务处理器负责风扇控制,这表明风扇控制器可能已回退到紧急全功率模式。
- 该问题无法在机架外的滑轨上复现
服务处理器运行我们自有的操作系统 [Hubris](https://github.com/oxidecomputer/hubris)。系统的每个部分(网络、热控、更新等)都作为独立的 **任务** 编写。Hubris 并非真正的实时操作系统(提供截止时间保证),但它确实有任务优先级的概念。我们的一种工作假设是,存在一个导致任务饥饿的软件错误。如果网络任务因其他任务占用了所有 CPU 时间而无法运行,它将无法通过网络响应。任务饥饿的可能罪魁祸首是某个任务陷入无限崩溃循环,所有 CPU 时间都花在重启该任务上。我们调整了任务重启时间,增加了更长的延迟以捕获这种情况。我们还希望能够观察 SP 是否仍在推进(即使没有网络访问),因此将机箱 LED 从“常亮”切换为“闪烁”。
幸运的是,通过这些调试更改,我们成功复现了问题,但结果仍然令人困惑:有时 LED 卡在常亮状态,有时又卡在关闭状态。负责 LED 闪烁的任务优先级接近最高,这限制了可能卡住的任务数量。
用 Rust 编写 Hubris 的众多优势之一,是消除了诸如缓冲区溢出之类的故障类别。Hubris 仍然特别容易遇到的一类问题,是栈溢出。这是因为 Hubris 需要为任务手动确定栈大小,而计算最大栈大小已被证明很棘手。我们对栈大小不足的检测能力,随着 `emit-stack-sizes` 特性的加入而得到提升,但仍可能遇到一些边界情况。发生栈溢出时,任务会安全重启。内核中的栈溢出可能会产生类似系统无法推进的行为。不幸的是,内核的栈余量相对较大(512 字节!),因此这种情况不太可能发生。
此时,我们确实需要从系统中获取更多调试信息。出于制造目的,我们配备了 SWD 调试接口。这些接口预计不会用于生产系统,**尤其**不会用于正在运行的机架系统。我们不得不创造性地拉线,在 Oxide 办公室同事的帮助下将调试探头连接上去。
一个 Cosmo 滑轨,上面不稳固地放置着调试探头
幸运的是,我们的电缆连接收效显著:在探头连接的情况下成功复现了问题!但这并未立即带来成果:调试探头实际上无法通过调试暂停来暂停 CPU,这限制了我们提取诊断信息的能力。我们的服务处理器使用 [Cortex-M7 STM32H7](https://www.st.com/en/microcontrollers-microprocessors/stm32h7-series.html),能让系统进入这种状态的方式很有限。
这让我们将重点放在了确定系统的哪些部分可能导致这种行为上。与第一代 Gimlet 系统相比,一个重大变化是增加了一个 FPGA 来控制更多系统部件(如主机闪存)。该 FPGA 通过一种简单的、老式的并行总线连接(类似于用于 RAM 的那种),并通过 STM32H7 的灵活内存控制器进行访问。正如手册(RM0433 第 22.1 节)所述:
```
其主要目的是:
* 将 AXI 事务转换为适当的外部设备协议
* 满足外部存储器设备的访问时间要求
```
CPU 可能卡住的一种方式是,它从未收到来自外部设备的总线确认。例如,FPGA 时序错误可能导致 CPU 在尝试读取寄存器时永久挂起。为了验证这一理论,我们创建了一个 FPGA 测试镜像,其中包含一个在读取时会故意挂起 FMC 总线的寄存器。这产生了与我们所观察到的非常相似的行为,强烈表明我们正在系统的正确部分寻找问题。
我们通常依靠完整的系统转储来调试 Hubris 问题。但除非能暂停 CPU,否则无法做到这一点。不过,ARM CPU 支持**向量捕获**:可以配置 CPU,使其在复位时在执行第一条指令前暂停。我们希望向量捕获复位能够充分解除 CPU 的卡死状态,同时不会破坏现有状态。这确实有效。我们丢失了包含程序计数器的运行寄存器状态,但 RAM 中其余 Hubris 状态在复位后得以保留,并且看起来相当一致。我们可以看到当时正在运行哪个 Hubris 任务,但其中没有发现任何任务正在访问 FMC。
我们的硬件工程师对 FPGA 时序进行了审查,确实发现我们可能未能满足内存接口所需的时序约束。我们合入了修复,并认为向量捕获转储之所以不一致,很可能是因为缓存。当我们关闭缓存进行实验时,转储变得一致,但再也无法复现实际的问题。
接下来几周,我们照常进行 Hubris 开发。在此期间,我们完成的一项更改与度量启动工作有关。我们的信任根 (RoT) 负责在启动时对 SP 闪存进行哈希,该哈希最终会被更高级别的软件使用。为了实现所需的安全属性,SP 在首次启动时可能会连续多次复位自身。在测试这一更改时,我们看到了相同的症状再次出现:Cosmo SP 会从网络消失,看起来像死了一样。事实证明,这一更改非常擅长复现问题,将原本可能超过 24 小时的复现率缩短至大约 10-20 分钟。初始转储仍然没有显示出明显的确凿证据,但我们仍然高度怀疑 FMC 总线,因为能产生如此症状的情况仍然有限。
高复现率让我们有机会尝试许多实验,但都没有结果:
- 调整复位频率以及正常启动前的复位次数
- 额外清除一次 FPGA 比特流
- 限制任务访问 FMC 总线
- 移除看似无关的整个任务
最后,仔细研究 STM32H7 手册带来了一个洞察:也许处理器本身正在执行我们意料之外的 FMC 总线访问!现代处理器持有大量程序员无法直接看到的内部状态。除了特定的同步点或缓存指令外,程序员无法知道 CPU 何时会将数据拉入缓存或从缓存写出。CPU 将数据从缓存写入内存被视为一次内存访问,因此 CPU 可能正在访问与当前程序计数器无关的地址。
Hubris 利用内存保护单元 (MPU) 来实现任务之间的隔离并强制执行特权级别。我们的配置对非特权任务使用 MPU,但对(特权)内核使用默认内存映射。在任务中,FMC 被映射为不可缓存的设备内存。根据我们对 STM32H7 手册的阅读,结果发现我们为 FMC 总线选择的基地址,其默认内存类型是普通可缓存。这意味着 FMC 根据是从任务还是内核访问,具有不同的属性。
ARMv7-M 参考手册的 A3.5.7 节有一整节内容关于内存属性不匹配以及在此情况下会丢失哪些属性。根据与硬件工程师的讨论,最值得怀疑的是“保持访问大小”这一行。我们的 FPGA 接口设计为 32 位访问,而 16 位或 8 位访问可能会引起问题。
需要指出的是,内核从未**有意**通过普通可缓存映射访问 FMC。最可能的情景是:
- CPU 正在运行访问 FMC 的非特权任务,发出一个存储操作,该操作进入处理器的存储缓冲区
- 发生中断,切换到使用默认内存映射的特权模式
- 该存储操作命中缓存,因为默认内存映射认为该地址是可缓存的
- 缓存以超出预期设备内存属性的方式尝试写入内存
A3.5.7 节的最后几行之一是:“ARM 强烈建议软件不要对同一位置的别名使用不匹配的属性。”ARM 默认内存映射(内核依赖于此)将不同的属性分配给地址空间的不同部分,其中有一个部分按照我们期望的方式设置:设备内存,不可缓存。结果发现,STM32H7 的 FMC 支持更改其基地址,使其出现在地址空间的这个部分,这很可能就是为了避免我们正在面临的具体问题。最终的修复是将基地址改为具有匹配属性的部分。自从该修复合入后,我们再未看到此问题出现。
[透明度](https://rfd.shared.oxide.computer/rfd/0552) 仍然是 Oxide 的价值观。调试现代 CPU 往往需要深入那些透明度很低的区域。“在什么情况下你将无法访问内存总线?”是一个很难回答的问题。这次调试得益于 ARM 和 STM 提供的文档,这些文档最终解释了我们的问题。鉴于调试此问题的难度,在供应商文档中突出这个潜在问题将对所有客户有益。Oxide 希望所有硬件供应商都能尽可能多地记录其部件,以造福客户。
相似文章
检查航天飞机I/O处理器中的电路板
详细检查航天飞机I/O处理器中的两块电路板,解释其多线程架构、网络接口以及使用基于熔丝的PROM进行微码存储。
追捕 EtherSlip(DOS 网络)中潜伏 34 年的指针 Bug
一位开发者讲述如何利用 Open Watcom 的堆损坏哨兵,追踪并修复 EtherSlip DOS 包驱动里一个存在了 34 年的 NULL 指针错误。
第69天:我们的COMMS代理在24小时内执行中崩溃了3次。它揭示的模式。
一个AI代理(COMMS)在关闭步骤反复崩溃,揭示了按需代理特有的故障模式:工作成功后审计追踪失败。修复方法涉及调整关闭时的生成超时,凸显了需要独立的生命周期检查点。
调试性能回退问题
Guix HPC 团队的一篇详细博客文章,描述他们如何在 Slingshot 互连上识别并调试 MPI 堆栈中的性能回退问题,展示了 Guix 的透明性和控制能力。
逆向工程386处理器的预取队列电路
详细介绍386处理器预取队列电路的逆向工程,解释所用的增量器、对齐网络和动态逻辑。