当微控制器用完 RAM 时会发生什么?

电器工程 微控制器 记忆 中央处理器 内存
2022-01-25 15:46:25

这可能只是一个巧合,但我注意到我使用的微控制器在 RAM 用完时会重新启动(如果是特定于硬件,则为 Atmega 328)。这就是微控制器在内存不足时所做的事情吗?如果没有,那会发生什么?

为什么/如何?堆栈指针肯定会盲目地增加到未分配的内存范围(或翻转),但随后会发生什么:是否有某种保护使其重新启动,或者它(除其他效果外)是覆盖关键的结果数据(我假设它与我认为直接从闪存运行的代码不同)?

我不确定这应该在这里还是在 Stack Overflow,请让我知道是否应该移动它,尽管我很确定硬件在其中发挥了作用。

更新

我应该指出我对内存损坏背后的实际机制特别感兴趣(它是 SP 翻转的结果 -> 这是否取决于 uC 的内存映射等)?

4个回答

通常,堆栈和堆相互崩溃。到那时,一切都变得一团糟。

根据 MCU,可能(或将会)发生几件事中的一件。

  1. 变量损坏
  2. 堆栈损坏
  3. 程序被破坏

当 1 发生时,您开始出现奇怪的行为 - 事情没有按照应有的方式进行。当 2 发生时,各种地狱都会崩溃。如果堆栈上的返回地址(如果有的话)被破坏,那么当前调用将返回到哪里是任何人的猜测。那时基本上MCU会开始做一些随机的事情。当3再次发生时,谁知道会发生什么。这只发生在您从 RAM 中执行代码时。

通常,当堆栈损坏时,一切都结束了。究竟发生了什么取决于 MCU。

可能是尝试首先分配内存失败,因此不会发生损坏。在这种情况下,MCU 可能会引发异常。如果没有安装异常处理程序,那么大多数情况下 MCU 将停止(相当于while (1);. 如果安装了处理程序,那么它可能会干净地重新启动。

如果内存分配确实继续,或者如果它尝试、失败并继续没有分配内存,那么你就进入了“谁知道?”的领域。MCU 可能最终通过正确的事件组合重新启动自身(中断导致最终重置芯片等),但不能保证会发生这种情况。

但是,如果启用,通常很有可能发生的是内部看门狗定时器(如果存在)超时并重新启动芯片。当程序通过这种崩溃完全退出时,重置计时器的指令通常不会运行,因此它会超时并重置。

另一种观点:微控制器不会耗尽内存。

至少,在正确编程时不会。对微控制器进行编程与通用编程并不完全一样,要正确地进行编程,您必须了解其约束并相应地进行编程。有一些工具可以帮助确保这一点。搜索它们并学习它们 - 至少如何阅读链接器脚本和警告。

然而,正如 Majenko 和其他人所说,一个编程不当的微控制器可能会耗尽内存,然后做任何事情,包括无限循环(这至少让看门狗定时器有机会重置它。你确实启用了看门狗定时器,不是吗? )

微控制器的通用编程规则避免了这种情况:例如,所有内存要么在堆栈上分配,要么静态(全局)分配;禁止使用“new”或“malloc”。递归也是如此,因此可以分析并显示子程序嵌套的最大深度以适合可用堆栈。

因此,可以在编译或链接程序时计算出所需的最大存储空间,并与您所针对的特定处理器的内存大小(通常在链接描述文件中编码)进行比较。

那么微控制器可能不会耗尽内存,但您的程序可能会。在这种情况下,你可以

  • 重写它,更小,或
  • 选择更大的处理器(它们通常具有不同的内存大小)。

一组常见的微控制器编程规则是MISRA-C,被电机行业采用。

在我看来,最佳实践是使用Ada的SPARK-2014子集。Ada 实际上很好地针对 AVR、MSP430 和 ARM Cortex 等小型控制器,并且本质上为微控制器编程提供了比 C 更好的模型。但是 SPARK 以注释的形式在程序中添加了注释,描述了程序正在做什么。

现在 SPARK 工具将分析程序,包括那些注释,并证明它的属性(或报告潜在的错误)。您不必浪费时间或代码空间来处理错误的内存访问或整数溢出,因为它们已被证明永远不会发生。

尽管 SPARK 涉及更多的前期工作,但经验表明它可以更快、更便宜地获得产品,因为您无需花时间去追逐神秘的重启和其他奇怪的行为。

MISRA-C 和 SPARK 的比较

我真的很喜欢 Majenko 的回答,并亲自为它 +1。但我想明确一点:

当微控制器内存不足时,任何事情都可能发生。

当它发生时,你真的不能依赖任何东西。当机器用完堆栈内存时,堆栈很可能会损坏。当这种情况发生时,任何事情都可能发生。变量值、溢出、临时寄存器都会损坏,从而中断程序流程。if/then/elses 可能评估不正确。返回地址乱码,使程序跳转到随机地址。您在程序中编写的任何代码都可以执行。(考虑像这样的代码:“if [condition] then {fire_all_missiles();}”)。当核心跳转到未连接的内存位置时,您还没有编写的一大堆指令也可以执行。所有的赌注都取消了。

AVR 在地址 0 处有复位向量。当你用随机垃圾覆盖堆栈时,你最终会循环并覆盖一些返回地址,它会指向“无处”;然后,当您从子程序返回到无处时,执行将循环到地址 0,通常是跳转到重置处理程序的位置。