technology 10 分钟阅读

Linux 7.3 悄然修复吞噬整个系统的固件问题

Linux 7.3 现在会在 EFI 固件调用挂起 120 秒后放弃等待,而不是无限期地悬着。这一改动源于 NVIDIA Grace 服务器的一次崩溃,它阻止了单个有问题的固件例程拖垮整个用户空间,同时为工程师提供了明确的信号——问题出在固件,而非代码。

  • Linux 7.3
  • EFI 固件
  • 内核超时
  • Breno Leitao
  • EFI Runtime Services

一次卡住的固件调用曾让一切停摆

Linux 7.3 中的一道新防护措施阻止了一类可能悄然停掉企业工作负载的 bug:一次挂起的 EFI 运行时服务调用不再需要无限期等待。

在此修改之前,内核会卡在固件调用内永不返回。调用方在等待期间持有 efi_runtime_lock 锁。每一个后续需要与 EFI 通信的进程——NVRAM 写入、通过 efivarfs 进行变量存储、ACPI PRM 处理器,甚至是设置唤醒时间这样简单的任务——都会阻塞在同一个锁后面。用户态进程在信号量上不断堆积,处于不可中断状态。外部唯一可见的症状是 workqueue 锁死消息,分别在 127 秒、157 秒、188 秒及更长时间后重复出现,直到某个外部监控系统最终重启了主机。

固件永远不会回来了。但内核无法知道这一点。

efi_runtime_lock 不是边缘问题。它序列化整个系统中所有的 EFI 运行时调用。当一个调用在固件内部挂起时,锁永远不会释放,所有依赖 EFI 的组件——TPM、NVRAM、电源管理路径、启动变量基础设施——都会积压。容器、initramfs 脚本、logrotate、systemd 定时器,以及任何读取或写入 EFI 变量的用户态工具都会阻塞。系统对大多数监控代理来说看起来是健康的,因为 CPU 没有空转;它只是卡住了。仪表盘显示绿灯,但机器实际上已经死机。

这个 Bug 在 NVIDIA Grace 服务器上被发现

Breno Leitao 是一位 Debian 开发者和 Meta 的内核工程师,他在 NVIDIA Grace 硬件上运行时发现此问题。他提交了补丁以强制对 EFI 运行时服务完成设置超时,这些改动被并入 Linux 7.3 的 EFI 特性合并分支。

在补丁系列的说明信中,Leitao 详细说明了正在发生的事情。PC 和链接寄存器卡在 EFI 固件内存中——固件就是不返回。工作线程在多次 workqueue 锁死报告之间一直卡住,直到外部系统强制重启。内核本身无法恢复。

这不是罕见的小概率事件。EFI 运行时服务在正常运行期间被频繁调用。任何固件不严谨的厂商——而且这样的厂商还很多——都可能触发调用挂起的状况。在这个修复之前,唯一的选择就是重启并希望下次调用能成功。在集群环境中,这意味着数十台服务器可能陷入相同状态,却没有任何明确的诊断信号。

NVIDIA Grace 平台加剧了这个问题,因为它使用基于 ARM 的超级芯片运行非 x86 架构。新型服务器芯片上的固件成熟度仍然参差不齐,Grace 架构通过不同于传统 x86 部署的 EFI 运行时调用引入了自己的初始化路径。在消费级 x86 硬件上可能只是附带说明的问题,在一个以营收损失而非不便来衡量正常运行时间的平台上,却会成为影响整个集群的故障。

超时设置为 120 秒。这是故意的。

超时阈值设定为 120 秒。Leitao 表示,这比任何合理的 EFI 运行时服务调用都要长。任何正常的固件例程都不应该耗时两分钟才完成。如果真的如此,那就说明出了问题,内核不应继续假装一切正常。

当超时触发时,固件被判定为卡死。内核从卡住的调用中退出,释放 efi_runtime_lock 锁,并向发起原始请求的一方返回错误。后续的 EFI 操作不再被原始挂起的工作线程阻塞。用户态恢复运行。系统保持在线。

实现方式是通过在 EFI 调用外围包裹一个带计时的等待。内核在调用固件例程之前启动一个计时器。如果例程在计时器到期前未完成,内核将其视为挂起并中止。确切机制并不会强制卸载固件——固件可能仍在执行,且可能处于错误状态——但内核切断了自身对结果的依赖并继续运行。这是一种隔离策略,而非修复。

这个区分很重要。超时并不能防止固件 bug 再次出现。同一路径上的后续 EFI 调用也可能挂起。超时能防止的是:一次孤立的调用挂起级联成整个系统的冻结。它将一个硬死锁转换为具有明确超时窗口的可恢复错误。

真正的收益在于诊断清晰度

更重要的结果可能不是恢复本身,而是它向工程师提供的信号。在此修改之前,workqueue 锁死可能意味着任何事情。内存泄漏、内核代码中的死锁、调度器饥饿、硬件故障。在集群规模上,相同的症状可能被归咎于数十种无关的根本原因。

现在,当系统触发这个超时时,dmesg 会产生一条明确的 EFI 固件过错消息。运维人员知道该往哪里看。厂商获得更清晰的 bug 报告。凌晨三点值班的工程师也不必花一个小时排除一半的内核子系统,然后才能决定重启。

这种诊断精确性在大规模场景下尤为重要。一台服务器挂起只是不便。数据中心里一百台服务器在同一固件 bug 上静默卡死,那就是事故。有了超时机制,触发事件会以足够的特异性被记录,使得事后分析可以立即开始,而不必在挖掘内核调用栈一整天之后才能启动。错误信息本身就成为了证据。

次生影响延伸到可观测性工具。Prometheus 导出器、Datadog 检查项以及已经监控 workqueue 锁死的内部告警管道,现在可以将超时事件与 EFI 固件错误关联起来并自动分类事故。这种分类进而将工单路由给固件厂商,而不是消耗值班工程师的时间。

为何此事超越 Linux 爱好者范畴

EFI 固件 bug 不是理论问题。它是任何在异构硬件上运行 Linux 的组织所面临的量产风险。服务器厂商按自己的节奏发布固件更新。某些平台带有有缺陷的实现,只在特定条件下显现。内核一直以来都优雅地吸收这些失败,听起来像个功能,直到它意味着你的工作负载静默死亡而你无法知道原因。

Meta 的参与值得注意。Leitao 的工作处于上游内核开发与大规模生产基础设施的交汇点。暴露这个 bug 的 NVIDIA Grace 服务器不是消费设备——它是基础设施级硬件,正常运行时间关乎营收,恢复时间以损失的金钱而非不便来衡量。修复方案源自云提供商内核团队的一员,而非固件厂商,这凸显了一个更广泛的模式:内核正在吸收本不应到达生产级固件的 bug,而超时防护措施是一种务实的临时方案。

120 秒的超时在设计上偏向保守。它在健康系统上很少触发。但当它确实触发时,它将一个 mystery stall 转变为具有明确来源的已知故障模式。这种改变不会登上头条,但会默默提升每一个运行 7.3 或更高版本的企业 Linux 部署的可靠性。

固件厂商向来有发布运行时服务的历史,它们默认内核会无限期等待。这次超时迫使其面对现实。它让一次挂起的固件调用的代价变得可衡量,而非不可见。久而久之,这种可见性应能推动厂商修复根本原因,而非依赖内核来吸收失败。在此之前,120 秒的闸门让系统保持运行,足够让工程师发现、诊断并采取行动——而不是等到客户投诉时才得知一台服务器已死。

固件依然是坏的。内核只是更聪明地假装它没坏。