RFC-0152:改进了 OOM 处理行为

RFC-0152:改进了 OOM 处理行为
状态已接受
领域
  • 驱动程序
  • 内核
  • 电源
说明

允许明显更多的用户空间代码退出,并为用户空间创建信号路径,以便在用户空间清理完成时通知内核,从而改善内存处理效果。

问题
  • 66786
Gerrit 更改
  • 622788
作者
审核人
提交日期(年-月-日)2021-12-20
审核日期(年-月-日)2022-02-09

总结

此 RFC 旨在提高捕获调试数据的可靠性,并在内存不足(“OOM”)事件期间尽量减少生成无用数据。这是通过有序关闭更多用户空间(而不是当前使用更基本的机制)来实现的。

设计初衷

在系统内存不足时收集数据有助于确定事件的根本原因。目前,我们会收集一些数据(例如内存报告),但不保证一定能改善我们对系统中所发生情况的了解。目前,收集所有相关数据颇具挑战性,因为系统只有很少的部分知道系统何时会耗尽内存。此外,在 OOM 后分析可用数据可能很困难。OOM 的处理方式通常会导致级联次级效应,从而给数据带来噪声。这屡次导致用户在困惑、冗长的讨论和错误的结论中浪费时间。

利益相关方

教员:hjfreyer@google.com

审核者:dgilhooley@google.com、frousseau@google.com、maniscalco@google.com、palmer@google.com、pankhurst@google.com、ppi@google.com、pshickel@google.com、rashaeqbal@google.com、shayba@google.com、surajmal

咨询人员:adanis@google.com、alexlegg@google.com、geb@google.com、johngro@google.com、wez@google.com

社交:此想法最初是围绕各种相关 bug 进行讨论的。之后,咨询关键利益相关方就提议的解决方案。

背景

在 Fuchsia 上处理内存不足 (OOM) 情况的过程如下:

  1. 如果可用内存继续下降,内存监控计时器检测到系统的可用内存达到 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 阈值。内核现已提交到重新启动系统。
  2. 内存监控计时器为用户空间生成 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 信号并声明暂停令牌暂停令牌的用途是防止内核中的多项内容尝试同时执行重新启动。
  3. 内存监控计时器会休眠 8 秒,然后重新启动。用户空间无法向内核表明其已准备好重新启动。
  4. driver_manager 观察 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 信号。driver_manager 启动时,它通过 system_get_event 系统调用订阅信号,这需要根作业的句柄。
  5. driver_manager 告知 fshost 关闭,然后告知所有驱动程序停止。系统这样做是为了尽量减少丢失的数据,并让硬件在重新启动之前保持一致的状态。
  6. driver_managerfshost 之外,用户空间会继续运行,直到内存监控计时器的计时器到期。在此期间,当程序尝试访问已分页且因文件系统消失而无法进行分页的可执行页面时,通常会出现各种崩溃。这些崩溃会产生大量噪声,如果有内容在监听串行日志,这些噪声可能会被记录下来。

当前的 OOM 处理方式不使用它,但已经有一种方法可以优雅地清理用户空间。此机制会以相反的依赖关系顺序停止组件(包括文件系统和驱动程序),并为所有组件提供清理机会。

memory_monitor 不参与 OOM 处理,但对低内存事件感兴趣。RFC-0091 创建了 ZX_SYSTEM_EVENT_IMMINENT_OUT_OF_MEMORY 事件,当系统可用内存达到略高于 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 阈值的水平时,内核会生成该事件。memory_monitor 会观察此信号,并尝试将其内存配置文件数据保存到存储空间。这是最佳操作,因为 memory_monitor 无法表明它已处理事件,并且系统可能随时达到下限阈值,并且可能会在 memory_monitor 刷新其数据之前关闭文件系统。

设计

该设计在概念上相当简单,并且使用了系统中的大量现有部分,但采用了新的方式。大体上讲,策略是:当内核检测到内存耗尽时,它会在内存中记录某种状态,发出关于 OOM 的用户空间的信号,内核在超时后等待用户空间回调到内核,用户空间收到信号,多个用户空间组件发挥其既定作用以按顺序关闭,最后,用户空间调用重新回到内核中,使得内核能够在 NV 中存储数据。

更详细地说,顺序如下:

  • 内核的内存监控计时器检测到系统内存非常低,并且它
    • 认领暂停令牌
    • 通过 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 向用户空间发出信号
    • 设置计时器
  • 用户空间信号由 pwrbtn-monitor 接收,并通过 fuchsia.hardware.power.statecontrol/Admin.Reboot 调用与 power_manager 进行通信。
  • 然后,power_manager 会通过 fuchsia.device.manager/SystemStateTransition.SetTerminationSystemState 调用告知 driver_manager,在退出之前,应将系统移至 REBOOT_KERNEL_INITIATED 电源状态。
  • power_manager 通过调用 fuchsia.sys2/SystemController.Shutdown 调用 component_manager 拆解组件拓扑。
  • component_manager 以相反的依赖关系顺序拆解组件拓扑,最终告知 driver_manager 退出。
  • driver_manager 看到它应该在退出之前执行到 REBOOT_KERNEL_INITIATED 状态的转换。
  • driver_manager 会调用 zx_system_powerctl,并在 cmd 值退出之前为其传递 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT
  • 内核收到系统调用并发送暂停令牌信号。
  • OOM 处理的其余部分会运行,以便将适当的信息写入 NVRAM,以便在重新启动后读取。
  • 内核会重新启动系统。

实现

控制内核 OOM 超时

此 RFC 提议添加内核启动选项来控制 OOM 超时。目前,OOM 超时硬编码为 8 秒。此 RFC 的实现会增加处理 OOM 而执行的代码量,因此需要延长超时时间。

对暂停令牌的更改

此 RFC 提议更改“暂停令牌”以包含内核事件对象,而不是简单地使用原子布尔值。暂停令牌将继续是一个不可撤销地声明的对象。暂停令牌的事件对象将在内核内用于协调重新启动。暂停令牌可让事件对象收到信号,而无需获取令牌。

OOM 处理流程

目前,当内核检测到 OOM 时,它会为用户空间生成 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 信号、获取暂停令牌,并启动 8 秒的计时器。此 RFC 提议,每当内核想要重新启动系统,但也为用户提供了执行某些操作的机会,内核应首先获取暂停令牌,然后通知用户空间,并等待一段有限的时间。等待时限应等于允许用户模式为了响应事件而运行的最长时间。这与当前的实现不同,当前的实现超时值既是最长等待期,也是最短等待期。一旦停止令牌的事件发出信号或达到超时时间,内核就会完成重新启动操作。如果发生 OOM,决定重新启动的内核代码位于内存监控计时器中,该监控计时器会创建 OOM 崩溃日志,将其存储在 NVRAM 中,然后重新启动,从而完成 OOM 处理。

如前所述,此 RFC 提议添加通过内核启动选项设置 OOM 超时的功能。

目前,用户空间 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 处理程序位于 driver_manager 中。此 RFC 建议将处理程序移至 pwrbtn-monitorpwrbtn-monitor 是一个现有的组件,存在于所有 build 中,用于控制某些硬件的电源状态。我们可以有效将 OOM 视为由软件生成的电源按钮按下由于 pwrbtn-monitor 的责任变多,我们提议将其重命名为 system-event-monitor

pwrbtn-monitor 收到信号时,会调用 fuchsia.hardware.power.statecontrol/Admin.Reboot。我们将为 OOM 添加一个新 RebootReasonpwrbtn-monitor 会将其传递给此调用。该调用会启动现有用户模式安全关闭路径,该路径以相反的依赖关系顺序解构组件拓扑,并以 driver_manager 更改硬件电源状态结尾。此 RFC 提议,在处理 OOM 时,driver_manager 应始终使用使用 zx_system_powerctl 系统调用的重新启动路径,并将新值 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOT 作为 cmd 参数的值传递。drive_manager 中的现有路径恰好使用了 zx_system_powerctl。在 x86 上,当 driver_manager 将重新启动的完成委托给板驱动程序,且板驱动程序执行系统调用时,就会发生这种情况。在 arm64 上,driver_manager 直接发出系统调用。此 RFC 中的更改正式要求重新启动路径中包含 zx_system_powerctl

当 Zircon 收到 cmd 值为 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOTzx_system_powerctl 时,处理程序代码会尝试向暂停令牌发出信号。如果未声明暂停令牌,则发送信号会失败,并且系统调用会返回错误。对于其他 cmd 值,处理程序代码保持不变,具体来说就是,它会尝试获取暂停令牌,如果获取失败令牌,则会永久休眠。如果因 OOM 而由内核发起重新启动,发出令牌信号将允许内存监控计时器完成其工作并重启系统。如果用户空间在内存监控计时器的超时到期之前未调用 zx_system_powerctl,则监控计时器将继续其关闭过程并重新启动系统,这与当前实现相比保持不变。

性能

我们预计,在某些情况下,OOM 处理会比目前需要更长的时间。目前,在 OOM 期间,driver_manager 会停止文件系统、停止驱动程序,然后不执行任何其他操作。内核检测到 OOM 后,会重新启动系统。

实现此 RFC 后,大部分用户空间都有机会对系统即将重新启动的情况做出反应。具体而言,power_manager 通过 RebootWatcher 协议通知监听器即将重新启动。power_manager 的客户端响应超时时间为 5 秒。在 power_manager 通知重新启动观察器后,它会告知 component_manager 拆解组件拓扑。组件拓扑以相反的依赖关系顺序销毁,这意味着并非所有组件同时停止。组件有停止的超时期限。实现此 RFC 后,更多代码可能会在 OOM 期间执行,并且可能会发生各种超时。这些因素使得重启的时间可能会超过 8 秒,尽管在当今的大多数系统上,此过程用时远短于 8 秒。

此 RFC 并未尝试解决在内核信号 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 后分配更多内存的问题。

向后兼容性

不存在向后兼容性问题,可以通过软转换进行更改。

安全注意事项

此 RFC 建议将 zx_system_get_event 系统调用从 driver_manager 移至 pwrbtn-monitor。此系统调用需要根作业的句柄,该句柄是高度敏感的句柄。pwrbtn-monitor 是一个聚焦的小组件,已经能够通过 fuchsia.hardware.power.statecontrol/Admin capability 控制系统电源状态。添加对根作业的访问权限可提高此组件的权限。

此 RFC 还建议增加重新启动超时。只有在我们认为 OOM 是一种攻击途径,且重新启动超时越久,攻击者就能有更多时间执行漏洞,这才是一个问题。

测试

无论是在用户空间在内核超时之前关闭还是未关闭时,都需要通过测试来验证系统是否按预期重新启动。如果这些测试不存在,系统会添加这些测试。

我们还希望性能测试能够分析用户空间拆解所需的时间。这些性能分析测试可用于告知内核超时值。

文档

API 文档的各个部分应该更新,但不需要进行新的概念性更新,因为此 RFC 更像是信号的重新连接,而不是从根本上改变系统行为的内容。

缺点、替代方案和未知情况

替代方案:用户空间处理程序位置

我们可以通过多种选项放置 ZX_SYSTEM_EVENT_OUT_OF_MEMORY 的用户空间处理程序。处理程序最好位于 ZBI 中并存在于所有产品中,以便尽早提供一致的处理体验。主要的备选候选对象是 power_manager、Close-shim 和 developer_manager。选择 pwrbtn-monitor 的主要原因是,此责任符合重新启动系统以响应事件的整体作业,OOM 只是软件生成的事件,而不是硬件事件。

替代方案:针对所有用户空间发起的重新启动,报告 NO_CRASH

目前,Zircon 会在 OOM 期间将数据写入永久性内存,而此 RFC 建议继续这种做法。作为替代方案,我们可以在每次用户空间触发调用 zx_system_powerctl 以重新启动系统时,将相同的数据写入永久性 RAM 中,无论内核是否检测到关于 OOM 的 OOM 以及有信号的用户空间都发出了信号。如果我们这样做,如果在 OOM 后成功关闭用户空间,反馈组件会看到 Zircon 给出的 NO_CRASH 重新启动原因。如果内核计时器已过期,并且 Zircon 在 OOM 后重新启动了系统,那么 Feedback 会从 Zircon 中看到 OOM 重新启动原因。

这种方法的缺点是,用户空间处理 OOM 的问题可能会导致系统知道自己已重新启动,但不知道是由 OOM 导致的。在这种情况下,反馈将看到 Zircon 有 NO_CRASH 重新启动原因,但在磁盘上找到没有持久的崩溃信息。在这种情况下,反馈仍会提交报告。

替代方案:允许在 OOM 重新启动期间向 zx_system_powerctl 发出兼容的请求

此 RFC 规定,一旦内核执行的 OOM 重新启动开始,只有两项操作才能完成重新启动:用户空间使用 ZX_SYSTEM_POWERCTL_ACK_KERNEL_INITIATED_REBOOTcmd 调用 zx_system_powerctl,或者内核的重新启动计时器过期。作为替代方案,我们可以允许对 zx_system_powerctl 的任何兼容调用来完成重新启动。兼容的调用是指也会重新启动系统的调用,而不考虑传递的 cmd 值。这样可以解决争用情况,即用户空间在内核发出 OOM 条件之前独立决定重新启动系统。内核可能会根据其在 zx_system_powerctl 调用中实际收到的 cmd 值来写入不同的重新启动原因。这样一来,就可以审核系统是否通常遵循预期的重新启动路径。

替代方案:通过内核对象发出用户空间处理完成信号

此 RFC 提议通过使用特定的 cmd 值调用 zx_system_powerctl 来完成由内核执行的 OOM 重新启动。可以改为更改内核到用户空间的信号机制,以便用户空间接收通道或事件对象。然后,用户空间可以发送消息或断言/取消断言一个信号,以指示可以继续重新启动。这种替代方法的优点是,完成重新启动的组件不需要访问根资源。zx_system_powerctl 需要访问根资源。这种替代方法需要完成更多工作,因为它对目前的内核/用户空间信号传输方式带来了更显著的变化。

缺点:某些竞赛仍是可能的

如今,内核可能会检测到 OOM 条件,声明暂停令牌,然后用户空间会调用 zx_system_powerctl,这是因为用户空间先前已决定重新启动。在这种情况下,对 zx_system_powerctl 的调用将失败。然后,driver_manager 退出。component_manager 继续拆解组件拓扑,最终到达 power_manager,然后被终止。终止 power_manager 会使根作业崩溃,因为 power_manager 已设置为对根作业至关重要。通常,Zircon 会在根作业终止时重新启动系统,但在这种情况下不会,因为 MemoryWatchdog 持有暂停令牌。相反,系统最终会达到 MemoryWatchdog 的超时,并重新启动系统。

此 RFC 允许出现类似的竞态。当发生 OOM 条件时,用户空间可能正在拆解,以准备重新启动。这可能是因为 pwrbtn-monitor 已经退出,这意味着用户空间中没有任何内容可观察来自内核的 OOM 信号。此外,pwrbtn-monitor 可能正在运行,但无法重新启动系统,因为 power_manager 仅允许一个运行中请求关闭或重新启动系统。较早的拆解请求最终会变为 driver_manager。如前所述,driver_managerzx_system_powerctl 的请求将失败,并且根作业将崩溃,但系统在 MemoryWatchdog 的超时到期之前不会重新启动。这场竞赛有多差?它是非常良性的,因为用户空间会自我清理。到根作业崩溃时,用户空间已完全清除了其预期内容。最大的缺点是重新启动不够及时。

另一种可能的争用情况是 reboot-on-terminate 组件恰好在错误的时间退出。组件可以自行配置,以便在退出 component_manager 时重新启动系统。component_manager 会通过调用 fuchsia.hardware.power.statecontrol/Admin.Reboot 来重新启动系统。如果在 component_manager 被告知拆除组件拓扑后,“终止时重新启动”组件退出,component_manager 不会尝试重新启动系统。如果此类“终止时重新启动”组件在组件调用 Admin.Reboot 之后、power_manager 告知 component_manager 拆除拓扑之前退出,系统将崩溃,因为如果 component_managerpower_manager 的调用失败,系统会崩溃。此 RFC 没有提议解决此问题。竞态可能会导致不可预测的行为,因为我们不知道在 component_manager 出现紧急警报时系统运行了多少内容。

未知:对完全耗尽内存的几率的影响

此 RFC 对系统在 OOM 处理期间内存完全耗尽风险的净影响尚不确定。建议的更改可以降低系统完全耗尽内存的可能性,因为大多数用户空间组件不监听退出信号,并且会在其客户端退出后立即终止。这样应该很快就会开始释放内存。我们希望真正观察退出信号的用户空间组件应立即退出,同时释放内存。建议的更改可能会增加内存不足的可能性,因为某些系统的关闭时间可能比现有的 8 秒超时时间长,因此为某些组件分配内存留出更多时间。此 RFC 还推迟了关闭文件系统(目前会快速退出)。文件系统缓存通常采用由内核管理的可舍弃内存的形式。目前尚不清楚文件系统运行时间是否会对内存产生重大的负面影响。