1 - Kontextwechsel [ID:36120]
50 von 117 angezeigt

Die Rechen-Einheit und die Speicher

Unsere Hardware hat in O-Stubs eine Rechen-Einheit und Speicher.

In Mp-Stubs haben wir zusätzlich maximal sieben weitere Rechen-Einheiten.

Das wollen wir multiplexen, damit wir deutlich mehr Anwendungen als Kerne gleichzeitig laufen lassen können.

Jede Anwendung soll also eine eigene illusionierte Hardware mit virtueller CP und Speicher für sich haben.

Dazu müssen wir beides virtualisieren.

Beim Speicher ist das einfach. Wir teilen ihn in Bereiche auf. Jede Anwendung bekommt ein Stück davon.

Allerdings funktioniert das bei der CPU so nicht. Wir können nicht einfach irgendwie die Instruktionen aufspalten.

Allerdings können wir die Ausführungszeit auf der CPU zwischen den Anwendungen aufteilen.

Aber wie kann man so eine Aufteilung umsetzen?

Wir schauen uns das mal anhand zweier Funktionen an, die sich abwechseln sollen.

Um es einfach zu halten, geben Foo und Ba jeweils in einer Schleife nur ihre Namen sowie eine herunterzählende Zahl aus.

Erster Versuch. Wir hängen einen Aufruf vom Ba ans Ende von Foo. Ein einseitiger Aufruf.

Wir sehen, dass zuerst alle Ausgaben von Foo getätigt werden, danach die Ausgaben vom Ba, welches am Ende aufgerufen wird.

Wir haben also einen sequenziellen Stabilbetrieb, nicht die gewünschte gleichzeitige Ausführung.

Nächster Versuch. Die Zellervariablen bekommen einen festen Speicher zugewiesen und wir setzen den Aufruf der jeweils anderen Funktion in die Schleifen,

und zwar bei beiden Funktionen. Ein gegenseitiger Aufruf also.

Und wenn wir das laufen lassen, dann sieht die Ausgabe auch auf einen ersten Blick gut aus.

Aber wenn wir die Aufruf Hierarchie betrachten, sehen wir, dass wir jedes Mal einen neuen Funktionsaufruf tätigen und entsprechend den Stapel füllen,

was in der Regel einen hohen Speicherverbrauch bedeutet und wir Gefahr laufen einen Stapelüberlauf zu provozieren,

insbesondere wenn wir eine Endlosrekursion haben.

Was wir statt dem Aufruf wollen, ist ein Umschalten. Der Kontext von dem alten Faden muss gesichert und erneut geladen werden.

Dabei bilden uns Stapelspeicherinhalt sowie die Register den Kontext.

Außerdem brauchen wir eine Umschaltfunktion. Bei uns heißt diese Kontextswitch.

Sie bekommt als ersten Parameter den Stackzeiger des aktuellen Fadens und wechselt dann zum neuen Faden, den zweiten Parameter.

Betrachten wir die Funktion erst einmal als Blackbox, ob sie denn unseren Anforderungen genügen würde.

Wir erweitern unsere Anwendung um den Aufruf mit statischen Variablen StackBar und StackFu für die Parameter.

Die Ausgabe entspricht wieder dem gewünschten Ergebnis.

Und auch unter der Haube sieht es gut aus. Wir wechseln zwischen den Anwendungen Fu und Bar hin und her,

und zwar ohne dass unser Speicherverbrauch stetig steigt. Also genau das, was wir erreichen wollen.

Aber was muss diese mystische Umschaltfunktion Kontextswitch dafür nun tun?

Zuerst müssen die Register des aktuellen Kontexts gesichert werden.

Wir können nun dafür einen statischen Speicher nehmen, aber noch einfacher ist, wir pushen diese einfach der Reihe nach auf den Stack.

Dann müssen wir uns lediglich den aktuellen Stapelzeiger merken.

Vom neuen Kontext laden wir nun den Stapelzeiger und stellen die dort irgendwann zuvor auf diesen Stapelgesicherten Register wieder her.

Der Instruktionszeiger ist das Register rep. Also wenn wir diesen dann wie die anderen Register wieder herstellen,

können wir gleich wieder an der entsprechenden Stelle fortsetzen, oder?

Nun leider ist es nicht ganz so leicht, denn wir können nicht einfach eine Adresse in das Register rep. schreiben.

Das erlaubt uns die x86 Architektur nicht.

Stattdessen müssen wir einen kleinen Umweg nehmen, indem wir die gewünschte Adresse auf den Stack pushen und dann die Instruktion red ausführen.

Diese poppt vom Stack den aktuellen Eintrag und setzt das als Instruktionszeiger.

Konkret der Kontextwechsel am Beispiel.

Wir haben unsere Struktur Stackpointe, welche derzeit nur einen einzigen Zeiger, den Kernel Stapelzeiger beinhaltet.

In BST werden wir auch noch den Benutzer Stapelzeiger speichern müssen, deshalb packen wir das schon einmal vorsorglich in eine Struktur.

Wir haben für jede Anwendung foo und bar eine solche Struktur instanziert.

Und wir gehen auch davon aus, dass diese bereits sinnvoll initialisiert wurden und wir also mitten im Betrieb drin sind,

gerade in der Abarbeitung von foo, kurz vor dem Aufruf vom Kontext Switch, mit welchem wir zu bar wechseln wollen.

Wie wir von der Aufrufkonvention bereits wissen, müssen vor einem Funktionsaufruf zuerst die flüchtigen Register gesichert werden.

Das generiert uns der Copiler bereits hin, hier entscheidet er beispielsweise, dass er Acht gesichert werden muss, indem es auf den Stack gepusht wird.

Beim Funktionsaufruf wird durch das Call noch die Rücksprungadresse auf den Stack gepusht,

Teil einer Videoserie :
Teil eines Kapitels:
Threadumschaltung

Zugänglich über

Offener Zugang

Dauer

00:11:31 Min

Aufnahmedatum

2020-08-11

Hochgeladen am

2021-09-20 19:06:16

Sprache

de-DE

Tags

betriebssysteme operating systems stubs context switch
Einbetten
Wordpress FAU Plugin
iFrame
Teilen