SP ist das Stapelregister, eine Verknüpfung zur Eingabe von r13. LR ist das Linkregister eine Verknüpfung für r14. Und PC ist der Programmzähler eine Abkürzung für die Eingabe von r15.
Wenn Sie einen Anruf ausführen, der als Verzweigungsverbindungsanweisung bl bezeichnet wird, wird die Rücksprungadresse in r14, dem Verbindungsregister, platziert. Der Programmzähler-PC wird in die Adresse geändert, zu der Sie verzweigen.
Es gibt einige Stapelzeiger in den herkömmlichen ARM-Kernen (die Cortex-m-Serie ist eine Ausnahme), wenn Sie einen Interrupt treffen, z. B. wenn Sie einen anderen Stapel verwenden als wenn Sie im Vordergrund ausgeführt werden, müssen Sie Ihren Code nicht einfach ändern sp oder r13 wie gewohnt hat die Hardware den Wechsel für Sie durchgeführt und verwendet den richtigen, wenn sie die Anweisungen dekodiert.
Der herkömmliche ARM-Befehlssatz (nicht Daumen) gibt Ihnen die Freiheit, den Stapel in einem Wachstum von niedrigeren Adressen zu höheren Adressen oder von hohen Adressen zu niedrigen Adressen zu verwenden. Die Compiler und die meisten Leute setzen den Stapelzeiger hoch und lassen ihn von hohen Adressen auf niedrigere Adressen herabwachsen. Zum Beispiel haben Sie möglicherweise einen RAM von 0x20000000 bis 0x20008000. Sie haben Ihr Linker-Skript so eingestellt, dass Ihr Programm so ausgeführt wird, dass 0x20000000 ausgeführt / verwendet wird, und Ihren Stapelzeiger in Ihrem Startcode auf 0x20008000 gesetzt, zumindest den System- / Benutzer-Stapelzeiger, den Sie aufteilen müssen den Speicher für andere Stapel, wenn Sie sie benötigen / verwenden.
Stapel ist nur Erinnerung. Prozessoren haben normalerweise spezielle Lese- / Schreibanweisungen für den Speicher, die auf dem PC basieren, und einige, die auf dem Stapel basieren. Die mindestens Stapel werden normalerweise als Push and Pop bezeichnet, müssen es aber nicht sein (wie bei den traditionellen Armanweisungen).
Wenn Sie zu http://github.com/lsasim gehen, habe ich einen Lehrprozessor erstellt und ein Assembler-Tutorial erstellt. Irgendwo dort drinnen diskutiere ich über Stapel. Es ist KEIN Arm-Prozessor, aber die Geschichte ist dieselbe, die direkt in das übersetzt werden sollte, was Sie auf dem Arm oder den meisten anderen Prozessoren verstehen möchten.
Angenommen, Sie haben 20 Variablen in Ihrem Programm, aber nur 16 Register minus mindestens drei davon (sp, lr, pc), die für spezielle Zwecke bestimmt sind. Sie müssen einige Ihrer Variablen im RAM behalten. Nehmen wir an, r5 enthält eine Variable, die Sie häufig genug verwenden, um sie nicht im RAM zu behalten, aber es gibt einen Codeabschnitt, in dem Sie wirklich ein anderes Register benötigen, um etwas zu tun, und r5 wird nicht verwendet. Sie können r5 speichern den Stapel mit minimalem Aufwand, während Sie r5 für etwas anderes wiederverwenden und später einfach wiederherstellen.
Traditionelle Armsyntax (also nicht bis zum Anfang):
...
stmdb r13!,{r5}
...temporarily use r5 for something else...
ldmia r13!,{r5}
...
stm ist mehrere speichern Sie können mehr als ein Register gleichzeitig speichern, bis zu allen in einer Anweisung.
db bedeutet Dekrementieren vor, dies ist ein sich nach unten bewegender Stapel von hohen Adressen zu niedrigeren Adressen.
Sie können hier r13 oder sp verwenden, um den Stapelzeiger anzugeben. Diese spezielle Anweisung ist nicht auf Stapeloperationen beschränkt, sondern kann für andere Zwecke verwendet werden.
Das ! bedeutet, dass das r13-Register nach Abschluss mit der neuen Adresse aktualisiert wird. Auch hier kann stm für Nicht-Stack-Operationen verwendet werden, sodass Sie das Basisadressregister möglicherweise nicht ändern möchten. in diesem Fall aus.
Dann listen Sie in den Klammern {} die Register auf, die Sie speichern möchten, durch Komma getrennt.
ldmia ist das Gegenteil, ldm bedeutet mehrere laden. ia bedeutet Inkrement nach und der Rest ist der gleiche wie stm
Wenn sich Ihr Stapelzeiger also auf 0x20008000 befindet, wenn Sie den Befehl stmdb drücken, da ein 32-Bit-Register in der Liste vorhanden ist, wird er dekrementiert, bevor er den Wert in r13 verwendet. 0x20007FFC schreibt dann r5 in 0x20007FFC und speichert den Wert 0x20007FFC in r13. Vorausgesetzt, Sie haben keine Fehler, wenn Sie zum ldmia-Befehl r13 gelangen, enthält r13 0x20007FFC. In der Liste r5 befindet sich ein einzelnes Register. Es liest also den Speicher bei 0x20007FFC und setzt diesen Wert in r5. Dies bedeutet u. A. Inkrement nach 0x20007FFC erhöht eine Registergröße auf 0x20008000 und das! bedeutet, schreiben Sie diese Nummer in r13, um die Anweisung zu vervollständigen.
Warum sollten Sie den Stapel anstelle eines festen Speicherorts verwenden? Das Schöne daran ist, dass r13 überall sein kann, wo es 0x20007654 sein kann, wenn Sie diesen Code oder 0x20002000 oder was auch immer ausführen, und der Code immer noch funktioniert, noch besser, wenn Sie diesen Code in einer Schleife verwenden oder mit Rekursion funktioniert und für jede Ebene Wenn Sie eine Rekursion durchführen, speichern Sie eine neue Kopie von r5. Je nachdem, wo Sie sich in dieser Schleife befinden, werden möglicherweise 30 Kopien gespeichert. Beim Abrollen werden alle Kopien wie gewünscht zurückgesetzt. mit einem einzigen festen Speicherort, der nicht funktioniert. Dies wird als Beispiel direkt in C-Code übersetzt:
void myfun ( void )
{
int somedata;
}
In einem solchen C-Programm befindet sich die Variable somedata auf dem Stapel. Wenn Sie myfun rekursiv aufrufen, haben Sie mehrere Kopien des Werts für somedata, je nachdem, wie tief die Rekursion ist. Da diese Variable nur innerhalb der Funktion verwendet wird und an keiner anderen Stelle benötigt wird, möchten Sie möglicherweise keine Menge Systemspeicher für diese Variable für die Lebensdauer des Programms brennen. Sie möchten nur diese Bytes in dieser Funktion und geben diesen Speicher frei, wenn nicht in dieser Funktion. Dafür wird ein Stack verwendet.
Eine globale Variable wurde nicht auf dem Stapel gefunden.
Zurück gehen...
Angenommen, Sie wollten diese Funktion implementieren und aufrufen. Sie haben Code / Funktion, in der Sie sich befinden, wenn Sie die Funktion myfun aufrufen. Die myfun-Funktion möchte r5 und r6 verwenden, wenn sie mit etwas arbeitet, aber sie möchte nicht den Papierkorb löschen, für den jemand r5 und r6 verwendet hat. Für die Dauer von myfun () möchten Sie diese Register auf dem Stapel speichern. Wenn Sie sich den Verzweigungsverbindungsbefehl (bl) und das Verbindungsregister lr (r14) ansehen, gibt es nur ein Verbindungsregister. Wenn Sie eine Funktion von einer Funktion aus aufrufen, müssen Sie das Verbindungsregister bei jedem Aufruf speichern, da Sie sonst nicht zurückkehren können .
...
bl myfun
<--- the return from my fun returns here
...
myfun:
stmdb sp!,{r5,r6,lr}
sub sp,#4 <--- make room for the somedata variable
...
some code here that uses r5 and r6
bl more_fun <-- this modifies lr, if we didnt save lr we wouldnt be able to return from myfun
<---- more_fun() returns here
...
add sp,#4 <-- take back the stack memory we allocated for the somedata variable
ldmia sp!,{r5,r6,lr}
mov pc,lr <---- return to whomever called myfun.
Hoffentlich können Sie sowohl die Stapelverwendung als auch das Linkregister sehen. Andere Prozessoren machen die gleichen Dinge auf andere Weise. Einige legen beispielsweise den Rückgabewert auf den Stapel, und wenn Sie die Rückgabefunktion ausführen, wissen sie, wohin sie zurückkehren müssen, indem sie einen Wert vom Stapel abziehen. Compiler C / C ++ usw. haben normalerweise eine "Aufrufkonvention" oder eine Anwendungsschnittstelle (ABI und EABI sind Namen für diejenigen, die ARM definiert hat). Wenn jede Funktion der aufrufenden Konvention folgt, werden die übergebenen Parameter an Funktionen übergeben, die gemäß der Konvention in den richtigen Registern oder auf dem Stapel aufgerufen werden. Und jede Funktion folgt den Regeln, in welchen Registern sie den Inhalt nicht aufbewahren muss und in welchen Registern sie den Inhalt aufbewahren muss. Dann können Funktionen Funktionen aufrufen, Funktionen aufrufen und Rekursionen und alle möglichen Dinge ausführen, solange Der Stack geht nicht so tief, dass er in den Speicher läuft, der für Globals und den Heap verwendet wird. Sie können also Funktionen aufrufen und den ganzen Tag von ihnen zurückkehren. Die obige Implementierung von myfun ist sehr ähnlich zu dem, was ein Compiler produzieren würde.
ARM hat jetzt viele Kerne und ein paar Befehlssätze. Die cortex-m-Serie funktioniert etwas anders, da sie nicht über eine Reihe von Modi und verschiedene Stapelzeiger verfügt. Und wenn Sie Daumenanweisungen im Daumenmodus ausführen, verwenden Sie die Push- und Pop-Anweisungen, die Ihnen nicht die Freiheit geben, ein Register wie stm zu verwenden. Es wird nur r13 (sp) verwendet, und Sie können nicht alle Register nur für eine bestimmte Teilmenge von Registern speichern. Mit den beliebten Arm-Assemblern können Sie verwenden
push {r5,r6}
...
pop {r5,r6}
im Armcode sowie im Daumencode. Für den Arm-Code werden die richtigen stmdb und ldmia codiert. (Im Daumenmodus haben Sie auch keine Wahl, wann und wo Sie db verwenden, dekrementieren vor und ia, inkrementieren nach).
Nein, Sie müssen absolut nicht die gleichen Register verwenden und Sie müssen nicht die gleiche Anzahl von Registern koppeln.
push {r5,r6,r7}
...
pop {r2,r3}
...
pop {r1}
Angenommen, es gibt keine weiteren Änderungen des Stapelzeigers zwischen diesen Anweisungen, wenn Sie sich daran erinnern, dass der sp für den Push um 12 Byte dekrementiert wird. Sagen wir, von 0x1000 auf 0x0FF4, r5 wird auf 0xFF4, r6 auf 0xFF8 und r7 auf 0xFFC des Stapels geschrieben Der Zeiger wechselt zu 0x0FF4. Der erste Pop nimmt den Wert bei 0x0FF4 und setzt diesen in r2, dann den Wert bei 0x0FF8 und setzt ihn in r3. Der Stapelzeiger erhält den Wert 0x0FFC. später beim letzten Pop ist der sp 0x0FFC, der gelesen wird, und der Wert in r1, der Stapelzeiger erhält dann den Wert 0x1000, wo er begonnen hat.
Das ARM ARM, ARM Architectural Reference Manual (infocenter.arm.com, Referenzhandbücher, finden Sie das für ARMv5 und laden Sie es herunter, dies ist das traditionelle ARM ARM mit ARM- und Daumenanweisungen) enthält Pseudocode für die ldm- und stm ARM-Anweisungen für das komplette Bild, wie diese verwendet werden. Ebenso gut handelt das ganze Buch vom Arm und wie man ihn programmiert. Vorne führt Sie das Modellkapitel des Programmierers durch alle Register in allen Modi usw.
Wenn Sie einen ARM-Prozessor programmieren, sollten Sie zunächst genau bestimmen, welchen Kern Sie haben (der Chiphersteller sollte Ihnen mitteilen, dass ARM keine Chips herstellt, sondern Kerne, die Chiphersteller in ihre Chips einsetzen). Gehen Sie dann zur Arm-Website und suchen Sie den ARM-ARM für diese Familie und den TRM (technisches Referenzhandbuch) für den spezifischen Kern einschließlich der Revision, falls der Hersteller dies angegeben hat (r2p0 bedeutet Revision 2.0 (zwei Punkte Null, 2p0)) Wenn es eine neuere Version gibt, verwenden Sie das Handbuch, das zu dem Handbuch gehört, das der Hersteller für sein Design verwendet hat. Nicht jeder Kern unterstützt jeden Befehl oder Modus. Der TRM teilt Ihnen die unterstützten Modi und Befehle mit. Der ARM ARM wirft eine Decke über die Funktionen für die gesamte Prozessorfamilie, in der dieser Kern lebt. Beachten Sie, dass der ARM7TDMI ein ARMv4 ist, NICHT ein ARMv7, ebenso der ARM9 ist kein ARMv9. ARMvNUMBER ist der Familienname ARM7, ARM11 ohne av ist der Kernname. Die neueren Kerne haben Namen wie Cortex und mpcore anstelle der ARMNUMBER-Sache, was die Verwirrung verringert. Natürlich mussten sie die Verwirrung noch verstärken, indem sie ein ARMv7-m (cortex-MNUMBER) und ein ARMv7-a (Cortex-ANUMBER) herstellten, die sehr unterschiedliche Familien sind, eine für schwere Lasten, Desktops, Laptops usw., die andere für Mikrocontroller, Uhren und blinkende Lichter an einer Kaffeemaschine und ähnliches. Google Beagleboard (Cortex-A) und das stm32 Value Line Discovery Board (Cortex-M), um ein Gefühl für die Unterschiede zu bekommen. Oder sogar das open-rd.org-Board, das mehrere Kerne mit mehr als einem Gigahertz verwendet, oder das neuere Tegra 2 von NVIDIA, der gleiche Deal, Super Scaler, Muti Core, Multi Gigahertz.
Entschuldigung für den sehr langen Beitrag, hoffe es ist nützlich.