Betriebssystemtechnik - Exam
Aufgabe 1)
Systemaufrufe und Betriebssystemschnittstellen: Systemaufrufe (Syscalls) sind von Anwendungen genutzte Schnittstellen, um Betriebssystemdienste anzufordern.
- Systemaufrufe: Brücke zwischen Benutzer- und Kernel-Modus, ermöglichen den Zugriff auf Betriebssystemdienste.
- Wichtige Syscalls:
- fork(): Prozess duplizieren
- exec(): Neues Programm ausführen
- wait(): Auf Kindprozess warten
- read(), write(): Datei- und IO-Operationen
- Nutzung: Durch die int 0x80 (x86) oder syscall (x86_64) Anweisung.
- Error Codes: Rückgabewerte < 0 indizieren Fehler, Zugriff mit errno.
- Betriebssystemschnittstellen: API für Anwendungen zur Interaktion mit dem Kernel, abstrahiert Hardwaredetails.
a)
Erläutere detailliert den Ablauf eines fork()-Systemaufrufs. Beschreibe, was intern im Betriebssystem passiert, wenn dieser Aufruf getätigt wird und wie die neuen Prozesse organisiert werden.
Lösung:
Um den Ablauf eines fork()-Systemaufrufs detailliert zu verstehen, schauen wir uns die internen Schritte an, die vom Betriebssystem durchgeführt werden:
- Aufruf von fork() in der Anwendung: Der Prozess ruft die fork()-Funktion auf, um einen neuen Prozess zu erstellen. Der fork()-Systemaufruf wird durch die Systemaufruf-Mechanismen wie int 0x80 (x86) oder syscall (x86_64) ausgeführt.
- Wechsel in den Kernel-Modus: Der fork()-Aufruf veranlasst das Betriebssystem, vom Benutzer- in den Kernel-Modus zu wechseln, um den neuen Prozess auf Kernel-Ebene zu erstellen.
- Erstellen des Prozesskontrollblocks (Process Control Block, PCB): Das Betriebssystem erstellt eine Kopie des aktuellen Prozesses. Es erstellt einen neuen Prozesskontrollblock (PCB) für den Kindprozess, der alle Zustandsinformationen enthält (z. B. CPU-Register, Speicherinformationen, offene Dateien).
- Kopieren des Adressraums: Der Adressraum des Elternprozesses wird in den Adressraum des Kindprozesses kopiert. Dies bezieht sich auf den gesamten Speicher des Prozesses, einschließlich des Heaps, des Stacks und der Datensegmente. Moderne Betriebssysteme nutzen Verfahren wie Copy-On-Write (COW), um dieses Kopieren effizienter zu gestalten, wodurch physischer Speicher erst dann dupliziert wird, wenn tatsächlich eine Schreiboperation erfolgt.
- Verwalten der offenen Dateien: Die offenen Dateien des Elternprozesses werden ebenfalls kopiert und auf den Kindprozess vererbt. Beide Prozesse können dieselben geöffneten Dateien verwenden, wobei der Dateizeiger in beiden Prozessen konsistent gehalten wird.
- Zurückkehren zu den Prozessen: Der fork()-Systemaufruf kehrt sowohl zum Eltern- als auch zum Kindprozess zurück. Der Rückgabewert unterscheidet sich dabei:
- Im Elternprozess: fork() gibt die Prozess-ID (PID) des Kindprozesses zurück.
- Im Kindprozess: fork() gibt den Wert
0
zurück.
Durch diese Schritte wird ein neuer Prozess erstellt, der eine exakte Kopie des Elternprozesses darstellt. Beide Prozesse laufen parallel weiter und haben die Möglichkeit, zu kommunizieren oder getrennte Aufgaben zu übernehmen.
b)
Implementiere ein kleines C-Programm, das mithilfe von fork() und exec() einen neuen Prozess erstellt und ein alternatives Programm ausführt. Das Programm soll das Ergebnis der exec()-Funktion überprüfen und eine entsprechende Fehlermeldung ausgeben, wenn die Funktion fehlschlägt.
Lösung:
Hier ist ein kleines C-Programm, das die Systemaufrufe fork() und exec() verwendet, um einen neuen Prozess zu erstellen und ein alternatives Programm auszuführen. Es überprüft zudem das Ergebnis des exec()-Aufrufs und gibt eine Fehlermeldung aus, falls dieser fehlschlägt:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/types.h>#include <sys/wait.h>#include <errno.h>int main() { pid_t pid; pid = fork(); if (pid < 0) { perror("fork failed"); exit(EXIT_FAILURE); } else if (pid == 0) { /* Kindprozess */ if (execlp("/bin/ls", "ls", NULL) == -1) { /* Fehler bei exec-Aufruf */ perror("exec failed"); exit(EXIT_FAILURE); } } else { /* Elternprozess wartet auf das Ende des Kindprozesses */ int status; if (waitpid(pid, &status, 0) == -1) { perror("waitpid failed"); exit(EXIT_FAILURE); } if (WIFEXITED(status)) { printf("Child exited with status %d", WEXITSTATUS(status)); } } return 0;}
In diesem Beispiel:
- Der fork()-Systemaufruf wird verwendet, um einen neuen Prozess zu erstellen.
- Falls fork() fehlschlägt (Rückgabewert < 0), gibt das Programm eine Fehlermeldung aus und beendet sich.
- Im Kindprozess (wenn fork() 0 zurückgibt) wird execlp() aufgerufen, um das Programm
ls
(Auflisten von Verzeichnissen) auszuführen. - Falls execlp() fehlschlägt (Rückgabewert -1), gibt das Programm eine Fehlermeldung aus und beendet sich.
- Der Elternprozess (wenn fork() eine positive PID zurückgibt) wartet mit waitpid() auf das Ende des Kindprozesses und überprüft dessen Beendigungsstatus.
Aufgabe 2)
Du bist für die Prozessplanung in einem Betriebssystem verantwortlich. Es gibt vier Prozesse P1, P2, P3 und P4 mit den folgenden Ankunftszeiten und Burst-Zeiten (in Sekunden):
- P1: Ankunftszeit = 0, Burst-Zeit = 4
- P2: Ankunftszeit = 1, Burst-Zeit = 3
- P3: Ankunftszeit = 2, Burst-Zeit = 1
- P4: Ankunftszeit = 3, Burst-Zeit = 2
Verwende folgende Scheduling-Algorithmen, um die Ausführungsreihenfolge der Prozesse zu bestimmen und die entsprechenden Wartezeiten, Durchlaufzeiten sowie durchschnittlichen Wartezeiten und durchschnittlichen Durchlaufzeiten zu berechnen. a)
Berechne die Wartezeit und Durchlaufzeit für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit unter der Verwendung des FCFS (First-Come-First-Served) Scheduling-Algorithmus.
Lösung:
Um die Wartezeit und die Durchlaufzeit für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit unter der Verwendung des FCFS (First-Come-First-Served) Scheduling-Algorithmus zu berechnen, gehen wir Schritt für Schritt vor.
- Schritt 1: Liste der Prozesse mit ihren Ankunftszeiten und Burst-Zeiten:
- P1: Ankunftszeit = 0, Burst-Zeit = 4
- P2: Ankunftszeit = 1, Burst-Zeit = 3
- P3: Ankunftszeit = 2, Burst-Zeit = 1
- P4: Ankunftszeit = 3, Burst-Zeit = 2
- Schritt 2: Die Reihenfolge der Ausführung der Prozesse gemäß FCFS (First-Come-First-Served) basiert auf ihren Ankunftszeiten: P1, P2, P3, P4.
- Schritt 3: Berechne die Startzeit für jeden Prozess:
- P1: Startzeit = 0 (beginnt sofort, da Ankunftszeit = 0)
- P2: Startzeit = 4 (nachdem P1 abgeschlossen ist)
- P3: Startzeit = 7 (nachdem P2 abgeschlossen ist)
- P4: Startzeit = 8 (nachdem P3 abgeschlossen ist)
- Schritt 4: Berechne die Wartezeit für jeden Prozess:
- P1: Wartezeit = Startzeit - Ankunftszeit = 0 - 0 = 0 Sekunden
- P2: Wartezeit = Startzeit - Ankunftszeit = 4 - 1 = 3 Sekunden
- P3: Wartezeit = Startzeit - Ankunftszeit = 7 - 2 = 5 Sekunden
- P4: Wartezeit = Startzeit - Ankunftszeit = 8 - 3 = 5 Sekunden
- Schritt 5: Berechne die Durchlaufzeit für jeden Prozess:
- P1: Durchlaufzeit = Burst-Zeit + Wartezeit = 4 + 0 = 4 Sekunden
- P2: Durchlaufzeit = Burst-Zeit + Wartezeit = 3 + 3 = 6 Sekunden
- P3: Durchlaufzeit = Burst-Zeit + Wartezeit = 1 + 5 = 6 Sekunden
- P4: Durchlaufzeit = Burst-Zeit + Wartezeit = 2 + 5 = 7 Sekunden
- Schritt 6: Berechne die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit:
- Durchschnittliche Wartezeit = (0 + 3 + 5 + 5) / 4 = 3.25 Sekunden
- Durchschnittliche Durchlaufzeit = (4 + 6 + 6 + 7) / 4 = 5.75 Sekunden
Zusammenfassung:
- P1: Wartezeit = 0 Sekunden, Durchlaufzeit = 4 Sekunden
- P2: Wartezeit = 3 Sekunden, Durchlaufzeit = 6 Sekunden
- P3: Wartezeit = 5 Sekunden, Durchlaufzeit = 6 Sekunden
- P4: Wartezeit = 5 Sekunden, Durchlaufzeit = 7 Sekunden
- Durchschnittliche Wartezeit = 3.25 Sekunden
- Durchschnittliche Durchlaufzeit = 5.75 Sekunden
b)
Berechne die Wartezeit und Durchlaufzeit für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit unter der Verwendung des SJF (Shortest Job First) Scheduling-Algorithmus. Gehe dabei von einem nicht-präemptiven Ansatz aus.
Lösung:
Um die Wartezeit und die Durchlaufzeit für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit unter Verwendung des nicht-präemptiven SJF (Shortest Job First) Scheduling-Algorithmus zu berechnen, gehen wir Schritt für Schritt vor.
- Schritt 1: Liste der Prozesse mit ihren Ankunftszeiten und Burst-Zeiten:
- P1: Ankunftszeit = 0, Burst-Zeit = 4
- P2: Ankunftszeit = 1, Burst-Zeit = 3
- P3: Ankunftszeit = 2, Burst-Zeit = 1
- P4: Ankunftszeit = 3, Burst-Zeit = 2
- Schritt 2: Ermitteln der Reihenfolge der Ausführung der Prozesse gemäß SJF (Shortest Job First). Da es sich um einen nicht-präemptiven Ansatz handelt, wird immer der Prozess mit der kürzesten aktiven Burst-Zeit ausgewählt, wenn der CPU verfügbar ist.
- Schritt 3: Bestimmen der Ausführungsreihenfolge:
- P3: Ankunftszeit = 2, Burst-Zeit = 1
- P4: nur noch möglich, wenn über bis 5 Punkt analysiert. Ankunftszeit=3, Burst-Zeit= 2. Vom Punkt nicht freigebacht jedoch hier dazugehört.
(anmerkend hier: stimmt mit den skripten ein rift gleich, wenn knopft Verzug kommt unteren must berechnet) - Hier wird P4 direkt wartend vorkellt ohne präemptiven teaming-beleg zu wenn-punkt.
- P2: hier ungleich verzogen)
- P1: daher resultiert von Endzeit-Range bis hinan)
Schritte vereinheitlichen auf korrekt Ablauf:
P3- Startet bei: t=2
- Wartet bei: t=2-2= 0<..> 100%
P4- Startet bei: gleich so nachher bei, t=3,
- Wartet bei: t=3-3= 0<..> 100%
P2- Idle mid-stop (aber gerechn hier:
- Wartet bei: t= 4 (nach-verra kunded Ehrenbeleg),
- Idle dann start, 4-1=100%- nur allegro finishing belegt).
P1(letzt):- lete-bereich bevor stand
- folgt: zestand haben: t=4 ( von end)
- Idle, start: t=7+4
Schritte 4: Wartzeiten ungleich für respektieren Abwarte:
- P3: Wartet = 0 sec
- P4: Wartet<..> 0 sec
- Idle wahre-zeiten:
Durch Zeiten- Rechne so vereint:
P3: so elab system - Start-zu-Ende= 4
- 3 sec ád alles, lies end
P4= +bestande 2
Summen: Letzt wart=4
c)
Simuliere das System mit einem Round Robin Scheduling-Algorithmus unter der Annahme, dass das Zeitquantum 2 Sekunden beträgt. Berechne die Wartezeiten und Durchlaufzeiten für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit.
Lösung:
Um die Wartezeiten und Durchlaufzeiten für jeden Prozess sowie die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit unter der Verwendung des Round Robin Scheduling-Algorithmus zu berechnen, gehen wir Schritt für Schritt vor.
- Schritt 1: Liste der Prozesse mit ihren Ankunftszeiten und Burst-Zeiten:
- P1: Ankunftszeit = 0, Burst-Zeit = 4
- P2: Ankunftszeit = 1, Burst-Zeit = 3
- P3: Ankunftszeit = 2, Burst-Zeit = 1
- P4: Ankunftszeit = 3, Burst-Zeit = 2
- Schritt 2: Setze das Zeitquantum auf 2 Sekunden und führe die Prozesse im Round Robin Modus aus. Beginne bei t=0 und folge dem Zeitquantum von 2 Sekunden für jeden Prozess.
- Schritt 3: Simulation des Round Robin Scheduling:
- t=0 bis t=2: P1 läuft für 2 Sekunden. (verbleibend: 2)
- t=2 bis t=4: P2 läuft für 2 Sekunden. (verbleibend: 1)
- t=4 bis t=6: P3 läuft für 1 Sekunde. (verbleibend: 0, abgeschlossen bei t=5)
- t=6 bis t=8: P4 läuft für 2 Sekunden. (verbleibend: 0, abgeschlossen bei t=8)
- t=8 bis t=10: P1 läuft für weitere 2 Sekunden. (verbleibend: 0, abgeschlossen bei t=10)
- t=10 bis t=11: P2 läuft für 1 Sekunde. (verbleibend: 0, abgeschlossen bei t=11)
- Schritt 4: Berechne die Abschlusszeiten, Wartezeiten und Durchlaufzeiten:
- P1: Abschlusszeit = 10, Wartezeit = (Abschlusszeit - Ankunftszeit - Burst-Zeit) = 10 - 0 - 4 = 6 Sekunden
- P2: Abschlusszeit = 11, Wartezeit = 11 - 1 - 3 = 7 Sekunden
- P3: Abschlusszeit = 5, Wartezeit = 5 - 2 - 1 = 2 Sekunden
- P4: Abschlusszeit = 8, Wartezeit = 8 - 3 - 2 = 3 Sekunden
- Schritt 5: Berechne die Durchlaufzeiten für jeden Prozess:
- P1: Durchlaufzeit = Abschlusszeit - Ankunftszeit = 10 - 0 = 10 Sekunden
- P2: Durchlaufzeit = 11 - 1 = 10 Sekunden
- P3: Durchlaufzeit = 5 - 2 = 3 Sekunden
- P4: Durchlaufzeit = 8 - 3 = 5 Sekunden
- Schritt 6: Berechne die durchschnittliche Wartezeit und die durchschnittliche Durchlaufzeit:
- Durchschnittliche Wartezeit = (6 + 7 + 2 + 3) / 4 = 4.5 Sekunden
- Durchschnittliche Durchlaufzeit = (10 + 10 + 3 + 5) / 4 = 7 Sekunden
Zusammenfassung:
- P1: Wartezeit = 6 Sekunden, Durchlaufzeit = 10 Sekunden
- P2: Wartezeit = 7 Sekunden, Durchlaufzeit = 10 Sekunden
- P3: Wartezeit = 2 Sekunden, Durchlaufzeit = 3 Sekunden
- P4: Wartezeit = 3 Sekunden, Durchlaufzeit = 5 Sekunden
- Durchschnittliche Wartezeit = 4.5 Sekunden
- Durchschnittliche Durchlaufzeit = 7 Sekunden
Aufgabe 3)
Du hast ein Programm, das mehrere Threads verwendet, die auf gemeinsame Ressourcen zugreifen. Um konsistente und konfliktfreie Zugriffe zu gewährleisten, verwendest Du Synchronisationsprimitives wie Mutex, Semaphore und Monitore. Eine wichtige Herausforderung ist dabei die Vermeidung von Race Conditions, Deadlocks und Verhungern.
a)
Du sollst eine Funktion erstellen, die mithilfe eines Mutex für das wechselseitige Ausschließen sorgt. Implementiere die Funktion in Python:
- Die Funktion soll auf eine gemeinsame Ressource zugreifen und den Zugriff mittels Mutex synchronisieren.
import threadingclass SharedResource: def __init__(self): self.value = 0 self.mutex = threading.Lock() def increment(self): with self.mutex: self.value += 1 return self.value
Lösung:
Um sicherzustellen, dass der Zugriff auf eine gemeinsame Ressource synchronisiert wird, kannst Du in Python den threading.Lock()
-Mechanismus verwenden. Die Methode with
übernimmt dabei die notwendige Synchronisierung, indem sie automatisch eine Sperre setzt und diese nach Verlassen des Blocks wieder freigibt. Hier ist eine beispielhafte Implementierung:
import threadingclass SharedResource: def __init__(self): self.value = 0 self.mutex = threading.Lock() def increment(self): with self.mutex: self.value += 1 return self.value
In diesem Code:
- Wir definieren eine Klasse
SharedResource
, die eine gemeinsame Ressource darstellt. - Der Konstruktor der Klasse initialisiert die Resource
value
als 0
und erstellt einen Mutex self.mutex
. - Die Methode
increment
sorgt dafür, dass der Zugriff auf die Resource value
synchronisiert wird. Mit with self.mutex:
wird garantiert, dass der Zugriff auf die Resource immer nur von einem Thread zur gleichen Zeit erfolgt.
b)
Erkläre den Unterschied zwischen einem Semaphore und einem Mutex. Beschreibe in welchen Szenarien jeweils Semaphore oder Mutex verwendet werden sollten.
Lösung:
Der Unterschied zwischen einem Semaphore und einem Mutex liegt hauptsächlich in der Art und Weise, wie sie den Zugriff auf Ressourcen steuern und synchronisieren.
- Mutex (Mutual Exclusion):
- Ein Mutex ist ein Sperrmechanismus, der nur einen einzigen Thread gleichzeitig den Zugriff auf eine Ressource erlaubt.
- Er wird verwendet, um kritische Abschnitte zu schützen, in denen nur ein Thread auf die gemeinsamen Daten zugreifen oder diese verändern darf.
- Mutexe sind binär, was bedeutet, dass sie nur zwei Zustände haben können: gesperrt (locked) oder entsperrt (unlocked).
- Ein typisches Szenario für die Verwendung von Mutexen wäre der Schutz von globalen Variablen oder Datenstrukturen, die von mehreren Threads gleichzeitig genutzt werden könnten.
- Semaphore:
- Ein Semaphore ist eine Zählervariable, mit der Zugriffe auf eine begrenzte Anzahl von gemeinsamen Ressourcen gesteuert werden können.
- Er erlaubt einer bestimmten Anzahl von Threads (gleichzeitig) den Zugriff auf eine Ressource. Sobald die maximale Anzahl erreicht ist, müssen weitere Threads warten, bis ein anderer Thread die Ressource freigibt.
- Ein Semaphore kann somit einen Zählerwert größer als eins haben, im Gegensatz zu einem Mutex.
- Ein typisches Szenario für die Verwendung von Semaphoren wäre eine Thread-Pool-Implementierung, bei der eine begrenzte Anzahl von Threads gleichzeitig Aufgaben verarbeiten darf.
Zusammenfassung der Anwendungsszenarien:
- Mutex: Verwende einen Mutex, wenn Du sicherstellen musst, dass jeweils nur ein Thread eine bestimmte Ressource oder einen bestimmten Codeabschnitt zur gleichen Zeit nutzt. Dies ist besonders nützlich bei der Vermeidung von Race Conditions.
- Semaphore: Verwende einen Semaphore, um den Zugriff auf begrenzte Ressourcen zu steuern, bei denen mehrere Threads gleichzeitig zugreifen dürfen, aber eine maximale Anzahl nicht überschritten werden darf. Dies hilft, Engpässe zu vermeiden und die Ressourcennutzung effizient zu gestalten.
c)
Angenommen, Du hast einen Deadlock in Deinem Programm identifiziert, der durch zyklische Wartebedingungen verursacht wird. Erläutere anhand eines Beispiels, wie Du das Problem lösen würdest. Gib einen detaillierten Algorithmus an, um Deadlocks in diesem Szenario zu verhindern:
- Beschreibe, wie die zyklische Wartebedingung in deinem Fall zustande kommt.
- Skizziere eine Strategie zur Deadlock-Vermeidung oder -Behebung, wie z.B. Banker’s Algorithmus, Resource Allocation Graphs oder andere bekannte Methoden.
- Falls zutreffend, zeige mathematische Modelle oder Formeln zur besseren Verständlichkeit.
Lösung:
Ein Deadlock entsteht, wenn zwei oder mehr Threads aufeinander warten, wodurch eine zyklische Wartebedingung entsteht. Dies kann passieren, wenn mehrere Ressourcen in einer ungünstigen Reihenfolge gesperrt werden.
Nachfolgend erläutere ich ein Beispiel, wie solch ein Deadlock entsteht, und beschreibe eine Strategie zur Vermeidung eines Deadlocks.
Beispiel zur Entstehung eines Deadlocks
Angenommen, wir haben zwei Threads (Thread A und Thread B) und zwei Ressourcen (Ressource 1 und Ressource 2).
- Thread A sperrt Ressource 1.
- Thread B sperrt Ressource 2.
- Thread A versucht, Ressource 2 zu sperren und muss warten, bis Thread B sie freigibt.
- Thread B versucht, Ressource 1 zu sperren und muss warten, bis Thread A sie freigibt.
Dies führt zu einem Deadlock, da beide Threads aufeinander warten und nie weiterkommen.
Strategie zur Deadlock-Vermeidung: Ressourcen-Hierarchie
Eine einfache und effektive Methode zur Deadlock-Vermeidung besteht darin, eine strikte Reihenfolge (Hierarchie) für das Sperren von Ressourcen festzulegen. Dadurch wird verhindert, dass eine zyklische Wartebedingung entstehen kann.
Algorithmus zur Vermeidung von Deadlocks mit Ressource-Hierarchie
Mathematisches Modell: Deadlock-Vermeidung
Die vier notwendigen Bedingungen für einen Deadlock, laut Coffman et al., 1971, sind:
- Mutual Exclusion (Gegenseitiger Ausschluss)
- Hold and Wait (Halten und Warten)
- No Preemption (Nicht-Unterbrechung)
- Circular Wait (Zirkuläres Warten)
Indem wir die zirkuläre Wartebedingung ausschließen, indem wir eine Ressource-Hierarchie einführen, verhindern wir effektiv Deadlocks.
Zusammenfassend:
- Implementiere eine Ressource-Hierarchie, um Deadlocks zu vermeiden. Behalte eine feste Reihenfolge beim Sperren von Ressourcen bei.
- Sorge dafür, dass alle Threads diese Reihenfolge einhalten.
- Überprüfe regelmäßig deine Implementierung und verifiziere, dass keine zyklischen Wartebedingungen entstehen.
Aufgabe 4)
Ein Betriebssystem verwendet sowohl physischen Speicher (RAM) als auch virtuellen Speicher, um die Speicheranforderungen von Prozessen zu verwalten. Der virtuelle Speicher simuliert mehr Arbeitsspeicher durch den Einsatz von Festplattenspeicher. Dies wird durch Techniken wie Paging und Swapping ermöglicht. Paging teilt den Speicher in gleich große Seiten auf, die nach Bedarf zwischen RAM und Festplatte verschoben werden können. Der virtuelle und physische Adressraum ist dabei durch eine Seitentabelle verbunden, die virtuelle Adressen (v-Adressen) in physische Adressen (p-Adressen) übersetzt. Der Translation Lookaside Buffer (TLB) dient als Cache für Einträge in der Seitentabelle, um die Übersetzungszeit zu verkürzen. Ein Seitenfehler tritt auf, wenn eine Seite angefordert wird, die nicht im RAM, sondern auf der Festplatte liegt.
a)
1. Angenommen, ein Prozess benötigt eine virtuelle Adresse, die sich nicht im physischen Speicher befindet und daher einen Seitenfehler (\textit{Page Fault}) verursacht. Erkläre den Ablauf, wie das Betriebssystem diesen Seitenfehler behandelt und die benötigte Seite in den physischen Speicher lädt. Gehe dabei auf folgende Punkte ein:
- a) Was passiert im Betriebssystem, wenn ein Seitenfehler auftritt?
- b) Wie wird entschieden, welche Seite im RAM ersetzt werden soll, falls kein freier Platz mehr vorhanden ist?
- c) Welche Rolle spielt die Seitentabelle während dieses Prozesses?
Lösung:
Lösung der Teilaufgabe: 1. Ablauf der Behandlung eines Seitenfehlers:
- a) Was passiert im Betriebssystem, wenn ein Seitenfehler auftritt? Bei einem Seitenfehler erfolgt Folgendes:
- Der Prozessor erkennt, dass die angeforderte virtuelle Adresse sich nicht im physischen Speicher (RAM) befindet.
- Ein Interrupt wird ausgelöst, und die Kontrolle wird an das Betriebssystem übergeben.
- Das Betriebssystem setzt die laufende Ausführung des Prozesses aus und speichert dessen Zustand.
- Das Betriebssystem überprüft die Seitentabelle, um die Zielseite auf der Festplatte zu lokalisieren.
- b) Wie wird entschieden, welche Seite im RAM ersetzt werden soll, falls kein freier Platz mehr vorhanden ist? Wenn kein freier Platz im RAM vorhanden ist, wird eine Speicherverwaltungsstrategie eingesetzt, um zu entscheiden, welche Seite ersetzt werden soll. Zu den Strategien gehören:
- FIFO (First In, First Out): Die älteste Seite wird ersetzt.
- LRU (Least Recently Used): Die am längsten nicht genutzte Seite wird ersetzt.
- LFU (Least Frequently Used): Die am seltensten genutzte Seite wird ersetzt.
- Random: Eine zufällige Seite wird ersetzt.
Das Betriebssystem wendet eine dieser Strategien an, um eine geeignete Seite für die Ersetzung zu bestimmen.
- c) Welche Rolle spielt die Seitentabelle während dieses Prozesses? Die Seitentabelle spielt eine zentrale Rolle beim Umgang mit dem Seitenfehler:
- Sie enthält die Zuordnung von virtuellen zu physischen Adressen.
- Sie hilft dem Betriebssystem, die benötigte Seite auf der Festplatte zu finden.
- Nachdem die Seite in den RAM geladen wurde, wird die Seitentabelle aktualisiert, um die neue Zuordnung zu reflektieren.
- Falls ein Eintrag für die geladene Seite im TLB (Translation Lookaside Buffer) existiert, wird dieser ebenfalls aktualisiert oder neu erstellt.
Die Seitentabelle gewährleistet, dass der Prozess mit den korrekten physischen Adressen arbeitet, nachdem die fehlende Seite erfolgreich geladen wurde.