Ja, meine Damen und Herren, wir sind wieder in der Vorlesung für
Technische Informatik.
Ich darf Sie wieder herzlich grüßen.
Wir kommen heute zu einem neuen Abschnitt und zwar zu einem Abschnitt
über die virtuelle Speicherverwaltung.
Die virtuelle Speicherverwaltung ist ja eigentlich eine Aufgabe des
Betriebssystems und der Fokus, der hier in der Vorlesung gemacht wird
oder der uns interessiert, ist der, dass wir sagen, wie kann jetzt der
Prozessor des Betriebssystems bei dieser Aufgabe der virtuellen
Speicherverwaltung mit unterstützen.
Wir werden also grundsätzlich noch einmal wiederholen und darauf
eingehen, was die Prinzipien sind von Softwareseite und die Prinzipien
von der Hardwareseite bezüglich der virtuellen Speicherverwaltung.
Warum braucht man eigentlich eine virtuelle Speicherverwaltung?
Das Problem ist, wir werden es dann später noch einmal sehen, dass wir
Programme schreiben, dass mehrere Benutzer am Rechner sitzen, wir
verwenden Daten und unser Problem ist letztendlich, dass die Programme
beziehungsweise die Daten relativ groß sind.
So groß, dass sie im Allgemeinen nicht komplett in den
Arbeitsspeicher, in den Hauptspeicher passen, weil wir von der
Ökonomie hier ausgehen und sagen, wir bauen einen relativ großen
Hauptspeicher, aber wir nützen, wie wir gesehen haben bei den Caches,
die lokale, die zeitliche und räumliche Lokalität von Programmen aus
und versuchen möglichst nahe am Prozessor die Daten zu halten und die
Programmteile zu halten, die benötigt werden.
Die Speicherhierarchie, so wie wir sie kennengelernt haben, ist hier
noch einmal aufgelistet.
Wir haben zum einen den Prozessor.
Auf dem Prozessor befinden sich die Register.
Diese Speicherkomponenten sind am schnellsten zugreifbar und die Daten
für die Register werden aus dem Cache geladen, aus dem Cache auf der
ersten Hierarchiestufe der Cache-Speicher, also aus den Level-1-Caches
oder abgekürzt L1-Caches.
Und der Zugriff hierauf erfolgt üblicherweise mit Einzelzugriffen.
Und der Cache-Speicher auf der ersten Ebene wird bedient, falls die
Informationen nicht vorhanden sind, aus dem Cache aus der zweiten
Hierarchiestufe.
Und falls die Daten hier nicht vorhanden sind, dann wird aus dem
Hauptspeicher nachgeladen.
Und alle Informationen, die nicht im Hauptspeicher sind, sind auf der
Festplatte abgelegt.
Und bei Bedarf werden die Informationen aus dem Hauptspeicher,
Entschuldigung, aus dem Archiv oder von der Festplatte in den
Hauptspeicher geladen, von dort in den Cache-Speicher und von dort
dann in die Register.
Das erfolgt zum einen mit Blockzugriffen, also die Informationen im
Cache werden mithilfe von Blöcken geladen.
Denn wenn wir schon einmal auf ein langsameres Medium zugreifen, dann
sollen gleich mehr Informationen geladen werden.
Das gleiche Prinzip gilt zwischen Festplatte oder Hintergrundspeicher
und dem Hauptspeicher.
Auch von hier werden nicht einzelne Daten geholt, sondern Daten in
größeren Einheiten, in sogenannten Seiten, die je nach Einstellung im
Betriebssystem oder je nach Betriebssystem 512 bis 4K weit groß sein
können, beziehungsweise auch noch größer.
Wir haben jetzt eine ganze Reihe von gesehen, dass wir die
Informationen möglichst nahe am Prozess erhalten, die wir benötigen
und falls notwendig, wird in der Speicher-Hierarchie nach unten
gegangen und Informationen, Programmteile oder Daten aus den
Komponenten der nächstniederen Ebene nachgeladen.
Und hierzu ist jetzt nicht nur die Hardware notwendig, beispielsweise
die Cache-Steuerung übernimmt das Holen von Informationen aus dem
Hauptspeicher, aber wie wird jetzt das Ganze erledigt, um jetzt
beispielsweise Informationen aus dem Hintergrundspeicher in den
Hauptspeicher zu laden.
Und da ist jetzt auch wiederum das Betriebssystem zuständig.
Und deshalb schauen wir uns jetzt mal an, was eigentlich die Aufgabe
des Betriebssystems ist.
Gehen wir mal von der allgemeinen Definition aus, wie es auch in der
DIN -Norm festgelegt ist.
Ein Betriebssystem ist also nach dieser Definition folgendermaßen
definiert.
Ein Betriebssystem umfasst die Programme eines digitalen
Rechensystems, die zusammen mit den Eigenschaften der Rechenanlage,
die Grundlage der möglichen Betriebsarten des digitalen Rechensystems
bilden und insbesondere die Abwicklung von Programmen steuern und
überwachen.
Also wir haben hier sozusagen die Schnittstelle zwischen dem Benutzer,
der ein Programm geschrieben hat, dieses Programm zur Ausführung
bringen möchte, und auf der anderen Seite die Hardware, auf der dieses
Programm zur Ausführung kommen soll.
Und das Betriebssystem sorgt jetzt dafür, dass eben aus einem Programm
ein Prozess wird, der gestartet wird und dann auf der Hardware zur
Ausführung kommt.
Und alle nötigen Aufgaben, die dazu notwendig sind, das Programm
laufen zu lassen, wird dann dieses Betriebssystem erledigen.
Das heißt also, im Prinzip bildet oder ist das Betriebssystem etwas,
was so die Lücke zwischen dem Anwender und der Hardware schließt.
Wir wollen ja, wenn wir ein Programm zur Ausführung bringen wollen
oder wenn wir einen Rechner bedienen wollen, nicht mit der
Rechnerschnittstelle arbeiten.
Wir haben ja gesehen, wie relativ schwierig diese Schnittstelle zu
sehen ist.
Sie haben ja Assembler programmiert, also Programme in Assembler zu
schreiben ist relativ schwierig.
Dann Programme auch auf dem Rechner zur Ausführung zu bringen, wenn
man sich auf der ganz niederen Ebene befindet, ist auch sehr
schwierig.
Außerdem bringt es einige Nachteile mit.
Man kann vielleicht nur als ein Einzelner auf dieser Ebene arbeiten.
Also wir wollen mit dem Hilfe des Betriebssystems auf einer relativ
hohen Ebene arbeiten, wenn wir Programme starten wollen.
Und dann auch dafür, dann sollte das Betriebssystem dafür sorgen, dass
eben diese Programme zur Ausführung kommen.
Wir haben also zwischen der Anwendung und der Hardware diese, wie es
heißt, semantische Lücke, weil eben der Benutzer auf einer sehr hohen
Ebene arbeiten möchte, möglichst problemorientiert und die Hardware
auf der einen Seite elementare, primitive Operationen nur zur
Verfügung stellt.
Und das Betriebssystem, das sorgt eben dafür, diese semantische Lücke
mit zu verkleinern.
Im Prinzip erweitert es die Fähigkeiten der Hardware, wie wir dann
jetzt auch gleich noch sehen werden.
Das Betriebssystem hat jetzt eine ganze Reihe von Aufgaben, damit wir
Programme zur Ausführung bringen können.
Wir haben auf der einen Seite den Rechner, der eine ganze Menge von
Ressourcen bereitstellt und diese ganzen Betriebsmittel zu verwalten
ist eine Aufgabe des Betriebssystems.
Dann stehen eine Reihe von Aufträgen an, also beispielsweise die
Programme der Benutzer, die zur Ausführung gelangen sollen.
Das sind im Wesentlichen von Betriebssystemseite her Prozesse.
Diese Prozesse zu verwalten, dass sie zur Ausführung gelangen, ist
ebenfalls Aufgabe des Betriebssystems.
Und weiterhin ist die Aufgabe des Betriebssystems, den Speicher, der
im System vorhanden ist, mit zu verwalten.
Die Betriebsmittelverwaltung, also das Resource Management, ist jetzt
dafür zuständig, die Komponenten, die im System sind, also die
kompletten Geräte, die wir in einem Rechner finden, die periferen
Geräte, wie beispielsweise eben die Festplatte, Floppy-Disk und
Ähnliches, mit zu verwalten, sodass darauf zugegriffen werden kann.
Und zwar einfach, effektiv und vor allen Dingen konfliktfrei, wenn
beispielsweise mehrere Benutzers in einem System sind, beziehungsweise
mehrere Prozesse am Laufen sind, zu bearbeiten sind, so dass dann
diese Betriebsmittel auch benutzt werden können und zwar konfliktfrei.
Dann haben wir die Aufgabe, Auftragsverwaltung, der sogenannte Task
Management, die Verwaltung der Prozesse.
Und was ein Prozess einnehmen kann, welche Zustände ein Prozess
einnehmen kann, ist jetzt hier noch mal dargestellt.
Er kann aktiv sein, das heißt, er befindet sich in der Bearbeitung, er
wird auf dem Rechner ausgeführt.
Bevor er ausgeführt werden kann, muss er bereit sein zur Ausführung.
Es ist wiederum ein Zustand und schließlich der dritte Zustand ist der
Zustand blockiert.
Vereinfacht gesagt ist, wir wollen, wenn wir uns jetzt Rechner
vorstellen, bei denen unter anderem mehrere Prozesse laufen, sei es
von einem Benutzer oder sei es von mehreren Benutzern, dafür Sorge
tragen, dass diese Prozesse, die im System sind, möglichst schnell
abgearbeitet werden, insgesamt abgearbeitet werden.
Jetzt kann es dazu kommen, dass wenn ein Prozess sich in der
Bearbeitung befindet, er beispielsweise Komponenten im System braucht,
Ressourcen, faire, faire Geräte, auf die er warten muss, dann wartet
er nicht und blockiert den Prozessor, sondern wird in einem Zustand
versetzt, blockiert und kann hier nicht mehr weiter arbeiten.
Von hier aus muss er wieder in den Zustand bereit gesetzt werden, wenn
beispielsweise die Ressource wieder zur Verfügung steht.
In der Zwischenzeit können andere Prozesse, die im Zustand bereit
sind, zur Ausführung gebracht werden, eben in den Zustand aktiv
versetzt werden, sodass dieses Programm dann oder der Prozess dann
ausgeführt wird.
Damit, wenn beispielsweise mehrere Benutzer sich im System befinden
und eben hier mehrere Prozesse haben, dass wir also mehr
Benutzerbetrieb haben, dann soll es natürlich auch so sein, dass jeder
Benutzer in einer gleichberechtigten Art und Weise die Rechenzeit
zugeteilt bekommt und deshalb können mehrere Prozesse im Zustand
bereit sein.
Und der Prozess, der gerade aktiv ist, der kann unterbrochen werden
und es kann ein neuer Prozess gestartet werden und das kann man in
einer Art Zeitscheibenverfahren machen, sodass jedem Prozess
beispielsweise eine gleich lange Zeitscheibe zugeteilt wird, was dem
Benutzer dann letztendlich nicht so auffällt, wenn hier eine kurze
Unterbrechung seines Programms erfolgt.
Also diese Aufgabenverwaltung ist hier notwendig, um mehrere Prozesse
möglichst schnell insgesamt zu bearbeiten, also einen möglichst hohen
Durchsatz der Prozesse zu erreichen.
Man spricht auch von einer sogenannten quasi-parallelen Bearbeitung.
Wenn wir einen Rechner haben mit einem Prozessor, dann kann der zu
einem Zeitpunkt sicherlich nur einen Prozess bearbeiten, ein Programm
ausführen, aber da sich mehrere Prozesse im bereiten Zustand befinden
können, können die bedient werden, wenn beispielsweise ein Prozess
Informationen, Daten benötigt, die beispielsweise ausgelagert sind aus
dem Hintergrundspeicher, bis die Daten dann bereitstehen, also in den
Hauptspeicher geladen sind, vergeht einige Zeit und in der Zeit können
dann beispielsweise andere Prozesse oder ein anderer Prozess, der
bereit ist, hier zur Ausführung gelangen.
Oder aber eben auch dieses Zeitscheibenmanagement, wo mehrere Prozesse
eine gewisse Zeit rechnen dürfen und dann wieder in den Zustand bereit
zurückversetzt werden, sodass ein anderer Prozess hier den Zuschlag
bekommt und ausgeführt werden kann.
Dass das alles möglichst effizient und möglichst vor dem Benutzer oder
den Benutzern verborgen bleibt, ist eine der wesentlichen Aufgaben
auch des Betriebssystems.
Und was uns am meisten interessiert, ist die Speicherverwaltung.
Wir haben nämlich den Zustand in der Weise, dass wir Programme haben,
die relativ groß sind, dass wir Daten verarbeiten, deren Menge sehr
sehr groß ist und dass diese Informationen nicht in den Hauptspeicher
passen.
Alle Programme und alle Daten, die zur Ausführung gelangen werden oder
die bearbeitet werden müssen, müssen im Hauptspeicher stehen und von
dort kann dann der Prozessor zugreifen.
Und jetzt ist die Idee folgende, dass man hergeht und sagt, im Prinzip
soll der Benutzer davon ausgehen, dass wir einen sehr sehr großen
Speicher, sehr sehr großen Hauptspeicher haben, aber das
Betriebssystem sorgt dann dafür, dass in der realen Welt, also der
Rechner selber, einen relativ kleinen Hauptspeicher hat und unter
Zuhilfenahmen des Hintergrundspeichers wird dem Benutzer dann mithilfe
des Betriebssystems, mithilfe der Speicherverwaltung, ein großer
Adressraum oder großer Speicherraum zur Verfügung gestellt.
Also zum einen, weil die Programme so funktionieren und zum anderen
eben auch, weil mehrere Benutzer gleichzeitig an einem Rechner sitzen
können und die eben auch gleichzeitig bedient werden können, damit es
auch erfolgt und nicht der komplette Speicher hin und her gefüllt
werden muss.
Auch hierfür braucht man entsprechende Speicherverwaltungen.
Die Idee ist, dass immer nur die Teile in den Hauptspeicher geladen
werden, die gerade benötigt werden und bei Bedarf werden dann die
Programmteile bzw.
die Daten aus dem Hintergrundspeicher nachgeladen in den
Hauptspeicher, von dort in den Cache-Speicher, aber das ist die Arbeit
der Cache-Verwaltung.
Man nennt es im Englischen Paging oder Swapping.
Man tauscht also Informationen zwischen Hintergrundspeicher und
Hauptspeicher aus.
Schaut also im Prinzip folgendermaßen aus.
Wir haben auf der einen Seite die CPU und den Hauptspeicher.
Der Hauptspeicher, der muss die Programmteile enthalten, die gerade
von der CPU benötigt werden, die also ausgeführt werden sollen, deren
Befehle jetzt ausgeführt werden sollen und ebenso die Daten, die zu
verarbeiten sind.
Und der Hauptspeicher ist dann aufgeteilt in Einheiten und in diesen
Einheiten stehen dann die Programmteile bzw.
die Daten.
Wenn wir jetzt mehrere Prozesse haben, die beispielsweise bereit sind
bzw.
wo einer aktiv ist bzw.
der andere im Zustand bereit, dann befinden sich Teile des Prozesses
im Hauptspeicher.
Teile des Prozesses 1 sind schraffiert.
Wir sehen hier Teile des Prozesses 1 und die restlichen Teile bzw.
die Teile, die zum Prozess, die im Hauptspeicher sind, sind als Kopien
im Hintergrundspeicher abgelegt.
Der Prozess 2, dort befinden sich ebenfalls Teile im Hauptspeicher und
eben in Kopie im Hintergrundspeicher und die restlichen Komponenten
oder Teile des Prozesses 2.
Und was also gemacht wird, ist, dass die Information aus dem
Hauptspeicher, Entschuldigung, aus dem Hintergrundspeicher in den
Hauptspeicher geladen werden, wenn sie von der CPU benötigt werden.
Und falls der Hauptspeicher voll ist, wenn also der Hauptspeicher voll
geladen ist und Informationen hier reingeladen werden sollen, dann ist
es notwendig, dass hier Teile des Hauptspeichers wiederum
überschrieben werden mit Informationen aus dem Hintergrundspeicher und
es ist notwendig, dass keine Informationen verloren gehen.
Das heißt, wenn sich Daten geändert haben, dann müssen sie natürlich
in korrekter Weise wieder in den Hintergrundspeicher zurück kopiert
werden.
Der Vorgang, dass Informationen, Programmteile, Daten aus dem
Hintergrundspeicher in den Hauptspeicher geladen werden, bleibt dem
Benutzer völlig verborgen.
Der sieht sein Programm, der schreibt, der hat eine Menge von Daten
und dass der Rechner, der Hauptspeicher keine so große Kapazität hat
und hier ein Austauschvorgang zwischen Hauptspeicher und
Hintergrundspeicher vorgeht, ist dann für ihn völlig verborgen.
Das heißt also, für den Anwender schaut der Hauptspeicher, die
Speicherkapazität aus, wesentlich größer, als ob sie wesentlich größer
ist, als wirklich physikalisch vorhanden.
Und da das sozusagen eine virtuelle Sicht ist, spricht man auch von
einem virtuellen Speicher, beziehungsweise das Konzept, dass nachdem
dieser Speicher verwaltet wird, heißt dann eben virtueller Speicher.
Und da diese Aufgabe, dass Daten aus dem Hintergrundspeicher in den
physikalischen Speicher geladen werden müssen, relativ aufwendig ist
und sehr viel Zeit kostet, sucht man natürlich nach Möglichkeiten, wie
dies schneller passieren kann.
Und deshalb unterstützen insbesondere Mikroprozessoren, die in
Allzweckrechnern eingesetzt werden, die virtuelle Speicherverwaltung
durch entsprechende Hardwarekomponenten, die auf dem Chip integriert
sind.
Und das ist die sogenannte Speicherverwaltung,
Speicherverwaltungseinheit oder im Englischen Memory Management Unit.
Und die wesentliche Aufgabe ist, auf der einen Seite haben wir ja, die
wesentliche Aufgabe dieser virtuellen Speicherverwaltung ist im
Wesentlichen die, wir gehen ja als Programmierer von Namen aus, davon
aus, dass wir einen sehr großen Adressraum haben.
Auf der anderen Seite haben wir einen physikalischen Adress,
physikalischen Speicher mit physikalischen Adressen.
Was also jetzt passieren muss, wenn Daten aus dem Hintergrundspeicher
in den Hauptspeicher gelangen, dass wir die virtuellen Adressen auf
die physikalischen Adressen abbilden, damit der Prozessor auf die
korrekten Daten im physikalisch vorhandenen Speicher zugreifen können.
Und die wesentliche Aufgabe ist, dass wir diese virtuellen Adressen in
physikalische Adressen umsetzen.
Und dieser Umsetzungsprozess von den virtuellen Adressen in die
physikalischen Adressen, das bedeutet eben relativ hohen Zeitaufwand
aufgrund der Umsetzung, die hier erfolgen muss.
Und da leistet diese Speicherverwaltungseinheit hier entsprechend
Unterstützung.
Wie, das werden wir dann jetzt im Folgenden sehen.
Schauen wir uns das noch einmal im Bild an.
Wir haben also jetzt den Namensraum des Benutzers.
Er verwendet ja, wenn er sein Programm schreibt, Namen für Daten, so
wie es er sinnvoll erachtet, damit es lesbar ist, damit es
problemorientiert ist.
Der Compiler übersetzt das Programm dann eben in entsprechende
Maschinensprache und sorgt auch dafür, dass hier entsprechende Namen
verwendet werden.
Wir kommen in den virtuellen Adressraum und diese virtuelle Adresse,
die jetzt vom Compiler vergeben worden ist, die muss dann umgesetzt
werden in die physikalische Adresse.
Nämlich in die Adresse, an der dann das Programm bzw.
der Befehl des Programms bzw.
das Datum, das zu verarbeiten ist, im Hauptspeicher steht.
Und diese Umsetzung von virtueller in physikalische Adresse ist die
Aufgabe des Betriebssystems.
Also, wir haben drei Komponenten.
Den Benutzer, der eben im Programm seine Objekte kennzeichnet, die
Programme, die Unterprogramme, die Variablen.
Wir haben einen Compiler, der diese Namen in logische Adressen
innerhalb des virtuellen Adressraums übersetzt.
Und wir haben die Speicherverwaltung, die jetzt diese Adressen umsetzt
in die Adressen, in der diese Objekte dann im Hauptspeicher zu finden
sind.
Das Umsetzen von virtueller in physikalischer Adresse, das erfolgt,
und das ist ganz wichtig, zur Laufzeit.
Beispiel, hier zeigt es noch einmal konkret an.
Wir haben also beispielsweise einen Unterprogrammaufruf und der
Programmierer gibt dem Unterprogramm den Namen weiter.
Der Compiler übersetzt dies in Jump-128.
Es ist praktisch relativ gesehen zum PC zu verstehen, also zum Inhalt
des Programmzählers, des Befehlszählers.
Und hieraus wird dann die logische Adresse gebildet, 4538, und die
physikalische Adresse, die dann von der virtuellen Speicherverwaltung
vergeben wird, beziehungsweise berechnet wird, ist dann die 23112.
Die virtuelle Speicherkapazität ist also wesentlich größer als die
effektive Hauptspeicherkapazität.
Die Speicherkapazität, die physikalisch vorhanden ist.
Das Betriebssystem lagert dann bei Bedarf Speicherbereiche vom
Hintergrundspeicher ein, beziehungsweise wenn neue Speicherbereiche
eingelagert werden sollen, muss möglicherweise ein anderer
Speicherbereich ausgelagert werden.
Diese Speicherverwaltungseinheiten, die unterstützen jetzt eben eine
eindeutige Adressberechnung.
Und wo stehen jetzt diese Informationen oder wie findet man jetzt die
eigentliche physikalische Adresse aus einer virtuellen Adresse?
Und diese Informationen, die stehen in sogenannten
Übersetzungstabellen, Adressübersetzungstabellen.
Dort steht zu einer virtuellen Adresse die physikalische Adresse, wo
eben der Teil, der benötigt wird, im Hauptspeicher zu finden ist.
Wir haben also einen virtuellen Adressraum.
Entschuldigung, auf dem Hauptspeicher sind die kompletten
Informationen, Entschuldigung, im Sekundärspeicher oder im
Hintergrundspeicher sind diese Informationen abgelegt und werden jetzt
bei Bedarf eben in den Hauptspeicher entsprechend geladen.
Was muss jetzt so das Betriebssystem tun, damit sie Informationen vom
Hintergrundspeicher bei Bedarf einlagern kann?
Das Betriebssystem muss natürlich wissen, wie viel Platz im
Hauptspeicher zur Verfügung steht, also wie viel freier Platz bereit
ist, um Daten oder Informationen aus dem Hintergrundspeicher
einzulagern.
Und deshalb führt das Betriebssystem Buch über die freien
Speicherbereiche im Hauptspeicher.
Und wenn jetzt neue Speicherbereiche eingelagert werden müssen, dann
wird nachgesehen, ob genügend Speicher vorhanden ist im Hauptspeicher.
Wenn ja, dann wird die Information in den Bereich gelegt, der frei
ist.
Wenn nicht, dann werden diejenigen Speicherinhalte ausgelagert, die
gegenüber ihrem Original auf dem Hintergrundspeicher geändert worden
sind.
Wir holen also Daten aus dem Hintergrundspeicher und legen die Kopie
im Hauptspeicher ab.
Wenn sich jetzt Daten ändern, dann sind natürlich diese geänderten
Daten wieder an die Stelle im Hintergrundspeicher zu kopieren.
Die Aufgabe kann man sich natürlich sparen bei Informationen, die sich
nicht verändern, also beispielsweise Programme, die aus dem
Hintergrundspeicher in den Hauptspeicher gelagert werden, die
verändern sich ja nicht.
Wir wollen ja keine selbstmodifizierenden Programme oder
selbstmodifizierenden Code.
Und deshalb ist es da nicht notwendig, hier erst zurückzukopieren.
Dieses Zurückkopieren kostet natürlich Zeit und deshalb lagert man nur
oder schreibt man nur die Speicherinhalte wieder in den
Hintergrundspeicher zurück, die sich geändert haben.
So, was wir also brauchen, ist eine eindeutige Abbildung von einem
sehr großen virtuellen Adressraum in den physikalischen Adressraum,
der relativ klein ist und das unterstützt jetzt die
Speicherverwaltungseinheit des Prozessors.
Alle Informationen, die dazu notwendig sind, die werden vom
Betriebssystem verwaltet beziehungsweise bereitgestellt.
Und dafür legt das Betriebssystem entsprechende Tabellen an,
sogenannte Übersetzungstabellen.
Was findet man also in diesen Übersetzungstabellen?
Wenn wir also von der virtuellen oder logischen Adresse ausgehen, die
der Compiler generiert hat, dann kann man diese virtuelle Adresse
unterteilen in eine sogenannte virtuelle Seitenadresse.
Und über diese virtuelle Seitenadresse findet man einen Eintrag in
einer Übersetzungstabelle und dieser Eintrag, der führt zur
physikalischen Seiten.
Adresse.
Wir zerlegen also den virtuellen Adressraum in Einheiten und lagern
Einheiten ein beziehungsweise aus.
Und diese Einheiten können jetzt feste Länge haben oder variabel lang
sein.
Beispielsweise, wenn sie eine feste Länge haben, dann spricht man auch
von Seiten.
Und über diese virtuelle Seitenadresse, über diesen Teil der
virtuellen Adresse, der jetzt hier als virtuelle Seitenadresse benannt
ist, kommt man in den Eintrag der Übersetzungstabelle, zu dem Eintrag
in der Übersetzungstabelle.
Und bekommt dort die sogenannte physikalische Seitenadresse.
Und diese physikalische Seitenadresse ist die Anfangsadresse der Seite
im Hauptspeicher.
Und der niederwärtige Teil der virtuellen Adresse ist dann der Offset
innerhalb dieser Seite im Hauptspeicher.
Dort steht dann das eigentliche Datum, auf das zugegriffen werden
soll.
Warum macht man jetzt es so, dass wir sagen, wir nehmen größere
Einheiten und machen das nicht mit jedem Datum oder jedem Befehl im
Hauptspeicher, sodass wir jede Speicherzelle adressieren.
Ganz einfach, wenn sozusagen wir hier nicht mit größeren Einheiten
arbeiten würden, also beispielsweise mit Zeiten, die länger sind,
zwischen 256 Byte und 4 Kilobyte beispielsweise, dann würden diese
Übersetzungstabellen sehr groß werden.
Weil wir dann für jede einzelne Adresse im virtuellen Adressraum
sozusagen einen Eintrag in diesen Umsetzungstabellen oder
Übersetzungstabellen benötigen.
Deshalb geht man hier und sagt, wir fassen größere Bereiche zusammen.
Und wie gesagt, es gibt eben den Unterschied, dass wir sagen, wir
nehmen Einheiten variabler Länge, das sind sogenannte Segmente, oder
wir nehmen Einheiten fester Länge und bezeichnen diese dann als
Seiten.
Das Wesentliche ist, dass diese Segmente oder die Seiten jeweils
zusammenhängende Adressbereiche umfassen.
Also Adressen von 0 bis 4K minus 1 ist der Bereich, den jetzt eine
Seite umfassen kann.
Was ist jetzt die Segmentierung?
Bei der Segmentierung wird der virtuelle Adressraum in Segmente
verschiedener Länge zerlegt.
Also wir haben variabel lange Segmente und jeder Prozess, der kann
jetzt ein oder mehrere Segmente haben.
Vor allen Dingen, wenn man mit Segmenten arbeitet, dann unterscheidet
man üblicherweise zwischen Segmenten, in denen nur der Programmcode
steht und Segmenten, in denen die Daten, die zu verarbeiten sind,
stehen.
Man trifft also eine logische Aufteilung des virtuellen Adressraums,
wenn man Segmente verwendet.
Und da man logische Einheiten verwendet, ist dies im Prinzip auch dem
Programmierer, dem Maschinenprogrammierer, sichtbar.
Diese einzelnen Segmente, da sie ja logisch zusammenhängende
Informationen enthalten, also Programme oder Daten, die können daher
auch relativ groß sein.
Jeder Programm, das man schreibt, ist sicherlich nicht gleich lang,
also unterschiedlich lang und deshalb ist es sinnvoll, wenn man mit
Segmenten arbeitet, dass diese Segmente variabel lang sind,
beziehungsweise die Daten, die zu verarbeiten sind, sind auch nicht
immer gleich lang, sondern man verwendet ja unterschiedlich viele
Daten, die zu verarbeiten sind.
Und wesentlich ist, dass eben hier der Programmierer diese Segmente
sieht.
Die andere Variante ist die Aufteilung des virtuellen Adressraums in
Seiten fester Länge.
Und entsprechend wird dann auch der physikalische Speicher in Seiten
fester Länge aufgeteilt.
Also Segmente, besser ist Seiten, aufgeteilt.
Und die Seiten, die sind jetzt je nach Betriebssystem oder je nach
Einstellung im Betriebssystem zwischen 246, 256 Byte und 4 Kilobyte
lang oder aber können, wenn wir heutige Mikroprozessoren, wie den
Pentium II oder den Pentium III anschauen, dann unterstützt der auch
Betriebssysteme, bei denen die Seiten zwei Megabyte lang sind und
länger.
So, und jetzt ist es so, dass ein Prozess auf viele dieser Seiten
verteilt wird.
Aber da besteht jetzt kein logischer Zusammenhang mehr.
Das ist eine reine Einteilung, eine feste Einteilung, wo jetzt Teile
des Programms abgelegt sind.
Und wenn eine Seite voll ist, kommt der nächste Teil des Programms in
die nächsten Seiten oder beziehungsweise die Daten werden eben
aufgeteilt in mehrere Seiten.
Und wenn ich Seiten fester Länge habe, dann habe ich Vorteile,
insbesondere dann, wenn ich hier mit dieser virtuellen
Speicherverwaltung arbeite.
Bei der Segmentierung habe ich folgenden Vorteil.
Ich habe die logische Programmstruktur, die dadurch wiedergegeben
wird.
Aufteilung in Programme und Programmcode und Daten.
Dann lagere ich, weil die Segmente relativ groß sind, immer so ein
Segment ein und verweile dadurch, dass ich sehr oft auf den
Hintergrundspeicher zugreifen muss.
Also nicht so oft die Daten transformieren muss, weil ich sie auf
einen Schub, wenn ich in so ein Datensegment lade, eben im
Hauptspeicher habe.
Aber wenn dann mal ein Datentransfer stattfindet, dann habe ich halt
den Nachteil, dass dann auf einmal sehr viele Daten aus dem
Hintergrundspeicher in den Hauptspeicher geladen werden müssen.
Und das zweite Problem ist, dass wenn ein Programm aus nur einem Code
und einem Datensegment besteht, wenn es also vom Betriebssystem so
festgelegt wird, so muss dann das komplette Segment, Programmsegment
und Datensegment in den Hauptspeicher eingelagert werden.
Und da kann es jetzt wiederum passieren, dass der Hauptspeicher nicht
ausreicht.
Haben wir also ein Problem, müssen wir weiter überlegen, was wir in
dem Fall machen, wenn wir ein komplettes Segment nicht in den
Hauptspeicher bekommen.
Werden wir aber die Lösung dann sehen.
Was ist bei der Seitenaufteilung der Vorteil?
Bei der Seitenaufteilung habe ich den Vorteil, ich verwende nur
relativ kleine Seiten, also beispielsweise bis zu 4K große Seiten, die
werden in diesen Einheiten jeweils vom Hintergrundspeicher in den
Hauptspeicher geladen.
Das sind relativ kleine Speichen, da ist fest, wie lange es dauert, um
so eine Seite aus dem Hintergrundspeicher in den Hauptspeicher zu
laden.
Und ich brauche auch nicht so viel verwalten, weil ich weiß, die
Seiten sind gleich lange und was jetzt der Vorteil ist, ich kann diese
Seiten an die Stellen im Hauptspeicher laden, die jetzt frei sind.
Der Nachteil ist natürlich, da ich auf einmal weniger lade, kann es
passieren, dass es notwendig ist, häufiger auf dem Hintergrundspeicher
zuzugreifen und damit einen häufigeren Datenverkehr habe.
Wenn wir jetzt zu älteren Prozessoren schauen, dann sieht man, dass
jeweils nur ein Verfahren unterstützt wird bei den Mikroprozessoren,
muss man dazu sagen.
Also beispielsweise der 80286 oder 6810, die haben die Segmentierung
mit unterstützt.
Seitenwechsel war bei Prozessoren wie dem Z8003 oder Z8004 bzw.
dem National 32000 üblich.
Wenn wir heute die Intel-Prozessoren anschauen, ab dem 80386
unterstützen diese dann beide Verfahren.
Also Segmentierung plus Seitenverfahren.
Warum macht man das?
Ich habe vorhin das Beispiel genannt, wenn ich relativ große Segmente
habe, die vom Betriebssystem eingestellt werden, oder nur ein
Programmsegment verwende und ein Codesegment verwende, dann kann es
passieren, dass es trotzdem nicht in den Hauptspeicher reinpasst.
Und wenn ich dann jedes Segment nochmal in Seiten aufteile, dann kann
ich Seiten eines Segments als Einheit verwenden und damit entsprechend
arbeiten und Seiten eines Segments ein- und auslagern.
Wie schaut jetzt diese segmentbasierte Speicherverwaltung im Prinzip
aus?
Wir haben eine virtuelle Adresse und diese virtuelle Adresse ist
aufgeteilt in zwei Bereiche.
Der höherwertige Teil der virtuellen Adresse, was hier mit
Segmentnummer abgekürzt ist, dieser Teil ist ein Index in eine
Segmenttabelle.
Man meldet mit diesem Teil einen Eintrag in einer Segmenttabelle, die
üblicherweise vom Betriebssystem angelegt wird und im Hauptspeicher
fest abgelegt ist, einen Eintrag aus.
Und dieser Eintrag, der besteht jetzt zum einen aus einer 32-Bit
sogenannten Basisadresse, das ist die Anfangsadresse des Segments und
als weiterer Information ist in diesem Eintrag, der ausgewählt wird,
die Länge des Segments.
Ein Segment, habe ich ja gesagt, ist variabel lang und damit hier es
nicht dazu kommen kann, dass beispielsweise mehrere Segmente so
überlagert werden, dass sie sich überschneiden, braucht man natürlich,
wenn man Segmente ein- oder auslagert bzw.
aus dem Hintergrundspeicher in den Hauptspeicher lagert, die
Information, wie lang so ein Segment ist und dies ist ebenfalls mit in
dieser Segmenttabelle abgelegt.
So, und da haben wir jetzt ein Beispiel.
Die Länge eines Segments beim Eintrag 0 ist 7.937 Bytes und die
Basisadresse ist 10.258 und die Länge des Segments, das beim ersten,
beim Eintrag Nummer 1 steht, ist 258 Bytes und steht an der Adresse 10
.000 und so weiter.
Was passiert also jetzt mit der Basisadresse?
Die Basisadresse, die zeigt jetzt auf die Anfangsadresse des Segments.
Und um auf das einzelne Datum oder eben den Befehl zu kommen, brauchen
wir noch einen sogenannten Offset und das ist die sogenannte Byte
-Nummer, die im niederwärtigen Teil der virtuellen Adresse steht.
Das heißt also, wir nehmen die 32-Bit-Basisadresse und addieren
letztendlich diese niederwärtigen 24-Bit, wobei die höherwertigen Bits
mit 0 ergänzt werden zu einer 32-Bit-Adresse auf und bekommen dann die
eigentliche physikalische Adresse.
Also, die virtuelle, hier ist das nochmal zusammengefasst, die
virtuelle Adresse wird in eine Segmentnummer.
Das sind die n höherwertigen Bits der virtuellen Adresse.
In unserem Beispiel waren es die 8 höherwertigen Bits und die
kennzeichnen jetzt das Segment.
Und die Byte-Nummer, das sind sozusagen, diese n Bits ist die Byte
-Nummer und die gibt den Abstand vom Segmentanfang an.
Also den relativen Abstand von der Basisadresse.
Das heißt, man bekommt jetzt eine Information, wie viele Seiten es
insgesamt geben darf.
Also insgesamt können wir 2 hoch n Segmente vergeben.
In unserem Beispiel hier haben wir 8-Bit-Segmentnummer.
Wir können also 256, also 2 hoch 8 gleich 256 Segmente vergeben.
Und innerhalb eines Segments können wir also 2 hoch 24, oder die Länge
eines Segments kann 2 hoch 24 Byte lang sein.
Das ist die 2 maximale Segmentgröße.
Und wie man von der virtuellen Adresse zur physikalischen Adresse
kommt, haben wir gesehen, das Betriebssystem verwaltet diese Segment
-Tabelle und legt jetzt an für jedes Segment die Nummer oder die
physikalische Adresse, wo es im Hauptspeicher ist.
Also die Anfangsadresse des Segments im Hauptspeicher.
Das Betriebssystem liefert die Information und die
Speicherverwaltungseinheit, die MMU im Prozessor, die nimmt diese
Information mit auf.
Warum?
Wenn wir sehen, wie ich schon gesagt habe, ich habe die Information,
diese Übersetzungstabelle im Hauptspeicher stehen.
Jetzt muss ich hergehen, um auf ein Datum zugreifen, zunächst auf
diese Tabelle zugreifen, muss dann die eigentliche Adresse berechnen,
also sprich aus der Segment-Basis-Adresse die Byte-Nummer aufaddieren
und komme dann zur eigentlichen Information, da greife ich das zweite
Mal auf den Hauptspeicher zu.
Und wenn ich das für alle Daten, die jetzt beispielsweise in einem
Segment sind, mache, dann muss ich jedes Mal, wenn ich auf so ein
Datum zugreife, zweimal zugreifen.
Das ist natürlich ein relativ hoher Aufwand und da kann natürlich
jetzt eine Speicherverwaltungseinheit, die im Prozessor ist,
mitunterstützend tätig sein, denn wenn einmal so eine Adresse
berechnet worden ist, wenn also einmal auf eine Segment-Tabelle
zugegriffen worden ist und diese Abbildung getroffen worden ist, von
der virtuellen Adresse in die physikalische Adresse, dann hat man
diese Information und legt sie dann in einem internen Register der
Speicherverwaltung ab.
Und dann kann der Prozessor hier drauf zugreifen und spart sich den
Weg über die Umsetzungstabelle oder Übersetzungstabelle.
Also die reale Adresse, haben wir gelernt, ergibt sich aus der Segment
-Basis -Adresse, auf die dann die virtuelle Byte-Nummer-Adresse
addiert wird.
Damit wir eben nicht über Segmente hinaus zugreifen, gibt es eben die
Angabe der Segmentlänge.
Ist ja auch sinnvoll, es darf nicht passieren, dass über ein Segment
hinaus zugegriffen werden darf.
Wenn das passieren sollte, kann es ja passieren, dass beispielsweise
Informationen eines anderen Benutzers, der sich im Rechner befindet,
hier überschrieben werden.
Oder andere Informationen, die der eigene Benutzer jetzt in einem
anderen Segment abgelegt hat, überschrieben werden.
Und das ist ein weiterer Punkt.
Dadurch, dass ich solche festen Längenangaben mache, kann jetzt auch
der Prozessor gleich mit überprüfen, wenn er auf so ein Segment
zugreift, ob so eine Grenze beim Zugriff überschritten wird.
Und wenn das passiert, dann meldet er halt, da ist was faul, hier
stimmt was nicht und meldet das mit Hilfe eines sogenannten Alarms.
Er schlägt also Alarm.
Das ist ein Schutzmechanismus, den hier der Prozessor mit anbietet.
Eine wichtige Aufgabe auch des Betriebssystems, dass eben hier
verschiedene Segmente nicht verwaschen werden, nicht überschrieben
werden, sondern dass die im Einzelnen für sich geschützt werden.
Der Prozessor unterstützt dies eben mit der Überprüfung der
Segmentgrenze.
Und meldet entsprechend, falls hier eine Überschreitung vorkommt.
Insgesamt können also 2 hoch m byte in einem Segment abgelegt werden.
Es ist natürlich klar, dass nicht unbedingt jedes Programm oder die
Daten, die verwendet werden, ein komplettes Segment ausnützen.
Oder diese 2 hoch m ausnützen.
Das heißt, man kann auch mit Verschnitt hier rechnen.
Wenn man die Segmentgrenzen an jeder Bytegrenze zulässt, dann ist
klar, dann können die Informationen geschickt im Hauptspeicher
abgelegt werden.
Wie schaut es jetzt aus, wenn wir also jetzt von einem virtuellen
Adressraum in einen physikalischen Adressraum kommen?
Wir sehen, wir haben den Adressraum, der jetzt die maximale
Segmentlänge hat und damit entsprechend aufgeteilt ist.
So haben wir ein Segment, das ist das Segment 0, das wir vorhin
gesehen haben, das jetzt in unserem Beispiel 7.937 Byte lang ist.
Wir haben ein zweites Segment, das dann an der nächsten Segmentgrenze
beginnt.
Und wir haben unser dritter Segment, das dann 3.846 Byte lang ist und
an dieser Segmentgrenze beginnt.
Und das, was hier ist, ist sozusagen der Verschnitt.
Wenn wir die Segmente an jeder Bytegrenze loslassen oder anfangen
lassen, dann könnte das Segment 1 auch an dieser Stelle hier beginnen.
Also an dieser Stelle beginnen.
So und jetzt müssen wir diese Segmente abbilden in den virtuellen
Adressraum.
Also beispielsweise wird das virtuelle Segment oder das Segment 0 an
diese physikalische Adresse abgebildet.
Das ist die Anfangsadresse des Segments im Hauptspeicher.
Das zweite Segment wird an die Adresse 1000 abgelegt und dann haben
wir Platz und legen das dritte Segment an dieser Adresse ab.
Wir können also jetzt die Informationen im Hauptspeicher aneinander
ablegen, so wie der Bedarf möglich ist.
Wie schaut es jetzt also aus, wenn wir hier mit entsprechenden Blöcken
arbeiten?
Das heißt, das Segment aufteilen in Blöcke.
Dann geht wieder die erste Information in die Segmenttabelle.
Der höherwertige Teil liefert also den Eintrag in der Segmenttabelle.
Wir haben hier jetzt 16 Bit für die Angabe der Segmentlänge.
Und jeder Segment ist also jetzt eine Anzahl von Blöcken lang.
Und wir haben hier in diesem Teil die Blocknummer als
Segmentbasisadresse.
Das heißt, wir lesen hier die Blockadresse raus, bekommen die
Blocknummer als physikalische Adresse und der niederwertige Teil ist
wieder die Byte-Nummer.
Das heißt, jetzt legen wir unsere Segmente nicht mehr an Byte-Grenzen
ab, sondern eben an Grenzen, die jetzt als Blöcke eingeteilt sind.
Anders gesagt, wir teilen das Segment in Blöcke auf und fangen jetzt
an, diesen Block jeweils auf die Anfangsadresse zu legen.
Also, die Segmentgrenzen finden jetzt nicht mehr an ihrer Byte
-Adresse, sondern die Segmentgrenzen sind jetzt ein Vielfaches von
Blöcke.
Und im virtuellen Adressraum, genauso wie im physikalischen
Adressraum, haben wir jetzt Blöcke, die jetzt in unserem Beispiel 256
Byte lang sind.
So, und damit haben wir n Bit, die jetzt die kürzere Byte-Nummer für
die Byte-Adressierung im Block sich auswirkt.
Also, diese Byte-Nummer ist natürlich jetzt wesentlich kürzer, in
unserem Beispiel jetzt 8 Bit lang.
Die 16 Bit, die in der Mitte stehen, bilden die Blocknummer.
Und damit brauchen wir also, wenn ein Block 256 Byte lang ist, diese 8
Bit, um jedes dieser Bytes innerhalb eines Blocks adressieren zu
können.
So, und bei der Adressumsetzung bilden wir jetzt die virtuelle
Adresssegmentnummer auf eine Blocknummer an und innerhalb dieses
Blocks haben wir dann die Byte-Nummer.
Schaut also jetzt folgendermaßen aus.
Wir haben das Segment 0, das jetzt aufgeteilt ist in eine Anzahl von
Blöcken.
Das zweite Segment besteht in unserem Beispiel jetzt aus 2 Blöcken und
das dritte Segment, das besteht jetzt aus 16 Blöcken und wir sehen
hier wiederum die Grenzen 64 K minus 1 im Hauptspeicher.
Jetzt wird die virtuelle Segmentnummer abgebildet auf eben eine
Blocknummer, im Hauptspeicher in unserem Fall jetzt die 5.
Und entsprechend wird hier das Segment 0 eingelagert und die
physikalische Blocknummer ist dann die Blocknummer 5.
Und das heißt die Blocknummer 0 des Segments 0 wird abgebildet auf die
5 und die Blocknummer 31 wird abgebildet auf die Blocknummer 36 des
Segments im physikalischen Speicher.
Ja, entsprechend läuft es für das Segment 1 und entsprechend für das
Segment 2.
Wie funktioniert es jetzt, wenn wir mit Seiten fester Länge arbeiten?
Im Prinzip ähnlich.
Auch hier haben wir wieder eine virtuelle Adresse und was wir brauchen
ist die physikalische Adresse, wohin die Information im Hauptspeicher
geladen wird.
Wir erinnern uns, das Betriebssystem ist dafür verantwortlich die
Informationen zu liefern.
Das Betriebssystem führt Buch über den freien Platz im Hauptspeicher
und bestimmt dann an welche physikalische Adresse jetzt eine
Information aus dem Hintergrundspeicher geladen werden soll.
Und diese Information wird abgelegt in der Seitentabelle.
Bei der Segmentverwaltung ist diese Information in der Segmenttabelle
abgelegt worden.
Und jetzt haben wir Seiten.
Seiten haben eine feste Länge, beispielsweise 4K.
Und die virtuelle Adresse ist jetzt aufgeteilt und wir haben hier in
unserem Beispiel 20 Bit.
Das heißt, der höherwertige Teil, der adressiert jetzt wieder einen
Eintrag in der Seitentabelle.
Und da wir 20 Bit haben, können wir 22 Einträge in einer solchen
Seitentabelle haben.
Also, die Seitennummer adressiert einen Eintrag in der Seitentabelle.
Und was steht jetzt in der Seitentabelle?
In der Seitentabelle steht jetzt eine Nummer des physikalischen
Hauptspeichers.
Der physikalische Speicher ist also auch aufgeteilt in Einheiten
fester Länge, in sogenannte Rahmen.
Und eine Seite wird in einen Rahmen abgelegt.
Und sinnvollerweise ist der Rahmen genauso lang wie eine Seite.
Das heißt, hier legt das Betriebssystem in dieser Seitentabelle eben
die Anfangsadresse eines Rahmens ab, indem eine Seite abgelegt wird.
Das heißt, diese 20 Bits hier, die liefern die Rahmennummer und muss
daher auch die 20 höchstwertigen Bits einer physikalischen Adresse
sein.
Ja, und die Byte-Nummer, die wird wieder direkt übernommen als
sogenannte Offset.
Und das ist dann die Adresse innerhalb einer Seite, innerhalb des
Rahmens, an dem sich jetzt das Datum befindet, auf das zugegriffen
werden muss.
Und bei einer Seitengröße von 4 Kilobyte brauchen wir eben 12 Bit, um
jedes Byte in einer Seite adressieren zu können.
Also, hier sehen wir nochmal, wir haben den virtuellen Adressraum und
den physikalischen Adressraum.
Und ein Segment, wie hier, kann jetzt aufgeteilt sein in Seiten fester
Länge oder der virtuelle Adressraum.
Und ein Segment ist ja nichts anderes als ein virtueller Adressraum.
Und der virtuelle Adressraum wird aufgeteilt in Seiten fester Länge.
Der physikalische Adressraum ist aufgeteilt in Rahmen.
Und in diese Rahmen werden jetzt Seiten eingelagert.
Und Sie sehen, die Seite 0 vom Segment 0 wird in den Blockrahmen 3
abgelegt.
Und die Seite 1 vom Segment 0 in den Blockrahmen 6 und so weiter.
Die 2 nach 2 und die 3 wird hier abgelegt.
Das heißt, wir sehen, im Hauptspeicher finden sich die einzelnen
Seiten eines Segments nicht mehr in der Reihenfolge, wie es im
ursprünglichen Segment ist.
Aber da wir mit unserem Prozessor und weil wir das ermöglichen, können
die Seitenbereiche oder die Seiten an beliebigen Stellen im
Hauptspeicher stehen.
Warum?
Weil wir immer mit entsprechenden Basisadressen arbeiten und relativ
zu Basisadressen arbeiten.
Und jetzt erinnern Sie sich ja vielleicht, dass wir relativ am Anfang
der Vorlesung uns mit Adressierungsarten oder ähnliches beschäftigt
haben.
Basisrelative Adressierungsarten sind gerade für diese Implementierung
von solchen Seiten und für die Implementierung der virtuellen
Speicherverwaltung von Bedeutung.
Also, was müssen wir jetzt aber noch mit bedenken?
Also, schauen wir uns die Probleme der virtuellen Speicherverwaltung
an.
Insgesamt haben wir drei Problemkreise, die wir lösen müssen.
Das erste ist, wann lagern wir Informationen, Segmente oder Seiten aus
dem Hintergrundspeicher in den Hauptspeicher ein?
Also, der Einlagerungszeitpunkt.
Wohin können wir jetzt Seiten oder Segmente in den Hauptspeicher
legen?
Also, das Zuweisungsproblem.
So, und jetzt wenn der Hauptspeicher voll ist, wenn der Hauptspeicher
belegt ist und es werden weitere Seiten benötigt, die aus dem
Hintergrundspeicher in den Hauptspeicher abgelegt werden sollen oder
in den Hauptspeicher kommen sollen und der Hauptspeicher ist voll, was
macht man dann?
Ja, es müssen dann Teile wieder ausgelagert werden bzw.
es müssen Speicherbereiche überschrieben werden.
Welche Bereiche sollen jetzt überschrieben werden genau zu dem
aktuellen Zeitpunkt?
Wir müssen also Seiten ersetzen oder Segmente ersetzen.
Wir brauchen also eine Strategie.
Welche Strategie kann hier sinnvoll sein?
Das ist das Ersetzungsproblem, was ebenfalls mit zu lösen ist.
Schauen wir uns den ersten Punkt an.
Was heißt der Einlagungsseiten?
Wann lagert man also die Informationen in den Hauptspeicher?
Das gängigste Verfahren ist, dass die Seiten bei Anforderung
eingelagert werden.
Also das sogenannte Demandpaging.
Immer dann, wenn ich Informationen brauche und sie sind nicht im
Hauptspeicher oder wenn der Prozessor Daten oder Programmteile braucht
und diese sind nicht im Hauptspeicher, dann werden sie vom
Hintergrundspeicher in den Hauptspeicher geholt.
Das ist das gängigste Verfahren.
Was ist dabei das Kriterium?
Wann kann das oder wann erfolgt das?
Der Prozessor oder wenn ein Zugriff erfolgt und ein Segment oder eine
Seite ist nicht im Hauptspeicher, dann muss das natürlich mitgeteilt
werden.
Und das wird mitgeteilt dadurch, dass beispielsweise beim Zugriff auf
eine solche Seite festgestellt wird, die Seite befindet sich nicht im
Hauptspeicher, weil sie ausgelagert ist, wird gekennzeichnet durch ein
entsprechendes Bit im Eintrag einer Seiten- oder Segmenttabelle und
dann kann der Prozessor hergehen und einen sogenannten Segment- oder
Seitenfehler alarmieren und dann wird eine Routine angesprungen, eine
Unterbrechungsbehandlungsroutine, die dann dafür sorgt, dass die Seite
aus dem Hintergrundspeicher eben in den Hauptspeicher gelagert wird.
Und ein solcher Fehler wird im Englischen Segment- oder Page-Fault
genannt.
Das zweite ist das Zuweisungsproblem, wohin wollen wir jetzt Segmente
oder Seiten legen, wenn sie aus dem Hintergrundspeicher in den
Hauptspeicher kommen sollen.
Das Problem beim Segmentierungsverfahren ist, und das ist gar nicht
trivial, wir müssen eine so große Lücke finden, wie dieses Segment
ist.
Also wir brauchen eine ausreichend große Lücke im Hauptspeicher, das
heißt wir müssen entsprechend die Freispeichereinheit des
Betriebssystems zurate ziehen und dann kann es notwendig sein, dass
größere Bereiche ausgelagert werden müssen, dass möglicherweise
Zwischenplätze freigemacht werden müssen.
Auf jeden Fall muss dafür gesorgt werden, dass ausreichend große Lücke
gefunden werden.
Und wie macht jetzt das Betriebssystem?
Es schaut nach und schaut, ob es die erste passende Lücke findet.
Oder sie schaut, ob es die kleinste passende Lücke findet.
Oder sie nimmt die größte passende Lücke.
Also das sind drei Strategien des Betriebssystems.
Das Problem, das bei allen Verfahren dabei auftritt, ist, dass wir,
wenn wir den Hauptspeicher jetzt anschauen, der ja aus einer Folge von
aufsteigenden Adressen besteht, und es kann passieren, dass nach einer
bestimmten Zeit das Segment D hier abgelegt ist, das Segment C hier,
das Segment B hier und das Segment A hier.
Und was übrig bleibt, sind solche Plätze, die frei sind, die nicht
belegt sind.
Und jetzt kann es passieren, wenn weitere Segmente eingelagert werden
müssen, dass ein solcher Bereich nicht ausreicht, um ein komplettes
Segment einzulagern.
Dann kann es passieren, dass wir ein Segment D auslagern müssen,
obwohl, wenn man diese Bereiche zusammennimmt, für ein Bereich E, für
ein Segment E, genügend Platz wäre.
Nur weil eben hier der Speicher sozusagen aufgeteilt ist und in keinem
dieser freien Plätze eine genügend große Lücke ist.
Das heißt, wir haben unbelegte Speicherbereiche und man spricht dann
von Fragmentierung oder externer Fragmentierung.
Und ja, wie kann man jetzt das hier mit umgehen?
Wie schaut es beim Zuweisungsproblem beim Seitenwechselverfahren aus?
Schauen wir uns erst das an.
Ja, da tritt das Problem nicht auf.
Warum?
Ja, weil wir Seiten fester Länge haben und der Hauptspeicher auch
aufgeteilt ist in Rahmen, die dieselbe Länge haben.
Und damit können die Seiten in einen freien Rahmen abgelegt werden und
da es eine Variable lang ist, haben wir dieses Fragmentierungsproblem
im Hauptspeicher nicht, also keine externe Fragmentierung.
Wir haben also immer passende Lücken.
Was aber besteht, ist das Problem der internen Fragmentierung.
Wir haben eine einheitliche Seitengröße und was also passieren kann,
ist, dass man auf der letzten Seite eines Programmmoduls mit einer
hohen Wahrscheinlichkeit auch Bereiche in einer Seite haben, die gar
nicht belegt sind, aber die man natürlich mit in den Hauptspeicher
reinholt, ohne dass wir sie dann auch letztendlich verwenden.
Aber das ist ein geringeres Problem.
Gerade bei den heutigen Prozessoren oder Rechnern, wo wir relativ
viele haben.
Und das dritte Problem ist das Ersetzungsproblem.
Welche Segmente können wir jetzt auslagern?
Natürlich kann man sagen, wir können jedes beliebige Segment
auslagern, aber vielleicht ist das gerade ein Segment, auf das
demnächst wieder zugegriffen werden muss.
Und dann muss es wieder eingelagert werden.
Also, zum einen kann man hergehen und kann sagen, die von einem
Prozess zu benutzenden Segmente sind in ihrer Anzahl limitiert.
Und dann kann man hergehen und kann sagen, bei Einlagerung eines neuen
Segments wird ein zuvor für diesen Prozess benutztes Segment
ausgelagert.
Man kann aber auch hergehen und kann sagen, man nimmt eine Strategie,
wie wir sie auch üblicherweise bei Seitenwechselverfahren machen,
nämlich first in, first out.
Also eine Seite, die am längsten im Hauptspeicher ist, die wird
ausgelagert.
Oder man nimmt die Seite, die zuletzt eingelagert worden ist und
ersetzt diese Seite.
Oder man führt Buch, wann auf eine Seite zugegriffen worden ist.
Und dann findet man die Seite, auf die am längsten nicht mehr
zugegriffen worden ist und überschreibt diese Seite.
Warum?
Weil man sich sagt, gut, wenn ich diese Seite nehme, auf die am
längsten nicht mehr zugegriffen worden ist, dann ist die
Wahrscheinlichkeit relativ hoch, dass in der nächsten Zukunft auch
nicht darauf zugegriffen wird.
Und damit bin ich wahrscheinlich sicherer, dass ich nicht eine Seite
auslagere, auf die ich in der nächsten Zukunft wieder zugreifen
möchte.
Wenn also beispielsweise bei dieser LIFO-Strategie ich hergehe und die
zuletzt eingelagerte Seite wieder ersetze, dann kann es passieren,
dass ich demnächst gleich wieder darauf zugreifen muss und diese Seite
wieder reinhole und die Seite, die ich gerade reingeholt habe,
möglicherweise wieder ersetze.
Und dann kann es natürlich zu sowas wie einem Flattereffekt kommen
zwischen dem Hauptspeicher und der Festplatte.
Bei der Least-Reason-De-Use ist diese Wahrscheinlichkeit nicht so hoch
und allerdings haben wir hier vielleicht einen höheren Aufwand.
Eine andere Möglichkeit ist, dass ich sage, ja, wenn ich auf Seiten
relativ wenig zugreife, suche ich mir die Seiten, auf die am wenigsten
häufig zugegriffen worden ist und sage, weil dort nicht oft darauf
zugegriffen worden ist, brauche ich sie auch nicht oft, also ersetze
ich diese Seite.
Und dann gibt es eine Least-Reference-Density-Strategie, das ist eine
Mischung aus LRU und LFU-Strategie, das heißt die Seite mit der
geringsten Zugriffsdichte, die definiert ist als die Anzahl der
Zugriffe durch den Einlagerungszeitraum, die wird ersetzt.
Und da kann man sich vorstellen, dass diese am aufwendigsten ist, weil
ich hier mehr Informationen bereithalten muss, die bei der Strategie
notwendig ist, um eine Entscheidung treffen zu können.
So, ein anderer Punkt ist, einfach auch die Seiten ersetzen, auf die
nicht zugegriffen, auf die keine Änderungen erfolgt sind.
Also beispielsweise auf Daten, die zwar gelesen worden sind, aber wo
keine Schreiboperationen draufgefunden haben, also Seiten, die nicht
beschrieben worden sind.
Und wenn auf eine Seite nicht schreiben zugegriffen worden ist, dann
sind auch keine Daten verändert worden und damit kann ich diesen
Rahmen im Hauptspeicher mit der neuen Seite überschreiben, weil ja
eine Kopie im Hintergrundspeicher vorhanden ist, die genau diese
Information liefert und die ich jederzeit wieder in den Hauptspeicher
reinholen kann.
Und ich spare mir das aufwendige Zurückschreiben in den
Hintergrundspeicher.
Also, stellen wir noch einmal Segmentierung und Seitenwechsel
gegenüber.
Segmente sind logische Programmeinheiten mit einer variablen Länge,
die bilden also die logische Struktur des Programms mit ab.
Da wir größere Einheiten haben, ist es wahrscheinlich so, dass wir
nicht so oft auf den Hintergrundspeicher zugreifen müssen, aber wenn
wir einmal darauf zugreifen, dann werden eben mehr Daten geholt, also
wir haben einen umfangreicheren Datentransmitter und wir haben das
Problem der externen Fragmentierung.
Das heißt, wir haben viele ungenutzte Speicherbereiche im
Hauptspeicher und in diesen kleinen ungenutzten Speicherbereichen kann
es sein, dass ein Segment nicht eingelagert werden kann.
Trotzdem kann es sein, dass die Summe dieser freien oder unbelegten
Speicherbereiche ausreichen würde.
Also externe Fragmentierung.
Was ist der Vorteil beim Seitenwechselverfahren?
Das ist ganz klar ein geringerer Verwaltungsaufwand.
Wir müssen ja nicht erst schauen, ob wir genügend lange oder genügend
große Lücke finden, sondern wir müssen im Prinzip nur schauen, ob eine
Lücke da ist, die ja dann genauso groß ist wie die Seite und wenn
keine frei ist, dann müssen wir eben entsprechend ersetzen und die
Ersetzungsstrategie loslassen.
Wir haben also eine bessere Hauptspeicherauslastung.
Wir können jede Lücke ausnützen, aber wir müssen häufiger Daten hin
und her schaufeln zwischen Hintergrundspeicher und Hauptspeicher.
Das Problem der internen Fragmentierung kann man hier auch noch
erwähnen.
Und mit der Speicherverwaltung hängt dann auch zusammen, wie schaut es
denn aus?
Wir haben die Caches kennengelernt und ich habe jetzt immer gesagt,
wir holen uns die Informationen aus dem Hintergrundspeicher, den
Hauptspeicher und verarbeiten sie dann.
Jetzt haben wir aber gelernt, dass zwischen Hauptspeicher und
Prozessor wir Caches gelagert haben und die holen sich wiederum Kopien
aus dem Hauptspeicher in den Cache und greifen dort drauf zu.
Das sind schnellere Speicher und dort können wir auch schnell drauf
zugreifen.
Aber wie adressiere ich jetzt den Cache?
Wir haben gelernt, wie Caches grundsätzlich adressiert werden, nämlich
inhaltsbezogen adressiert werden.
Nämlich, dass wir einen Teil der Adresse verwenden und je nach
Organisation adressieren wir damit zunächst einen Block im Cache
-Speicher und innerhalb dieses Blocks haben wir dann eine Zeile, die
ausgewählt wird.
Aber welche Adresse nehmen wir jetzt her?
Nachdem wir gelernt haben, wir haben eine virtuelle Adresse, die hier
den virtuellen Adressraum kennzeichnet und wir haben physikalische
Adressen in unserem physikalischen Speicher.
Nehme ich jetzt virtuelle Adressen, um hier die Information im Cache
abzulegen oder hier Informationen im Cache zu adressieren oder nehme
ich physikalische Adressen, um hier die Information zu adressieren.
Und beide Möglichkeiten gibt es, nämlich den sogenannten virtuellen
Cache und der nimmt für die Adressierung einen Teil der virtuellen
Adresse.
Und ich nehme einen physikalischen Cache und dort habe ich die
physikalische Adresse.
Wenn ich virtuelle Adressen verwende, wo muss dann der Cache liegen?
Ja, der muss dann genau zwischen MMU und CPU liegen.
Denn die CPU schickt eine logische Adresse raus, die dann von der MMU
umgesetzt wird in eine physikalische Adresse.
Das ist ja die eigentliche Aufgabe der virtuellen Speicherverwaltung.
Und wenn ich jetzt einen virtuellen Cache habe, der nimmt dann die
höherwertigen Bits der logischen Adresse und legt diese dann als Tags
im Cache ab, um auf diesen Block dann zuzugreifen.
Beim physikalischen Cache, ja, da greife ich die physikalische Adresse
als Tag ab und lege sie im Cache ab.
Also der höherwertige Teil der physikalischen Adresse wird als Tag
abgelegt.
Das heißt, der Cache-Speicher ist der MMU nachgelagert.
Was sind jetzt die Nachteile des virtuellen Caches?
Die Vorteile des virtuellen Caches sind, wenn ich eine logische
Adresse habe, dann gehe ich mit dieser Adresse in den Cache und wenn
ich einen Treffer habe, kann ich die Information sofort weiter
verarbeiten.
Ich muss also nicht erst den Weg über die Speicherverwaltungseinheit
gehen.
Das heißt, ich habe keine Verzögerung wegen der Adressberechnung der
MMU.
So, was ist der Vorteil der physikalischen Caches?
Ja, ich kann zum Teil mit weniger Bits auskommen, weil die
physikalische Adresse, die ich benötige, ja im Allgemeinen viel
kleiner ist als die logische Adresse.
Und wenn die MMU auch ein Prozessorchip ist, dann kann ich eben den
Cache außerhalb des Prozessors weiter erweitern.
Das heißt, ich kann den Off-Chip-Cache dann noch größer machen.
So, schauen wir uns die segmentorientierte Speicherverwaltung im
Prinzip an einem Beispiel an.
Also, wir gehen davon aus, dass der Speicher, der virtuelle
Adressraum, in Segmente aufgeteilt ist.
Und damit wird der physikalische Speicher auch in physikalische
Segmente, also zusammenhängende Speicherbereiche variabler Länge,
unterteilt.
Wir unterscheiden zwischen Code- und Datensegmenten.
Und so ein Segment kann jetzt variabel lang sein, beispielsweise von
64 KB bis 4 GB.
Und wie das bei den 80x86 Prozessoren ausschaut, also bei den 803,
804, 806 oder Pentium, Pentium II, III oder IV, das kann man jetzt
hier an dem Beispiel sehen.
Wir haben wieder die virtuelle Adresse und der höherwertige Teil der
Adresse bildet den Selektor.
Der Selektor ist insgesamt 13, 14, Entschuldigung, 14, 15 Bit lang.
Entschuldigung, jetzt war ich taub.
15 Bit ist immer unterschiedlich.
Und Sie sehen, wir nehmen jetzt nicht mehr eine Adresse von 0 bis 31,
sondern wir haben eine Adresse insgesamt von 0 bis 47.
Jetzt wissen wir aber, der 803, 806 und alle Nachfolger sind ja 32 Bit
Rechner, das heißt wir haben eine 32 Bit Adresse.
Woher kommt jetzt der Selektor?
Der Selektor ist also ein zusätzlicher Teil der Adresse und die
Aufgabe dieses Selektors ist jetzt die Basisadresse zu liefern für das
Segment.
Dieser Selektor, der steht in einem speziellen Register, in einem
sogenannten Selektorregister, in einem sogenannten Segmentregister.
Und dieser Selektor ist ein Index, der im Hauptspeicher, der auf die
Segmentbasisadresse zeigt.
Und das ist die Anfangsadresse des Segments.
Und diese Segmentbasisadresse, also diese Anfangsadresse des Segments,
die wird jetzt ergänzt durch das Offset.
Und diese 32 Bit Adresse ist jetzt die sogenannte lineare Adresse,
innerhalb der sich die Information im Segment befindet.
Diese lineare Adresse ist also auch eine virtuelle Adresse.
Aber zur Unterscheidung eben zur virtuellen Adresse spricht man von
linearer Adresse, weil ich ja als gesamte virtuelle Adresse zusätzlich
diesen Selektor mithabe.
Ja, und diese Segmentbasisadresse, die steht in einem sogenannten
Deskriptor.
Das ist der sogenannte Segmentdeskriptor.
Und dieser Segmentdeskriptor, der steht in der Deskriptortabelle für
die Segmente.
Und hier stehen alle Segmentdeskriptoren für die Segmente, die
verwendet werden.
Und wenn ich die Basisadresse des Segments über diesen Selektor habe,
dann ist dieser Selektor nichts anderes als ein Index in diese
Deskriptortabelle.
Und damit bekomme ich jetzt den Anfangsadresse des Segments.
In diesem Deskriptor steht auch noch drin, wie lang das Segment ist
und weitere Attribute, die hier notwendig sind.
Wie das genau passiert, ja ich weiß, es ist schon so weit, aber lasst
es mich den einen Satz noch sagen.
Und wenn es noch länger dauert, dann kann man noch länger bleiben.
Also wie das im Einzelnen aufgebaut ist, wie die Segmente aufgebaut
sind, wie diese Skriptoren aufgebaut sind, und wie das im Einzelnen
dann auch mit der Seitenverwaltung funktioniert, das erzähle ich Ihnen
dann morgen.
Danke für die Aufmerksamkeit.