Wie ist die Richtung des Stapelwachstums in den meisten modernen Systemen?


Antworten:


147

Das Stapelwachstum hängt normalerweise nicht vom Betriebssystem selbst ab, sondern vom Prozessor, auf dem es ausgeführt wird. Solaris läuft beispielsweise unter x86 und SPARC. Mac OSX (wie bereits erwähnt) läuft unter PPC und x86. Linux läuft auf allem, von meinem großen Honkin 'System z bei der Arbeit bis zu einer mickrigen kleinen Armbanduhr .

Wenn die CPU eine Auswahlmöglichkeit bietet, gibt die vom Betriebssystem verwendete ABI / Aufrufkonvention an, welche Auswahl Sie treffen müssen, wenn Ihr Code den Code aller anderen aufrufen soll.

Die Prozessoren und ihre Richtung sind:

  • x86: unten.
  • SPARC: wählbar. Der Standard-ABI verwendet down.
  • PPC: runter, denke ich.
  • System z: In einer verknüpften Liste mache ich dir nichts vor (aber immer noch unten, zumindest für zLinux).
  • ARM: wählbar, aber Thumb2 verfügt nur über kompakte Codierungen für Down (LDMIA = Inkrement nach, STMDB = Dekrement vor).
  • 6502: down (aber nur 256 Bytes).
  • RCA 1802A: beliebig, vorbehaltlich der SCRT-Implementierung.
  • PDP11: unten.
  • 8051: auf.

Der 1802 zeigte mein Alter in den letzten paar Jahren und war der Chip, mit dem die frühen Shuttles gesteuert wurden (ich vermute, dass die Türen geöffnet waren, basierend auf der Rechenleistung, die er hatte :-) und meinem zweiten Computer, dem COMX-35 ( nach meinem ZX80 ).

PDP11-Details von hier , 8051-Details von hier .

Die SPARC-Architektur verwendet ein Schiebefenster-Registermodell. Zu den architektonisch sichtbaren Details gehört auch ein kreisförmiger Puffer von Registerfenstern, die gültig sind und intern zwischengespeichert werden, mit Traps, wenn diese über- oder unterlaufen. Siehe hier für Details. Wie im SPARCv8-Handbuch erläutert , entsprechen die Anweisungen SAVE und RESTORE den Anweisungen ADD plus Register-Fenster-Rotation. Die Verwendung einer positiven Konstante anstelle des üblichen Negativs würde einen nach oben wachsenden Stapel ergeben.

Die oben erwähnte SCRT-Technik ist eine andere - die 1802 verwendete einige oder sechzehn 16-Bit-Register für SCRT (Standard-Call- und Return-Technik). Einer war der Programmzähler, man konnte jedes Register als PC mit der SEP RnAnweisung verwenden. Einer war der Stapelzeiger und zwei waren so eingestellt, dass sie immer auf die SCRT-Code-Adresse zeigten, einer für den Aufruf, einer für die Rückgabe. Kein Register wurde auf besondere Weise behandelt. Beachten Sie, dass diese Details aus dem Speicher stammen und möglicherweise nicht vollständig korrekt sind.

Wenn beispielsweise R3 der PC war, R4 die SCRT-Anrufadresse war, R5 die SCRT-Rücksprungadresse war und R2 der "Stapel" war (Anführungszeichen, wie sie in der Software implementiert sind), SEP R4würde R4 als PC festgelegt und die Ausführung des SCRT gestartet Rufnummer.

Es würde dann R3 auf dem R2 "Stack" speichern (ich denke, R6 wurde für die temporäre Speicherung verwendet), es nach oben oder unten anpassen, die zwei Bytes nach R3 abrufen, sie in R3 laden , dann tun SEP R3und an der neuen Adresse laufen.

Um zurückzukehren, würde SEP R5es die alte Adresse vom R2-Stapel abziehen, zwei hinzufügen (um die Adressbytes des Aufrufs zu überspringen), sie in R3 laden und SEP R3den vorherigen Code ausführen.

Nach all dem stapelbasierten Code 6502/6809 / z80 ist es anfangs sehr schwer, den Kopf herumzureißen, aber dennoch elegant, wenn man den Kopf gegen die Wand schlägt. Eines der meistverkauften Merkmale des Chips war auch eine vollständige Suite von 16 16-Bit-Registern, obwohl Sie sofort 7 davon verloren haben (5 für SCRT, zwei für DMA und Interrupts aus dem Speicher). Ahh, der Triumph des Marketings über die Realität :-)

System z ist tatsächlich ziemlich ähnlich und verwendet seine R14- und R15-Register für Anruf / Rückgabe.


3
Um die Liste zu erweitern, kann ARM in beide Richtungen wachsen, kann jedoch durch eine bestimmte Siliziumimplementierung auf die eine oder andere eingestellt werden (oder kann per Software ausgewählt werden). Die wenigen, mit denen ich mich befasst habe, waren schon immer im Grow-Down-Modus.
Michael Burr

1
In dem kleinen Teil der ARM-Welt, den ich bisher gesehen habe (ARM7TDMI), wird der Stack vollständig in Software gehandhabt. Rücksprungadressen werden in einem Register gespeichert, das bei Bedarf von der Software gespeichert wird, und Anweisungen vor / nach dem Inkrementieren / Dekrementieren ermöglichen es, sie und andere Dinge in beide Richtungen auf den Stapel zu legen.
Starblue

1
Auf dem HPPA ist der Stack aufgewachsen! Ziemlich selten unter einigermaßen modernen Architekturen.
tml

2
Für Neugierige gibt es hier eine gute Ressource zur Funktionsweise des Stacks unter z / OS: www-03.ibm.com/systems/resources/Stack+and+Heap.pdf
Dillon Cower

1
Vielen Dank an @paxdiablo für Ihr Verständnis. Manchmal nehmen die Leute es als persönlichen Affront, wenn Sie einen solchen Kommentar abgeben, besonders wenn es sich um einen älteren handelt. Ich weiß nur, dass es einen Unterschied gibt, weil ich in der Vergangenheit selbst den gleichen Fehler gemacht habe. Pass auf.
CasaDeRobison

23

In C ++ (anpassbar an C) stack.cc :

static int
find_stack_direction ()
{
    static char *addr = 0;
    auto char dummy;
    if (addr == 0)
    {
        addr = &dummy;
        return find_stack_direction ();
    }
    else
    {
        return ((&dummy > addr) ? 1 : -1);
    }
}

14
Wow, es ist lange her, dass ich das Schlüsselwort "auto" gesehen habe.
Paxdiablo

9
(& dummy> addr) ist undefiniert. Das Ergebnis der Eingabe von zwei Zeigern an einen Vergleichsoperator wird nur definiert, wenn die beiden Zeiger innerhalb desselben Arrays oder derselben Struktur zeigen.
Sigjuice

2
Der Versuch, das Layout Ihres eigenen Stacks zu untersuchen - etwas, das C / C ++ überhaupt nicht spezifiziert - ist zunächst "nicht portierbar", daher würde mich das nicht wirklich interessieren. Es sieht jedoch so aus, als würde diese Funktion nur einmal richtig funktionieren.
Ephemient

9
Es ist nicht nötig, ein staticdafür zu verwenden. Stattdessen können Sie die Adresse als Argument an einen rekursiven Aufruf übergeben.
R .. GitHub STOP HELPING ICE

5
plus, durch eine Verwendung static, wenn Sie dies mehr als einmal aufrufen, können die nachfolgenden Anrufe scheitern ...
Chris Dodd

7

Der Vorteil des Herabwachsens besteht darin, dass sich der Stapel in älteren Systemen normalerweise oben im Speicher befand. Programme füllten normalerweise den Speicher von unten, so dass diese Art der Speicherverwaltung die Notwendigkeit minimierte, den unteren Rand des Stapels an einer sinnvollen Stelle zu messen und zu platzieren.


3
Kein "Vorteil", wirklich eine Tautologie.
Marquis von Lorne

1
Keine Tautologie. Der Punkt ist, dass zwei wachsende Speicherbereiche nicht stören (es sei denn, der Speicher ist ohnehin voll), wie @valenok hervorhob.
YvesgereY

6

Der Stapel wächst auf x86 (definiert durch die Architektur, Pop-Inkrement-Stapelzeiger, Push-Dekremente).


5

In MIPS und vielen modernen RISC - Architekturen (wie PowerPC RISC-V, SPARC ...) gibt es keine pushund popAnweisungen. Diese Operationen werden explizit ausgeführt, indem der Stapelzeiger manuell angepasst und dann der Wert relativ zum angepassten Zeiger geladen / gespeichert wird. Alle Register (mit Ausnahme des Nullregisters) sind universell einsetzbar, so dass theoretisch jedes Register ein Stapelzeiger sein kann und der Stapel in jede vom Programmierer gewünschte Richtung wachsen kann

Das heißt, der Stapel wächst normalerweise auf den meisten Architekturen nach unten, wahrscheinlich um den Fall zu vermeiden, dass die Stapel- und Programmdaten oder Heap-Daten wachsen und miteinander kollidieren. Es gibt auch die großen Adressierungsgründe, die in der Antwort von sh-s erwähnt wurden . Einige Beispiele: MIPS-ABIs wachsen nach unten und verwenden $29(AKA $sp) als Stapelzeiger, RISC-V ABI wächst ebenfalls nach unten und verwendet x2 als Stapelzeiger

In Intel 8051 wächst der Stapel, wahrscheinlich weil der Speicherplatz so klein ist (128 Byte in der Originalversion), dass kein Heap vorhanden ist und Sie den Stapel nicht darauf legen müssen, damit er vom wachsenden Heap getrennt wird vom Boden

Weitere Informationen zur Stapelverwendung in verschiedenen Architekturen finden Sie unter https://en.wikipedia.org/wiki/Calling_convention

Siehe auch


2

Nur eine kleine Ergänzung zu den anderen Antworten, die, soweit ich sehen kann, diesen Punkt nicht berührt haben:

Wenn der Stapel nach unten wächst, haben alle Adressen innerhalb des Stapels einen positiven Versatz relativ zum Stapelzeiger. Negative Offsets sind nicht erforderlich, da sie nur auf nicht genutzten Stapelspeicherplatz verweisen. Dies vereinfacht den Zugriff auf Stapelpositionen, wenn der Prozessor die Stapelzeiger-relative Adressierung unterstützt.

Viele Prozessoren verfügen über Anweisungen, die Zugriffe mit einem Nur-Positiv-Offset relativ zu einem Register ermöglichen. Dazu gehören viele moderne Architekturen sowie einige alte. Beispielsweise bietet der ARM Thumb ABI stapelzeigerbezogene Zugriffe mit einem positiven Offset, der in einem einzelnen 16-Bit-Befehlswort codiert ist.

Wenn der Stapel nach oben wachsen würde, wären alle nützlichen Offsets relativ zum Stapelzeiger negativ, was weniger intuitiv und weniger bequem ist. Es steht auch im Widerspruch zu anderen Anwendungen der registerbezogenen Adressierung, beispielsweise für den Zugriff auf Felder einer Struktur.


2

Auf den meisten Systemen wächst der Stapel ab, und mein Artikel unter https://gist.github.com/cpq/8598782 erklärt, warum er abfällt. Es ist ganz einfach: Wie können zwei wachsende Speicherblöcke (Heap und Stack) in einem festen Speicherblock angeordnet werden? Die beste Lösung ist, sie an die entgegengesetzten Enden zu legen und aufeinander zu wachsen zu lassen.


dieser Kern scheint jetzt tot zu sein :(
Ven

@Ven - Ich kann es schaffen
Brett Holman

1

Es wächst nach unten, weil der dem Programm zugewiesene Speicher die "permanenten Daten" enthält, dh den Code für das Programm selbst unten und dann den Heap in der Mitte. Sie benötigen einen weiteren festen Punkt, von dem aus Sie auf den Stapel verweisen können, sodass Sie oben bleiben. Dies bedeutet, dass der Stapel nach unten wächst, bis er möglicherweise an Objekte auf dem Heap angrenzt.


0

Dieses Makro sollte es zur Laufzeit ohne UB erkennen:

#define stk_grows_up_eh() stk_grows_up__(&(char){0})
_Bool stk_grows_up__(char *ParentsLocal);

__attribute((__noinline__))
_Bool stk_grows_up__(char *ParentsLocal) { 
    return (uintptr_t)ParentsLocal < (uintptr_t)&ParentsLocal;
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.