Ontwerp en bouw een besturingssysteem/Blue Screen of Death/Interrupt Service Routines
Nu moet er voor elke (voor ons relevante) interrupt nog een Interrupt Service Routine (ISR) worden geschreven. Daarna moeten al die routines worden geregistreerd, zodat ze op het juiste moment worden aangeroepen.
Foutmeldingen
[bewerken]De eerste 32 indices in de IDT (index 0 tot en met 31) reageren op foutmeldingen. Zoals in de theorie over foutmeldingen aangegeven, pushen sommige fouten een foutcode op de stack, en andere niet. Om het makkelijker te maken pushen we een 0 op de stack als de foutmelding geen foutcode pusht, zodat de stack altijd evenveel elementen bevat. Daarna pushen we altijd de index van de interrupt die vuurde.
Voor elke mogelijke foutmelding moet er een routine worden geschreven. De interrupts moeten worden uitgeschakeld (met de cli
instructie) en we pushen eventueel 0 op de stack als er geen foutcode gepusht is. Vervolgens pushen we het interruptnummer, en springen we naar Isrs_GeneralCode
waar de rest van de in de theorie beschreven acties worden uitgevoerd. Oftewel: alle relevante registers worden opgeslagen en na het afhandelen van de interrupt moeten de registers weer worden ingesteld op hun oorspronkelijke waarde. Ten slotte moeten de interrupts weer worden ingeschakeld terwijl de processor verder gaat met de uitvoering van de actieve code.
De volgende zeer repetitieve code kan je zelf verder invullen. Let op het verschil tussen een interrupt die geen foutcode op de stack pusht (zoals interrupt 0) en een interrupt die dat wel doet (bijvoorbeeld interrupt 8).
/kernel/isrs.asm
global Interrupt_0
...
global Interrupt_8
...
global Interrupt_31
; Interrupt 0: Deling door nul
; Foutcode: Nee
Interrupt_0:
cli
push byte 0
push byte 0
jmp Isrs_GeneralCode
...
; Interrupt 8: Double fault
; Foutcode: Ja
Interrupt_8:
cli
push byte 8
jmp Isrs_GeneralCode
...
; Interrupt 31: Gereserveerd
; Foutcode: Nee
Interrupt_31:
cli
push byte 0
push byte 31
jmp Isrs_GeneralCode
Algemene routine
[bewerken]De volgende routine wordt voor alle interrupts aangeroepen, en geeft de controle door aan onze C code van waaruit we kunnen bepalen wat we ermee doen. De code voor Isrs_GeneralCode()
is dan als volgt:
/kernel/isrs.asm
; We roepen Isrs_Handler() aan, in isrs.c.
extern Isrs_Handler
; De algemene code die voor alle Interrupt Service Routines geldt.
Isrs_GeneralCode:
; Sla de registers op.
pushad
push ds
push es
push fs
push gs
; Stel de segment selectors in voor de kernel.
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
; Bewaar de pointer naar de bovenkant van de stack (het dichtst bij 0).
; We roepen Isrs_Handler() aan en geven de pointer door die als een
; pointer naar een ThreadState structuur wordt ontvangen.
mov eax, esp
push eax
mov eax, Isrs_Handler
call eax
; Pop de stack schoon na de aanroep.
pop eax
; Herstel de registers.
pop gs
pop fs
pop es
pop ds
popad
; Verwijder de foutcode (4 bytes) en het interruptnummer (4 bytes)
; van de stack.
add esp, 8
; Ga terug naar de code die het systeem aan het uitvoeren was toen de
; interrupt vuurde, en activeer tegelijkertijd de interrupts weer.
iret
Interrupts afhandelen
[bewerken]De Isrs_Handler()
functie handelt álle interrupts af. Voorlopig laten we alleen een melding zien en gaan we in een oneindige loop wanneer er een fout (een interrupt code kleiner dan of gelijk aan 31) op treed.
/kernel/isrs.c
// Een array met een naam voor elk van de 32 foutmeldingen.
achar* errormessages[] =
{
"Deling door nul",
....
"Reserved"
};
// Handelt alle interrupts af.
void Isrs_Handler(ThreadState* state)
{
if (state->interruptNumber <= 31)
{
Console_WriteLine(errormessages[state->interruptNumber]);
Console_WriteLine(String_Format(" Foutcode: 0x{0:X}", state->errorCode));
for (;;);
}
}
De ThreadState
structuur beschrijft de stack vanaf de pointer. Door hoe de stack groeit, is het bovenste (eerstgenoemde) element in de ThreadState
structuur ook het bovenste (laatste toegevoegde) element van de stack. De ThreadState
ziet er dan zo uit:
/kernel/include/isrs.h
#ifndef __ISRS_H
#define __ISRS_H
typedef struct ThreadState ThreadState;
struct ThreadState
{
uint32 gs; // (17e) Vanaf hier en lager gepusht door Isrs_GeneralCode:
uint32 fs; // (16e)
uint32 es; // (15e)
uint32 ds; // (14e)
uint32 edi; // (13e) Vanaf hier en lager gepusht door PUSHAD:
uint32 esi; // (12e)
uint32 ebp; // (11e)
uint32 esp; // (10e)
uint32 ebx; // ( 9e)
uint32 edx; // ( 8e)
uint32 ecx; // ( 7e)
uint32 eax; // ( 6e)
uint32 interruptNumber; // ( 5e) Gepusht door onze functie Interrupt_*.
uint32 errorCode; // ( 4e) Gepusht door onze functie Interrupt_*, of door de processor.
uint32 eip; // ( 3e) Vanaf hier en lager gepusht door de processor:
uint32 cs; // ( 2e)
uint32 eflags; // ( 1e)
};
#endif // __ISRS_H
Initialisatie
[bewerken]Ten slotte moeten we al die routines nog registreren, zodat ze worden aangeroepen als de interrupt vuurt. Ook deze code kan je gemakkelijk aanvullen.
/kernel/isrs.c
extern void Interrupt_0();
extern void Interrupt_1();
...
extern void Interrupt_31();
void Isrs_Initialize()
{
Idt_SetDescriptor(0, (unint)Interrupt_0, 0x08, 0x8E);
Idt_SetDescriptor(1, (unint)Interrupt_1, 0x08, 0x8E);
...
Idt_SetDescriptor(31, (unint)Interrupt_31, 0x08, 0x8E);
}
BERICHT: Vergeet Isrs_Initialize() niet te definieren in isrs.h.