Es hängt wirklich vom System ab, aber moderne Betriebssysteme mit virtuellem Speicher neigen dazu, ihre Prozessabbilder zu laden und Speicher wie folgt zuzuweisen:
+---------+
| stack | function-local variables, return addresses, return values, etc.
| | often grows downward, commonly accessed via "push" and "pop" (but can be
| | accessed randomly, as well; disassemble a program to see)
+---------+
| shared | mapped shared libraries (C libraries, math libs, etc.)
| libs |
+---------+
| hole | unused memory allocated between the heap and stack "chunks", spans the
| | difference between your max and min memory, minus the other totals
+---------+
| heap | dynamic, random-access storage, allocated with 'malloc' and the like.
+---------+
| bss | Uninitialized global variables; must be in read-write memory area
+---------+
| data | data segment, for globals and static variables that are initialized
| | (can further be split up into read-only and read-write areas, with
| | read-only areas being stored elsewhere in ROM on some systems)
+---------+
| text | program code, this is the actual executable code that is running.
+---------+
Dies ist der allgemeine Prozessadressraum auf vielen gängigen virtuellen Speichersystemen. Das "Loch" ist die Größe Ihres gesamten Speichers abzüglich des von allen anderen Bereichen belegten Speicherplatzes. Dies gibt dem Haufen viel Platz, in den er hineinwachsen kann. Dies ist auch "virtuell", dh es wird über eine Übersetzungstabelle Ihrem tatsächlichen Speicher zugeordnet und kann tatsächlich an einer beliebigen Stelle im tatsächlichen Speicher gespeichert werden. Auf diese Weise wird ein Prozess vor dem Zugriff auf den Speicher eines anderen Prozesses geschützt und jeder Prozess wird davon ausgegangen, dass er auf einem vollständigen System ausgeführt wird.
Beachten Sie, dass die Positionen von z. B. Stapel und Heap auf einigen Systemen möglicherweise in einer anderen Reihenfolge liegen ( weitere Informationen zu Win32 finden Sie in der Antwort von Billy O'Neal unten).
Andere Systeme können sehr unterschiedlich sein. DOS wurde beispielsweise im Real-Modus ausgeführt , und die Speicherzuweisung beim Ausführen von Programmen sah ganz anders aus:
+-----------+ top of memory
| extended | above the high memory area, and up to your total memory; needed drivers to
| | be able to access it.
+-----------+ 0x110000
| high | just over 1MB->1MB+64KB, used by 286s and above.
+-----------+ 0x100000
| upper | upper memory area, from 640kb->1MB, had mapped memory for video devices, the
| | DOS "transient" area, etc. some was often free, and could be used for drivers
+-----------+ 0xA0000
| USER PROC | user process address space, from the end of DOS up to 640KB
+-----------+
|command.com| DOS command interpreter
+-----------+
| DOS | DOS permanent area, kept as small as possible, provided routines for display,
| kernel | *basic* hardware access, etc.
+-----------+ 0x600
| BIOS data | BIOS data area, contained simple hardware descriptions, etc.
+-----------+ 0x400
| interrupt | the interrupt vector table, starting from 0 and going to 1k, contained
| vector | the addresses of routines called when interrupts occurred. e.g.
| table | interrupt 0x21 checked the address at 0x21*4 and far-jumped to that
| | location to service the interrupt.
+-----------+ 0x0
Sie können sehen, dass DOS den direkten Zugriff auf den Betriebssystemspeicher ohne Schutz ermöglichte, was bedeutete, dass User-Space-Programme im Allgemeinen direkt auf alles zugreifen oder es überschreiben konnten, was ihnen gefiel.
Im Prozessadressraum sahen die Programme jedoch in der Regel ähnlich aus, nur wurden sie als Codesegment, Datensegment, Heap, Stapelsegment usw. beschrieben und etwas anders zugeordnet. Aber die meisten allgemeinen Bereiche waren noch da.
Nachdem das Programm und die erforderlichen gemeinsam genutzten Bibliotheken in den Speicher geladen und die Teile des Programms in die richtigen Bereiche verteilt wurden, beginnt das Betriebssystem mit der Ausführung Ihres Prozesses, wo immer sich seine Hauptmethode befindet, und Ihr Programm übernimmt von dort aus und führt bei Bedarf Systemaufrufe durch es braucht sie.
Verschiedene Systeme (eingebettet, was auch immer) können sehr unterschiedliche Architekturen aufweisen, wie z. B. stapellose Systeme, Harvard-Architektursysteme (wobei Code und Daten in einem separaten physischen Speicher gespeichert werden), Systeme, die das BSS tatsächlich im Nur-Lese-Speicher halten (ursprünglich von der Programmierer) usw. Aber das ist der allgemeine Kern.
Du sagtest:
Ich weiß auch, dass ein Computerprogramm zwei Arten von Speicher verwendet: Stapel und Heap, die ebenfalls Teil des Primärspeichers des Computers sind.
"Stapel" und "Haufen" sind nur abstrakte Konzepte und keine (notwendigerweise) physikalisch unterschiedlichen "Arten" von Erinnerungen.
Ein Stack ist lediglich eine Last-In-First-Out-Datenstruktur. In der x86-Architektur kann es tatsächlich zufällig adressiert werden, indem ein Versatz vom Ende verwendet wird. Die häufigsten Funktionen sind jedoch PUSH und POP zum Hinzufügen bzw. Entfernen von Elementen. Es wird häufig für funktionslokale Variablen (sogenannte "automatische Speicherung"), Funktionsargumente, Rücksprungadressen usw. verwendet (mehr unten).
Ein "Heap" ist nur ein Spitzname für einen Speicherblock, der bei Bedarf zugewiesen werden kann und zufällig adressiert wird (dh Sie können direkt auf einen beliebigen Speicherort zugreifen). Es wird häufig für Datenstrukturen verwendet, die Sie zur Laufzeit zuweisen (in C ++, Verwenden von new
und delete
und malloc
und Freunde in C usw.).
Der Stapel und der Heap in der x86-Architektur befinden sich beide physisch in Ihrem Systemspeicher (RAM) und werden wie oben beschrieben durch Zuweisung des virtuellen Speichers in den Prozessadressraum abgebildet.
Die Register (immer noch auf x86) befinden sich physisch im Prozessor (im Gegensatz zum RAM) und werden vom Prozessor aus dem TEXT-Bereich geladen (und können abhängig von den CPU-Anweisungen auch von einer anderen Stelle im Speicher oder an anderen Stellen geladen werden) tatsächlich ausgeführt werden). Es handelt sich im Wesentlichen nur um sehr kleine, sehr schnelle Speicherplätze auf dem Chip, die für verschiedene Zwecke verwendet werden.
Das Registerlayout hängt stark von der Architektur ab (tatsächlich sind die Register, der Befehlssatz und das Speicherlayout / -design genau das, was unter "Architektur" zu verstehen ist), und daher werde ich nicht darauf eingehen, sondern Ihnen empfehlen, eine zu verwenden Assembler-Kurs, um sie besser zu verstehen.
Ihre Frage:
Ab wann wird der Stack für die Ausführung der Anweisungen verwendet? Anweisungen gehen vom RAM zum Stapel, zu den Registern?
Der Stapel (in Systemen / Sprachen, die sie haben und verwenden) wird am häufigsten wie folgt verwendet:
int mul( int x, int y ) {
return x * y; // this stores the result of MULtiplying the two variables
// from the stack into the return value address previously
// allocated, then issues a RET, which resets the stack frame
// based on the arg list, and returns to the address set by
// the CALLer.
}
int main() {
int x = 2, y = 3; // these variables are stored on the stack
mul( x, y ); // this pushes y onto the stack, then x, then a return address,
// allocates space on the stack for a return value,
// then issues an assembly CALL instruction.
}
Schreiben Sie ein einfaches Programm wie dieses, kompilieren Sie es dann zu Assembly ( gcc -S foo.c
wenn Sie Zugriff auf GCC haben) und werfen Sie einen Blick darauf. Die Montage ist ziemlich einfach zu verfolgen. Sie können sehen, dass der Stapel für lokale Funktionsvariablen und zum Aufrufen von Funktionen, Speichern ihrer Argumente und Rückgabewerte verwendet wird. Dies ist auch der Grund, warum Sie etwas tun wie:
f( g( h( i ) ) );
All dies wird nacheinander aufgerufen. Es baut buchstäblich einen Stapel von Funktionsaufrufen und deren Argumenten auf, führt sie aus und löscht sie dann, wenn sie sich wieder nach unten (oder oben) drehen. Wie oben erwähnt, befindet sich der Stapel (auf x86) jedoch tatsächlich in Ihrem Prozessspeicher (im virtuellen Speicher) und kann daher direkt bearbeitet werden. Dies ist kein separater Schritt während der Ausführung (oder zumindest orthogonal zum Prozess).
Zu Ihrer Information, das Obige ist die C-Aufrufkonvention , die auch von C ++ verwendet wird. Andere Sprachen / Systeme können Argumente in einer anderen Reihenfolge auf den Stapel übertragen, und einige Sprachen / Plattformen verwenden nicht einmal Stapel und gehen auf unterschiedliche Weise vor.
Beachten Sie auch, dass dies keine tatsächlichen Zeilen der Ausführung von C-Code sind. Der Compiler hat sie in maschinensprachliche Anweisungen in Ihrer ausführbaren Datei konvertiert. Sie werden dann (allgemein) aus dem TEXT-Bereich in die CPU-Pipeline, dann in die CPU-Register kopiert und von dort ausgeführt. [Das war falsch. Siehe Ben Voigts Korrektur unten.]