OOStuBS/MPStuBS
Aufgabe 2: Interruptbehandlung für OOStuBS/MPStuBS

Lernziele

  • Behandlung asynchroner Ereignisse (Interrupts)
  • Problematik und Schutz kritischer Abschnitte

Aufgabenbeschreibung

In dieser Aufgabe soll eine einfache Interruptbehandlung für Unterbrechungen von der Tastatur implementiert werden. Die Aufgabe wird grob in vier Teile gegliedert werden:

  • Vervollständigen der IOAPIC-Klasse
  • Implementation des Dispatching der Interrupt-Service-Routinen (guardian() und Plugbox) sowie des Panic-Gates
  • Bereitstellen der Keyboard-Interrupt Service-Routine
  • Locking

Zunächst muss die Klasse IOAPIC ergänzt werden, sodass am I/O-APIC die nötigen Einstellungen vorgenommen werden und ein Interrupt-Signal an die Prozessoren durchgestellt wird. Dann soll die Klasse Plugbox implementiert werden, die zusammen mit der Funktion guardian() softwareseitig als Dispatcher von Interruptservice-Routinen dient. Kommt ein Interruptsignal an den Prozessor an, wird der Interruptvektor bestimmt. Der Vektor wird guardian() als Parameter übergeben und basierend auf dem Vektor wird mithilfe der Plugbox ein Gate-Objekt ausgewählt, welches den Code der Interruptbehandlung beinhalten soll.

Panic und Keyboard sollen zwei Gate-Objekte werden, die vom guardian() an den passenden Stellen zur Interruptbehandlung verwendet werden. Keyboard behandelt Keyboard-Interrupts, indem es das eingegebene Zeichen (falls es valide ist) auf dem Bildschirm ausgibt. Panic hingegen hält das System komplett an, falls ein anderer, nicht erwarteter Interrupt gekommen ist.

Um die entsprechenden Geräte überall in OOStuBS/MPStuBS nutzen zu können, sollen von den Klassen Plugbox, IOAPIC und Keyboard globale Instanzen der Objekte namens plugbox, ioapic und keyboard angelegt werden. Ein globales Objekt lapic der Klasse LAPIC exisiert bereits, da dieses während des Startup-Vorganges benötigt wird (wenn man dieses benutzen möchte, reicht also eine extern Deklaration des Objekts).

(A) Konfigurieren des IOAPIC

Zunächst muss der I/O-APIC konfiguriert werden. Beim Boot-Vorgang werden alle Unterbrechnungen am I/O-APIC und der CPU ausmaskiert. Also muss zunächst der I/O-APIC entsprechend konfiguriert werden, dass die Interrupts von externen Geräten, im Speziellen der Tastatur, durchkommen. Der LAPIC ist bereits richtig vorkonfiguriert, Änderungen an der Klasse LAPIC müssen daher nicht vorgenommen werden.

Die Interrupt-Deskriptor-Table (IDT) wird beim Booten bereits korrekt durch den StuBS-Code initialisiert, sodass alle Interrupts im Aufgruf von guardian() resultieren. So kann dieser Aufgabenteil leicht getestet werden, indem eine Ausgabe im guardian() vorgenommen und ein Interrupt der Tastatur durch Drücken einer Taste getriggert wird. Die Tastatur hat allerdings die Eigenheit, keine Interrupts mehr zu senden, wenn der Puffer des Tastaturcontrollers voll ist. Wird ein Tastaturinterrupt also sonst nicht mehr weiter behandelt, werden irgendwann keine Unterbrechungen der Tastatur mehr ankommen. Es kann je nach Umgebung auch nötig sein, den Puffer vor Beginn einmal komplett zu leeren.

Um unerwünschte geschachtelte Interrupts (also Interrupt-Behandlung innerhalb laufender Interrupt-Behandlungen) müsst ihr euch keine Gedanken machen, weil der Prozessor mit Eintritt in die ISR erst einmal keine weiteren Interrupts annimmt. Erst mit dem Ende der Wrapper-Assemblerfunktion um den guardian() (also der Instruktion iret) werden die Interrupts wieder eingeschaltet. Es ist allerdings wichtig, jeden angenommenen Interrupt zu quittieren. Die Kommunikation mit dem LAPIC haben wir bereits vorimplementiert, ihr müsst einzig LAPIC::ackIRQ() in der ISR rufen. Interrupt-Service-Routinen können nur angenommen werden, solange sich StuBS wenigstens in der main() (bzw. main_ap()) befindet; am Ende der Main-Funktionen werden die Interrupts maskiert und der Prozessor schlafen gelegt (CPU::die())

(B) Dispatchen von ISRs und Panic-Gate

Am Ende des ersten Teils wird bei einem Tastatur-Interrupt die guardian() Funktion aufgerufen. Nun muss der guardian() um Dispatching-Funktionalität ergänzt werden. Zur Auswahl, welcher Code als Interrupt-Service-Routine ausgeführt werden soll, wird der Interrupt-Vektor mitgegeben. Mit Hilfe des Vektors kann das passende Treiber-Objekt (hier Gate) ausgewählt werden, um den anstehenden Interrupt zu behandeln. Die Gate Klasse gibt dabei das Interface für die Treiber vor, sodass sich der guardian() darauf verlassen kann, dass es eine trigger()-Methode gibt. Andernfalls würde bereits der Compiler eine Fehlermeldung werfen.

Die Plugbox dient als Datenstruktur für die Gate-Objekte und soll vom guardian() verwendet werden. Die Treiber müssen sich vorher in der Plugbox einstecken (bspw. Keyboard::plugin()). Initial sollen alle Zeiger in der Plugbox auf ein globales Panic-Objekt eingestellt sein.

(C) Keyboard-Gate

Die Klasse Keyboard soll derart implementiert werden, dass sie Interrupts von der Tastatur auflösen kann, d.h. aus dem Puffer des Tastaturcontrollers liest und die zurückgegebenen Zeichen auf dem CGA-Bildschirm in je einer neuen Zeile ausgibt. Die Tastenkombination „Ctrl-Alt-Delete“ soll zusätzlich einen Reboot auslösen (Hinweis: Der Reboot wird hardwareseitig durch den Tastaturcontroller durchgeführt).

(D) Testanwendung (OO- und MPStuBS) und Locking (MPStuBS)

Nun soll die Testanwendung an die Interruptverarbeitung angepasst werden.

In der Application::action() (die von main() aufgerufen werden soll), sollen in einer Endlosschleife ein int-Zähler hochgezählt werden. Dieser wird mittels CGA_Screen::setpos() und den Stream-Operationen von O_Stream an einer festen Position im kout-Objekt ausgegen. Mittels Tastatur-Interrupts lassen sich die Ausgaben nun durcheinander bringen. Ihr sollt das Problem mit den in der CPU-Klasse gegebenen Methoden lösen.

Locking

Für MPStuBS sollen die Ausgaben aller Prozessoren über kout laufen, allerdings an verschiedenen Positionen. Nun benötigen wir nicht einmal mehr Interrupts um die Ausgabe durcheinander zu bringen. Zur Lösung reichen die in der CPU-Klasse vorgegebenen Methoden nicht auch mehr aus.

Implementiert also eine Klasse Spinlock (optional auch Ticketlock), um parallele Kontrollflüsse zu synchronisieren.

Klassenübersicht für Aufgabe 2

dot_a2-sra.png
Klassenübersicht für Aufgabe 2

Vorgaben

Die Implementierung der Klassen CPU und LAPIC haben wir bereits für euch übernommen. Für die eigentliche Tastaturabfrage könnt ihr eure Klasse Keyboard_Controller aus Aufgabe 1 weiter verwenden (Hinweis: Die Funktion Keyboard_Controller::key_hit kann an die nun veränderte Situation angepasst werden).

Hilfen und Dokumentation