Was passiert, wenn ein eingebettetes Programm beendet wird?

29

Was passiert in einem eingebetteten Prozessor, wenn die Ausführung diese endgültige returnAnweisung erreicht? Stromverbrauch etc, mit einem langen ewigen NOP am Himmel? oder werden NOPs fortlaufend ausgeführt oder wird ein Prozessor vollständig heruntergefahren?

Ich frage unter anderem, ob ein Prozessor ausgeschaltet werden muss, bevor die Ausführung abgeschlossen ist, und ob er die Ausführung jemals beendet, wenn er zuvor ausgeschaltet wurde.

Toby
quelle
22
Es hängt von deinen Überzeugungen ab. Einige sagen, es wird wiedergeboren.
Telaclavo
9
Ist es für eine Rakete?
Lee Kowalkowski
6
Einige Systeme unterstützen die HCF- Anweisung (Halt and Catch Fire) . :)
Stefan Paul Noack
1
es wird zur Selbstzerstörungsroutine verzweigen

Antworten:

41

Dies ist eine Frage, die mein Vater mir immer gestellt hat. " Warum durchläuft es nicht einfach alle Anweisungen und hört am Ende auf? "

Schauen wir uns ein pathologisches Beispiel an. Der folgende Code wurde im C18-Compiler von Microchip für den PIC18 kompiliert:

void main(void)
{

}

Es erzeugt die folgende Assembler-Ausgabe:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

Wie Sie sehen, wird main () aufgerufen und enthält am Ende eine return-Anweisung, die wir jedoch nicht explizit angegeben haben. Wenn main zurückkehrt, führt die CPU den nächsten Befehl aus, der einfach ein GOTO ist, um zum Anfang des Codes zurückzukehren. main () wird einfach immer wieder aufgerufen.

Nun, nachdem wir das gesagt haben, ist dies nicht die Art und Weise, wie Menschen Dinge normalerweise tun würden. Ich habe noch nie einen eingebetteten Code geschrieben, mit dem main () so beendet werden kann. Meistens würde mein Code ungefähr so ​​aussehen:

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

Daher würde ich main () normalerweise nie beenden.

"OK, ok" sagst du. All dies ist sehr interessant, da der Compiler sicherstellt, dass es nie eine letzte return-Anweisung gibt. Aber was passiert, wenn wir das Problem erzwingen? Was ist, wenn ich meinen Assembler mit der Hand codiere und keinen Sprung zurück zum Anfang mache?

Nun, offensichtlich würde die CPU einfach die nächsten Anweisungen ausführen. Diese würden ungefähr so ​​aussehen:

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

Die nächste Speicheradresse nach der letzten Anweisung in main () ist leer. Auf einem Mikrocontroller mit FLASH-Speicher enthält eine leere Anweisung den Wert 0xFFFF. Zumindest auf einem PIC wird dieser Op-Code als 'nop' oder 'no operation' interpretiert. Es tut einfach nichts. Die CPU würde diese Nops bis zum Ende des Speichers weiter ausführen.

Was ist danach?

Bei der letzten Anweisung lautet der Anweisungszeiger der CPU 0x7FFe. Wenn die CPU 2 zu ihrem Befehlszeiger hinzufügt, erhält sie 0x8000, was als Überlauf auf einem PIC mit nur 32k FLASH angesehen wird, und springt so auf 0x0000 zurück, und die CPU setzt die Ausführung von Befehlen gerne am Anfang des Codes fort , als wäre es zurückgesetzt worden.


Sie haben auch gefragt, ob das Gerät ausgeschaltet werden muss. Grundsätzlich können Sie tun, was Sie wollen, und das hängt von Ihrer Anwendung ab.

Wenn Sie eine Anwendung hatten, die nach dem Einschalten nur eine Aktion ausführen musste, und dann nichts anderes tun, können Sie einfach eine Weile warten (1). am Ende von main (), damit die CPU nichts Auffälliges mehr tut.

Wenn die CPU aufgrund der Anwendung heruntergefahren werden musste, stehen je nach CPU möglicherweise verschiedene Energiesparmodi zur Verfügung. CPUs haben jedoch die Angewohnheit, wieder aufzuwachen, sodass Sie sicherstellen müssen, dass keine zeitliche Begrenzung für den Ruhezustand festgelegt wurde und kein Watch Dog Timer aktiv ist usw.

Sie könnten sogar einige externe Schaltkreise organisieren, die es der CPU ermöglichen würden, ihre eigene Stromversorgung vollständig zu unterbrechen, wenn sie fertig ist. Siehe diese Frage: Verwenden eines Tasters als einrastender Ein-Aus-Kippschalter .

Raketenmagnet
quelle
20

Bei kompiliertem Code hängt dies vom Compiler ab. Der von mir verwendete Rowley CrossWorks gcc ARM-Compiler springt zum Code in der Datei crt0.s, die eine Endlosschleife enthält. Der Microchip C30-Compiler für die 16-Bit-Geräte dsPIC und PIC24 (ebenfalls auf gcc-Basis) setzt den Prozessor zurück.

Natürlich werden die meisten eingebetteten Programme niemals so beendet und führen Code kontinuierlich in einer Schleife aus.

Leon Heller
quelle
13

Hier sind zwei Punkte zu beachten:

  • Genau genommen kann ein eingebettetes Programm nicht "fertig" werden.
  • In den seltensten Fällen muss ein eingebettetes Programm einige Zeit ausgeführt und dann "beendet" werden.

Das Konzept eines Programmabschlusses existiert normalerweise in einer eingebetteten Umgebung nicht. Auf einer niedrigen Ebene führt eine CPU Anweisungen aus, solange dies möglich ist. es gibt keine "final return statement". Eine CPU kann die Ausführung stoppen, wenn sie auf einen nicht behebbaren Fehler stößt oder explizit angehalten wird (in einen Ruhemodus, einen Energiesparmodus usw. versetzt). Beachten Sie jedoch, dass selbst Ruhemodi oder nicht behebbare Fehler im Allgemeinen nicht garantieren, dass kein Code mehr ausgeführt wird ausgeführt werden. Sie können aus dem Energiesparmodus aufwachen (so werden sie normalerweise verwendet), und sogar eine gesperrte CPU kann einen NMI-Handler ausführen (dies ist bei Cortex-M der Fall). Ein Watchdog wird auch weiterhin ausgeführt, und Sie können ihn möglicherweise auf einigen Mikrocontrollern nicht deaktivieren, sobald er aktiviert ist. Die Details variieren stark zwischen den Architekturen.

Bei Firmware, die in einer Sprache wie C oder C ++ geschrieben ist, wird das Beenden von main () durch den Startcode bestimmt. Hier ist zum Beispiel der relevante Teil des Startcodes aus der STM32 Standard Peripheral Library (für eine GNU-Toolchain sind meine Kommentare):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

Dieser Code tritt in eine Endlosschleife ein, wenn main () zurückkehrt, allerdings auf nicht offensichtliche Weise ( bl mainwird lrmit der Adresse des nächsten Befehls geladen, was praktisch ein Sprung zu sich selbst ist). Es werden keine Versuche unternommen, die CPU anzuhalten oder in einen Energiesparmodus usw. zu versetzen. Wenn Sie dies in Ihrer Anwendung rechtmäßig benötigen, müssen Sie dies selbst tun.

Beachten Sie, dass das Verbindungsregister gemäß ARMv7-M ARM A2.3.1 beim Zurücksetzen auf 0xFFFFFFFF gesetzt wird und eine Verzweigung zu dieser Adresse einen Fehler auslöst. Daher haben die Designer von Cortex-M beschlossen, eine Rückgabe des Reset-Handlers als abnormal zu behandeln, und es ist schwierig, mit ihnen zu streiten.

Apropos legitimer Bedarf, die CPU nach Abschluss der Firmware zu stoppen, ist kaum vorstellbar, dass ein Abschalten Ihres Geräts nicht besser funktioniert. (Wenn Sie Ihre CPU "für immer" deaktivieren, können Sie nur ein Aus- und Wiedereinschalten oder einen externen Hardware-Reset durchführen.) Sie können ein ENABLE-Signal für Ihren DC / DC-Wandler deaktivieren oder die Stromversorgung ausschalten auf andere Weise, wie es ein ATX-PC tut.

Dorn
quelle
1
"Sie können aus dem Energiesparmodus aufwachen (so werden sie normalerweise verwendet), und sogar eine gesperrte CPU kann einen NMI-Handler ausführen (dies ist bei Cortex-M der Fall)." eine Buch- oder Filmhandlung. :)
Mark Allen
Das "bl main" lädt "lr" mit der Adresse der folgenden Anweisung (das "bx lr"), nicht wahr? Gibt es einen Grund zu der Annahme, dass "lr" etwas anderes enthält, wenn "bx lr" ausgeführt wird?
Supercat
@supercat: du hast natürlich recht. Ich habe meine Antwort bearbeitet, um den Fehler zu beheben und ein wenig zu erweitern. Wenn man darüber nachdenkt, ist die Art und Weise, wie sie diese Schleife implementieren, ziemlich seltsam. Sie hätten es leicht tun können loop: b loop. Ich frage mich, ob sie eigentlich eine Rückkehr vorhatten, aber vergessen hatten zu sparen lr.
Dorn
Es ist neugierig. Ich würde erwarten, dass eine Menge ARM-Code mit LR beendet wird, das den gleichen Wert wie bei der Eingabe hat, aber ich weiß nicht, dass dies garantiert ist. Eine solche Garantie wäre nicht oft nützlich, aber um sie aufrechtzuerhalten, müssten Routinen, die r14 in ein anderes Register kopieren und dann eine andere Routine aufrufen, Anweisungen hinzugefügt werden. Wenn lr bei der Rückkehr als "unbekannt" betrachtet wird, könnte man das Register "bx", das die gespeicherte Kopie enthält. Dies würde jedoch zu einem sehr merkwürdigen Verhalten mit dem angegebenen Code führen.
Supercat
Eigentlich bin ich mir ziemlich sicher, dass Non-Leaf-Funktionen lr retten sollen. Diese drücken normalerweise lr auf den Stapel im Prolog und kehren zurück, indem sie den gespeicherten Wert in pc einfügen. Dies ist, was z. B. ein C- oder C ++ main () tun würde, aber die Entwickler der fraglichen Bibliothek haben in Reset_Handler offensichtlich nichts dergleichen getan.
Thorn
9

Wenn Sie nachfragen return, denken Sie zu hoch. Der C-Code wird in Maschinencode übersetzt. Wenn Sie also stattdessen daran denken, dass der Prozessor Anweisungen blind aus dem Speicher zieht und ausführt, hat er keine Ahnung, welche die "endgültigen" sind return. Prozessoren haben also kein inhärentes Ende, sondern es ist Sache des Programmierers, den Endfall zu behandeln. Wie Leon in seiner Antwort hervorhebt, haben Compiler ein Standardverhalten programmiert, aber oft möchte der Programmierer ihre eigene Sequenz zum Herunterfahren (ich habe verschiedene Dinge getan, wie in einen Energiesparmodus zu wechseln und anzuhalten oder auf das Anschließen eines USB-Kabels zu warten in und dann neu starten).

Viele Mikroprozessoren verfügen über Stoppbefehle, mit denen der Prozessor gestoppt wird, ohne die Speicherkapazität zu beeinträchtigen. Andere Prozessoren können sich auf das "Anhalten" verlassen, indem sie einfach wiederholt zu derselben Adresse springen. Möglicherweise gibt es Optionen, aber es liegt am Programmierer, da der Prozessor einfach die Anweisungen aus dem Speicher liest, auch wenn dieser Speicher nicht als Anweisungen gedacht war.

Klox
quelle
7

Das Problem ist nicht eingebettet (ein eingebettetes System kann Linux oder sogar Windows ausführen), sondern eigenständig oder "Bare-Metal": Das (kompilierte) Anwendungsprogramm ist das einzige, was auf dem Computer ausgeführt wird (es spielt keine Rolle, ob es sich um ein handelt) Mikrocontroller oder Mikroprozessor).

Für die meisten Sprachen definiert die Sprache nicht, was passiert, wenn das Hauptprogramm beendet wird und es kein Betriebssystem gibt, zu dem zurückgekehrt werden kann. Für C hängt es davon ab, was sich in der Startdatei befindet (häufig crt0.s). In den meisten Fällen kann (oder muss) der Benutzer den Startcode eingeben. Die endgültige Antwort lautet also: Was Sie schreiben, ist der Startcode oder was zufällig in dem von Ihnen angegebenen Startcode enthalten ist.

In der Praxis gibt es 3 Ansätze:

  • Treffen Sie keine besonderen Maßnahmen. Was passiert, wenn die Hauptrückgabe undefiniert ist?

  • springe auf 0 oder benutze ein anderes Mittel, um die Anwendung neu zu starten.

  • Geben Sie eine enge Schleife ein (oder deaktivieren Sie Interrupts und führen Sie eine Halt-Anweisung aus), und sperren Sie den Prozessor für immer.

Was angemessen ist, hängt von der Anwendung ab. Eine Fur-Elise-Grußkarte und ein Bremssteuersystem (um nur zwei eingebettete Systeme zu nennen) sollten wahrscheinlich neu starten. Der Nachteil beim Neustart ist, dass das Problem möglicherweise unbemerkt bleibt.

Wouter van Ooijen
quelle
5

Ich habe mir neulich einen zerlegten ATtiny45-Code (C ++, kompiliert von avr-gcc) angesehen, der am Ende des Codes auf 0x0000 springt. Grundsätzlich einen Reset / Neustart durchführen.

Wenn dieser letzte Sprung zu 0x0000 vom Compiler / Assembler ausgelassen wird, werden alle Bytes im Programmspeicher als 'gültiger' Maschinencode interpretiert und laufen den ganzen Weg, bis der Programmzähler auf 0x0000 wechselt.

Bei AVR ist ein 00-Byte (Standardwert, wenn eine Zelle leer ist) ein NOP = No Operation. Es läuft also sehr schnell und braucht nur etwas Zeit.

jippie
quelle
1

Im Allgemeinen wird der kompilierte mainCode anschließend mit dem Startcode verknüpft (er kann in die vom Chiphersteller bereitgestellte, von Ihnen geschriebene Toolchain integriert werden usw.).

Der Linker platziert dann den gesamten Anwendungs- und Startcode in Speichersegmenten. Die Beantwortung der Fragen hängt daher von folgenden Faktoren ab: 1. Code vom Start, da dies beispielsweise Folgendes kann:

  • ende mit leerer Schleife ( bl lroder b .), die "Programmende" ähnelt, aber zuvor aktivierte Interrupts und Peripheriegeräte funktionieren weiterhin,
  • Beenden Sie mit einem Sprung zum Programmanfang (entweder starten Sie das Programm neu oder jsut to main).
  • ignoriere einfach "was als nächstes kommt" nach dem anruf an main return.

    1. Im dritten Punkt, wenn der Programmzähler nach der Rückkehr von einfach inkrementiert wird main Verhalten , hängt dies vom Linker (und / oder dem Linker-Skript, das während der Verknüpfung verwendet wird) ab.
  • Wenn eine andere Funktion / Code nach Ihrer platziert mainwird, wird sie mit ungültigen / undefinierten Argumentwerten ausgeführt.

  • Wenn der folgende Speicher mit einer fehlerhaften Befehlsausnahme beginnt, wird möglicherweise eine Ausnahme generiert und die MCU wird schließlich zurückgesetzt (wenn die Ausnahme einen Reset generiert).

Wenn Watchdog aktiviert ist, wird die MCU trotz aller Endlosschleifen, in denen Sie sich befinden, zurückgesetzt (natürlich, wenn sie nicht erneut geladen wird).

kwesolowski
quelle
-1

Der beste Weg, ein eingebettetes Gerät zu stoppen, besteht darin, mit NOP-Anweisungen für immer zu warten.

Die zweite Möglichkeit besteht darin, das Gerät zu schließen, indem Sie das Gerät selbst verwenden. Wenn Sie ein Relais mit Ihren Anweisungen steuern können, müssen Sie nur den Schalter öffnen, der Ihr Embedded-Gerät mit Strom versorgt, und schon ist Ihr Embedded-Gerät stromlos .

Enes Unal
quelle
Das beantwortet die Frage wirklich nicht.
Matt Young
-4

Es wurde im Handbuch klar erklärt. Normalerweise wird von der CPU eine allgemeine Ausnahme ausgelöst, da sie auf einen Speicherort zugreift, der sich außerhalb des Stapelsegments befindet. [Speicherschutz Ausnahme].

Was hast du mit dem eingebetteten System gemeint? Mikroprozessor oder Mikrocontroller? Wie auch immer, es ist im Handbuch definiert.

In der x86-CPU schalten wir den Computer aus, indem wir den Befehl an den ACIP-Controller senden. Aufrufen des Systemverwaltungsmodus. Dieser Controller ist also ein E / A-Chip und muss nicht manuell ausgeschaltet werden.

Weitere Informationen finden Sie in der ACPI-Spezifikation.

Standard Sandun
quelle
3
-1: Der TS hat keine bestimmte CPU erwähnt, nehmen Sie also nicht zu viel an. Verschiedene Systeme behandeln diesen Fall auf sehr unterschiedliche Weise.
Wouter van Ooijen