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 inasm
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 thefault_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 tofault_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 theisr.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 (;;);