Was passiert, wenn Mikrocontrollern der RAM ausgeht?

Es kann nur ein Zufall sein, aber ich habe bemerkt, dass die von mir verwendeten Mikrocontroller neu gestartet wurden, als ihnen der RAM ausging (Atmega 328, falls hardwarespezifisch). Ist das das, was Mikrocontroller tun, wenn ihnen der Speicher ausgeht? Wenn nicht, was passiert dann?

Warum wie? Der Stapelzeiger wird sicherlich blind auf einen nicht zugewiesenen Speicherbereich erhöht (oder überrollt), aber was passiert dann: Gibt es eine Art Schutz, der ihn neu startet, oder ist er (neben anderen Effekten) das Ergebnis des Überschreibens von kritisch Daten (von denen ich annehme, dass sie sich von dem Code unterscheiden, der meiner Meinung nach direkt vom Flash ausgeführt wird)?

Ich bin mir nicht sicher, ob dies hier oder auf Stack Overflow sein sollte, bitte lassen Sie mich wissen, ob dies verschoben werden soll, obwohl ich mir ziemlich sicher bin, dass Hardware dabei eine Rolle spielt.

Aktualisieren

Ich sollte darauf hinweisen, dass ich mich besonders für den tatsächlichen Mechanismus hinter der Speicherbeschädigung interessiere (ist dies das Ergebnis des SP-Rollovers -> hängt das von der Speicherzuordnung des uC usw. ab)?

Einige Mikros werden zurückgesetzt, wenn Sie versuchen, auf ungültige Adressen zuzugreifen. Es ist eine wertvolle Funktion, die in Hardware implementiert ist. In anderen Fällen kann es dazu führen, dass es an einen beliebigen Ort springt (sagen Sie, Sie haben die Rücksendeadresse für eine ISR verprügelt), möglicherweise Daten anstelle von Code ausgeführt, wenn die Architektur dies zulässt, und sich möglicherweise in einer Schleife verfängt, die der Watchdog herausbringt von.
Einem Prozessor kann nicht der RAM ausgehen, es gibt keine Anweisung, die dazu führt, dass ihm der RAM ausgeht. Das Ausgehen von RAM ist ausschließlich ein Softwarekonzept.

Antworten (4)

Im Allgemeinen prallen Stack und Heap aufeinander. An diesem Punkt wird alles chaotisch.

Abhängig von der MCU kann (oder wird) eines von mehreren Dingen passieren.

  1. Variablen werden beschädigt
  2. Der Stapel wird beschädigt
  3. Das Programm wird beschädigt

Wenn 1 passiert, bekommen Sie ein seltsames Verhalten - Dinge tun nicht das, was sie sollten. Wenn 2 passiert, bricht die Hölle los. Wenn die Rücksendeadresse auf dem Stack (falls vorhanden) beschädigt ist, kann man nur raten, wohin der aktuelle Aufruf zurückkehren wird. Zu diesem Zeitpunkt wird die MCU im Grunde anfangen, zufällige Dinge zu tun. Wenn 3 wieder passiert, wer weiß schon, was passieren würde. Dies geschieht nur, wenn Sie Code aus dem RAM ausführen.

Im Allgemeinen ist alles vorbei, wenn der Stack beschädigt wird. Was passiert, hängt von der MCU ab.

Es kann sein, dass der Versuch, den Speicher überhaupt zuzuweisen, fehlschlägt, sodass die Beschädigung nicht auftritt. In diesem Fall kann die MCU eine Ausnahme auslösen. Wenn kein Ausnahmehandler installiert ist, wird die MCU meistens einfach angehalten (ein Äquivalent von while (1);. Wenn ein Handler installiert ist, wird sie möglicherweise sauber neu gestartet.

Wenn die Speicherzuordnung weitergeht oder wenn sie es versucht, fehlschlägt und einfach ohne zugewiesenen Speicher fortgesetzt wird, dann befinden Sie sich in den Bereichen "Wer weiß?". Die MCU startet sich möglicherweise durch die richtige Kombination von Ereignissen neu (durch Unterbrechungen, die zum Zurücksetzen des Chips usw. führen), aber es gibt keine Garantie dafür.

Was jedoch normalerweise mit hoher Wahrscheinlichkeit passieren kann, wenn es aktiviert ist, ist, dass der interne Watchdog-Timer (falls vorhanden) abläuft und den Chip neu startet. Wenn das Programm durch diese Art von Absturz völlig AWOL geht, werden die Anweisungen zum Zurücksetzen des Timers im Allgemeinen nicht ausgeführt, sodass er abläuft und zurückgesetzt wird.

Vielen Dank für Ihre Antwort, es ist eine ausgezeichnete Zusammenfassung der Auswirkungen. Vielleicht hätte ich jedoch angeben sollen, dass ich mehr Details über den tatsächlichen Mechanismus dieser Beschädigungen hätte: Wird der gesamte Arbeitsspeicher Stack und Heap zugewiesen, sodass der Stack-Zeiger überrollt und frühere Variablen/Adressen überschreibt? Oder hängt das weniger von der Speicherzuordnung jedes Mikros ab? Optional (es ist wahrscheinlich ein Thema für sich) würde mich interessieren, wie diese Hardware-Handler implementiert werden.
Es hängt hauptsächlich vom Compiler und der verwendeten Standard-C-Bibliothek ab. Es hängt manchmal auch davon ab, wie der Compiler konfiguriert ist (Linker-Skripte usw.).
Könnten Sie das vertiefen, vielleicht mit ein paar Beispielen?
Nicht wirklich, nein. Einige Systeme weisen verschiedenen Segmenten begrenzten Speicherplatz zu, andere nicht. Einige verwenden Linker-Skripte, um Segmente zu definieren, andere nicht. Wählen Sie einen Mikrocontroller aus, der Sie interessiert, und recherchieren Sie, wie seine Speicherzuweisungen funktionieren.,

Eine alternative Ansicht: Mikrocontrollern geht nicht der Speicher aus.

Zumindest nicht, wenn es richtig programmiert ist. Das Programmieren eines Mikrocontrollers ist nicht genau wie das Programmieren für allgemeine Zwecke. Um es richtig zu machen, müssen Sie sich seiner Einschränkungen bewusst sein und entsprechend programmieren. Es gibt Tools, die dabei helfen, dies sicherzustellen. Suchen Sie sie heraus und lernen Sie sie kennen - zumindest wie man Linker-Skripte und Warnungen liest.

Wie Majenko und andere jedoch sagen, kann ein schlecht programmierter Mikrocontroller keinen Speicher mehr haben und dann alles tun, einschließlich einer Endlosschleife (was dem Watchdog-Timer zumindest die Möglichkeit gibt, ihn zurückzusetzen. Sie haben den Watchdog-Timer aktiviert, nicht wahr? )

Übliche Programmierregeln für Mikrocontroller vermeiden dies: Zum Beispiel wird der gesamte Speicher entweder auf dem Stapel oder statisch (global) zugewiesen; "new" oder "malloc" sind verboten. Dies gilt auch für die Rekursion, sodass die maximale Tiefe der Verschachtelung von Unterprogrammen analysiert und gezeigt werden kann, dass sie in den verfügbaren Stapel passt.

Somit kann der maximal benötigte Speicherplatz berechnet werden, wenn das Programm kompiliert oder gelinkt wird, und mit der Speichergröße (häufig im Linker-Skript kodiert) für den spezifischen Prozessor verglichen werden, auf den Sie abzielen.

Dann geht dem Mikrocontroller möglicherweise nicht der Speicher aus, aber Ihrem Programm möglicherweise. Und in diesem Fall kommen Sie dazu

  • umschreiben, kleiner, oder
  • Wählen Sie einen größeren Prozessor (sie sind oft mit unterschiedlichen Speichergrößen erhältlich).

Ein gängiges Regelwerk für die Programmierung von Mikrocontrollern ist MISRA-C , das von der Automobilindustrie übernommen wurde.

Aus meiner Sicht ist es die beste Methode, die SPARK-2014- Teilmenge von Ada zu verwenden. Ada zielt tatsächlich ziemlich gut auf kleine Controller wie AVR, MSP430 und ARM Cortex ab und bietet von Natur aus ein besseres Modell für die Mikrocontroller-Programmierung als C. Aber SPARK fügt dem Programm Anmerkungen in Form von Kommentaren hinzu, die beschreiben, was das Programm tut.

Jetzt analysieren die SPARK-Tools das Programm, einschließlich dieser Anmerkungen, und beweisen Eigenschaften darüber (oder melden potenzielle Fehler). Sie müssen keine Zeit oder Codeplatz verschwenden, wenn Sie sich mit fehlerhaften Speicherzugriffen oder Integer-Überläufen befassen, da diese nachweislich nie vorkommen.

Obwohl mit SPARK mehr Vorlaufarbeit verbunden ist, zeigt die Erfahrung, dass es schneller und billiger zu einem Produkt kommen kann, da Sie keine Zeit damit verbringen, mysteriösen Neustarts und anderen seltsamen Verhaltensweisen nachzujagen.

Ein Vergleich von MISRA-C und SPARK

+1 dazu. Das Portieren malloc()(und sein C++-Begleiter new) auf den AVR ist eines der schlimmsten Dinge, die die Arduino-Leute hätten tun können, und hat zu vielen, vielen sehr verwirrten Programmierern mit fehlerhaftem Code sowohl in ihrem Forum als auch im Arduino-Stack-Austausch geführt. Es gibt sehr, sehr wenige Situationen, in denen mallocein ATmega von Vorteil ist.
+1 für Philosophie, -1 für Realismus. Wenn das Zeug richtig programmiert wäre, gäbe es keine Notwendigkeit für diese Frage. Die Frage war, was passiert, wenn Mikrocontrollern der Speicher ausgeht. Wie verhindert werden kann, dass ihnen der Speicher ausgeht, ist eine andere Frage. Andererseits ist die Rekursion ein mächtiges Werkzeug, sowohl zum Lösen von Problemen als auch zum Erschöpfen des Stacks.
@PkP: Ich werde -1 aus Gründen des Realismus vorläufig nicht zustimmen. Wenn der eingebettete Programmierer denken kann "wie kann ich verhindern ..." und nicht "was passiert, wenn ...", dann machen wir Fortschritte. Die Rekursion kann beispielsweise oft linearisiert werden, es gibt nicht viele Aufgaben auf einem Controller, bei denen eine echte Rekursion erforderlich ist. Jetzt könnten wir einfach eine Niederlage und gefährliche Programmierung akzeptieren (und ich stimme zu, dass Sie das manchmal tun müssen: Wir müssen die Schlachten sorgfältig auswählen), aber lassen Sie uns das nicht zum Standard machen.
@Brian, da ich kein Idiot bin, stimme ich dir offensichtlich zu. Ich denke nur gerne aus umgekehrter Sicht darüber nach - ich hoffe, dass Sie, wenn Sie die schrecklichen Folgen erkennen, wenn der Speicher (Stapel) knapp wird, nach Wegen suchen, dies zu verhindern. Auf diese Weise haben Sie einen echten Impuls, gute Programmierpraktiken zu finden, anstatt nur guten Programmierratschlägen zu folgen ... und wenn Sie an die Speichergrenze stoßen, setzen Sie die guten Praktiken eher durch, selbst auf Kosten der Bequemlichkeit. Es ist nur ein Aussichtspunkt...
@PkP: höre dich laut und deutlich. Ich habe dies als alternative Ansicht bezeichnet - weil es die Frage nicht wirklich beantwortet!
Mikrocontrollern geht also der Speicher aus. Ich weiß, dass sie das nicht sollen, und ich tue alles, um das zu vermeiden (beginnend mit mallocs() verwende ich sie mit einem maximalen Längenwert, um etwas RAM zu sparen, aber das Worst-Case-Szenario ist das gleiche wie ein statisch zugewiesenes, also frage ich mich wenn es tatsächlich vorzuziehen ist), bin ich nur neugierig und dies ist auch nützliches Wissen für das Debugging in der Zukunft. C ist meine bevorzugte Sprache (kein Fan von Ada). Kennen Sie kostenlose, einfachere Tools zur statischen Codeanalyse (MISRA scheint proprietär zu sein)? Eigentlich frage ich mich: Ist die RAM-Schätzung nicht plattform-/linkerabhängig?
Danke für deine Antwort, das hätte mein erster Satz sein sollen!
"Kein Fan von Ada" Das höre ich oft - aber selten von jemandem mit neuerer Erfahrung (2005 oder 2012, nicht Ada-83!). Warum in Ihrem Fall? Entschuldigung, ich bin kein Experte für C-Tools, obwohl ich positive Dinge über FRAMA-C höre.
Ich behaupte nicht, ein bestimmtes Argument dagegen zu haben, ich mag einfach nicht die Syntax und den Codierungsstil wie der von Pascal. Ich bin immer noch offen für neue Alternativen, aber im Moment erlaubt mir die Kombination aus C, C++, VHDL und Python, so ziemlich alles bequem zu machen, was ich will.
Interessant. Ein Vorteil von Ada ist für mich die Ähnlichkeit zu VHDL, was den Wechsel zwischen SW und HW erleichtert. (Natürlich wünschte ich, VHDL wäre nicht so eingeschränkt, nachdem ich Ada eine Weile benutzt habe.) Ich benutze manchmal immer noch C, aber nur, wenn der Kunde genug bezahlt.
@MisterMystère: Mikrocontrollern geht im Allgemeinen nicht der Speicher aus. Ein Mikrocontroller, der beim ersten Einschalten 4096 Bytes RAM hat, wird für immer 4096 Bytes haben. Es ist möglich, dass der Code fälschlicherweise versucht, auf nicht vorhandene Adressen zuzugreifen, oder erwartet, dass zwei verschiedene Methoden zur Berechnung von Adressen auf unterschiedliche Speicher zugreifen, wenn dies nicht der Fall ist, aber der Controller selbst führt einfach die ihm gegebenen Anweisungen aus.

Ich mag Majenkos Antwort sehr und habe ihr selbst +1 gegeben. Aber ich möchte das auf den Punkt bringen:

Wenn einem Mikrocontroller der Speicher ausgeht, kann alles passieren.

Sie können sich wirklich auf nichts verlassen, wenn es passiert. Wenn dem Computer der Stapelspeicher ausgeht, wird der Stapel höchstwahrscheinlich beschädigt. Und wenn das passiert, kann alles passieren. Variablenwerte, Überläufe, temporäre Register werden alle beschädigt und unterbrechen den Programmablauf. If/then/elses kann falsch auswerten. Absenderadressen werden verstümmelt, wodurch das Programm zu zufälligen Adressen springt. Jeder Code, den Sie in das Programm geschrieben haben, kann ausgeführt werden. (Betrachten Sie Code wie: "if [condition] then {fire_all_missiles();}"). Auch eine ganze Reihe von Anweisungen, die Sie nicht geschrieben haben, können ausgeführt werden, wenn der Kern zu einem nicht verbundenen Speicherplatz springt. Alle Wetten sind aus.

Danke für den Nachtrag, die Zeile fire_all_missiles() hat mir besonders gut gefallen.

Der AVR hat den Vektor an der Adresse Null zurückgesetzt. Wenn Sie den Stapel mit zufälligem Müll überschreiben, werden Sie schließlich eine Schleife machen und eine Rücksprungadresse überschreiben, die auf "nirgendwo" zeigt. Wenn Sie dann von einer Subroutine zu diesem Nirgendwo zurückkehren, wird die Ausführung eine Schleife zur Adresse 0 machen, wo sich normalerweise ein Sprung zum Reset-Handler befindet.