Einheit 4: Speicher
Lernziele und Kompetenzen
Grundlagen von Adressräume und Speichervirtualisierung kennen lernen
Unterschiedliche Adressierung von Programminstruktionen, Heap und Stack verstehen
Früher war alles viel einfacher:
Das Betriebssystem war vollständig im Hauptspeicher präsent
Ein laufendes Programm (= Prozess) konnte den Rest des Speichers nutzen
Dadurch was alles sehr einfach zu programmieren
Beispiel
Betriebssystem im Speicherbereich 0KB bis 64KB
Das laufende Programm nutzt den gesamten restlichen Speicher ab 64KB
Adressräume
Jeder Prozess hat einen eigenen Speicherinhalt.
Im Beispiel zuvor passt dieser der gesamte Rest des verfügbaren Speichers.
Gibt es mehr Prozesse, muss der Speicher aufgeteilt
werden, d.h. jeder Prozess erhält einen eigenen Speicherbereich, seinen sog. Adressraum (engl. address space).
Auf diesen Adressraum hat nur der Prozess selbst Zugriff, kein anderer Prozess kann auf diesen Adresraum zugreifen
Genügt der Speicher nicht, muss bei jedem Context Switch der Adressraum weggespeichert und später neu geladen werden
Adressräume sind also einfach zu verwendende Abstraktionen des Speichers
Ein Adressraum beinhaltet alle Bestandteile des laufenden Programms
Aufbau von Adressräumen
Code
Einfach zu laden, da nicht veränderbar
Stack und Heap
Wachsen und schrumpfen
Durch entgegengesetzte Anordnung ist dies gleichzeitig möglich
Heap wächst "positiv"
Stack wächst "negativ"
Da der Prozess nicht weiß, dass er sich in einem Adressraum befindet, »denkt« er wurde bei Adresse 0 KB in den Speicher geladen
Für jeden Prozess beginnt der Adressraum an Adresse 0 KB
Allerdings liegt der Prozess dabei jedoch wo ganz anders
Hier sprechen wir von einer sog. virtuellen Adresse (engl. virtual address)
Bei jedem Zugriff auf eine Variable, eine Konstante oder bei Laden eines Befehls aus dem Code-Segment muss daher die Adress in die echte physikalische Adresse umgerechnet werden.
Speicherarten: Stack
Wird implizit (automatisch) reserviert
Compiler reserviert für die Variable
x
entsprechend Speicher auf dem StackSpeicher wird bei Aufruf von
func
alloziert und beim Verlassen der Routine wieder freigegeben
Speicherarten: Heap
Speicher muss explizit durch Entwickler alloziert werden
Hinweis: Compiler reserviert Speicher für Pointer, z.B.
int *x
, auf dem StackProzess fordert Speicher auf dem Heap für ein Integer an
Zurück kommt die Speicheradresse, an der der Integer Wert auf dem Heap liegt
Speicher reservieren
Speicher für eine Fließkommazahl reservieren:
Compiler kennt die Größe des Datentyps
Sie auch? 8 Byte, 32-Bit Fließkommazahlen, war schon dran, oder?
Array für 10 Integer-Werte reservieren
Speicher freigeben
Allozierten Speicher wieder freigeben
malloc, free und das Betriebssystem
malloc
undfree
beziehen sich immer nur auf den virtuellen Adressraum eines ProzessesAuch wenn ein Speicherleck gebaut wird und der gesamte Heap voll läuft gilt:
Das Betriebssystem holt sich nach Prozessende den gesamten Speicher zurück
Kann aber Probleme bei langlaufenden Prozessen (Web Server o.ä. machen)
Viel größeres Problem wenn im Betriebssystem selbst ein Speicherleck enthalten ist
malloc
undfree
sind selbst keine SysCallsbrk
undsbrk
sind SysCalls zum Ändern des Heapsmmap
zum Erzeugen eines neuen Speicher-Mappings in den virtuellen Adressraum
Ziele der Speichervirtualisierung mittels Adressräumen
Transparenz: Der Prozess weiß nichts von seinem Glück und denkt er greift auf physikalischen Speicher zu
Effizienz: In Bezug auf Speicher- als auch Zeit (z.B. unterstützt durch Hardware-Features)
Sicherheit: Prozess müssen voreinander geschützt sein
Typische Fehler beim Umgang mit Speicher
Was könnte beim Verwalten von Speicher schon schiefgehen?
Vergessen Speicher zu reservieren
▶ Resultiert in sog. » Segmentation Fault«
Korrekt wäre:
Nicht genügend Speicher reserviert
Das kann laufen, kann aber auch abstürzen
Je nachdem ob
malloc
hier ggf. ein Byte mehr alloziertVerlassen sollten Sie sich darauf allerdings nicht… 🙈
Speicher reserviert, aber vergessen zu initialisieren
Egal ob initialisiert oder nicht, es wird auf jeden Fall etwas aus dem Speicher gelesen
Und zwar das was vorher drin war 😲
Nennt sich dann »Uninitialized Read«
Speicher nicht freigegeben
Ein Klassiker
Hatten wir schon einmal zu Beginn der Vorlesungsreihe
Herzlichen Glückwunsch, Sie haben ein Speicherleck (engl. memory leak) gebaut 🤦♂️
Kann man auch bei höheren Programmiersprachen erreichen, indem Referenzen nicht »aufgeräumt« wer
Speicher freigegeben obwohl er noch benötigt wird
Klingt schon so, als wäre das keine gute Idee
Nennt sich »Dangling Pointer«
GGf. noch benötigte Daten können ab dann durch erneutes
malloc
überschreiben werdenMan sollte denken, das sollte kein Unterschied machen
Ergebnis ist allerdings nicht exakt definiert
Nennt sich »Double Free«
Immer wieder gut, um die zugrundeliegenden Bibliotheken zur Speicherverwaltung maximal zu verwirren 😵
Speicher mehrfach freigeben
Man sollte denken, das sollte kein Unterschied machen
Ergebnis ist allerdings nicht exakt definiert
Nennt sich »Double Free«
Immer wieder gut, um die zugrundeliegenden Bibliotheken zur Speicherverwaltung maximal zu verwirren 😵
Aufgabe 1: Stack
Teilaufgabe 1
Schreiben Sie ein Java Programm, dass schnellstmöglich einen StackOverflowError liefert.
Teilaufgabe 2
Recherchieren Sie, wie sich das Verhalten Ihres Programmes beeinflussen lässt, in dem Sie mehr Stack zur Verfügung stellen. Rufen Sie das Programm so auf, dass es ca. die doppelte Zeit läuft, bis der StackOverflowError auftritt.
Aufgabe 2: Heap
Teilaufgabe 1
Schreiben Sie ein Java Programm, das schnellstmöglich eine OutOfMemoryError generiert.
Teilaufgabe 2
Recherchieren Sie, wie sich das Verhalten Ihres Programmes beeinflussen lässt, in dem Sie mehr Stack zur Verfügung stellen. Rufen Sie das Programm so auf, dass es ca. die doppelte Zeit läuft, bis der OutOfMemoryError auftritt.
Hausaufgabe
Bearbeiten Sie die Programmieraufgabe 1 (Lab 01: Stack)
Last updated