- Lernen
- Informatik & Informationswissenschaften
- Informatik
- Betriebssysteme – Prozesse, Speicher, Threads und Dateisysteme
- Adressräume und Adressübersetzung
Adressräume und Adressübersetzung
malloc und free bis zu den Systemaufrufen, die dahinterstehen. Übersetze Adressen Schritt für Schritt mit Basis- und Grenzregistern sowie durch Segmentierung. Erfahre außerdem, wie Freispeicherverwaltungen Anforderungen unterschiedlicher Größe platzieren, vom First-Fit-Verfahren bis zum Buddy-Allokator, und dabei interne und externe Fragmentierung begrenzen.MittelEinheit 3 von 9 in Kurs: Betriebssysteme – Prozesse, Speicher, Threads und Dateisysteme
Was du lernst
- Beschreibe den Adressraum, den ein laufendes Programm sieht, warum das Betriebssystem Speicher virtualisiert und welche Ziele jedes Verfahren zur Speichervirtualisierung erfüllen muss.
- Beschreibe einen Adressraum und seinen AufbauErkläre, was ein Adressraum ist, wie Code, Heap und Stack darin angeordnet sind und worin sich virtuelle und physische Adressen unterscheiden.
- Bewerte die Speichervirtualisierung anhand ihrer ZieleBewerte ein Verfahren zur Speichervirtualisierung anhand von Transparenz, Effizienz und Schutz und unterscheide den Hardwaremechanismus von den Strategien des Betriebssystems.
- Beschreibe einen Adressraum und seinen Aufbau
- Unterscheide Stack- und Heap-Speicher, fordere Heap-Speicher mit den Standardaufrufen an und gib ihn wieder frei, erkenne mögliche Fehler und erkläre, woher der Speicher stammt.
- Reserviere Speicher mit der Speicherverwaltungsschnittstelle und gib ihn wieder freiUnterscheide automatischen Stack-Speicher von Heap-Speicher, verwende malloc und free im Grundsatz korrekt und erkenne die wichtigsten Arten von Speicherfehlern.
- Erkläre, wie der Allokator Speicher vom Betriebssystem erhältUnterscheide die Aufgabe des Betriebssystems, den Adressraum eines Prozesses zu verwalten, von der Aufgabe der Bibliothek, den Heap zu verwalten, und beschreibe die Systemaufrufe zum Vergrößern und Verkleinern des Speichers.
- Reserviere Speicher mit der Speicherverwaltungsschnittstelle und gib ihn wieder frei
- Erkläre, wie hardwaregestützte dynamische Relokation jede virtuelle Adresse in eine physische umwandelt, was Hardware und Betriebssystem jeweils beitragen und wo die Grenzen des Verfahrens liegen.
- Übersetze virtuelle Adressen mit Basis- und GrenzregisternWende dynamische Relokation an, indem du zu jeder virtuellen Adresse die Basis addierst und sie anhand der Grenze prüfst, und vergleiche sie mit statischer Relokation.
- Teile die Aufgaben zwischen Hardware und Betriebssystem aufZähle auf, was die Hardware bereitstellen und was das Betriebssystem bei Prozesserzeugung, Kontextwechseln, Ausnahmen und Beendigung tun muss, damit das Verfahren mit Basis- und Grenzregister funktioniert.
- Bewerte das Verfahren mit Basis- und GrenzregisterWäge Geschwindigkeit und Schutz des Verfahrens mit Basis- und Grenzregister gegen den verschwendeten Speicher ab und erkläre, warum es zur Segmentierung führt.
- Übersetze virtuelle Adressen mit Basis- und Grenzregistern
- Gib jedem logischen Segment eines Adressraums ein eigenes Basis- und Grenzregister, übersetze und prüfe Adressen mit Segmentregistern und beurteile, was die Segmentierung vom Betriebssystem verlangt.
- Übersetze Adressen für jedes Segment getrenntPlatziere Code, Heap und Stack unabhängig voneinander im physischen Speicher und übersetze eine Adresse, indem du ihren Offset innerhalb des Segments berechnest.
- Bestimme, auf welches Segment eine Adresse verweistBestimme das Segment anhand der obersten Bits einer virtuellen Adresse oder anhand der Art, wie die Adresse gebildet wurde, und ermittle den Offset binär.
- Berücksichtige Stack, Speicherschutz und gemeinsame SpeichernutzungÜbersetze Stackadressen mit einem negativen Offset, verwende Schutzbits pro Segment und erkläre, wie Prozesse Code sicher gemeinsam nutzen.
- Erkläre die Unterstützung der Segmentierung und bewerte das VerfahrenBeschreibe, was das Betriebssystem für die Segmentierung tun muss, vergleiche grob- und feingranulare Segmentierung und erkläre die externe Fragmentierung, die das Verfahren begrenzt.
- Übersetze Adressen für jedes Segment getrennt
- Erkläre, wie eine Freispeicherverwaltung freien Speicher erfasst und Anforderungen unterschiedlicher Größe bedient, vergleiche Platzierungsstrategien und beurteile interne und externe Fragmentierung.
- Unterscheide interne und externe FragmentierungDefiniere die beiden Arten der Fragmentierung, erkenne sie in Beispielen und erkläre, warum ein Heap-Allokator den Speicher nicht kompaktieren kann.
- Erfasse freien Speicher mit einer FreispeicherlisteTeile freie Blöcke auf und verschmelze sie, nutze Verwaltungsköpfe zur Freigabe ohne Größenangabe und lege die Freispeicherliste im freien Speicher selbst an.
- Vergleiche PlatzierungsstrategienWende Best Fit, Worst Fit, First Fit und Next Fit auf eine Freispeicherliste an und wäge ihre Geschwindigkeit gegen die verursachte Fragmentierung ab.
- Nutze nach Größenklassen getrennte Freispeicherlisten sowie Slab- und Buddy-AllokatorenErkläre, wie größenspezifische Listen, Objektcaches und Buddy-Blöcke mit Zweierpotenzgrößen die Speicherzuweisung beschleunigen, und berechne Verschwendung und Verschmelzung beim Buddy-Allokator.
- Unterscheide interne und externe Fragmentierung
Adressräume und Adressübersetzung einfach erklärt
1Eine private Speichersicht für jeden Prozess
Ein Zeiger gehört zu dem Prozess, der ihn verwendet. Sein Zahlenwert allein bezeichnet keine physische Speicherzelle. Adressräume ermöglichen es, mehrere Programme im Speicher zu halten, während jedes mit seiner eigenen Sicht auf Code und Daten läuft. Betriebssystem und Prozessor müssen diese Sicht erhalten, Zugriffe schnell übersetzen und verhindern, dass ein Programm ein anderes beschädigt. Diese Ziele erklären sowohl den Aufbau, den ein Programm sieht, als auch die dahinter verborgene Technik.
1.1 Der Speicher aus Sicht eines Prozesses
Ein laufendes Programm arbeitet mit einem Adressraum: seiner eigenen Sicht auf den Speicher mit nummerierten Bytepositionen. Das Betriebssystem platziert den Speicher des Programms im Rechner und sorgt dafür, dass seine Adressen die richtigen physischen Bytes erreichen. Der Aufbau aus Sicht des Programms und die Anordnung in der Hardware sind verschiedene Dinge.
Warum mehrere Programme im Speicher bleiben
Frühe Systeme konnten neben dem Betriebssystem ein Programm halten. Das Betriebssystem konnte beispielsweise die ersten 64 KB und das Programm den übrigen Speicher belegen. Der Mehrprogrammbetrieb hielt mehr als einen Prozess bereit, sodass die CPU arbeiten konnte, während ein anderer Prozess auf eine Ein- oder Ausgabe wartete. Das Zeitteilverfahren wechselte die CPU häufig genug zwischen Prozessen, um interaktive Benutzer zu bedienen.
Den gesamten Speicher eines Prozesses bei jedem Wechsel auf die Festplatte zu schreiben, würde den Wechsel viel zu langsam machen. Mehrere Prozesse im physischen Speicher zu halten, vermeidet dieses Kopieren, schafft aber ein Schutzproblem. Eine von einem Prozess erzeugte Adresse darf es ihm nicht erlauben, das Betriebssystem oder einen benachbarten Prozess zu überschreiben.
Speichergrößen folgen hier der in diesen Modellen verwendeten binären Konvention: 1 KB sind 1.024 Bytes, auch als 1 KiB geschrieben; 1 MB sind 1.024 KB. Ein 16 KB großer Adressraum hat 16.384 Bytepositionen, nummeriert von 0 bis 16.383. Die Zahl 16.384 ist die Endgrenze, keine gültige Byteadresse.
Code, Daten, Heap und Stack
| Bereich | Inhalt | Verwendung |
|---|---|---|
| Code | Maschinenbefehle | Die CPU ruft hier Befehle ab. |
| Statische Daten | Globale und statische lokale Variablen | Der Speicher bleibt während der Programmausführung bestehen. |
| Heap | Zur Laufzeit angeforderte Blöcke | Das Programm fordert Blöcke an und gibt sie frei. |
| Stack | Zustand von Funktionsaufrufen und gewöhnliche lokale Variablen | Aufrufe erzeugen Stackframes; bei der Rückkehr werden sie freigegeben. |
In einer klassischen Lehrbuchdarstellung mit 16 KB belegt der Code die niedrigen Adressen von 0 bis zur 1-KB-Grenze. Der Heap beginnt oberhalb des Codes und wächst nach oben. Der Stack beginnt an der oberen Grenze und wächst nach unten. Die freie Lücke dazwischen gibt beiden Bereichen Platz zum Wachsen. Reale Anordnungen unterscheiden sich; beispielsweise hat jeder Thread seinen eigenen Stack. Die Aufgaben der Bereiche bleiben auch dann hilfreich, wenn sich ihre Positionen ändern.
Verfolge einen Zeiger zum richtigen Speicher
Eine virtuelle Adresse bezeichnet ein Byte im Adressraum eines Prozesses. Eine physische Adresse bezeichnet ein Byte in der Speicherhardware des Rechners. Wenn C-Code printf("%p", (void *) &x) ausführt, gibt er eine virtuelle Adresse aus. Der ausgegebene Zeiger verrät nicht, welches physische Byte x speichert.
Angenommen, zwei Prozesse verwenden beide die virtuelle Adresse 1.000. Jede Adresse wird im eigenen Adressraum des jeweiligen Prozesses interpretiert, sodass die Zugriffe unterschiedliche physische Bytes erreichen können. Ebenso muss Code an der virtuellen Adresse 0 nicht die physische Adresse 0 belegen. Das Betriebssystem kann ihn anderswo platzieren und die Hardwareübersetzung so einrichten, dass sie ihn erreicht.
Der Prozessor übersetzt Befehlsabrufe sowie Lese- und Schreibzugriffe. Das Betriebssystem wählt und verwaltet die Abbildungen; dabei kann es auch physische Speicherorte ändern und die virtuellen Adressen des Programms stabil halten.
Wichtige Erkenntnisse
- Ein Adressraum ist die Sicht eines Prozesses auf den Speicher, nicht seine physische Platzierung.
- Code, statische Daten, Heap und Stack erfüllen unterschiedliche Speicheraufgaben.
- Virtuelle Adressen werden innerhalb eines Prozesses interpretiert; gleiche Werte können unterschiedliche physische Bytes erreichen.
- Ein 16 KB großer Adressraum enthält nach der binären KB-Konvention die Bytes 0 bis 16.383.
1.2 Transparenz, Effizienz und Schutz
Ein Speichersystem muss mehr leisten, als eine Zahl korrekt zu übersetzen. Es muss gewöhnliche Programme ausführen lassen, ohne dass diese ihre physische Platzierung verwalten, die Übersetzung mit geringem Aufwand durchführen und die Grenzen durchsetzen, die gemeinsame Nutzung sicher machen. Diese Anforderungen heißen Transparenz, Effizienz und Schutz.
Bewahre die Sicht des Programms
Transparenz bedeutet, dass ein Programm so läuft, als hätte es privaten Speicher. Es verwendet seine eigenen Adressen für Code und Daten, ohne zu verfolgen, welche anderen Prozesse vorhanden sind oder wo deren Bytes liegen. Betriebssystem und Hardware bewahren diese Sicht im Hintergrund. Um einen Prozess im physischen Speicher zu verschieben, sollte der Prozess seine Zeiger nicht umschreiben müssen.
Die Illusion erfordert keine eigene physische Kopie jedes Bytes für jeden Prozess. Geschützter Code, der nur gelesen werden darf, kann beispielsweise gemeinsam genutzt werden. Entscheidend ist, dass die gemeinsame Nutzung das Verhalten und die Zugriffsrechte bewahrt, auf die jeder Prozess Anspruch hat.
Berücksichtige beide Arten von Aufwand
Jeder Befehlsabruf sowie jeder Lese- und Schreibzugriff muss übersetzt werden. Eine Betriebssystemroutine für jeden Zugriff auszuführen, würde die gewöhnliche Ausführung zu langsam machen. Die Hardware führt die häufigen Operationen aus; Betriebssystemcode behandelt Einrichtung, Änderungen und Fehler.
Auch Speichereffizienz ist wichtig. Ein kleiner, schneller Übersetzungsmechanismus kann dennoch erheblichen Speicher verschwenden. Ein einzelner Bereich mit Basis- und Grenzregister reserviert die ungenutzte Lücke zwischen Heap und Stack. Bei der Segmentierung muss für diese Lücke kein physischer Speicher bereitgestellt werden, dafür entstehen aber physische Bereiche unterschiedlicher Größe und Lücken dazwischen. Metadaten, ungenutzter reservierter Speicher und Lücken beeinflussen alle, wie viel nützliche Arbeit in den Speicher passt.
| Ziel | Frage |
|---|---|
| Transparenz | Verwendet das Programm weiterhin seine eigenen Adressen, ohne die Platzierung zu verwalten? |
| Zeiteffizienz | Wie viel zusätzlichen Aufwand verursacht jeder Speicherzugriff? |
| Speichereffizienz | Wie viel Speicher wird durch Metadaten oder unbrauchbaren Platz verbraucht? |
| Schutz | Kann der Prozess nur auf Bereiche zugreifen und Operationen ausführen, die ihm erlaubt sind? |
Halte einen unzulässigen Zugriff an der Grenze auf
Ohne Schutz könnte ein fehlgeleiteter Zeiger in einem Texteditor einen anderen Prozess oder den Kernel überschreiben. Der Schaden könnte sich viel später in einem anderen Codeteil zeigen. Werden Grenzen durchgesetzt, weist die Hardware einen Zugriff außerhalb des erlaubten Bereichs ab und löst eine Ausnahme aus. Das Betriebssystem behandelt die Ausnahme und beendet üblicherweise den fehlerhaften Prozess, wobei häufig eine Speicherzugriffsverletzung gemeldet wird.
Schutz isoliert Prozesse voneinander. Er macht die Speichernutzung in C innerhalb eines erlaubten Heap-Bereichs nicht sicher. Ein Schreibzugriff über einen zugewiesenen Block hinaus kann weiterhin einen anderen Block desselben Prozesses treffen. Prozessgrenzen und Grenzen von Blöcken des Allokators sind unterschiedliche Grenzen.
Trenne die Operation von der Entscheidung
Ein Mechanismus beschreibt, wie eine Operation ausgeführt wird. Das Addieren eines Basisregisterwerts und das Prüfen eines Grenzregisters sind Mechanismen. Eine Strategie wählt zwischen möglichen Handlungen: wo ein Prozess platziert wird, welche freie Lücke genutzt wird oder wann ein Segment verschoben wird.
Diese Trennung erlaubt es dem Betriebssystem, seine Platzierungsstrategie zu ändern und dieselbe Hardwareübersetzung beizubehalten. Speichervirtualisierung beginnt mit dieser privaten, geschützten Sicht; sie ist nicht durch die Nutzung der Festplatte als zusätzlicher Speicher definiert.
Wichtige Erkenntnisse
- Transparenz bewahrt die Sicht des Programms, wenn sich die physische Platzierung ändert.
- Zeiteffizienz ist bei jedem Speicherzugriff wichtig; zur Speichereffizienz gehören ungenutzte Bereiche und Metadaten.
- Schutz isoliert Prozesse, setzt aber nicht jede Grenze eines Heap-Blocks durch.
- Mechanismen führen Operationen aus; Strategien wählen zwischen Alternativen.
- Die Hardware übersetzt gewöhnliche Zugriffe; das Betriebssystem verwaltet die Übersetzung und behandelt Fehler.
2Von C-Speicherzuweisungen zu Betriebssystembereichen
Eine Heap-Anforderung durchläuft zwei Ebenen der Speicherverwaltung. Die C-Bibliothek findet einen Block für das Programm, während das Betriebssystem die Bereiche bereitstellt und schützt, aus denen Blöcke herausgeteilt werden. Anhand dieser Ebenen lässt sich erklären, warum eine Zeigervariable und der ihr zugewiesene Speicher unterschiedliche Lebensdauern haben, warum die Freigabe eines Blocks die Speichernutzung des Prozesses unverändert lassen kann und warum eine erfolgreiche Kompilierung wenig darüber aussagt, ob spätere Heap-Zugriffe zulässig sind.
2.1 Blöcke anfordern und ihre Lebensdauer beachten
Eine Zeigervariable und der Speicher, auf den sie zeigt, sind getrennte Objekte. Ihre Lebensdauern zu verstehen, ist der Ausgangspunkt für die C-Speicherverwaltungsschnittstelle. Gewöhnliche lokale Variablen nutzen typischerweise automatischen Stack-Speicher, Heap-Blöcke bleiben bis zu ihrer Freigabe bestehen, und globale sowie lokale static-Variablen haben während der gesamten Programmausführung Speicher.
Verfolge die Speicherzuweisung, nicht nur den Zeiger
void f(void) {
int x;
int *p = malloc(sizeof(int));
}
Im üblichen Stackaufbau gehören x und p zu diesem Aufruf von f. Die angeforderte Ganzzahl gehört zum Heap. Bei der Rückkehr wird die lokale Zeigervariable entfernt; der Heap-Block wird dadurch nicht freigegeben. Bleibt keine Kopie der Adresse erhalten, hat das Programm den Zugriff auf eine aktive Speicherbelegung verloren: ein Speicherleck.
malloc(size) fordert mindestens so viele Bytes an und gibt einen Zeiger zurück oder NULL, wenn die Anforderung nicht erfüllt werden kann. Die Bytes werden für das Programm nicht initialisiert. Verwende für 10 Ganzzahlen malloc(10 * sizeof(int)); ist eine Ganzzahl 4 Bytes groß, fordert das 40 Bytes an. Reserviere für eine Zeichenkette ihre Zeichen plus ein Byte für das abschließende Nullzeichen: malloc(strlen(s) + 1).
sizeof(a) misst das Objekt mit dem Namen a. Ist a ein Zeiger, ergibt sich die Größe des Zeigers, nicht die Größe des zugewiesenen Speichers. Ein 8 Byte großer Zeiger kann auf einen 40 Byte großen Block verweisen.
Kenne die Aufgaben der Bibliotheksoperationen
| Aufruf | Hauptzweck |
|---|---|
malloc | Einen Block erhalten, dessen Bytes nicht gelöscht sind. |
calloc | Einen mit Nullbytes gefüllten Block erhalten. |
realloc | Die Größe eines Blocks ändern und ihn gegebenenfalls verschieben. |
free | Einen zugewiesenen Block an den Allokator zurückgeben. |
free erhält den zurückgegebenen Zeiger ohne Größenargument. Der Allokator speichert Metadaten zur Größe, üblicherweise in einem Verwaltungskopf vor dem Block. Übergib den zurückgegebenen Zeiger eines aktuell zugewiesenen Blocks; ein Zeiger ins Innere des Blocks ist kein gültiger Ersatz. free(NULL) ist zulässig und bewirkt nichts. Diese Funktionen sind Routinen der C-Bibliothek, keine Systemaufrufe.
Erkenne Fehler an Lebensdauer oder Grenze
Ein Zeiger ohne gültige Speicherzuweisung darf nicht so verwendet werden, als zeige er bereits auf Speicher. Neu zugewiesene Bytes vor ihrer Initialisierung zu lesen, ist ein Lesen nicht initialisierter Daten. Schreiben über das Ende des zugewiesenen Bereichs hinaus ist ein Pufferüberlauf; das Weglassen des abschließenden Bytes einer Zeichenkette kann einen solchen verursachen.
Nach free sind alte Zeiger auf den Block hängende Zeiger. Ein Zugriff über einen solchen Zeiger ist ein Zugriff nach Freigabe. Den Block erneut freizugeben, ist eine doppelte Speicherfreigabe. Die Freigabe einer Adresse, die der Allokator nicht für einen aktiven Block zurückgegeben hat, ist eine ungültige Freigabe. Ein Speicherleck ist etwas anderes: Der Block bleibt zugewiesen, aber das Programm gibt ihn nicht mehr frei, obwohl es das sollte.
Verstehe, was die Hardware prüfen kann
Die Kompilierung kann trotz all dieser Fehler gelingen. Die Hardware prüft normalerweise Prozessbereiche und Berechtigungen, nicht jede Speicherzuweisung der Bibliothek. Das Überschreiten einer Blockgrenze innerhalb des zulässigen Heaps des Prozesses kann undefiniertes Verhalten auslösen, ohne sofort einen Fehler zu verursachen. Das Programm kann Daten beschädigen, später abstürzen, bedeutungslose Werte lesen oder scheinbar funktionieren. Valgrind und AddressSanitizer können viele solcher Fehler während der Ausführung erkennen.
Wichtige Erkenntnisse
- Die Lebensdauer eines Heap-Blocks ist von der Lebensdauer der Zeigervariablen getrennt.
- Verwende
sizeoffür den gespeicherten Typ;sizeofauf einem Zeiger misst nicht dessen Block. malloc,calloc,reallocundfreesind Bibliotheksroutinen.- Die Speicherzuweisung für eine Zeichenkette benötigt Platz für ihr abschließendes Nullbyte.
free(NULL)bewirkt nichts; ein Zeiger ins Innere eines zugewiesenen Blocks ist kein gültiger Zeiger zur Freigabe des Blocks.- Die Bereichsprüfungen der Hardware können Speicherfehler an Blockgrenzen innerhalb des Heaps übersehen.
2.2 Wie der Allokator seinen Speicher erhält
Die C-Bibliothek und das Betriebssystem verwalten Speicher auf unterschiedlichen Ebenen. Das Betriebssystem richtet Bereiche im Adressraum eines Prozesses ein und stellt den zugehörigen physischen Speicher bereit. Der Allokator teilt verfügbaren Heap-Speicher in Blöcke auf, erfasst freie Stücke und verwendet sie über mehrere Aufrufe hinweg erneut.
Verschiebe den Program Break
Der Program Break ist die Adresse unmittelbar hinter dem Ende des traditionellen Heaps. brk setzt diese Grenze auf eine gewählte Adresse. sbrk(n) verschiebt sie um eine Anzahl von Bytes und gibt bei Erfolg den alten Program Break zurück. sbrk(0) meldet den aktuellen Program Break, ohne ihn zu verschieben.
Angenommen, der Program Break liegt bei 40.960 und der Allokator ruft erfolgreich sbrk(8192) auf. Der Rückgabewert ist 40.960. Die neue Grenze ist:
Die hinzugefügten Bytes belegen die virtuellen Adressen 40.960 bis 49.151. Der neue Program Break, 49.152, liegt unmittelbar hinter dem hinzugefügten Bereich. Verwechselt man den Rückgabewert mit der neuen Grenze, verschiebt das jede spätere Platzierungsberechnung.
Anwendungen nutzen die Speicherverwaltungsbibliothek, statt den Program Break direkt zu verschieben. Die Bibliothek erfasst, welche Teile ihres Bereichs ihr gehören. Unabhängige Änderungen dieser Grenze können ihre Verwaltungsdaten ungültig machen.
Erhalte eine separate Speicherabbildung
mmap kann einen anonymen Bereich erzeugen: Speicher, der nicht an eine Datei gebunden und anfangs mit Nullbytes gefüllt ist. munmap entfernt eine Speicherabbildung. Ein Allokator kann separate Speicherabbildungen für große Speicherzuweisungen verwenden und sie bei der Freigabe an das Betriebssystem zurückgeben.
In glibc liegt der anfängliche Standardschwellenwert für diesen Weg bei 128 KB oder 131.072 Bytes und kann nach oben angepasst werden. Eine ausreichend große Speicherzuweisung erhält typischerweise eine separate Speicherabbildung, wenn sie nicht aus vorhandenem freiem Speicher bedient werden kann. Dies ist eine Strategie der Bibliothek, keine allgemeingültige Trennlinie in der Sprache C.
Gib auf Bibliotheks- oder Betriebssystemebene frei
| Ereignis | Was sich ändert |
|---|---|
| Einen kleinen wiederverwendbaren Block freigeben | Der Allokator kann daraus eine weitere Anforderung bedienen. |
| Eine separate Speicherabbildung entfernen | Der abgebildete Bereich gehört nicht mehr zum Prozess. |
| Den Prozess beenden | Das Betriebssystem gewinnt den Speicher des Prozesses zurück. |
Ein Aufruf von free gibt einen kleinen Block üblicherweise an den Speichervorrat der Bibliothek zurück. Der Prozess kann danach daher denselben Speicherverbrauch ausweisen. Der Speicher steht zur Wiederverwendung bereit, ohne das Betriebssystem erneut anfordern zu müssen.
Beim Prozessende werden auch Speicherzuweisungen zurückgewonnen, die das Programm verloren hat. Während der Lebensdauer eines langlebigen Servers verbrauchen wiederholte Speicherlecks jedoch immer mehr seines verfügbaren Speichers. Die Rückgewinnung beim Beenden verhindert diesen Speicherdruck während der Ausführung nicht.
Verbinde Heap-Wachstum und Übersetzung
Im Segmentierungsmodell ist der Heap ein zusammenhängendes physisches Segment. Damit er wachsen kann, müssen unmittelbar darüber freie physische Bytes liegen. Sind diese Bytes belegt, kann das Betriebssystem das Segment in eine größere Lücke verschieben und seinen Übersetzungszustand aktualisieren. Ist kein geeigneter Bereich verfügbar, schlägt das Wachstum fehl. Kann der Allokator nicht genügend Platz erhalten, gibt malloc NULL zurück.
Wichtige Erkenntnisse
- Das Betriebssystem stellt Bereiche bereit; die Bibliothek unterteilt sie in Blöcke.
- Ein erfolgreiches
sbrkgibt den alten Program Break zurück, nicht den neuen. - Ein anonymes
mmapbeschafft einen separaten, mit Nullen gefüllten Bereich;munmapentfernt ihn. - Die Freigabe eines Blocks erlaubt meist seine Wiederverwendung, ohne den vom Betriebssystem zugewiesenen Speicher des Prozesses zu verringern.
- Ein segmentierter Heap benötigt zusammenhängenden physischen Speicher, um zu wachsen.
3Übersetzung mit Basis- und Grenzregister
Das Verfahren mit Basis- und Grenzregister wandelt eine prozessrelative Adresse mit einem Vergleich und einer Addition in eine physische Adresse um. Seine Einfachheit macht die Aufgabenteilung gut sichtbar: Die Hardware prüft jeden Zugriff, während das Betriebssystem einen Platz auswählt, beim Prozesswechsel den Übersetzungszustand wiederherstellt und Fehler behandelt. Verfolgst du einen Prozess durch diese Ereignisse, zeigt sich auch der größte Nachteil: Selbst ungenutzte Adressen zwischen Heap und Stack belegen einen Teil seines physischen Platzes.
3.1 Eine Adresse prüfen und übersetzen
Dynamische Relokation übersetzt Adressen während der Programmausführung. Im Modell mit Basis- und Grenzregister belegt ein Prozess einen zusammenhängenden physischen Bereich. Beginne mit gleich großen Adressräumen, die in den physischen Speicher passen; ein Registerpaar beschreibt dann den Bereich des aktuell laufenden Prozesses.
Prüfe zuerst die virtuelle Adresse
Das Basisregister enthält die physische Adresse des ersten Bytes des Bereichs. Das Grenzregister, auch Limitregister genannt, enthält die Größe des Bereichs in Bytes. Für eine nicht negative virtuelle Adresse gilt:
Ist die Prüfung erfolgreich, gilt:
Andernfalls löst die Hardware eine Ausnahme aus. Eine Größe ist eine Anzahl, daher liegt die Adresse, die der Grenze entspricht, bereits außerhalb des Bereichs. Negative virtuelle Adressen sind ebenfalls ungültig.
Gehe die Bytegrenzen durch
Betrachte einen 8 KB großen Adressraum mit Basis 20.480 und Grenze 8.192. Seine virtuellen Bytes reichen von 0 bis 8.191. Die virtuelle Adresse 1.000 besteht die Prüfung und erreicht das physische Byte 21.480. Das letzte zulässige virtuelle Byte, 8.191, erreicht 28.671. Adresse 9.000 besteht die Prüfung nicht, weil sie mindestens 8.192 beträgt; die Hardware muss sie abweisen, bevor sie den physischen Zugriff ausführt.
Befehlsabrufe verwenden dieselbe Übersetzung wie Daten. Lädt ein Befehl an der virtuellen Adresse 128 von der virtuellen Adresse 3.000, erreicht der Abruf die physische Adresse 20.608 und der Lesezugriff 23.480. Das sind zwei Speicherzugriffe mit zwei übersetzten Adressen, keine gemeinsame Übersetzung für den gesamten Befehl.
Halte die Grenzkonventionen auseinander
| Inhalt des Grenzregisters | Was verglichen wird |
|---|---|
| Bereichsgröße | Die virtuelle Adresse muss kleiner als die Größe sein. |
| Physische Endgrenze | Die übersetzte Adresse muss unter dieser Grenze liegen. |
Derselbe 8-KB-Bereich hat die physische Endgrenze 28.672. Beide Konventionen erlauben genau die Bytes 20.480 bis 28.671. Der Vergleich der physischen Adresse 21.480 mit der Größe 8.192 vermischt die beiden Konventionen und weist einen zulässigen Zugriff fälschlich ab.
Verschiebe Speicher, ohne Zeiger zu ändern
Die MMU, die Speicherverwaltungseinheit, führt die Prüfungen und die Addition aus. Das Betriebssystem kann einen Prozess anhalten, seinen Bereich an eine andere Stelle kopieren und die Basis ändern. Der Prozess verwendet weiterhin dieselben virtuellen Adressen, weshalb die Relokation dynamisch ist.
Bei der statischen Relokation schreibt dagegen ein Ladeprogramm eingebettete Adressen beim Laden des Codes um. Wird ein Verweis von 1.000 auf 21.480 umgeschrieben, wird eine physische Platzierung im Programm festgehalten. Ein späteres Verschieben erfordert weiteres Umschreiben, und das Umschreiben allein hindert ein Programm nicht daran, eine Adresse außerhalb seines Bereichs zu bilden.
Addiere eine Basis nur zu einer virtuellen Adresse. Sie zu einer bereits übersetzten physischen Adresse zu addieren, verschiebt den Speicherort ein zweites Mal und zerstört die Abbildung.
Wichtige Erkenntnisse
- Enthält das Grenzregister eine Größe, prüfe die virtuelle Adresse, bevor du die Basis addierst.
- Der zulässige Bereich schließt die Adresse aus, die der Grenze entspricht.
- Sowohl Befehlsabrufe als auch Datenzugriffe werden übersetzt.
- Eine physische Endgrenze und eine Grenze als Größe in Bytes erfordern unterschiedliche Vergleiche.
- Dynamische Relokation ändert die Platzierung, ohne virtuelle Zeiger umzuschreiben.
3.2 Übersetzungszustand einrichten und wechseln
Die Hardware enthält Übersetzungsregister für den Prozess, der auf einer CPU läuft. Das Betriebssystem verwahrt den gespeicherten Zustand wartender Prozesse. Der Schutz hängt davon ab, dass nur privilegierter Betriebssystemcode diesen Zustand ändern darf.
Stelle die Hardwareoperationen bereit
Die CPU benötigt Benutzer- und Kernelmodus, ein Basis- und Grenzregisterpaar in ihrer MMU sowie Schaltungen für Prüfung und Addition. Außerdem benötigt sie privilegierte Operationen, um das Registerpaar zu ändern und Ausnahmebehandlungsroutinen zu installieren. Unzulässige Adressen und Versuche, privilegierte Befehle im Benutzermodus auszuführen, müssen Ausnahmen auslösen.
Könnte ein Benutzerprogramm seine Basis ändern, könnte es seine Adressen in den Kernel oder in einen anderen Prozess umlenken. Die Grenzprüfungen würden dann den falschen Bereich schützen. Die Beschränkung von Änderungen ist daher Teil des Schutzmechanismus und nicht nur eine administrative Erleichterung.
Erzeuge und starte einen Prozess
Beim Systemstart installiert das Betriebssystem seine Trap- und Ausnahmebehandlungsroutinen, startet den Interrupt-Timer und bereitet seine Aufzeichnungen über freien Speicher vor. Wenn ein Prozess startet, findet das Betriebssystem einen physischen Platz für seinen Adressraum. Bei gleich großen Plätzen kann eine Freispeicherliste einfach erfassen, welche Plätze ungenutzt sind.
Das Betriebssystem markiert den gewählten Platz als belegt, erfasst Basis und Grenze des Prozesses, lädt die Register und wechselt in den Benutzermodus. Der Prozess läuft dann direkt auf der CPU. Die Hardware übersetzt gewöhnliche Zugriffe, ohne für jedes Byte das Betriebssystem aufzurufen.
Stelle beim Kontextwechsel das richtige Paar wieder her
Ein Timerinterrupt übergibt die Kontrolle an das Betriebssystem. Es sichert die Register des bisherigen Prozesses einschließlich Basis und Grenze in dessen Prozesskontrollblock und stellt dann die Werte des nächsten Prozesses wieder her, bevor es in den Benutzermodus zurückkehrt.
Angenommen, die Basis von A ist 20.480 und die von B 40.960, beide mit der Grenze 8.192. Die virtuelle Adresse 1.000 von A erreicht 21.480. Nach dem Wechsel zu B erreicht derselbe virtuelle Wert 41.960. Würden die CPU-Register ohne den Übersetzungszustand wiederhergestellt, würde B mit seinen Zeigern auf den Bereich von A zugreifen.
Die CPU enthält das aktive Paar; jeder Prozess hat sein eigenes gespeichertes Paar. Stelle dir weder ein einziges dauerhaftes Paar für den ganzen Rechner vor noch nimm an, dass die Paare aller Prozesse gleichzeitig auf einer CPU aktiv sind.
Verschieben, Fehler behandeln und beenden
Um A an die physische Adresse 49.152 zu verschieben, nimmt das Betriebssystem A von der CPU, kopiert seinen 8-KB-Bereich und aktualisiert die gespeicherte Basis von A. Sobald A mit dieser geladenen Basis weiterläuft, erreicht die virtuelle Adresse 1.000 die Adresse 50.152. Seine virtuellen Zeigerwerte müssen nicht geändert werden.
Bei einem ungültigen Zugriff löst die Hardware eine Ausnahme aus und übergibt die Ausführung im Kernelmodus an die registrierte Behandlungsroutine. Das Betriebssystem beendet üblicherweise den verursachenden Prozess. Bei der Beendigung gibt es den Platz an die Freispeicherliste zurück und gibt die Prozessdaten frei.
Wichtige Erkenntnisse
- Nur privilegierter Code kann Übersetzungsregister ändern.
- Die CPU enthält den aktiven Zustand; Prozesskontrollblöcke enthalten den gespeicherten Zustand.
- Ein Kontextwechsel muss Basis und Grenze zusammen mit den anderen Registern wiederherstellen.
- Für die Relokation muss der Prozess angehalten, sein Speicher kopiert und seine gespeicherte Basis aktualisiert werden.
- Die Hardware löst bei Zugriffsfehlern Ausnahmen aus; das Betriebssystem behandelt sie.
- Bei der Beendigung wird der physische Platz an die Freispeicherliste zurückgegeben.
3.3 Warum ein einfacher Platz Speicher verschwendet
Das Verfahren mit Basis- und Grenzregister erfüllt zwei Ziele mit geringem Aufwand: Die Übersetzung ist transparent und jede Adresse wird anhand des zugewiesenen Bereichs geprüft. Seine größte Schwäche liegt im Verhältnis zwischen diesem Bereich und den Bytes, die der Prozess tatsächlich nutzt.
Reserviere die gesamte Spanne
Die Hardware benötigt bei der häufigen Ausführung nur eine Addition und einen Vergleich. Ein Allokator für Plätze fester Größe kann zudem schnell einen freien Platz auswählen. Keiner dieser Vorteile bedeutet, dass der Platz mit nützlichen Daten gefüllt ist.
In der klassischen Anordnung liegen Code und Heap nahe dem einen Ende und der Stack nahe dem anderen. Ein Basis- und Grenzregisterpaar übersetzt das gesamte Intervall. Die ungenutzte Lücke zwischen Heap und Stack belegt ebenso physischen Speicher wie die genutzten Teile.
Für einen 16 KB großen Adressraum, der 1 KB Code, 2 KB Heap und 1 KB Stack nutzt:
Die Verschwendung ist interne Fragmentierung: ungenutzte Bytes innerhalb eines bereits einem Prozess zugewiesenen Bereichs. Sie entsteht nicht dadurch, dass der Heap-Allokator einen schlechten Block wählt. Selbst ein lückenlos gefüllter 2-KB-Heap lässt die große Lücke in der Mitte reserviert.
Unterscheide drei Größenfragen
Die Spanne des Adressraums, die aktuell genutzten Bytes und der verfügbare physische Speicher sind unterschiedliche Größen. Ein byteadressierter 32-Bit-Adressraum umfasst Bytes oder 4 GB. Ihn als einen physischen Block zu reservieren, erfordert die gesamten 4 GB, selbst wenn der Prozess aktuell nur einige Megabyte nutzt.
| Einschränkung | Folge |
|---|---|
| Ein zusammenhängender Bereich | Getrennte freie Lücken können einen Prozessbereich nicht aufnehmen. |
| Gleich große Plätze | Kleine Prozesse verschwenden Platz; größere passen möglicherweise nicht hinein. |
| Ein dünn besetzter Adressraum | Ungenutzte virtuelle Lücken verbrauchen weiterhin reservierten physischen Speicher. |
Unterschiedlich große Plätze heben die Beschränkung auf gleiche Größen auf, beseitigen aber nicht den Bedarf an einer zusammenhängenden Lücke. Außerdem entsteht ein Platzierungsproblem: Das Betriebssystem muss zwischen freien Lücken unterschiedlicher Größe wählen. Wird ein Platz verkleinert, können die niedrigen Codeadressen und eine weit entfernte hohe Stackadresse nicht beide erhalten bleiben, wenn die gesamte Spanne in derselben einfachen Abbildung gültig bleiben muss.
Relokiere die Bereiche getrennt
Code, Heap und Stack müssen im physischen Speicher nicht benachbart sein, damit die Sicht des Programms erhalten bleibt. Erhält jeder Bereich eine eigene Basis und Grenze, kann das Betriebssystem nur für seine genutzte Ausdehnung Speicher zuweisen und die virtuelle Lücke ohne physischen Speicher lassen. Das ist Segmentierung, die Verallgemeinerung eines Relokationspaars auf mehrere logische Bereiche.
Für die obige 16-KB-Anordnung benötigen die drei genutzten Bereiche vor ihrem eigenen Verwaltungsaufwand insgesamt nur 4 KB. Die Speicherersparnis entsteht dadurch, dass andere Einheiten relokiert werden, nicht dadurch, dass die Addition des Basisregisterwerts schneller wird.
Wichtige Erkenntnisse
- Die gesamte virtuelle Spanne belegt einen zusammenhängenden physischen Platz.
- Ungenutzter Speicher innerhalb dieses Platzes ist interne Fragmentierung.
- Gleich große Plätze beschränken große Prozesse und verschwenden Platz bei kleinen.
- Segmentierung spart Speicher, indem sie genutzte Bereiche unabhängig voneinander relokiert.
4Übersetzung mit getrennten Segmenten
Ein einzelnes Übersetzungsregisterpaar reserviert die Lücken innerhalb eines Adressraums. Segmentierung gibt Code, Heap und Stack unabhängige physische Speicherorte, sodass nur für ihre aktuelle Ausdehnung Speicher bereitgestellt werden muss. Zur Übersetzung einer Adresse gehört nun, das Segment und seinen Offset zu finden; beim nach unten wachsenden Stack kommt eine Vorzeichenänderung hinzu. Die gewonnene Flexibilität ermöglicht Schutz und gemeinsame Codenutzung, verlangt aber vom Betriebssystem, zusammenhängende Lücken für Bereiche unterschiedlicher Größe zu finden.
4.1 Jedem Bereich eine eigene Übersetzung geben
Ein Segment ist ein zusammenhängender Bereich eines Adressraums mit einem bestimmten Zweck. Im klassischen Modell erhalten Code, Heap und Stack jeweils ein eigenes Basis- und Größenregisterpaar. Ihre physischen Bereiche können unabhängig voneinander platziert werden, während der Prozess seinen vertrauten virtuellen Aufbau behält.
Stelle nur für die genutzten Bereiche Speicher bereit
Verwende einen 16 KB großen Adressraum mit den folgenden Platzierungen. Die Größen verwenden binäre KB oder KiB.
| Segment | Virtueller Bereich | Physische Basis oder obere Grenze | Größe |
|---|---|---|---|
| Code | 0 bis 2.047 | Basis 40.960 | 2.048 Bytes |
| Heap | 4.096 bis 7.167 | Basis 26.624 | 3.072 Bytes |
| Stack | 14.336 bis 16.383 | Obere Grenze 53.248 | 2.048 Bytes |
Code und Heap wachsen zu höheren Adressen hin; die Stackübersetzung verwendet die obere Grenze und einen negativen Offset. Die drei physischen Bereiche benötigen zusammen nur 7 KB. Die virtuellen Lücken dazwischen benötigen keinen physischen Speicher. Ein Segment bleibt zusammenhängend; nur die Segmente selbst müssen nicht mehr nebeneinanderliegen.
Übersetze ein nach oben wachsendes Segment
Bestimme zuerst das Segment. Berechne dann den Offset von seinem virtuellen Anfang aus. Prüfe, ob der Offset nicht negativ und kleiner als die aktuelle Segmentgröße ist, und addiere ihn dann zur physischen Basis:
Für die virtuelle Adresse 500 ist der Code-Offset 500. Er liegt unter 2.048, sodass der Zugriff die physische Adresse 41.460 erreicht.
Für die virtuelle Adresse 5.000 ist der Heap-Offset:
Die Größenprüfung ist erfolgreich, weil 904 unter 3.072 liegt. Das Ergebnis ist:
Die vollständige virtuelle Adresse zur Heap-Basis zu addieren, ergäbe 31.624. Dabei würde fälschlich die virtuelle Anfangsposition des Heaps in eine Berechnung übernommen, die nur den Abstand innerhalb des Segments benötigt.
Prüfe das Segment, nicht nur die Spanne des Adressraums
Die virtuelle Adresse 7.500 passt zwar noch in 14 Bits, ist aber deshalb nicht zulässig. Ihr Heap-Offset beträgt 3.404 und liegt damit außerhalb des aktuellen Bereichs von 3.072 Bytes. Die Hardware löst eine Ausnahme aus. Eine Adresse kann also innerhalb der gesamten Spanne des Adressraums und dennoch außerhalb jedes gültigen Bereichs liegen.
Der Begriff Speicherzugriffsverletzung entstand im Zusammenhang mit ungültigen Zugriffen bei der Segmentierung und wird weiterhin für unzulässige Zugriffe auf Systemen mit anderen Übersetzungsverfahren verwendet. Der Fehlername allein bezeichnet nicht den Übersetzungsmechanismus des Rechners.
Wichtige Erkenntnisse
- Jedes Segment hat eine eigene Basis und Größe.
- Der Offset eines nach oben wachsenden Segments wird von dessen virtuellem Anfang aus gemessen.
- Addiere nur den geprüften Offset zur physischen Basis.
- Eine Adresse innerhalb der gesamten virtuellen Spanne kann dennoch eine Ausnahme auslösen.
- Ungenutzte virtuelle Lücken zwischen Segmenten benötigen keinen physischen Speicher.
4.2 Das Segment auswählen und seinen Offset bestimmen
Bevor die Hardware Basis und Größe eines Segments verwenden kann, muss sie entscheiden, welches Segment ein Zugriff bezeichnet. Ein expliziter Entwurf legt diese Auswahl in Adressbits ab. Ein impliziter Entwurf leitet sie daraus ab, wie die CPU die Adresse gebildet hat.
Teile eine 14-Bit-Adresse auf
Verwende die obersten zwei Bits für die Segmentnummer und die unteren zwölf Bits für den Offset. Das Zwei-Bit-Feld hat vier mögliche Werte. Verwende 00 für Code, 01 für den Heap und 11 für den Stack; lasse 10 ungenutzt.
Das Offsetfeld kann 4.096 verschiedene Werte von 0 bis 4.095 darstellen. Das maximale virtuelle Fenster jedes Segments ist daher 4 KB groß, selbst wenn der aktuell gültige Bereich des Segments kleiner ist. Maximale Kapazität und aktuelle Größe sind unterschiedliche Begrenzungen.
Für die virtuelle Adresse 5.000 lautet die 14-Bit-Darstellung:
01 0011 1000 1000
Das führende 01 wählt den Heap. Das verbleibende 0011 1000 1000 entspricht dezimal 904. Bei einer Heap-Größe von 3.072 und einer physischen Basis von 26.624 besteht der Offset die Größenprüfung und erreicht die physische Adresse 27.528.
Verwende eine Maske und eine Verschiebung
Dieselbe Aufteilung lässt sich als Code ausdrücken:
segment = (va & 0x3000) >> 12;
offset = va & 0x0FFF;
Die Maske 0x3000 behält die beiden Selektorbits; die Verschiebung um zwölf Stellen bewegt sie an die unteren Positionen und ergibt so die Segmentnummer. Die Maske 0x0FFF setzt diese Selektorbits auf null und behält den Offset. Für va = 0x1388 sind die Ergebnisse Segment 1 und Offset 0x388 oder 904.
Addiere 0x1388 nicht direkt zur Heap-Basis. Der Wert enthält sowohl den Segmentselektor als auch den Offset. Der Selektor wählt die Register; nur der Offset geht in die Übersetzung innerhalb des Segments ein. Die resultierende physische Adresse muss das Bitmuster des Selektors nicht beibehalten.
Erkenne die Kosten der Aufteilung
Ein Zwei-Bit-Selektor erzeugt vier Fenster. Bleibt 10 ungenutzt, ist ein Viertel der virtuellen Spanne von 16 KB nicht verfügbar. Kein Segment kann über sein 4-KB-Fenster hinaus wachsen, nur weil mehr physischer Speicher frei wird. Andere Entwürfe können eine andere Aufteilung verwenden; fasst man Code und Heap in einem Segment zusammen, kann ein einzelnes Selektorbit dieses vom Stack unterscheiden.
Lasse stattdessen die Zugriffsart entscheiden
Bei einem impliziten Entwurf verwenden Befehlsabrufe das Codesegment, weil ihre Adresse aus dem Befehlszähler stammt. Verweise über den Stapelzeiger oder Framepointer verwenden das Stacksegment. Andere Zugriffe verwenden in diesem vereinfachten Modell das Heap-Segment.
| Auswahlmethode | Woher die Auswahl stammt |
|---|---|
| Explizit | Ein Segmentfeld innerhalb der virtuellen Adresse |
| Implizit | Das Register oder die Zugriffsart, mit dem oder der die Adresse gebildet wurde |
Wichtige Erkenntnisse
- Selektorbits wählen den Übersetzungszustand; Offsetbits bestimmen ein Byte innerhalb des Fensters.
- Zwölf Offsetbits erlauben ein maximales Segmentfenster von 4.096 Bytes.
- Maskierung und Verschiebung trennen die Felder, ohne Selektorbits in die Addition zur Basis zu übernehmen.
- Die implizite Auswahl nutzt die Quelle einer Adresse, etwa den Befehlszähler oder den Stapelzeiger.
4.3 Den Stack übersetzen und Berechtigungen durchsetzen
Der Stack wächst zu niedrigeren Adressen hin. Seine Übersetzung benötigt neben Basis und Größe ein Richtungsbit. In diesem Modell steht 1 für Wachstum nach oben und 0 für Wachstum nach unten. Zugriffsberechtigungen bilden eine weitere unabhängige Prüfung: Ein Byte kann innerhalb der Grenzen liegen und die angeforderte Operation trotzdem verbieten.
Betrachte die Stackbasis als obere Grenze
Bei einem nach oben wachsenden Segment bezeichnet die Basis sein erstes physisches Byte. Bei einem nach unten wachsenden Segment bezeichnet sie die Grenze unmittelbar oberhalb seines höchstgelegenen physischen Bytes. Im durchgehenden Beispiel umfassen die Stackbasis 53.248 und die Größe 2.048 die physischen Bytes 51.200 bis 53.247.
Wandle für das durch 11 ausgewählte 4-KB-Stackfenster das zwölf Bit große Offsetfeld in einen negativen Versatz um:
Das Feld reicht von 0 bis 4.095, daher ist dieser Versatz immer negativ. Er ist gültig, wenn sein Betrag die aktuelle Stackgröße nicht überschreitet. Addiere dann den vorzeichenbehafteten Versatz zur Basis.
Rechne einen zulässigen Stackzugriff durch
Die virtuelle Adresse 15.500 wählt den Stack. Sein Fenster beginnt virtuell bei 12.288, daher ist ihr Offsetfeld 3.212. Wandle das Feld um:
Die Tiefe 884 liegt innerhalb der Größe 2.048. Die Übersetzung ergibt:
Die positive Zahl 3.212 zur Basis zu addieren, würde auf eine Stelle oberhalb des Stacks verweisen. Das Feld ist noch nicht der Versatz, der für ein nach unten wachsendes Segment benötigt wird.
Die virtuelle Adresse 13.000 ergibt dagegen das Feld 712 und den Versatz −3.384. Seine Tiefe überschreitet 2.048, sodass die Hardware eine Ausnahme auslöst. Vergrößert das Betriebssystem den Stack erfolgreich, können durch die größere Stackgröße weitere niedrige Adressen gültig werden.
Berücksichtige den unteren Endpunkt
Ein Versatz von −2.048 erreicht das niedrigste Byte des Stacks, 51.200, und ist zulässig. Ein Versatz von −2.049 liegt außerhalb. Die Prüfung nach unten schließt daher einen Betrag ein, der der Größe entspricht. Das ergibt dieselbe Byteanzahl wie die Regel für Wachstum nach oben, obwohl diese einen Offset ausschließt, der der Größe entspricht: Die Basis eines nach oben wachsenden Segments ist ein Byte, die eines nach unten wachsenden Segments eine Endgrenze.
Prüfe die angeforderte Operation
Schutzbits erfassen Lese-, Schreib- und Ausführungsberechtigung. Code darf typischerweise gelesen und ausgeführt werden; Heap und Stack dürfen typischerweise gelesen und beschrieben werden. Ein Schreibzugriff auf Code mit Lese- und Ausführungsberechtigung löst selbst bei gültigem Offset eine Ausnahme aus. Auch das Ausführen von Daten löst eine Ausnahme aus, wenn die Ausführungsberechtigung fehlt.
Code, der nur gelesen werden darf, kann gemeinsam genutzt werden. Das Betriebssystem kann die Codesegmente zweier Prozesse auf denselben physischen Bereich verweisen lassen und beiden Schreibzugriffe verbieten. Die Befehlsbytes belegen eine physische Kopie, während jeder Prozess einen eigenen Heap und Stack behält.
Wichtige Erkenntnisse
- Die Basis eines nach unten wachsenden Segments bezeichnet seine obere Grenze, kein gespeichertes Byte.
- Ziehe die maximale Fenstergröße vom Offsetfeld ab, um den negativen Versatz zu erhalten.
- Das niedrigste Byte des Stacks ist zulässig, wenn der Betrag des Versatzes der aktuellen Größe entspricht.
- Sowohl die Grenzen als auch die angeforderte Operation müssen die Hardwareprüfungen bestehen.
- Die gemeinsame Nutzung von Code ohne Schreibberechtigung spart physischen Speicher, während private beschreibbare Bereiche getrennt bleiben.
4.4 Physische Segmente unterschiedlicher Größe verwalten
Segmentierung ersetzt einen großen Bereich durch mehrere kleinere. Dadurch muss für leere virtuelle Lücken kein physischer Speicher bereitgestellt werden. Das Betriebssystem muss nun aber Bereiche unterschiedlicher Größe platzieren und vergrößern und dabei den Übersetzungszustand jedes Prozesses bewahren.
Sichere jeden Teil der Abbildung
Bei einem Kontextwechsel reichen Basis und Größe allein nicht mehr aus. Das Betriebssystem sichert Basis, Größe, Wachstumsrichtung und Berechtigungen jedes Segments und stellt sie wieder her. Würde ein Stack mit der falschen Richtung oder Code mit den falschen Berechtigungen wiederhergestellt, änderte das die Bedeutung ansonsten identischer Adressen.
Benötigt ein Allokator mehr Heap-Speicher, muss das Betriebssystem das physische Heap-Segment erweitern. Freie Bytes unmittelbar darüber erlauben ein direktes Wachstum. Andernfalls kann das Betriebssystem es in eine geeignete größere Lücke verschieben und seine Basis aktualisieren. Kann es keinen geeigneten Bereich erhalten, schlägt das Wachstum fehl.
Wähle die Granularität
| Verfahren | Organisation | Folge für die Verwaltung |
|---|---|---|
| Grobgranulare Segmentierung | Einige Bereiche wie Code, Heap, Stack | Ein kleiner Satz Segmentregister beschreibt den Prozess. |
| Feingranulare Segmentierung | Viele Bereiche, möglicherweise einer pro Datenstruktur | Eine im Speicher liegende Segmenttabelle ermöglicht mehr Möglichkeiten zur gemeinsamen Nutzung und zum Schutz. |
Multics verwendete feingranulare Segmentierung. Mehr Segmente erlauben kleinere Kontrolleinheiten, erfordern aber mehr Übersetzungsmetadaten und Verwaltung.
Finde eine ausreichend große Lücke
Physische Segmente unterschiedlicher Größe hinterlassen freie Lücken unterschiedlicher Größe, wenn Prozesse starten, wachsen und enden. Externe Fragmentierung entsteht, wenn insgesamt genügend Bytes frei sind, aber keine einzelne Lücke das angeforderte Segment aufnehmen kann.
Angenommen, die freien Lücken sind 8 KB, 6 KB und 10 KB groß. Zusammen sind das 24 KB. Ein 12 KB großes Heap-Segment passt dennoch nicht hinein: Die größte verfügbare Lücke ist nur 10 KB groß. Die Lücken auf dem Papier zusammenzufassen, macht ihre physischen Adressen nicht benachbart.
Kompaktierung kann diese Anordnung bereinigen. Das Betriebssystem hält Prozesse an, kopiert Segmente nebeneinander und aktualisiert ihre Basen. Virtuelle Adressen funktionieren weiterhin, weil die Übersetzung die physischen Verschiebungen verbirgt. Das Ergebnis ist ein großer zusammenhängender freier Bereich, doch das Kopieren ist aufwendig und neue Speicherzuweisungen können den Speicher erneut fragmentieren.
Platzierungsstrategien wie First Fit und Best Fit versuchen, ungünstige Reste ohne Kopieren zu begrenzen. Keine Platzierungsstrategie für unterschiedliche Größen beseitigt externe Fragmentierung für jede Folge von Anforderungen. Auch ein einzelnes großes Segment kann innerhalb seiner eigenen Ausdehnung Platz verschwenden, wenn nur verstreute Teile genutzt werden.
Erkenne das Modell in modernen Systemen
Seitenverwaltung teilt Speicher in Stücke fester Größe und vermeidet, dass jeder logische Bereich eine einzige physische Lücke belegen muss. Das Segmentierungsmodell bleibt hilfreich, um Bereiche, Schutz und gemeinsame Nutzung zu verstehen. Im 64-Bit-Modus von x86-64 gelten die gewöhnlichen Segmentbasen für Code, Daten und Stack als null und ihre Grenzen werden nicht geprüft; FS und GS behalten nutzbare Basen für Daten pro Thread oder CPU. Eine Meldung über einen unzulässigen Zugriff als Speicherzugriffsverletzung bedeutet nicht, dass das klassische Modell mit drei Segmenten aktiv ist.
Wichtige Erkenntnisse
- Kontextwechsel stellen neben Basen und Größen auch Richtung und Berechtigungen wieder her.
- Feingranulare Segmentierung stellt über eine Segmenttabelle mehr unabhängige Bereiche bereit.
- Externe Fragmentierung hängt von den Größen einzelner Lücken ab, nicht nur von ihrer Summe.
- Kompaktierung verschiebt physische Segmente und aktualisiert Basen, während virtuelle Zeiger erhalten bleiben.
- Platzierungsstrategien verringern Fragmentierung, ohne ihre Beseitigung zu garantieren.
- Die gewöhnliche x86-64-Übersetzung unterscheidet sich von der klassischen Segmentierung.
5Freie Blöcke und ihre Kosten verwalten
Ein Allokator muss wissen, welche Bytes frei sind, und für jede Anforderung einen ausreichend großen Block auswählen. Blockaufteilung und Verschmelzen verändern die Freispeicherliste; Platzierungsstrategien bestimmen, welche Lücken für spätere Anforderungen übrig bleiben. Verwaltungsköpfe, Speicherausrichtung und das Aufrunden auf Zweierpotenzen verursachen eigene Kosten. Wenn du diese Änderungen verfolgst, kannst du fehlgeschlagene Speicherzuweisungen erklären, Strategien vergleichen und verstehen, warum spezialisierte Caches und Buddy-Allokatoren Flexibilität gegen Geschwindigkeit eintauschen.
5.1 Verschwendung innerhalb oder zwischen Blöcken erkennen
Die Freispeicherverwaltung erfasst verfügbaren Speicher und wählt aus, wo jede Anforderung platziert wird. Gleich große Anforderungen können eine Liste austauschbarer Blöcke nutzen. Unterschiedliche Größen sind schwieriger: Sowohl ein Heap-Allokator als auch ein Betriebssystem, das Segmente platziert, muss einen zusammenhängenden Block finden, der für die nächste Anforderung groß genug ist.
Erkenne externe Fragmentierung
Betrachte einen 60 Byte großen Bereich mit freien Bytes 0–19, einem zugewiesenen Stück bei 20–39 und freien Bytes 40–59. Vierzig Bytes sind frei, aber keine der beiden freien Lücken kann eine Anforderung von 25 Bytes aufnehmen. Das aktive Stück in der Mitte verhindert, dass die Lücken als ein Bereich behandelt werden.
Das ist externe Fragmentierung: Außerhalb zugewiesener Stücke ist Kapazität frei, aber ihre Anordnung macht sie für eine bestimmte zusammenhängende Anforderung unbrauchbar. Die freien Bytes sind nicht verschwunden. Sie können weiterhin kleinere Anforderungen bedienen oder nutzbar werden, wenn dazwischenliegende Stücke freigegeben werden.
Erkenne interne Fragmentierung
Ein Allokator, der Anforderungen auf Vielfache von 16 aufrundet, gibt für eine Anforderung von 20 Bytes ein 32 Byte großes Stück aus. Die 12 zusätzlichen Bytes liegen innerhalb eines bereits vergebenen Stücks. Diese Verschwendung ist interne Fragmentierung.
Die ungenutzte Lücke zwischen Heap und Stack in einem Platz mit Basis- und Grenzregister ist ein weiteres Beispiel. Der Buddy-Allokator verursacht dieselbe Art von Verschwendung, wenn er auf Zweierpotenzen aufrundet. Die Verwaltungsebene ist unterschiedlich, aber der Ort des ungenutzten Speichers ist das bestimmende Merkmal.
| Verschwendung | Wo sie liegt | Warum sie ins Gewicht fällt |
|---|---|---|
| Interne Fragmentierung | Innerhalb eines zugewiesenen Bereichs | Die reservierten Bytes übersteigen den nutzbaren Inhalt. |
| Externe Fragmentierung | In getrennten freien Lücken | Keine einzelne Lücke ist groß genug für den angeforderten Bereich. |
Ein Allokator kann unter beidem leiden. Das Aufrunden einer Anforderung kann Bytes innerhalb ihres Stücks verschwenden, während andernorts freie Blöcke getrennt bleiben.
Verschmelze freie Nachbarn, ohne aktive Daten zu verschieben
Das Verschmelzen freier Blöcke verbindet physisch benachbarte freie Blöcke. Wird das mittlere Stück im 60-Byte-Bereich freigegeben, können alle drei Stücke zu einer 60 Byte großen Lücke verschmelzen. Bleibt das mittlere Stück zugewiesen, kann das Verschmelzen es nicht überbrücken. Verschmelzen verringert externe Fragmentierung, garantiert aber nicht für jede Anforderung eine ausreichend große Lücke.
Mehr Speicher für einen Heap kann den Druck verringern, ändert aber nicht die Adressen bestehender aktiver Blöcke. Ihre Platzierung kann weiterhin unbrauchbare kleine Lücken hinterlassen.
Erkläre, warum das Betriebssystem Segmente kompaktieren kann
Das Betriebssystem kann physische Segmente kopieren und ihre Basen aktualisieren. Programme behalten virtuelle Adressen, sodass die Abbildung diese Verschiebungen verbergen kann. Ein allgemeiner C-Heap-Allokator hat keine Übersetzungsschicht zwischen zurückgegebenen Zeigern und Stellen innerhalb des Heaps. Ein Programm kann einen Zeiger in viele Objekte kopiert haben, und der Allokator kann nicht jede Kopie finden und umschreiben.
Würde ein aktiver Heap-Block allein verschoben, würden diese Zeiger weiterhin auf seine alte Position verweisen. Deshalb hält die gewöhnliche malloc-Verwaltung aktive Blöcke an ihren zurückgegebenen Adressen, bis sie freigegeben werden. Sie muss die Fragmentierung durch Platzierung, Wiederverwendung, Aufteilung und Verschmelzen freien Speichers handhaben.
Wichtige Erkenntnisse
- Interne Verschwendung liegt innerhalb zugewiesener Bereiche; externe Fragmentierung liegt in getrennten freien Lücken.
- Die gesamte freie Kapazität belegt nicht, dass eine zusammenhängende Anforderung hineinpasst.
- Das Verschmelzen verbindet benachbarte freie Blöcke, ohne aktive Blöcke zu verschieben.
- Die Relokation von Segmenten durch das Betriebssystem kann Zeiger durch Übersetzung erhalten; eine gewöhnliche Kompaktierung des C-Heaps kann das nicht.
5.2 Verwaltungsköpfe, Aufteilungen und Verschmelzungen verfolgen
Eine Freispeicherliste erfasst freie Blöcke nach Adresse und Größe. Bei einer Zuweisung wird ein Block aufgeteilt oder aus der Liste entfernt; bei einer Freigabe wird ein Stück zurückgegeben und möglicherweise mit Nachbarn verschmolzen. Die Größenkonvention ist wichtig: Eine erfasste Größe kann die nutzbaren Nutzdaten oder das gesamte Stück einschließlich Metadaten bezeichnen.
Teile einfache Bereiche auf und verschmelze sie
Ignoriere zunächst Verwaltungsköpfe. In einem 60 Byte großen Bereich mit den freien Einträgen (0, 20) und (40, 20) gibt eine am Anfang platzierte Anforderung von 8 Bytes die Adresse 0 zurück. Die verbleibenden Einträge sind (8, 12) und (40, 20).
Beginne erneut mit der ursprünglichen Anordnung und gib die zugewiesenen Bytes 20–39 in der Mitte frei. Ohne Verschmelzen können drei Einträge zu je 20 Bytes eine Anforderung von 30 Bytes einzeln weiterhin nicht bedienen. Das Verschmelzen benachbarter Bereiche ergibt (0, 60), womit dies möglich ist. Die Adressreihenfolge hilft dem Allokator, Nachbarn zu erkennen; benachbarte Listeneinträge allein reichen nicht aus, wenn ihre physischen Bereiche nicht aneinandergrenzen.
Finde den Verwaltungskopf über den Benutzerzeiger
free(ptr) erhält keine Größe. Ein einfacher Allokator speichert einen Verwaltungskopf mit der Größe unmittelbar vor den Nutzdaten, manchmal zusammen mit einem Prüfwert oder einer magischen Zahl. Durch Abziehen der Länge des Verwaltungskopfs findet er die Metadaten.
Bei einem 16 Byte großen Verwaltungskopf belegt eine Anforderung von 96 Bytes insgesamt 112 Bytes. Das Programm erhält einen Zeiger 16 Bytes nach dem Anfang des Stücks. Die Freigabe gibt den gesamten Bereich von 112 Bytes einschließlich Verwaltungskopf für die weitere Verwaltung zurück. Die Speicherausrichtung kann eine Aufrundung hinzufügen; diese Beispielgrößen erfüllen bereits die Ausrichtung auf 16 Bytes.
Verfolge eine eingebettete Freispeicherliste
Angenommen, ein 4.096 Byte großer Heap beginnt bei 32.768. Ein freier Knoten benötigt 16 Bytes und erfasst die nutzbaren Bytes nach dem Knoten. Anfangs erfasst er 4.080 nutzbare Bytes.
Drei Anforderungen von je 96 Bytes verbrauchen jeweils 112 Bytes:
| Anforderung | Adresse des Verwaltungskopfs | Zurückgegebener Zeiger |
|---|---|---|
| Erste | 32.768 | 32.784 |
| Zweite | 32.880 | 32.896 |
| Dritte | 32.992 | 33.008 |
Der verbleibende freie Knoten beginnt bei 33.104. Seine nutzbare Größe beträgt 3.744 Bytes. Die Rechnung lautet:
Die Freigabe des Zeigers 32.896 führt zum Verwaltungskopf bei 32.880. Dieses Stück wird zu einem freien Knoten mit 96 nutzbaren Bytes. Es kann nicht mit dem letzten Knoten bei 33.104 verschmelzen, weil die dritte Speicherzuweisung dazwischenliegt. Werden alle drei Speicherzuweisungen freigegeben und verschmolzen, beschreibt ein Knoten wieder 4.080 nutzbare Bytes. Durch das Einbeziehen der alten inneren Verwaltungsköpfe werden deren Bytes innerhalb des verschmolzenen Bereichs verfügbar.
Halte die Verwaltungsdaten im verwalteten Bereich
Eingebettete Listenknoten liegen im freien Speicher selbst, sodass die Verwaltung der Liste keine separate Speicherzuweisung für jeden Eintrag benötigt. Der Knoten enthält eine Größe und einen Verweis auf den nächsten freien Block. Passt kein Block, kann der Allokator mit sbrk oder mmap weiteren Speicher anfordern; kann er nicht genug Speicher erhalten, führt das zu NULL.
Ein Überlauf kann einen benachbarten Verwaltungskopf oder Knoten überschreiben. Das erklärt, warum der sichtbare Fehler bei einem späteren malloc oder free auftreten kann, nachdem der Schreibzugriff die Liste beschädigt hat.
Wichtige Erkenntnisse
- Unterscheide die nutzbare Größe vom gesamten Bereich einschließlich Metadaten.
- Ein 16 Byte großer Verwaltungskopf lässt eine Anforderung von 96 Bytes 112 Bytes verbrauchen.
- Finde einen Verwaltungskopf, indem du seine Größe vom zurückgegebenen Zeiger abziehst.
- Blockaufteilung erhält einen nutzbaren Rest; Verschmelzen erfordert benachbarte freie Bereiche.
- Eingebettete Knoten speichern Listenmetadaten in den freien Blöcken selbst.
- Ein dazwischenliegendes aktives Stück verhindert das Verschmelzen.
- Das Überschreiben von Verwaltungsdaten kann bei späteren Allokatoraufrufen Fehler verursachen.
5.3 Einen freien Block auswählen
Eine Platzierungsstrategie wählt zwischen Blöcken, die für eine Anforderung groß genug sind. Unterschiedliche Entscheidungen hinterlassen unterschiedliche Lücken für spätere Anforderungen. Neben der Fragmentierung spielt die Suchzeit eine Rolle, und keine Strategie ist für jede Arbeitslast überlegen.
Vergleiche die Suchregeln
| Strategie | Auswahl | Wichtigster Kompromiss |
|---|---|---|
| Best Fit | Kleinster passender Block | Eine vollständige Suche in einer einfachen Liste kann winzige Reste hinterlassen. |
| Worst Fit | Größter Block | Eine vollständige Suche in einer einfachen Liste kann eine wertvolle große Lücke verbrauchen. |
| First Fit | Erster passender Block in Listenreihenfolge | Endet früh, aber kleine Reste sammeln sich nahe dem Anfang. |
| Next Fit | Erster passender Block ab einer gespeicherten Suchposition | Verteilt Suchen über die Liste und beginnt bei Bedarf wieder am Anfang. |
Für Best Fit und Worst Fit muss eine einfache unsortierte Liste vollständig durchsucht werden. Andere Strukturen können passende Größen schneller finden. First Fit bedeutet den ersten Block in der gewählten Listenreihenfolge; das muss nicht die kleinste Adresse sein, sofern die Liste nicht nach Adressen sortiert ist.
Verfolge die erste Anforderung
Nimm freie Blöcke von 30, 12, 50 und 20 Bytes in Adressreihenfolge. Ignoriere Verwaltungsköpfe und nimm an, dass bei der Aufteilung jeder Rest erhalten bleiben kann. Eine Anforderung von 18 Bytes verändert die Liste wie folgt:
- Best Fit nutzt 20 und hinterlässt 30, 12, 50, 2.
- Worst Fit nutzt 50 und hinterlässt 30, 12, 32, 20.
- First Fit nutzt 30 und hinterlässt 12, 12, 50, 20.
- Next Fit beginnt beim vierten Block, nutzt 20 und hinterlässt 30, 12, 50, 2.
Next Fit benötigt eine Anfangsposition, um die Auswahl zu bestimmen. Der Name allein bezeichnet nicht den ersten Eintrag der Liste. Bei der Verfolgung muss dieser Suchzustand zwischen Anforderungen erhalten bleiben.
Sieh, warum die nächste Anforderung das Urteil ändert
Eine anschließende Anforderung verlangt 45 Bytes. Best Fit und First Fit haben beide den 50-Byte-Block erhalten, daher gelingt die Anforderung bei beiden. Der größte verbleibende Block bei Worst Fit ist 32 Bytes groß, sodass die Anforderung trotz insgesamt 94 freier Bytes fehlschlägt:
Dieser Fehlschlag ist externe Fragmentierung. Die frühere Entscheidung hat die einzige ausreichend große Lücke aufgeteilt. Best Fit hat dagegen einen 2 Byte großen Rest hinterlassen, der für nützliche spätere Anforderungen zu klein sein kann. Ein unmittelbar kleiner Rest ist kein Beleg dafür, dass eine Strategie den Speicher über die gesamte Folge von Anforderungen gut nutzt.
Stimme Strategie und Datenstruktur aufeinander ab
Das Durchsuchen Tausender Einträge kann aufwendig sein, selbst wenn jede Platzierung gut ist. Praktische Allokatoren gruppieren Blöcke nach Größe oder verwenden durchsuchbare Strukturen wie balancierte Bäume. Eine nach Adressen sortierte Liste unterstützt außerdem das Verschmelzen, weil physische Nachbarn leichter zu finden sind.
Der Zuweisungsmechanismus (finden, aufteilen, Metadaten aktualisieren) kann gleich bleiben, während sich die Strategie ändert. Um Strategien fair zu vergleichen, verfolge dieselben Anforderungen und Freigaben, halte die Regeln für den Verwaltungsaufwand gleich und berücksichtige sowohl die entstehenden Lücken als auch den Suchaufwand.
Wichtige Erkenntnisse
- Best Fit wählt den kleinsten ausreichenden Block; Worst Fit wählt den größten.
- First Fit hängt von der Listenreihenfolge ab; Next Fit hängt zusätzlich von der gespeicherten Suchposition ab.
- Eine Wahl, die eine Anforderung bedient, kann eine spätere größere Anforderung verhindern.
- Keine Platzierungsstrategie ist für jede Arbeitslast die beste.
- Datenstrukturen beeinflussen den Suchaufwand unabhängig von der Auswahlregel.
5.4 Größen, Objekte und Buddy-Blöcke wiederverwenden
Die allgemeine Suche in einer Freispeicherliste ist flexibel, kann aber aufwendig sein. Spezialisierte Allokatoren machen häufige Anforderungen vorhersehbar, indem sie Größen gruppieren, wiederverwendbare Objekte erhalten oder Blockgrößen auf Zweierpotenzen beschränken.
Verwende häufige Größen und initialisierte Objekte wieder
Nach Größenklassen getrennte Freispeicherlisten halten für häufige Größen separate Listen bereit und verwenden für andere Anforderungen einen allgemeinen Allokator. Eine Anforderung einer häufigen Größe kann direkt mit einem Block aus ihrer Liste bedient werden. Gleich große Blöcke erzeugen untereinander keine externe Fragmentierung, aber die Aufteilung der Kapazität auf Listen ist eine Strategieentscheidung: Übrige Blöcke einer Größe können eine andere Größe möglicherweise nicht bedienen.
Ein Slab-Allokator unterhält einen Objektcache für einen wiederkehrend benötigten Kernelobjekttyp, etwa einen Inode, eine Sperre oder einen Prozessdeskriptor. Ein Slab ist in Objekte dieses Typs unterteilt. Der Cache erhält bei Bedarf weitere Slabs und kann einen vollständig freien Slab an einen allgemeinen Allokator zurückgeben. Jeff Bonwick entwickelte diesen Ansatz für Solaris.
Der Cache hält freigegebene Objekte in einem initialisierten Zustand und vermeidet so wiederholtes Einrichten und Zerstören. Das spart über die Suche selbst hinaus Zeit.
Teile einen Buddy-Bereich auf
Ein binärer Buddy-Allokator beginnt mit einem Bereich, dessen Größe eine Zweierpotenz ist, und rundet jede Anforderung auf eine Blockgröße auf, die einer Zweierpotenz entspricht. Er teilt einen freien Block wiederholt in gleiche Hälften, bis eine Hälfte gerade noch passt. Die beiden Hälften einer Teilung sind Buddies.
Runde für eine Anforderung von 5 KB in einem 64-KB-Bereich 5 KB auf 8 KB auf. Teile 64 in zwei 32-KB-Blöcke, dann den gewählten 32-KB-Block in zwei 16-KB-Blöcke und anschließend den gewählten 16-KB-Block in zwei 8-KB-Blöcke. Die Rückgabe des ersten 8-KB-Blocks hinterlässt freie Buddies bei den Offsets 8 KB, 16 KB und 32 KB mit den jeweiligen Größen 8 KB, 16 KB und 32 KB.
Die Zuweisung verschwendet 3 KB innerhalb ihres 8-KB-Blocks. Das ist interne Fragmentierung, die für regelmäßiges Aufteilen und Verschmelzen in Kauf genommen wird.
Finde den einzig zulässigen Partner zum Verschmelzen
Für Offsets, die vom Anfang des verwalteten Bereichs aus gemessen werden, gilt:
Das exklusive Oder kehrt das der Blockgröße entsprechende Bit um. Bei einem 8-KB-Block mit Offset 0 liegt sein Buddy bei Offset 8.192. Bei einem 16-KB-Block mit Offset 16.384 liegt sein Buddy bei 0.
Wird der erste 8-KB-Block freigegeben, verschmilzt er mit seinem freien 8-KB-Buddy. Der entstehende 16-KB-Block kann mit seinem freien Buddy verschmelzen, danach kann der entstehende 32-KB-Block erneut verschmelzen. Das Verschmelzen endet, wenn der benötigte gleich große Buddy belegt ist. Dass zwei Blöcke benachbart und frei sind, reicht nicht aus, wenn sie keine Buddies aus derselben Teilung sind.
Eine Anforderung von 20 KB erhält 32 KB, wobei 12 KB oder 37,5 % des Blocks verschwendet werden. Linux kombiniert einen Buddy-Allokator für physischen Speicher mit Objektcaches nach dem Slab-Prinzip. Allokatoren für Programme mit mehreren Threads verwenden außerdem lokale Caches oder mehrere Arenen, um das Warten auf eine gemeinsame Zuweisungssperre zu verringern; glibc, jemalloc und tcmalloc nutzen Varianten dieser Ideen.
Wichtige Erkenntnisse
- Nach Größenklassen getrennte Freispeicherlisten beschleunigen Anforderungen häufiger Größen.
- Slab-Caches verwenden wiederkehrend benötigte Objekttypen erneut und können wiederholte Initialisierung vermeiden.
- Der Buddy-Allokator rundet auf Zweierpotenzen auf und teilt in gleiche Hälften.
- Der Buddy wird mit dem bereichsrelativen Offset XOR der Blockgröße bestimmt.
- Auf jeder Ebene kann nur ein freier, gleich großer Buddy verschmolzen werden.
- Das Aufrunden auf Zweierpotenzen nimmt interne Fragmentierung für eine einfache Verwaltung in Kauf.
Adressräume und Adressübersetzung: Beispiele
1Verfolge einen Zeiger durch eine private Speichersicht
8 Schritte
- 1EinführungEin Rechner, mehrere Speichersichten
Mehrere Prozesse können im physischen Speicher bleiben, während die CPU zwischen ihnen wechselt. Jeder benötigt eine stabile Sicht auf seine eigenen Befehle und Daten, auch wenn andere Prozesse starten, enden oder verschoben werden.
Ein Adressraum stellt diese Sicht bereit. Der Zeiger, den ein Prozess verwendet, gehört zu dieser Sicht; Betriebssystem und Hardware bestimmen, welches physische Byte der Zugriff erreicht.
- 2ErklärungPlatziere die Inhalte des Programms
Code speichert Befehle, statische Daten speichern globale und statische lokale Variablen, der Heap speichert angeforderte Blöcke und der Stack speichert den Zustand von Funktionsaufrufen.
In der klassischen Anordnung wachsen Heap und Stack von unten beziehungsweise oben in eine Lücke hinein. Bei binären Größen bedeutet 1 KB 1.024 Bytes, auch 1 KiB genannt. Ein 16-KB-Adressraum enthält die Bytes 0 bis 16.383; seine obere Grenze ist selbst kein Byte des Adressraums.
- 3BeispielGib derselben Zahl zwei Bedeutungen
Prozess A und Prozess B verwenden beide die virtuelle Adresse 1.000. Die Übersetzung von A kann sie zu einem physischen Byte führen, die von B zu einem anderen. Der Zahlenwert des Zeigers allein bezeichnet keinen gemeinsam genutzten Speicher.
Ebenso zeigt die Ausgabe von
&xeine virtuelle Adresse. Wird der zugrunde liegende physische Speicher verschoben, muss sich der Zeigerwert, den das Programm sieht, nicht ändern. - 4HerausforderungDeute zwei ausgegebene Zeiger
Zwei Prozesse geben denselben Zeigerwert aus. Müssen sie dasselbe physische Byte lesen? Erkläre, was das Ziel bestimmt.
---reveal---
Nein. Jeder ausgegebene Zeiger wird im Adressraum seines eigenen Prozesses interpretiert. Die aktive Abbildung bestimmt das physische Ziel, sodass gleiche virtuelle Zahlen unterschiedliche Bytes erreichen können. Dasselbe physische Byte würde Abbildungen erfordern, die tatsächlich Speicher gemeinsam nutzen.
- 5ErklärungÜbersetze schnell und setze Grenzen durch
Transparenz bewahrt die private Sicht des Programms. Zeiteffizienz hält den Aufwand für die Übersetzung bei jedem Abruf sowie jedem Lese- und Schreibzugriff gering. Speichereffizienz begrenzt Metadaten und verschwendete Kapazität. Schutz verhindert unberechtigte Zugriffe.
Die Hardware übernimmt die gewöhnliche Übersetzung. Das Betriebssystem richtet die Abbildungen ein, wählt Platzierungen und übernimmt die Kontrolle, wenn ein Zugriff eine Ausnahme auslöst.
- 6HerausforderungTrenne die Entscheidung von der Operation
Eine Speicherverwaltung wählt statt der ersten geeigneten Lücke nun die kleinste geeignete Lücke. Die Hardware prüft weiterhin die Grenze und addiert eine Basis. Hat sich der Mechanismus oder die Strategie geändert?
---reveal---
Die Platzierungsstrategie hat sich geändert: Sie trifft nun eine andere Wahl zwischen Lücken. Der Übersetzungsmechanismus ist unverändert. Dieselbe Hardware kann einen Bereich übersetzen und schützen, unabhängig davon, welche geeignete Lücke das Betriebssystem gewählt hat.
- 7HerausforderungVerbinde Verschiebung und Schutz
Ein Betriebssystem verschiebt einen angehaltenen Prozess in einen neuen physischen Bereich, erhält aber seine Zeigerwerte. Warum erfordert das sowohl Übersetzung als auch Kontrolle darüber, wer die Abbildung ändern darf?
---reveal---
Die Übersetzung leitet die unveränderten virtuellen Zeiger auf die kopierten Bytes um. Nur privilegierter Betriebssystemcode darf die neue Abbildung einrichten; andernfalls könnte ein Prozess sich selbst in einen anderen Prozess oder den Kernel umlenken. Stabile Zeiger sorgen für Transparenz, während kontrollierte Abbildungen die Isolation bewahren.
- 8ZusammenfassungInterpretiere den Zeiger in seinem Prozess
Ein Adressraum organisiert Code und Daten eines Prozesses unabhängig von der physischen Platzierung. Ein virtueller Zeiger erhält seine Bedeutung durch die Abbildung des laufenden Prozesses.
Bewerte einen Entwurf anhand der Sicht, die er bewahrt, seiner Zeit- und Speicherkosten und der Zugriffsgrenzen, die er durchsetzt. Strategien wählen Platzierungen; Mechanismen führen Übersetzung und Prüfungen aus.
2Verfolge eine Heap-Anforderung durch Bibliothek und Betriebssystem
12 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungBehalte beide Zuständigkeiten im Blick
- 2BeispielUnterscheide einen Zeiger vom zugewiesenen Speicher
- 3ErklärungFordere genügend Bytes an
- 4HerausforderungSage voraus, was eine Rückkehr überdauert
- 5ErklärungGib nur einen aktiven Block frei
- 6KernaussageEin zulässiger Bereich kann einen unzulässigen Objektzugriff enthalten
- 7ErklärungVergrößere einen Bereich und teile ihn dann auf
- 8HerausforderungBestimme die neu hinzugefügten Bytes
- 9VerbindungGib einen Block frei oder entferne eine Speicherabbildung
- 10HerausforderungErkläre einen gleichbleibenden Speicherverbrauch
- 11HerausforderungVerfolge das Wachstum bei Segmentierung
- 12ZusammenfassungVerfolge Lebensdauer und Zuordnung getrennt
3Führe einen Prozess mit Basis- und Grenzregister aus und verschiebe ihn
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungBeschreibe einen physischen Platz
- 2ErklärungWende die Größenkonvention an
- 3BeispielÜbersetze Befehlsabruf und Lesezugriff
- 4HerausforderungPrüfe das letzte Byte
- 5TippErkenne die andere Grenzregisterkonvention
- 6ErklärungSchütze die Übersetzungsregister
- 7BeispielWechsle die Speichersicht mit dem CPU-Zustand
- 8HerausforderungFinde eine fehlende Wiederherstellung
- 9VerbindungVerschiebe einen Prozess, ohne Zeiger umzuschreiben
- 10BeispielBestimme die ungenutzte Spanne
- 11HerausforderungWähle die Einheit der Relokation
- 12HerausforderungBehalte die Grenze nach einer Verschiebung bei
- 13ZusammenfassungHalte Koordinaten und Kosten auseinander
4Übersetze Code, Heap und einen nach unten wachsenden Stack
17 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungTeile den Prozess in logische Bereiche auf
- 2ErklärungRichte die Beispielanordnung ein
- 3BeispielBestimme den Heap-Offset
- 4HerausforderungPrüfe ein Byte außerhalb des Heaps
- 5ErklärungWähle Register mit Selektorbits aus
- 6BeispielBestimme beide Felder
- 7HerausforderungUnterscheide Kapazität und aktuelle Nutzung
- 8VerbindungLasse den Zugriff ein Segment auswählen
- 9ErklärungMiss von einer oberen Grenze nach unten
- 10BeispielBerechne einen vorzeichenbehafteten Stackversatz
- 11HerausforderungBerücksichtige das niedrigste Byte des Stacks
- 12KernaussagePrüfe die Berechtigung nach dem Finden einer gültigen Stelle
- 13ErklärungStelle mehr als eine Basis wieder her
- 14HerausforderungBetrachte Lücken getrennt
- 15VerbindungVerstehe die Grenzen der Kompaktierung
- 16HerausforderungVerschiebe ein gemeinsam genutztes Segment sicher
- 17ZusammenfassungBestimme den Bereich vor dem physischen Byte
5Verfolge freie Blöcke und wähle Zuweisungsstrategien
18 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungFrag dich, ob ein Block ausreicht
- 2BeispielBestimme, wo ungenutzte Kapazität liegt
- 3HerausforderungPrüfe, was Verschmelzen erreichen kann
- 4VerbindungErkläre, warum Zeiger auf aktive Blöcke die Kompaktierung des Heaps verhindern
- 5ErklärungBerücksichtige den Verwaltungskopf
- 6BeispielPlatziere drei Blöcke
- 7BeispielPrüfe den verbleibenden Bereich
- 8HerausforderungBestimme Verwaltungskopf und Bereich
- 9ErklärungLasse die Strategie den Block auswählen
- 10BeispielVergleiche die erste Wahl
- 11HerausforderungBlicke auf die nächste Anforderung
- 12VerbindungMache häufige Anforderungen vorhersehbar
- 13BeispielRunde einen Buddy-Bereich auf und teile ihn auf
- 14ErklärungFinde den passenden Buddy
- 15HerausforderungVerfolge die Verschmelzungsebenen
- 16KernaussageBezahle die Einfachheit innerhalb des Blocks
- 17HerausforderungTrenne Platzierung und Verschmelzen
- 18ZusammenfassungVerfolge Anordnung und Speicherbilanz
Adressräume und Adressübersetzung: Übungsaufgaben
Set 1: Adressräume, Prüfungen und erste Speicherzuweisungen10 Übungsfragen
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Frage 4 von 10
sizeof(p), wenn p die Adresse des zugewiesenen Speichers enthält?Antwort zeigenAntwort ausblenden
sizeof(p) ist das Zeigerobjekt, dessen Größe mit 8 Bytes angegeben ist. Der zugewiesene Speicher umfasst 48 Bytes, aber sizeof folgt keinem Zeiger, um einen Heap-Block zu messen. Wer hier die Größe des zugewiesenen Speichers verwendet, verwechselt den Zeiger mit seinem Ziel.Frage 5 von 10
Antwort zeigenAntwort ausblenden
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Set 2: Prozesswechsel und Platzierungsentscheidungen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
sbrk(4096) erweitert den Heap. Wie lauten der Rückgabewert und der neue Program Break?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 3: Grenzen und Allokatorzustand verfolgen10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 4: Fehler erklären und Abbildungen bewahren10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 5: Adressübersetzung und Speicherzuweisung gemeinsam analysieren10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
sbrk(1500) erfolgreich. Was gibt sbrk zurück und wo liegt nun das erste bereits vorhandene Byte des Heaps?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
Adressraum
Aktive Speicherbelegung
Anonyme Speicherabbildung
mmap-Anforderung einen großen separaten Bereich erhalten.Arena
Aufrundung der Zuweisungsgröße
Ausnahmebehandlungsroutine
B
Basisregister
Best-Fit-Verfahren
Bitmaske
va & 0x0FFF behält zwölf untere Bits und setzt die beiden Segmentbits einer 14-Bit-Adresse auf null.Blockaufteilung
brk
brk setzt die Endgrenze des traditionellen Heaps auf eine angegebene Adresse und erlaubt dem Allokator so, die Größe dieses Bereichs zu ändern.Buddy
Buddy-Allokator
C
calloc
calloc ist eine Speicherverwaltungsfunktion der C-Bibliothek, die mit Nullbytes gefüllten Speicher bereitstellt, anders als der nicht gelöschte Speicher, den malloc zurückgibt.calloc erhaltener Puffer enthält zunächst Nullbytes, bevor das Programm Daten schreibt.D
Doppelte Speicherfreigabe
free über kopierte Zeiger auf denselben Block können den Zustand der Speicherverwaltungsbibliothek beschädigen.Dünn besetzter Adressraum
Dynamische Relokation
E
Eingebetteter Freispeicherlistenknoten
Externe Fragmentierung
F
Feingranulare Segmentierung
First-Fit-Verfahren
free
free ist eine C-Bibliotheksfunktion, die einen aktiven zugewiesenen Block an den Allokator zurückgibt; die Übergabe von NULL ist erlaubt und hat keine Wirkung.Freispeicherliste
Freispeicherverwaltung
G
Gemeinsame Codenutzung
Grenzregister
Grobgranulare Segmentierung
H
Hängender Zeiger
Heap
I
Implizite Segmentauswahl
Interne Fragmentierung
Isolation
K
Kompaktierung
Kontextwechsel
L
Lese- und Ausführungsberechtigung
Lesen nicht initialisierter Daten
malloc.M
malloc
malloc ist eine C-Bibliotheksfunktion, die einen Block einer angegebenen Größe in Bytes anfordert und dessen Adresse oder bei Fehlschlag NULL zurückgibt, ohne seinen Inhalt zu löschen.malloc(6 * sizeof(int)), um die Elementgröße zu berücksichtigen.Mechanismus
munmap
munmap entfernt einen abgebildeten Bereich aus dem Adressraum eines Prozesses, anders als die Rückgabe eines kleinen Blocks an einen Bibliotheksvorrat zur Wiederverwendung.N
Nach Größenklassen getrennte Freispeicherlisten
Negativer Versatz
Next-Fit-Verfahren
Nutzdaten
O
Offsetfeld
P
Physische Adresse
Privilegierter Befehl
Program Break
Prozesskontrollblock
Pufferüberlauf
R
realloc
realloc ist eine C-Bibliotheksfunktion, die die Größe eines zugewiesenen Blocks ändert und seinen Inhalt in einen Block an einer anderen Adresse verschieben kann.S
sbrk
sbrk verändert den Program Break um einen angeforderten Zuwachs in Bytes und gibt bei Erfolg den alten Program Break zurück; ein Zuwachs von null meldet den aktuellen Wert.sbrk(1024) bei Program Break 20.000 gibt 20.000 zurück und hinterlässt den neuen Program Break bei 21.024.Schutzbits
Segment
Segmentierung
Segmentoffset
Segmentselektor
01 den Heap auswählen, während die unteren zwölf Bits sein Byte bestimmen.Segmenttabelle
Slab-Allokator
Speicherleck
Speicherverwaltungseinheit
Speicherzugriffsverletzung
Sperrkonkurrenz
Stack
Statische Relokation
Statischer Speicher
Strategie
T
Transparenz
V
Verschmelzen freier Blöcke
Verwaltungskopf
free verarbeiten kann.Virtuelle Adresse
W
Wachstumsrichtung
Worst-Fit-Verfahren
Z
Zusammenhängender Speicherbereich
Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.