Teil 5 - Interrupts — Änderungen 11377 → 11391

Zeile 17: Zeile 17:
 
Ich gehe im Folgenden davon aus, dass die im Artikel beschriebenen Code- und Datensegmente in der Reihenfolge angelegt worden sind, in der sie im Artikel vorgeschlagen sind. Die TSS-Einträge brauchen wir vorerst noch nicht. Wir haben also in etwa den folgenden Code:
 
Ich gehe im Folgenden davon aus, dass die im Artikel beschriebenen Code- und Datensegmente in der Reihenfolge angelegt worden sind, in der sie im Artikel vorgeschlagen sind. Die TSS-Einträge brauchen wir vorerst noch nicht. Wir haben also in etwa den folgenden Code:
  
−
<c>#define GDT_FLAG_DATASEG 0x02
+
<syntaxhighlight lang=c>#define GDT_FLAG_DATASEG 0x02
 
#define GDT_FLAG_CODESEG 0x0a
 
#define GDT_FLAG_CODESEG 0x0a
 
#define GDT_FLAG_TSS    0x09
 
#define GDT_FLAG_TSS    0x09
Zeile 42: Zeile 42:
  
 
     load_gdt();
 
     load_gdt();
−
}</c>
+
}</syntaxhighlight>
  
 
Testen lässt sich die GDT ganz einfach: Wenn der Kernel nach dem Laden der GDT und dem Neuladen der Segmentregister noch läuft, war es wohl hinreichend richtig. Wenn der Rechner neu startet, stimmt noch irgendetwas nicht ganz.
 
Testen lässt sich die GDT ganz einfach: Wenn der Kernel nach dem Laden der GDT und dem Neuladen der Segmentregister noch läuft, war es wohl hinreichend richtig. Wenn der Rechner neu startet, stimmt noch irgendetwas nicht ganz.
Zeile 50: Zeile 50:
  
 
Für einen ersten Test nimmst Du am besten einen einfachen Handler her, ohne aus dem Interrupt zurückzuspringen:
 
Für einen ersten Test nimmst Du am besten einen einfachen Handler her, ohne aus dem Interrupt zurückzuspringen:
−
<c>void int_handler(void)
+
<syntaxhighlight lang=c>void int_handler(void)
 
{
 
{
 
     kprintf("Ein Interrupt!\n");
 
     kprintf("Ein Interrupt!\n");
 
     while(1);
 
     while(1);
−
}</c>
+
}</syntaxhighlight>
 
Nachdem Du die GDT und IDT eingerichtet hast (und den Handler für Interrupt 0, also in den ersten Eintrag der IDT eingetragen hast), kannst Du versuchen, diesen Interrupt auszulösen:
 
Nachdem Du die GDT und IDT eingerichtet hast (und den Handler für Interrupt 0, also in den ersten Eintrag der IDT eingetragen hast), kannst Du versuchen, diesen Interrupt auszulösen:
−
<c>asm volatile("int $0x0");</c>
+
<syntaxhighlight lang=c>asm volatile("int $0x0");</syntaxhighlight>
 
Wenn alles funktioniert, bekommst Du jetzt unsere Erfolgsmeldung aus dem Interrupt.
 
Wenn alles funktioniert, bekommst Du jetzt unsere Erfolgsmeldung aus dem Interrupt.
  
Zeile 64: Zeile 64:
 
Ein übliches Modell ist es, auf den Interrupts 0 bis 0x1f die Exceptions zu haben, direkt anschließend von 0x20 bis 0x2f die IRQs und ab 0x30 die Software-Interrupts, z.B. Syscalls. In diesem Fall müssten wir den beiden PICs also mitteilen, sie sollen die IRQs ab 0x20 bzw. 0x28 anfangen lassen:
 
Ein übliches Modell ist es, auf den Interrupts 0 bis 0x1f die Exceptions zu haben, direkt anschließend von 0x20 bis 0x2f die IRQs und ab 0x30 die Software-Interrupts, z.B. Syscalls. In diesem Fall müssten wir den beiden PICs also mitteilen, sie sollen die IRQs ab 0x20 bzw. 0x28 anfangen lassen:
  
−
<c>// Master-PIC initialisieren
+
<syntaxhighlight lang=c>// Master-PIC initialisieren
 
outb(0x20, 0x11); // Initialisierungsbefehl fuer den PIC
 
outb(0x20, 0x11); // Initialisierungsbefehl fuer den PIC
 
outb(0x21, 0x20); // Interruptnummer fuer IRQ 0
 
outb(0x21, 0x20); // Interruptnummer fuer IRQ 0
Zeile 78: Zeile 78:
 
// Alle IRQs aktivieren (demaskieren)
 
// Alle IRQs aktivieren (demaskieren)
 
outb(0x21, 0x0);
 
outb(0x21, 0x0);
−
outb(0xa1, 0x0);</c>
+
outb(0xa1, 0x0);</syntaxhighlight>
  
 
Dabei ist <i>outb</i> eine Funktion, um einen 8-Bit-Wert an einen [[I/O Ports | I/O-Port]] zu schreiben, und kann wie in [[Inline-Assembler mit GCC]] beschrieben definiert werden:
 
Dabei ist <i>outb</i> eine Funktion, um einen 8-Bit-Wert an einen [[I/O Ports | I/O-Port]] zu schreiben, und kann wie in [[Inline-Assembler mit GCC]] beschrieben definiert werden:
−
<c>static inline void outb(uint16_t port, uint8_t data)
+
<syntaxhighlight lang=c>static inline void outb(uint16_t port, uint8_t data)
 
{
 
{
 
     asm volatile ("outb %0, %1" : : "a" (data), "Nd" (port));
 
     asm volatile ("outb %0, %1" : : "a" (data), "Nd" (port));
−
}</c>
+
}</syntaxhighlight>
  
 
Nachdem die IRQs auf sinnvolle Nummern gelegt sind und Handler in der IDT installiert sind, kannst Du Hardware-Interrupts aktivieren:
 
Nachdem die IRQs auf sinnvolle Nummern gelegt sind und Handler in der IDT installiert sind, kannst Du Hardware-Interrupts aktivieren:
−
<c>asm volatile("sti");</c>
+
<syntaxhighlight lang=c>asm volatile("sti");</syntaxhighlight>
 
'''Achtung''': Der Kernel kann von der ''sti''-Instruktion an an jeder beliebigen Stelle unterbrochen werden, auch wenn die Initialisierung an dieser Stelle noch nicht vollständig abgeschlossen ist. Es ist daher in der Regel eine gute Idee, Interrupts erst als allerletztes in der Kernelinitialisierung zu aktivieren.
 
'''Achtung''': Der Kernel kann von der ''sti''-Instruktion an an jeder beliebigen Stelle unterbrochen werden, auch wenn die Initialisierung an dieser Stelle noch nicht vollständig abgeschlossen ist. Es ist daher in der Regel eine gute Idee, Interrupts erst als allerletztes in der Kernelinitialisierung zu aktivieren.
  
Zeile 97: Zeile 97:
 
Ein vorgeschalteter Teil soll den Prozessorzustand sichern und der eigentliche Handler soll nur noch ungefähr so aussehen:
 
Ein vorgeschalteter Teil soll den Prozessorzustand sichern und der eigentliche Handler soll nur noch ungefähr so aussehen:
  
−
<c>void handle_interrupt(struct cpu_state* cpu)
+
<syntaxhighlight lang=c>void handle_interrupt(struct cpu_state* cpu)
 
{
 
{
 
     if (cpu->intr <= 0x1f) {
 
     if (cpu->intr <= 0x1f) {
Zeile 111: Zeile 111:
 
         // Hier den Hardwareinterrupt behandeln
 
         // Hier den Hardwareinterrupt behandeln
 
     }
 
     }
−
}</c>
+
}</syntaxhighlight>
  
 
Das lässt sich einfach genug erreichen, dazu nehmen wir zur Abwechslung wieder einmal Assembler zur Hilfe. Wir müssen dazu für jeden Interrupt einen eigenen Handler definieren, der aber nichts anderes macht, als die Interruptnummer auf den Stack zu legen und in einen gemeinsamen Handler für alle Interrupts zu springen. Außerdem müssen wir aufpassen, weil manche Exceptions einen Fehlercode auf den Stack legen, bei allen anderen Interrupts fehlt er. Um alle Interrupts gleich aussehen zu lassen, legen wir vor der Interruptnummer noch eine 0 auf den Stack, wenn kein Fehlercode vorhanden ist. Wir definieren also:
 
Das lässt sich einfach genug erreichen, dazu nehmen wir zur Abwechslung wieder einmal Assembler zur Hilfe. Wir müssen dazu für jeden Interrupt einen eigenen Handler definieren, der aber nichts anderes macht, als die Interruptnummer auf den Stack zu legen und in einen gemeinsamen Handler für alle Interrupts zu springen. Außerdem müssen wir aufpassen, weil manche Exceptions einen Fehlercode auf den Stack legen, bei allen anderen Interrupts fehlt er. Um alle Interrupts gleich aussehen zu lassen, legen wir vor der Interruptnummer noch eine 0 auf den Stack, wenn kein Fehlercode vorhanden ist. Wir definieren also:
  
−
<asm>.macro intr_stub nr
+
<syntaxhighlight lang=asm>.macro intr_stub nr
 
.global intr_stub_\nr
 
.global intr_stub_\nr
 
intr_stub_\nr:
 
intr_stub_\nr:
Zeile 156: Zeile 156:
  
 
// Syscall
 
// Syscall
−
intr_stub 48</asm>
+
intr_stub 48</syntaxhighlight>
  
 
Im zweiten Teil müssen wir jetzt den <i>intr_common_handler</i> definieren. Er soll den CPU-Zustand auf den Stack speichern und dann die oben genannte Funktion <i>handle_interrupt</i> aufrufen. Nachdem der Interrupt fertig behandelt ist, stellen wir den CPU-Zustand wieder her und springen zurück in das unterbrochene Programm. Das könnte ungefähr so aussehen:
 
Im zweiten Teil müssen wir jetzt den <i>intr_common_handler</i> definieren. Er soll den CPU-Zustand auf den Stack speichern und dann die oben genannte Funktion <i>handle_interrupt</i> aufrufen. Nachdem der Interrupt fertig behandelt ist, stellen wir den CPU-Zustand wieder her und springen zurück in das unterbrochene Programm. Das könnte ungefähr so aussehen:
  
−
<asm>.extern handle_interrupt
+
<syntaxhighlight lang=asm>.extern handle_interrupt
 
intr_common_handler:
 
intr_common_handler:
 
     // CPU-Zustand sichern
 
     // CPU-Zustand sichern
Zeile 189: Zeile 189:
  
 
     // Ruecksprung zum unterbrochenen Code
 
     // Ruecksprung zum unterbrochenen Code
−
     iret</asm>
+
     iret</syntaxhighlight>
  
 
Die passende Definition für struct cpu_state sähe in diesem Fall so aus, dass sie einfach die auf den Stack gepushten Werte enthält. Zur Erinnerung, der Stack wächst nach unten, daher fängt die Struktur mit dem zuletzt gepushten (also eax) an:
 
Die passende Definition für struct cpu_state sähe in diesem Fall so aus, dass sie einfach die auf den Stack gepushten Werte enthält. Zur Erinnerung, der Stack wächst nach unten, daher fängt die Struktur mit dem zuletzt gepushten (also eax) an:
−
<c>struct cpu_state {
+
<syntaxhighlight lang=c>struct cpu_state {
 
     // Von Hand gesicherte Register
 
     // Von Hand gesicherte Register
 
     uint32_t  eax;
 
     uint32_t  eax;
Zeile 211: Zeile 211:
 
     uint32_t  esp;
 
     uint32_t  esp;
 
     uint32_t  ss;
 
     uint32_t  ss;
−
};</c>
+
};</syntaxhighlight>
  
 
Nachdem die Handler für die einzelnen Interrupts nun fertig geschrieben sind, müssen sie (falls noch nicht geschehen) natürlich noch in die IDT eingetragen werden, damit sie beim Auftreten eines Interrupts angesprungen werden. Andernfalls würde der Code nie ausgeführt werden.
 
Nachdem die Handler für die einzelnen Interrupts nun fertig geschrieben sind, müssen sie (falls noch nicht geschehen) natürlich noch in die IDT eingetragen werden, damit sie beim Auftreten eines Interrupts angesprungen werden. Andernfalls würde der Code nie ausgeführt werden.
  
 
Eine geeignete Implementierung für Exceptions ist die oben schon vorgeschlagene Fehlermeldung mit anschließendem Anhalten des Systems (später könnte man das aktuell ausgeführte Programm beenden, aber wir haben so etwas ja noch nicht). Für Hardware-Interrupts muss zumindest ein EOI (End of Interrupt) an den PIC gesendet werden, damit der Interrupt gleich beim nächsten Mal wieder gemeldet wird und nicht noch vom letzten Aufruf blockiert ist:
 
Eine geeignete Implementierung für Exceptions ist die oben schon vorgeschlagene Fehlermeldung mit anschließendem Anhalten des Systems (später könnte man das aktuell ausgeführte Programm beenden, aber wir haben so etwas ja noch nicht). Für Hardware-Interrupts muss zumindest ein EOI (End of Interrupt) an den PIC gesendet werden, damit der Interrupt gleich beim nächsten Mal wieder gemeldet wird und nicht noch vom letzten Aufruf blockiert ist:
−
<c>if (cpu->intr >= 0x20 && cpu->intr <= 0x2f) {
+
<syntaxhighlight lang=c>if (cpu->intr >= 0x20 && cpu->intr <= 0x2f) {
 
     if (cpu->intr >= 0x28) {
 
     if (cpu->intr >= 0x28) {
 
         outb(0xa0, 0x20);
 
         outb(0xa0, 0x20);
 
     }
 
     }
 
     outb(0x20, 0x20);
 
     outb(0x20, 0x20);
−
}</c>
+
}</syntaxhighlight>
  
 
Mit diesen Interrupthandlern ließe sich an dieser Stelle beispielsweise schon ein [[Keyboard Controller|Tastaturtreiber]] schreiben.
 
Mit diesen Interrupthandlern ließe sich an dieser Stelle beispielsweise schon ein [[Keyboard Controller|Tastaturtreiber]] schreiben.