💾
Betriebssysteme
  • Kursinformationen
    • Termine
  • Einheit 1: Git
    • Einheit 1: Hausaufgabe
  • Einheit 2: C Programmierung
    • Einheit 2: Hausaufgabe
  • Virtualization
    • Exercise: Process Creation
  • Speicher
    • Exercise: Memory
    • Lab 01: Stack
  • Scheduler
    • Exercise: Scheduler
  • Fortgeschrittene Scheduler
  • Fortgeschrittene Speicherverwaltung
    • Exkurs: Free List
    • Lab 02: Free List
  • Threads
    • Exkurs: Bugs durch Nebenläufigkeit
    • Exercise: Deadlock
  • Einheit 9: Semaphore
  • Einheit 10: Input / Ouput
  • Einheit 11: Harddisks & Dateisysteme
  • Einheit 12: Virtualisierung & Container Technologien
  • Einheit 13:
  • Lab 03: Semaphore
  • Lab 04: Canonical Treiber Implementierung
  • Page 1
Powered by GitBook
On this page
  • Lernziele und Kompetenzen
  • Früher war alles viel einfacher:
  • Adressräume
  • Aufbau von Adressräumen
  • Speicherarten: Stack
  • Speicherarten: Heap
  • Speicher reservieren
  • Speicher freigeben
  • Ziele der Speichervirtualisierung mittels Adressräumen
  • Typische Fehler beim Umgang mit Speicher
  • Nicht genügend Speicher reserviert
  • Speicher reserviert, aber vergessen zu initialisieren
  • Speicher nicht freigegeben
  • Speicher freigegeben obwohl er noch benötigt wird
  • Speicher mehrfach freigeben
  • Hausaufgabe
Edit on GitHub

Speicher

PreviousExercise: Process CreationNextExercise: Memory

Last updated 6 months ago

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 Stack

  • Speicher wird bei Aufruf von func alloziert und beim Verlassen der Routine wieder freigegeben

void func() {
  int x; // declares an integer on the stack
  ...
}

Speicherarten: Heap

  • Speicher muss explizit durch Entwickler alloziert werden

  • Hinweis: Compiler reserviert Speicher für Pointer, z.B. int *x, auf dem Stack

  • Prozess fordert Speicher auf dem Heap für ein Integer an

  • Zurück kommt die Speicheradresse, an der der Integer Wert auf dem Heap liegt

void func() {
  int *x = (int *) malloc(sizeof(int));
  ...
}

Speicher reservieren

Speicher für eine Fließkommazahl reservieren:

double *d = (double *) malloc(sizeof(double));
  • 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

int *x = malloc(10 * sizeof(int));

Speicher freigeben

Allozierten Speicher wieder freigeben

double *x = malloc(10 * sizeof(int));
…
free(x);

malloc, free und das Betriebssystem

  • malloc und free beziehen sich immer nur auf den virtuellen Adressraum eines Prozesses

  • Auch 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 und free sind selbst keine SysCalls

  • brk und sbrk sind SysCalls zum Ändern des Heaps

  • mmap 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

char *src = “hello world”;
char *dst;                // Speicher nicht reserviert
strcpy (dst, scr); 

▶ Resultiert in sog. » Segmentation Fault«

Korrekt wäre:

char *src = “hello world”;
char *dst = (char *) malloc(strlen(src) + 1);
strcpy (dst, scr); 

Nicht genügend Speicher reserviert

char *src = “hello world”;
char *dst = (char *) malloc(strlen(src)); // String um 1 Zeichen zu kurz
strcpy (dst, scr); 
  • Das kann laufen, kann aber auch abstürzen

  • Je nachdem ob malloc hier ggf. ein Byte mehr alloziert

  • Verlassen 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 werden

  • 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 😵

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 😵

Hausaufgabe

Bearbeiten Sie die Programmieraufgabe 1 ()

Lab 01: Stack