CLOSE

What is an ISR (Interrupt Service Routine)?

An Interrupt Service Routine (ISR), also known as an interrupt handler, is a special type of function in computing that is automatically executed in response to an interrupt signal. Interrupts are signals emitted by hardware or software when an event needs immediate attention. ISRs are fundamental in operating systems and embedded systems for managing hardware and software interrupts.

1 isr.c:

#include <system.h>

/*
 * Exception Handlers
 * These extern declarations refer to the ISR (Interrupt Service Routine)
 * handlers written in assembly. They will handle CPU exceptions.
 */
extern void _isr0();
extern void _isr1();
extern void _isr2();
extern void _isr3();
extern void _isr4();
extern void _isr5();
extern void _isr6();
extern void _isr7();
extern void _isr8();
extern void _isr9();
extern void _isr10();
extern void _isr11();
extern void _isr12();
extern void _isr13();
extern void _isr14();
extern void _isr15();
extern void _isr16();
extern void _isr17();
extern void _isr18();
extern void _isr19();
extern void _isr20();
extern void _isr21();
extern void _isr22();
extern void _isr23();
extern void _isr24();
extern void _isr25();
extern void _isr26();
extern void _isr27();
extern void _isr28();
extern void _isr29();
extern void _isr30();
extern void _isr31();

/*
 * isrs_install
 * This function sets up the IDT entries for the 32 CPU exceptions.
 * Each entry in the IDT (Interrupt Descriptor Table) is set to point to one
 * of the ISR handlers defined above.
 */
void isrs_install() {
    /* Exception Handlers */
    idt_set_gate(0, (unsigned)_isr0, 0x08, 0x8E);
    idt_set_gate(1, (unsigned)_isr1, 0x08, 0x8E);
    idt_set_gate(2, (unsigned)_isr2, 0x08, 0x8E);
    idt_set_gate(3, (unsigned)_isr3, 0x08, 0x8E);
    idt_set_gate(4, (unsigned)_isr4, 0x08, 0x8E);
    idt_set_gate(5, (unsigned)_isr5, 0x08, 0x8E);
    idt_set_gate(6, (unsigned)_isr6, 0x08, 0x8E);
    idt_set_gate(7, (unsigned)_isr7, 0x08, 0x8E);
    idt_set_gate(8, (unsigned)_isr8, 0x08, 0x8E);
    idt_set_gate(9, (unsigned)_isr9, 0x08, 0x8E);
    idt_set_gate(10, (unsigned)_isr10, 0x08, 0x8E);
    idt_set_gate(11, (unsigned)_isr11, 0x08, 0x8E);
    idt_set_gate(12, (unsigned)_isr12, 0x08, 0x8E);
    idt_set_gate(13, (unsigned)_isr13, 0x08, 0x8E);
    idt_set_gate(14, (unsigned)_isr14, 0x08, 0x8E);
    idt_set_gate(15, (unsigned)_isr15, 0x08, 0x8E);
    idt_set_gate(16, (unsigned)_isr16, 0x08, 0x8E);
    idt_set_gate(17, (unsigned)_isr17, 0x08, 0x8E);
    idt_set_gate(18, (unsigned)_isr18, 0x08, 0x8E);
    idt_set_gate(19, (unsigned)_isr19, 0x08, 0x8E);
    idt_set_gate(20, (unsigned)_isr20, 0x08, 0x8E);
    idt_set_gate(21, (unsigned)_isr21, 0x08, 0x8E);
    idt_set_gate(22, (unsigned)_isr22, 0x08, 0x8E);
    idt_set_gate(23, (unsigned)_isr23, 0x08, 0x8E);
    idt_set_gate(24, (unsigned)_isr24, 0x08, 0x8E);
    idt_set_gate(25, (unsigned)_isr25, 0x08, 0x8E);
    idt_set_gate(26, (unsigned)_isr26, 0x08, 0x8E);
    idt_set_gate(27, (unsigned)_isr27, 0x08, 0x8E);
    idt_set_gate(28, (unsigned)_isr28, 0x08, 0x8E);
    idt_set_gate(29, (unsigned)_isr29, 0x08, 0x8E);
    idt_set_gate(30, (unsigned)_isr30, 0x08, 0x8E);
    idt_set_gate(31, (unsigned)_isr31, 0x08, 0x8E);
}

/*
 * Array of exception messages
 * This array contains human-readable descriptions of each of the 32 CPU exceptions.
 * These messages are used in the fault_handler to display the type of exception.
 */
unsigned char *exception_messages[] = {
    "division by zero",
    "debug",
    "non-maskable interrupt",
    "breakpoint",
    "detected overflow",
    "out-of-bounds",
    "invalid opcode",
    "no coprocessor",
    "double fault",
    "coprocessor segment overrun",
    "bad TSS",
    "segment not present",
    "stack fault",
    "general protection fault",
    "page fault",
    "unknown interrupt",
    "coprocessor fault",
    "alignment check",
    "machine check",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved",
    "reserved"
};

/*
 * fault_handler
 * This function handles CPU exceptions.
 * It prints out an error message based on the exception number and halts the system.
 */
void fault_handler(struct regs *r) {
    if (r->int_no < 32) {  // Check if the interrupt number is an exception (0-31)
        puts(exception_messages[r->int_no]);
        puts(" exception. System halted.\n");
        for (;;);  // Infinite loop to halt the system
    }
}

Function Declarations:

  • The code declares external functions _isr0() through _isr31(), which is assumed to be define in asm file. These function are the actual handlers for each exception.
    • extern void _isrX(); = Declare external ISR functions, which are implemented in assembly. These functions handle different CPU exceptions.

isrs_install() Function:

  • This function initializes the exception handlers by setting up entries in the Interrupt Descriptor Table (IDT) using the idt_set_gate() function.
  • Each exception is associated with its corresponding ISR function, which handles the exception.

exception_messages Array:

  • This array holds human-readable error messages corresponding to each exception.
  • This array contains strings that that describe each of the 32 CPU exceptions.
  • The messages are used by the fault_handler() function to print a description of the exception when it occurs.

fault_handler() Function

  • This function is called when an exception occurs. It takes a pointer to a struct regs as an argument.
  • It checks if the exception number is less than 32 (the total number of defined exceptions). If it is, it prints the corresponding error message and halts the system.

2 isr_asm.asm

; Interrupt Service Routines
global _isr0
global _isr1
global _isr2
global _isr3
global _isr4
global _isr5
global _isr6
global _isr7
global _isr8
global _isr9
global _isr10
global _isr11
global _isr12
global _isr13
global _isr14
global _isr15
global _isr16
global _isr17
global _isr18
global _isr19
global _isr20
global _isr21
global _isr22
global _isr23
global _isr24
global _isr25
global _isr26
global _isr27
global _isr28
global _isr29
global _isr30
global _isr31

;  0: Divide By Zero Exception
_isr0:
	cli
	push byte 0
	push byte 0
	jmp isr_common_stub

;  1: Debug Exception
_isr1:
	cli
	push byte 0
	push byte 1
	jmp isr_common_stub

;  2: Non Maskable Interrupt Exception
_isr2:
	cli
	push byte 0
	push byte 2
	jmp isr_common_stub

;  3: Int 3 Exception
_isr3:
	cli
	push byte 0
	push byte 3
	jmp isr_common_stub

;  4: INTO Exception
_isr4:
	cli
	push byte 0
	push byte 4
	jmp isr_common_stub

;  5: Out of Bounds Exception
_isr5:
	cli
	push byte 0
	push byte 5
	jmp isr_common_stub

;  6: Invalid Opcode Exception
_isr6:
	cli
	push byte 0
	push byte 6
	jmp isr_common_stub

;  7: Coprocessor Not Available Exception
_isr7:
	cli
	push byte 0
	push byte 7
	jmp isr_common_stub

;  8: Double Fault Exception (With Error Code!)
_isr8:
	cli
	push byte 8
	jmp isr_common_stub

;  9: Coprocessor Segment Overrun Exception
_isr9:
	cli
	push byte 0
	push byte 9
	jmp isr_common_stub

; 10: Bad TSS Exception (With Error Code!)
_isr10:
	cli
	push byte 10
	jmp isr_common_stub

; 11: Segment Not Present Exception (With Error Code!)
_isr11:
	cli
	push byte 11
	jmp isr_common_stub

; 12: Stack Fault Exception (With Error Code!)
_isr12:
	cli
	push byte 12
	jmp isr_common_stub

; 13: General Protection Fault Exception (With Error Code!)
_isr13:
	cli
	push byte 13
	jmp isr_common_stub

; 14: Page Fault Exception (With Error Code!)
_isr14:
	cli
	push byte 14
	jmp isr_common_stub

; 15: Reserved Exception
_isr15:
	cli
	push byte 0
	push byte 15
	jmp isr_common_stub

; 16: Floating Point Exception
_isr16:
	cli
	push byte 0
	push byte 16
	jmp isr_common_stub

; 17: Alignment Check Exception
_isr17:
	cli
	push byte 0
	push byte 17
	jmp isr_common_stub

; 18: Machine Check Exception
_isr18:
	cli
	push byte 0
	push byte 18
	jmp isr_common_stub

; 19: Reserved
_isr19:
	cli
	push byte 0
	push byte 19
	jmp isr_common_stub

; 20: Reserved
_isr20:
	cli
	push byte 0
	push byte 20
	jmp isr_common_stub

; 21: Reserved
_isr21:
	cli
	push byte 0
	push byte 21
	jmp isr_common_stub

; 22: Reserved
_isr22:
	cli
	push byte 0
	push byte 22
	jmp isr_common_stub

; 23: Reserved
_isr23:
	cli
	push byte 0
	push byte 23
	jmp isr_common_stub

; 24: Reserved
_isr24:
	cli
	push byte 0
	push byte 24
	jmp isr_common_stub

; 25: Reserved
_isr25:
	cli
	push byte 0
	push byte 25
	jmp isr_common_stub

; 26: Reserved
_isr26:
	cli
	push byte 0
	push byte 26
	jmp isr_common_stub

; 27: Reserved
_isr27:
	cli
	push byte 0
	push byte 27
	jmp isr_common_stub

; 28: Reserved
_isr28:
	cli
	push byte 0
	push byte 28
	jmp isr_common_stub

; 29: Reserved
_isr29:
	cli
	push byte 0
	push byte 29
	jmp isr_common_stub

; 30: Reserved
_isr30:
	cli
	push byte 0
	push byte 30
	jmp isr_common_stub

; 31: Reserved
_isr31:
	cli
	push byte 0
	push byte 31
	jmp isr_common_stub

extern fault_handler

isr_common_stub:
	pusha
	push ds
	push es
	push fs
	push gs
	mov ax, 0x10
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov eax, esp
	push eax
	mov eax, fault_handler
	call eax
	pop eax
	pop gs
	pop fs
	pop es
	pop ds
	popa
	add esp, 8
	iret

Declaration of ISRs:

  • The code declares global labels for each ISR, ranging from _isr0 to _isr31. These labels represent the entry points for handling specific exceptions.

ISR Implementation:

  • Each ISR label is followed by code that handles a specific exception.
  • For example, _isr0 handles the "Divide By Zero Exception." The code starts by disabling interrupts (cli), then pushes the error code and the interrupt number onto the stack (push byte 0, push byte 0), and jumps to a common ISR stub (isr_common_stub).

Common ISR Stub:

  • The isr_common_stub is a common routine that prepares the CPU state for handling exceptions and calls the fault_handler function.
  • It saves the CPU state (registers, segment registers) by pushing them onto the stack (pusha, push ds, push es, push fs, push gs).
  • It sets the data and extra segment registers (mov ax, 0x10, mov ds, ax, mov es, ax, mov fs, ax, mov gs, ax) to ensure a consistent environment.
  • It saves the current stack pointer (mov eax, esp, push eax), prepares for a function call to fault_handler (mov eax, fault_handler, call eax), and restores the stack pointer afterwards (pop eax).
  • Finally, it restores the CPU state and returns from the interrupt (pop gs, pop fs, pop es, pop ds, popa, add esp, 8, iret).

External Function fault_handler:

  • The external function fault_handler handles the exceptions. This function is defined in the isr.c file.

3 Modify system.h

Let's define the declaration for the kernel and define a structure for storing register values during interrupt handling.

/* IDT */
extern void idt_install();
extern void idt_set_gate(unsigned char num, unsigned long base, unsigned short sel, unsigned char flags);

/* ISRS */
extern void isrs_install();

/* Registers */
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;
};

Registers Structure:

The struct regs structure defines a layout for storing register values during interrupt handling. It includes fields for general-purpose registers (eax, ebx, ecx, edx, esi, edi, ebp), segment registers (ds, es, fs, gs, ss, cs), instruction pointer (eip), flags (eflags), stack pointer (esp), interrupt number (int_no), and error code (err_code).

This structure allows interrupt handlers to access the CPU state at the time of the interrupt, enabling them to analyze the cause of the interrupt and take appropriate actions.

4 Modify main.c

Modify the kernel main function to call isrs_install().

main() {
	gdt_install();
	idt_install();

..........................

	isrs_install();

..........................
	init_video();
	puts("TheJat!\n");
	for (;;);