当微控制器内存不足时会发生什么?


12

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

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

我不确定这应该在此处还是在Stack Overflow上,请告诉我是否应该移动它,尽管我很确定硬件在其中起作用。

更新资料

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


8
如果您尝试访问无效的地址,某些微控制器将重置。这是在硬件中实现的宝贵功能。在其他时候,它可能最终跳到任意地方(例如,您破坏了ISR的返回地址),如果体系结构允许,则可能执行数据而不是代码,并且可能陷入看门狗将其发布的循环中的。
Spehro Pefhany,2015年

2
处理器不能用完RAM,没有指令可以使它用完RAM。用完RAM完全是一个软件概念。
user253751 '16

Answers:


14

通常,堆栈和堆会相互崩溃。到那时,一切都变得混乱了。

根据MCU的不同,可能会(或将)发生以下几种情况之一。

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

当发生1时,您开始出现奇怪的行为-事情没有按预期进行。当2发生时,地狱的所有方式都会松散。如果堆栈上的返回地址(如果有的话)已损坏,那么当前调用将返回的位置将是所有人的猜测。那时基本上,MCU将开始做随机的事情。当3再次发生时,谁知道会发生什么。仅当您在RAM之外执行代码时才会发生这种情况。

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

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

如果内存分配确实继续进行,或者尝试失败,并且仅继续进行而没有分配内存,那么您将进入“谁知道?”的境界。MCU可能最终通过正确的事件组合重新启动自身(中断导致最终复位芯片等),但是并不能保证会发生这种情况。

但是,如果启用了此功能,通常很可能发生内部看门狗定时器(如果存在)超时并重启芯片的情况。当程序通过这种崩溃完全停止工作时,通常不会运行用于重置计时器的指令,因此它将超时并重置。


感谢您的回答,它是效果的绝佳总结。也许我应该已经指定了一些细节,但我想进一步了解这些损坏的实际机制:是否将整个RAM分配给堆栈和堆,以便堆栈指针翻转并覆盖较早的变量/地址?还是更少依赖每个微控制器的内存映射?可选(它本身可能是一个主题),我会对学习如何实现这些硬件处理程序感兴趣。
米斯特先生(MisterMystère),2015年

1
它主要取决于使用的编译器和标准C库。有时也取决于编译器的配置方式(链接脚本等)。
Majenko

您可以列举几个例子来扩展一下吗?
米斯特先生(MisterMystère)2015年

不是,不是 有些系统为不同的段分配有限的空间,有些则没有。有些使用链接描述文件来定义段,有些则没有。选择一个您感兴趣的微控制器,并对其内存分配的工作方式进行一些研究。,
Majenko 2015年

12

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

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

但是,正如Majenko和其他人所说,编程错误的微控制器可能会耗尽内存,然后执行包括无限循环在内的任何操作(这至少使看门狗计时器有机会重置它。您确实启用了看门狗计时器,不是吗? )

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

因此,可以在编译或链接程序时计算所需的最大存储空间,并将其与目标内存的内存大小(通常在链接程序脚本中进行编码)进行比较。

这样,微控制器可能不会用完内存,但是您的程序可能会用完。在这种情况下,您可以

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

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

我认为最佳实践是使用Ada 的SPARK-2014子集。Ada实际上可以很好地针对AVR,MSP430和ARM Cortex等小型控制器,并且固有地为微控制器编程提供了比C更好的模型。但是SPARK以注释的形式向程序添加了注释,以描述程序的工作方式。

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

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

MISRA-C和SPARK的比较


3
+1。将AVR 移植malloc()(和C ++伴侣new)到AVR是arduino人们可能做的最糟糕的事情之一,并且已经导致很多很多非常困惑的程序员在他们的论坛上以及arduino堆栈交换中都遇到了代码破损的情况。在极少数情况下,malloc使用ATmega会有好处。
康纳·沃尔夫

3
+1代表哲学,-1代表现实。如果对东西进行适当的编程,就不需要这个问题了。问题是微控制器内存不足会发生什么。如何防止它们的内存不足是另一个问题。另一个要点是,递归是解决问题耗尽堆栈的强大工具。
PkP 2015年

2
@Brian,因为我不是白痴,所以我显然同意你的看法。我只是想从相反的角度考虑它-我希望希望当您意识到内存不足(堆栈)的可怕后果时,您将寻找防止它发生的方法。这样一来,您便有真正的动力去寻找良好的编程习惯,而不是仅仅遵循良好的编程建议……而当您遇到内存障碍时,即使牺牲便利,您也更有可能实施良好的惯例。这只是一个观点...
PkP 2015年

2
@PkP:大声而清晰地听到你的声音。我将其标记为替代视图-因为它实际上并未回答问题!
Brian Drummond 2015年

2
@MisterMystère:微处理器通常不会耗尽内存。首次通电时具有4096字节RAM的微控制器将永远具有4096字节。代码可能会错误地尝试访问不存在的地址,或者期望两种不同的计算地址方法在不存在时会访问不同的内存,但控制器本身只会执行所给出的指令。
超级猫

6

我真的很喜欢Majenko的回答,并且自己为+1。但我想澄清一点:

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

当它发生时,您真的不能依靠任何东西。当计算机的堆栈内存不足时,堆栈很可能已损坏。发生这种情况时,任何事情都可能发生。变量值,溢出,临时寄存器都被破坏,从而破坏了程序流。If / then / elses可能会评估错误。返回地址乱码,使程序跳转到随机地址。您在程序中编写的任何代码都可以执行。(请考虑以下代码:“如果[条件],则{fire_all_missiles();}”)。当内核跳转到未连接的内存位置时,也可以执行大量您尚未编写的指令。所有赌注都关闭了。


2
感谢您的附录,我特别喜欢fire_all_missiles()行。
米斯特先生(MisterMystère)

1

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.