TEXT | CODE |
Interrupts
Wir implementieren nun die Interrupt- (und im nächsten Kapitel
die Fault-) Handler und starten dabei mit den Interrupt-Nummern:
| |
|
< constants > ≡ #define IRQ_TIMER 0
#define IRQ_KBD 1
#define IRQ_SLAVE 2 // Here the slave PIC connects to master
#define IRQ_COM2 3
#define IRQ_COM1 4
#define IRQ_FDC 6
#define IRQ_IDE 14 // primary IDE controller; secondary has IRQ 15
|
Weiter geht es mit den in- und out-Befehlen
| |
|
< function prototypes > ≡ unsigned char inportb (unsigned short port);
unsigned short inportw (unsigned short port);
void outportb (unsigned short port, unsigned char data);
void outportw (unsigned short port, unsigned short data);
|
die wir wir in der Vorlesung implementieren; der
Code für inportb und outportb steht bereits in
der Datei printf.c, weswegen wir ihn hier weglassen.
| |
|
< function implementations > ≡ unsigned short inportw (unsigned short port) {
unsigned short retval;
asm volatile ("inw %%dx, %%ax" : "=a" (retval) : "d" (port));
return retval;
}
void outportw (unsigned short port, unsigned short data) {
asm volatile ("outw %%ax, %%dx" : : "d" (port), "a" (data));
}
|
Damit können wir nun die PICs einrichten. Ihre Ports haben
die folgenden Adressen:
| |
|
< constants > +≡ // I/O Addresses of the two programmable interrupt controllers
#define IO_PIC_MASTER_CMD 0x20 // Master (IRQs 0-7), command register
#define IO_PIC_MASTER_DATA 0x21 // Master, control register
#define IO_PIC_SLAVE_CMD 0xA0 // Slave (IRQs 8-15), command register
#define IO_PIC_SLAVE_DATA 0xA1 // Slave, control register
|
Wir müssen bei der Initialisierung die Interrupt-Nummern umbiegen; detaillierte Erläuterungen dazu finden Sie im Buch-Kapitel 12 auf der Webseite.
| |
|
< remap the interrupts to 32..47 > ≡ < PIC: program/initialize the PICs >
< PIC: set the initial interrupt mask >
|
| |
|
< PIC: program/initialize the PICs > ≡ outportb (IO_PIC_MASTER_CMD, 0x11); // ICW1: initialize; begin programming
outportb (IO_PIC_SLAVE_CMD, 0x11); // ICW1: dito, for PIC2
outportb (IO_PIC_MASTER_DATA, 0x20); // ICW2 for PIC1: offset 0x20
// (remaps 0x00..0x07 -> 0x20..0x27)
outportb (IO_PIC_SLAVE_DATA, 0x28); // ICW2 for PIC2: offset 0x28
// (remaps 0x08..0x0f -> 0x28..0x2f)
outportb (IO_PIC_MASTER_DATA, 0x04); // ICW3 for PIC1: there's a slave on IRQ 2
// (0b00000100 = 0x04)
outportb (IO_PIC_SLAVE_DATA, 0x02); // ICW3 for PIC2: your slave ID is 2
outportb (IO_PIC_MASTER_DATA, 0x01); // ICW4 for PIC1 and PIC2: 8086 mode
outportb (IO_PIC_SLAVE_DATA, 0x01);
|
Im zweiten Schritt setzen wir die Interrupt-Maske (und schalten alle Interrupts aus).
| |
|
< PIC: set the initial interrupt mask > ≡ outportb (IO_PIC_MASTER_DATA, 0x00); // PIC1: mask 0
outportb (IO_PIC_SLAVE_DATA, 0x00); // PIC2: mask 0
|
Weiter geht es mit der Interrupt Descriptor Table. Ihre Einträge haben folgenden Aufbau:
| |
|
< type definitions > ≡ struct idt_entry {
unsigned int addr_low : 16; // lower 16 bits of address
unsigned int gdtsel : 16; // use which GDT entry?
unsigned int zeroes : 8; // must be set to 0
unsigned int type : 4; // type of descriptor
unsigned int flags : 4;
unsigned int addr_high : 16; // higher 16 bits of address
} __attribute__((packed));
|
und es gibt (wie bei der GDT) zusätzlich einen Pointer auf den Anfang der Tabelle:
| |
|
< type definitions > +≡ struct idt_ptr {
unsigned int limit : 16;
unsigned int base : 32;
} __attribute__((packed));
|
Wir verwenden zwei globale Variablen für die Tabelle und den Pointer:
| |
|
< global variables > ≡ struct idt_entry idt[256] = { 0 };
struct idt_ptr idtp;
|
Wir können einen Deskriptor mit
| |
|
< function prototypes > +≡ void fill_idt_entry (unsigned char num, unsigned long address,
unsigned short gdtsel, unsigned char flags, unsigned char type);
|
füllen:
| |
|
< function implementations > +≡ void fill_idt_entry (unsigned char num, unsigned long address,
unsigned short gdtsel, unsigned char flags, unsigned char type) {
if (num >= 0 && num < 256) {
idt[num].addr_low = address & 0xFFFF; // address is the handler address
idt[num].addr_high = (address >> 16) & 0xFFFF;
idt[num].gdtsel = gdtsel; // GDT sel.: user mode or kernel mode?
idt[num].zeroes = 0;
idt[num].flags = flags;
idt[num].type = type;
}
}
|
Wir definieren Interrupt-Handler-Funktionen irq0 bis irq15
in der Assembler-Datei. Im C-Programm müssen wir die Funktionen
als extern deklarieren:
| |
|
< function prototypes > +≡ extern void irq0(), irq1(), irq2(), irq3(), irq4(), irq5(), irq6(), irq7();
extern void irq8(), irq9(), irq10(), irq11(), irq12(), irq13(), irq14(), irq15();
|
Dann können wir die Handler auf die einzelnen IDT-Einträge verteilen:
| |
|
< install the interrupt handlers > ≡ < install the IDT >
< install the fault handlers >
< remap the interrupts to 32..47 >
set_irqmask (0xFFFF); // initialize IRQ mask
enable_interrupt (IRQ_SLAVE); // IRQ slave
// flags: 1 (present), 11 (DPL 3), 0; type: 1110 (32 bit interrupt gate)
fill_idt_entry (32, (unsigned int)irq0, 0x08, 0b1110, 0b1110);
fill_idt_entry (33, (unsigned int)irq1, 0x08, 0b1110, 0b1110);
fill_idt_entry (34, (unsigned int)irq2, 0x08, 0b1110, 0b1110);
fill_idt_entry (35, (unsigned int)irq3, 0x08, 0b1110, 0b1110);
fill_idt_entry (36, (unsigned int)irq4, 0x08, 0b1110, 0b1110);
fill_idt_entry (37, (unsigned int)irq5, 0x08, 0b1110, 0b1110);
fill_idt_entry (38, (unsigned int)irq6, 0x08, 0b1110, 0b1110);
fill_idt_entry (39, (unsigned int)irq7, 0x08, 0b1110, 0b1110);
fill_idt_entry (40, (unsigned int)irq8, 0x08, 0b1110, 0b1110);
fill_idt_entry (41, (unsigned int)irq9, 0x08, 0b1110, 0b1110);
fill_idt_entry (42, (unsigned int)irq10, 0x08, 0b1110, 0b1110);
fill_idt_entry (43, (unsigned int)irq11, 0x08, 0b1110, 0b1110);
fill_idt_entry (44, (unsigned int)irq12, 0x08, 0b1110, 0b1110);
fill_idt_entry (45, (unsigned int)irq13, 0x08, 0b1110, 0b1110);
fill_idt_entry (46, (unsigned int)irq14, 0x08, 0b1110, 0b1110);
fill_idt_entry (47, (unsigned int)irq15, 0x08, 0b1110, 0b1110);
|
wobei wir noch
| |
|
< function prototypes > +≡ static void set_irqmask (unsigned short mask);
static void enable_interrupt (int number);
unsigned short get_irqmask ();
|
implementieren:
| |
|
< function implementations > +≡ static void set_irqmask (unsigned short mask) {
outportb (IO_PIC_MASTER_DATA, (char)(mask % 256) );
outportb (IO_PIC_SLAVE_DATA, (char)(mask >> 8) );
}
unsigned short get_irqmask () {
return inportb (IO_PIC_MASTER_DATA)
+ (inportb (IO_PIC_SLAVE_DATA) << 8);
}
static void enable_interrupt (int number) {
set_irqmask (
get_irqmask () // the current value
& ~(1 << number) // 16 one-bits, but bit "number" cleared
);
}
|
Das Installieren der Interrupt Handler im Code Chunk
< install the interrupt handlers > muss bei der
Systeminitialisierung erfolgen, also:
| |
|
< kernel main: initialize system > ≡ < install the interrupt handlers >
|
Unsere Interrupt-Handler erhalten die Registerinhalte,
dafür legen wir einen eigenen Datentyp an:
| |
|
< type definitions > +≡ struct regs {
unsigned int gs, fs, es, ds;
unsigned int edi, esi, ebp, esp, ebx, edx, ecx, eax;
unsigned int int_no, err_code;
unsigned int eip, cs, eflags, useresp, ss;
};
|
In der Assembler-Datei liegen die Anfangsstücke der
Interrupt-Handler:
| |
|
< start.asm > ≡ global irq0, irq1, irq2, irq3, irq4, irq5, irq6, irq7
global irq8, irq9, irq10, irq11, irq12, irq13, irq14, irq15
%macro irq_macro 1
cli ; disable interrupts
push byte 0 ; error code (none)
push byte %1 ; interrupt number
jmp irq_common_stub ; rest is identical for all handlers
%endmacro
irq0: irq_macro 32
irq1: irq_macro 33
irq2: irq_macro 34
irq3: irq_macro 35
irq4: irq_macro 36
irq5: irq_macro 37
irq6: irq_macro 38
irq7: irq_macro 39
irq8: irq_macro 40
irq9: irq_macro 41
irq10: irq_macro 42
irq11: irq_macro 43
irq12: irq_macro 44
irq13: irq_macro 45
irq14: irq_macro 46
irq15: irq_macro 47
extern irq_handler ; defined in the C source file
irq_common_stub: ; this is the identical part
pusha
push ds
push es
push fs
push gs
push esp ; pointer to the struct regs
call irq_handler ; call C function
pop esp
pop gs
pop fs
pop es
pop ds
popa
add esp, 8
iret
|
-- wir haben in der Vorlesung besprochen, warum die Register
in dieser Reihenfolge auf den Stack gelegt (und später wieder
ausgelesen) werden.
(\emph{Achtung: Sie können diesen Code Chunk nicht in dieser
NoWeb-Datei verändern; er wird nicht exportiert.})
Es fehlt nun noch die C-Funktion irq_handler():
| |
|
< function implementations > +≡ void irq_handler (struct regs *r) {
int number = r->int_no - 32; // interrupt number
void (*handler)(struct regs *r); // type of handler functions
if (number >= 8)
outportb (IO_PIC_SLAVE_CMD, END_OF_INTERRUPT); // notify slave PIC
outportb (IO_PIC_MASTER_CMD, END_OF_INTERRUPT); // notify master PIC
handler = interrupt_handlers[number];
if (handler != NULL) handler (r);
}
|
Sie verwendet die Konstante
| |
|
< constants > +≡ #define END_OF_INTERRUPT 0x20
|
und das Array
| |
|
< global variables > +≡ void *interrupt_handlers[16] = { 0 };
|
Um einen Interrupt-Handler zu installieren (also seine
Adresse in das Array einzutragen), verwenden wir
| |
|
< function prototypes > +≡ void install_interrupt_handler (int irq, void (*handler)(struct regs *r));
|
mit folgender Implementierung:
| |
|
< function implementations > +≡ void install_interrupt_handler (int irq, void (*handler)(struct regs *r)) {
if (irq >= 0 && irq < 16)
interrupt_handlers[irq] = handler;
}
|
Bei der Initialisierung des Kernels müssen wir noch
das IDTR-Register laden:
| |
|
< install the IDT > ≡ idtp.limit = (sizeof (struct idt_entry) * 256) - 1; // must do -1
idtp.base = (int) &idt;
idt_load ();
|
wobei der letzte Befehl wieder als Assembler-Code vorliegt:
| |
|
< function prototypes > +≡ extern void idt_load ();
|
| |
|
< start.asm > +≡ extern idtp ; defined in the C file
global idt_load
idt_load: lidt [idtp]
ret
|
Faults
Das Fault-Handling funktioniert ganz ähnlich wie die
Interrupt-Behandlung, darum gehen wir hier auf die Details
nur kurz ein.
Die Funktionen isr0(), \dots, isr31() implementieren
wir wieder als Assembler-Funktionen; im C-Programm müssen
wir ihre Namen bekannt machen:
| |
|
< function prototypes > +≡ extern void isr0(), isr1(), isr2(), isr3(), isr4(), isr5(),
isr6(), isr7(), isr8(), isr9(), isr10(), isr11(), isr12(),
isr13(), isr14(), isr15(), isr16(), isr17(), isr18(), isr19(),
isr20(), isr21(), isr22(), isr23(), isr24(), isr25(), isr26(),
isr27(), isr28(), isr29(), isr30(), isr31();
|
Um die IDT-Einträge für die 32 Fault-Handler anzulegen, benutzen
wir ein kleines Makro:
| |
|
< macros > ≡ #define FILL_IDT(i) \
fill_idt_entry (i, (unsigned int)isr##i, 0x08, 0b1110, 0b1110)
|
und können dann mit wenigen Zeilen die 32 Eintragungen vornehmen:
| |
|
< install the fault handlers > ≡ FILL_IDT( 0); FILL_IDT( 1); FILL_IDT( 2); FILL_IDT( 3); FILL_IDT( 4);
FILL_IDT( 5); FILL_IDT( 6); FILL_IDT( 7); FILL_IDT( 8); FILL_IDT( 9);
FILL_IDT(10); FILL_IDT(11); FILL_IDT(12); FILL_IDT(13); FILL_IDT(14);
FILL_IDT(15); FILL_IDT(16); FILL_IDT(17); FILL_IDT(18); FILL_IDT(19);
FILL_IDT(20); FILL_IDT(21); FILL_IDT(22); FILL_IDT(23); FILL_IDT(24);
FILL_IDT(25); FILL_IDT(26); FILL_IDT(27); FILL_IDT(28); FILL_IDT(29);
FILL_IDT(30); FILL_IDT(31);
|
In der Assembler-Datei geben wir an, dass die Symbole exportiert
werden sollen:
| |
|
< start.asm > +≡ global isr0, isr1, isr2, isr3, isr4, isr5, isr6, isr7, isr8
global isr9, isr10, isr11, isr12, isr13, isr14, isr15, isr16, isr17
global isr18, isr19, isr20, isr21, isr22, isr23, isr24, isr25, isr26
global isr27, isr28, isr29, isr30, isr31
|
Dann brauchen wir ein Array mit Fehlermeldungen; hinter
Fault-Nummer 18 kommt nichts mehr: Die restlichen Werte sind
reserviert.
| |
|
< global variables > +≡ char *exception_messages[] = {
"Division By Zero", "Debug", // 0, 1
"Non Maskable Interrupt", "Breakpoint", // 2, 3
"Into Detected Overflow", "Out of Bounds", // 4, 5
"Invalid Opcode", "No Coprocessor", // 6, 7
"Double Fault", "Coprocessor Segment Overrun", // 8, 9
"Bad TSS", "Segment Not Present", // 10, 11
"Stack Fault", "General Protection Fault", // 12, 13
"Page Fault", "Unknown Interrupt", // 14, 15
"Coprocessor Fault", "Alignment Check", // 16, 17
"Machine Check", // 18
"Reserved", "Reserved", "Reserved", "Reserved", "Reserved",
"Reserved", "Reserved", "Reserved", "Reserved", "Reserved",
"Reserved", "Reserved", "Reserved" // 19..31
};
|
Den eigentlichen Fault-Handler
| |
|
< function prototypes > +≡ void fault_handler (struct regs *r);
|
präsentieren wir hier in der vereinfachten Variante (die noch
keine Prozesse berücksichtigt). Der Handler gibt einige Informationen
aus und hält dann das System an.
| |
|
< function implementations > +≡ void fault_handler (struct regs *r) {
if (r->int_no >= 0 && r->int_no < 32) {
< fault handler: display status information >
if ( (unsigned int)(r->eip) < 0xc0000000 ) { // user mode
< fault handler: terminate process >
}
printf ("System Stops\n");
asm ("cli; \n hlt;");
}
}
|
In der Ausgabe erscheinen Name und Nummer des Faults
sowie die Inhalte einiger Register.
| |
|
< fault handler: display status information > ≡ printf ("'%s' (%d) Exception at 0x%08x.\n",
exception_messages[r->int_no], r->int_no, r->eip);
printf ("eflags: 0x%08x errcode: 0x%08x\n", r->eflags, r->err_code);
printf ("eax: %08x ebx: %08x ecx: %08x edx: %08x \n",
r->eax, r->ebx, r->ecx, r->edx);
printf ("eip: %08x esp: %08x int: %8d err: %8d \n",
r->eip, r->esp, r->int_no, r->err_code);
printf ("ebp: %08x cs: %d ds: %d es: %d fs: %d ss: %x \n",
r->ebp, r->cs, r->ds, r->es, r->fs, r->ss);
|
|