Aufgabe 3: Pro-/Epilog
Wir wissen aus dem Video zu Interrupts und Traps auf dem x86er, wie die CPU intern auf Unterbrechungen reagiert, also bei einem Signal auf der Interruptleitung in unsere Behandlungsroutine wechselt. Zumeist wollen wir damit auf asynchrone Ereignisse reagieren, die von externen Geräten wie der Tastatur kommen – aber wie sind diese nun verschalten, damit sie die Unterbrechung der CPU auslösen können?
Ursprünglich gab es dafür den Programmable Interrupt Controller, PIC 8259A, an den bis zu 8 Geräte angeschlossen werden können. Der Controller prüft dauernd diese 8 Leitungen, hat dabei eine feste Prioritätenreihenfolge. Auch der Interruptvektor wird entsprechend der Leitungen hochgezählt, änderbar ist nur der Startvektor, und zwar über I/O Ports. Üblicherweise war an der ersten Leitung des PICs der Timer, an der zweiten die Tastatur verdrahtet, Leitungen drei und vier für Seriell, fünf für den PC Speaker, sechs für das Diskettenlaufwerk und die letzte Leitung für den Parallelport, an dem zum Beispiel ein Drucker hing.
Wenn nun auf der angeschlossenen Tastatur eine Taste gedrückt wird, fragt dies der PIC ab und meldet es der CPU. Ja, in der Standardeinstellung des PICs entsprechen die Vektoren tatsächlich den x86 Traps, weshalb die Tastatur einen Interruptvektor 1
verursacht (wegen der 0
-Indizierung) . Nach der Unterbrechungsbehandlung muss die CPU ein End-Of-Interrupt senden, erst danach lässt der PIC weitere Interrupts zu.
Da die 8 Geräte schon bald nicht mehr ausreichten, wurde an die dritte Leitung ein weiterer PIC geschlossen. Mit diesem wurden Uhr, Maus, Koprozessor und Festplatte verbunden, jedoch nun mit einer entsprechend seltsamen Prioritätenreihenfolge und zudem erhöhten Latenzen. Aber das fundamentale Problem vom PIC ist, dass dieser nur für eine einzelne CPU entwickelt wurde – für Mehrkernsysteme ist er nicht geeignet.
Als Nachfolger wurde der Advanced Programmable Interrupt Controller, kurz APIC, entwickelt. Dieser führt eine Aufteilung in Local APIC (kurz LAPIC) für jede CPU sowie einen gemeinsamen I/O APIC ein. An den I/O APIC können bis zu 24 Geräte angeschlossen werden, theoretisch ist sogar der Betrieb mehrerer *I/O APIC*s möglich. Dabei kann jedem Gerät eine beliebige Interruptvektornummer zugewiesen sowie auch jeweils die Ziel CPUs konfiguriert werden.
Beispielsweise können wir den I/O APIC so konfigurieren, dass die Tastatur an der zweiten Leitung einen Interruptvektor 33
an CPU 1 schickt. Wird nun eine Taste gedrückt, erkennt dies der I/O APIC und legt eine entsprechende Nachricht auf den APIC Bus. Der Local APIC der CPU 1 empfängt dies und teilt es der CPU mit. Nach der erfolgreichen Bearbeitung wird vom LAPIC ein ACK
an den I/O APIC gesendet.
Für die Konfiguration der Geräte müssen die Register des I/O APIC entsprechend konfiguriert werden. Allerdings gibt es keinen direkten Zugriff auf diese Register, Es sind nur ein Index und Datenregister im Speicher eingeblendet, mit welchem ein indirekter Zugriff auf die internen Register erfolgen kann. Die Adresse des internen Registers muss in das Indexregister geschrieben werden, welches an der Speicheradresse 0xfec0 0000
liegt. Im Anschluss kann über das Datenregister 16 Bytes weiter auf den Inhalt des gewählten internen Registers lesend und schreibend zugegriffen werden.
Dabei gibt es für uns folgende relevante interne Register: Im ersten Register mit Index 0 wird die 4-bit lange ID des I/O APIC gespeichert, welche für die Nachrichten auf dem Bus wichtig ist und entsprechend konfiguriert werden muss. Dann gibt es noch 24 Redirection Table Einträge. Jeder Eintrag konfiguriert einen der Eingänge. Da der Eintrag 64 Bit lang ist, belegt jeder Eintrag zwei interne Register, für das Tastaturbeispiel wäre das RT[1]
und somit die internen Register 0x12
und 0x13
.
Dabei sind die Einträge wie folgt aufgebaut: An der niedrigsten Adresse ist die auszulösende Vektornummer. Der Delivery Mode erlaubt unter anderem eine Zustellung an alle CPUs oder mit Lowest Priority ein Durchwechseln der CPUs. Der Destination Mode beeinflusst das Destination Field: Mit logical kann man mittels Bitmaske eine Gruppe von Zielprozessoren angeben, bei physical kann eine konkrete CPU adressiert werden. Weiterhin können noch technische Details wie Pegel- oder Flankenerkennung sowie active high oder active low konfiguriert werden. Dies ist Geräteabhängig und muss aus dem Handbuch entnommen werden.