Aufgabe 3: Pro-/Epilog
In dieser Aufgabe kümmern wir uns um asynchrone Ereignisse, Unterbrechungen durch externe Geräte, in unserem Fall primär durch die Tastatur, aber optional auch die Maus. Wie wir in der Musterlösung sehen, unterbrechen die Tastaturereignisse abwechselnd alle Kerne, erkennbar an den unterschiedlichen Farben. Dieses Verhalten konfigurieren wir mittels des I/O APIC*s. Damit tauchen nun auch Synchronisationsprobleme auf – diese kritischen Abschnitte können wir mit *Ticket- oder Spinlock*s schützen. Optional gibt es die Möglichkeit einen *GDB Stub einzubauen, welcher zusammen mit der seriellen Schnittstelle ein remote debugging erlaubt.
Dazu müssen die Gerätetreiber wie Keyboard
implementiert werden, welche sich dann bei der Plugbox
für gewisse Interrupts registrieren. Die Unterbrechungsbehandlung interrupt_handler
soll dann diese Plugbox
nutzen, um das Geräteobjekt zum jeweiligen Interruptvektor zu bekommen. Daneben muss noch der PS/2-Controller an den Interruptbetrieb angepasst werden und die I/O APIC Konfiguration implementiert werden.
Und wie konfiguriere ich diesen Advanced Programmable Interrupt Controller? Dazu muss ich wissen, wo – an welcher der 24 Eingänge – ein Gerät am I/O APIC angeschlossen ist – was aber je nach Rechner unterschiedlich sein kann. Abhilfe schafft die Systemkonfiguration ACPI, diese besteht aus vielen Tabellen, in welcher auch die gesuchten Informationen stehen. Diese Angaben über die Verdrahtung der angeschlossenen Geräte aus den ACPI Tabellen werden während der Initialisierung von unserer APIC
Klasse eingelesen und über die Funktion getIOAPICSlot
zur Verfügung gestellt. Ebenfalls kommt da auch die noch zu setzende ID des I/O APIC her.
Nun müssen wir auch noch einstellen, wer bei Interrupts benachrichtigt werden soll. Im Gegensatz zu OOStuBS ist das bei MPStuBS nicht so einfach, wir wollen zu Übungszwecken eine Gleichverteilung auf alle Kerne. Dazu setzen wir die Priorität eines jeden LAPIC bei der Initialisierung auf 0
, das ist bereits so implementiert, und wählen im I/O APIC die niedrigste Priorität als Zustellmodus. Außerdem verwenden wir die logische Adressierung für die Ziel-CPUs, damit können mittels Bitmaske alle CPUs angegeben werden.
Das sieht dann im Redirection Table Eintrag so aus: Gleich oben sieht man die Bitmasken für die Ziel CPUs. Das kann man auch dynamisch machen, aber man sollte keinesfalls nicht-existente CPUs adressieren, das mag zumindest die Testhardware nicht. Dazu muss natürlich auch Delivery und Destination Mode entsprechend gesetzt sein. Ob der Interrupt aktiv ist, und falls ja, ob er Flanken- oder Pegel-gesteuert ist, hängt vom jeweiligen Slot beziehungsweise Gerät ab. Die Tastatur ist zum Beispiel Pegel-gesteuert. Die Felder Remote Interrupt Request Register und Delivery Status können wir nicht ändern. Und für den Interrupt Vektor sollte man sich systemweit einig sein welches Gerät was verwendet, entsprechend gibt es unter machine
im Header cpu_interrupt.h
ein enum
dafür.
Wenn ihr das nun in KVM testet, kann es sein, dass die Tastaturinterrupts immer auf der gleichen CPU ankommen. Das könnte auch am Vector Hashing liegen, die Interruptnummer wird bei neueren KVM Versionen Modulo Anzahl CPUs genommen und zugeteilt. Das ist zwar eine gute Optimierung bezüglich Cachelokalität und so weiter, aber wir wollen für den Lerneffekt eine gleichmäßige Verteilung an alle CPUs. Ihr könnt das dem KVM mit einem Eintrag in der Modulkonfiguration und einem Neustart austreiben, alternativ könnt ihr auch das Modul unloaden und mit geändertem Parameter neu laden, Details stehen dazu auf der Webseite unter FAQ.
Wie läuft nun ganz konkret ein Interrupt bei einem Tastendruck in StuBS ab? Nun, zuerst müssen wir vorbereitend alles Konfigurieren. Der I/O APIC muss initialisiert werden, ID gemäß getIOAPICID
setzen und alle Redirection Table Einträge präventiv auf inaktiv setzen. Das Keyboard
meldet sich dann in der Plugbox
an, findet mittels getIOAPICSlot
heraus wo es angeschlossen ist und kann damit den entsprechenden Redirection Table Eintrag konfigurieren. Dann brauchen wir für die Unterbrechungen noch die Einsprungsfunktionen, auf welche in der Interrupt Deskriptor Tabelle (IDT
) verwiesen wird – das ist jedoch bereits in der Vorgabe erledigt. Was noch fehlt, ist die eigentliche Unterbrechungsbehandlung in der Funktion interrupt_handler
. Und bevor wir loslegen können müssen wir auch noch die Unterbrechungen auf jeder CPU einschalten.
Wird nun eine Taste gedrückt, so meldet dies der Tastaturprozessor an den PS/2-Controller, dieser aktiviert beispielsweise die zweite Interruptleitung zum I/O APIC. Der I/O APiC schaut dann im zweiten Redirection Table Eintrag nach und sieht, dass er den Vector 33
senden muss, und wählt nach der Priorität zum Beispiel die 3. CPU aus. Die Nachricht wird auf den APIC-Bus gelegt und vom LAPIC der 3. CPU empfangen. Dieser signalisiert den Interrupt der CPU, welche nun den derzeitigen Ablauf unterbricht, den 33. Eintrag in der Interrupt Deskriptor Tabelle lädt und zur Einsprungsfunktion interrupt_entry_33
wechselt. Diese sichert noch die restlichen Register und ruft interrupt_handler
mit der Vektornummer als Parameter auf. Die Unterbrechungsbehandlung wählt dann mittels Plugbox
das entsprechende Objekt aus – hier eben Keyboard
, und ruft dort trigger
auf. Dort kann nun die Taste am Bildschirm ausgegeben werden, danach wird zurückgekehrt und noch dem LAPIC das Ende des Interrupts signalisiert. Dann werden die Register wiederhergestellt und die CPU setzt dort fort wo sie unterbrochen wurde.