Repository navigation
Interrupts and Exceptions
cupid-os implements a comprehensive interrupt and exception handling system that allows the kernel to respond to hardware events and CPU exceptions. This system is crucial for device interaction, error handling, and system stability.
The interrupt system consists of three main components:
- Interrupt Descriptor Table (IDT) - Maps interrupts to handlers
- Programmable Interrupt Controller (PIC) - Manages hardware interrupts
- Interrupt Service Routines (ISRs) - Handle specific interrupts
CPU exceptions are synchronous events triggered by program execution errors or special instructions. cupid-os handles 32 CPU exceptions (0-31), with detailed information provided for the most common ones.
| Vector | Exception | Description | Error Code |
|---|---|---|---|
| 0 | Division by Zero | Division by zero attempted | No |
| 1 | Debug | Debug exception or breakpoint | No |
| 2 | NMI | Non-maskable interrupt | No |
| 3 | Breakpoint | Breakpoint instruction (INT 3) |
No |
| 4 | Overflow |
INTO instruction overflow |
No |
| 5 | Bound Range |
BOUND instruction out of range |
No |
| 6 | Invalid Opcode | Invalid instruction executed | No |
| 7 | Device Not Available | FPU/MMX/SSE instruction on unavailable device | No |
| 8 | Double Fault | Exception during exception handling | Yes |
| 9 | Coprocessor Segment | Floating-point segment overrun (obsolete) | No |
| 10 | Bad TSS | Invalid Task State Segment | Yes |
| 11 | Segment Not Present | Segment not present in memory | Yes |
| 12 | Stack-Segment Fault | Stack segment fault | Yes |
| 13 | General Protection Fault | General protection violation | Yes |
| 14 | Page Fault | Page not present or access violation | Yes |
| 15 | Reserved | Reserved for future use | No |
| 16-31 | Various | Floating-point and other exceptions | Varies |
When an exception occurs, the CPU:
- Saves state: Pushes EFLAGS, CS, EIP (and error code if applicable)
- Looks up handler: Uses exception vector to find IDT entry
- Transfers control: Jumps to the ISR specified in the IDT
- Executes handler: ISR processes the exception
-
Returns: Uses
IRETto restore state and continue/resume
Page faults receive special handling in cupid-os:
if (r->int_no == 14) {
uint32_t cr2;
__asm__ volatile("mov %%cr2, %0" : "=r"(cr2));
// Enable interrupts for interactive prompt
__asm__ volatile("sti");
pf_action_t action = shell_pagefault_prompt(r, cr2);
__asm__ volatile("cli");
if (action == PF_ACTION_REBOOT) {
system_reboot();
}
// PF_ACTION_CONTINUE: return and retry instruction
return;
}Instead of halting, page faults drop into an interactive shell where users can:
- Inspect the faulting address (
CR2) - View error code details
- Choose to continue, reboot, or get more information
Hardware interrupts are asynchronous events from devices. The PIC manages 16 IRQs (0-15), which are remapped to interrupt vectors 32-47 to avoid conflicts with CPU exceptions.
| IRQ | Vector | Device | Typical Use |
|---|---|---|---|
| 0 | 32 | Timer | System timer (PIT) |
| 1 | 33 | Keyboard | PS/2 keyboard |
| 2 | 34 | Cascade | Slave PIC |
| 3 | 35 | COM2 | Serial port 2 |
| 4 | 36 | COM1 | Serial port 1 |
| 5 | 37 | LPT2 | Parallel port 2 |
| 6 | 38 | Floppy | Floppy disk |
| 7 | 39 | LPT1 | Parallel port 1 |
| 8 | 40 | RTC | Real-time clock |
| 9 | 41 | Free | Available |
| 10 | 42 | Free | Available |
| 11 | 43 | Free | Available |
| 12 | 44 | PS/2 Mouse | PS/2 mouse |
| 13 | 45 | FPU | Floating-point unit |
| 14 | 46 | IDE | Primary IDE |
| 15 | 47 | IDE | Secondary IDE |
The PIC is initialized during kernel startup:
void pic_init(void) {
// Save current masks
uint8_t mask1 = inb(PIC1_DATA);
uint8_t mask2 = inb(PIC2_DATA);
// ICW1: Start initialization
outb(PIC1_COMMAND, ICW1_INIT | ICW1_ICW4);
outb(PIC2_COMMAND, ICW1_INIT | ICW1_ICW4);
// ICW2: Set vector offsets (32 for master, 40 for slave)
outb(PIC1_DATA, 32);
outb(PIC2_DATA, 40);
// ICW3: Tell master about slave at IRQ2
outb(PIC1_DATA, 4); // Slave at IRQ2
outb(PIC2_DATA, 2); // Slave identity
// ICW4: Set 8086 mode
outb(PIC1_DATA, ICW4_8086);
outb(PIC2_DATA, ICW4_8086);
// Restore masks
outb(PIC1_DATA, mask1);
outb(PIC2_DATA, mask2);
// Enable timer (IRQ0) and keyboard (IRQ1)
pic_clear_mask(0);
pic_clear_mask(1);
}IRQs can be individually masked/unmasked:
void pic_set_mask(uint8_t irq); // Disable IRQ
void pic_clear_mask(uint8_t irq); // Enable IRQAfter handling an interrupt, send EOI to the PIC:
void pic_send_eoi(uint8_t irq) {
if (irq >= 8)
outb(PIC2_COMMAND, 0x20); // EOI to slave PIC
outb(PIC1_COMMAND, 0x20); // EOI to master PIC
}The IDT is a 256-entry table that maps interrupt vectors to handler functions. Each entry contains:
- Handler address: 32-bit pointer to ISR
- Segment selector: Code segment (0x08)
- Gate type: Interrupt gate (0x8E) or trap gate (0x8F)
- Privilege level: Ring 0 (kernel)
- Present flag: Entry is valid
struct idt_entry {
uint16_t base_low; // Lower 16 bits of handler address
uint16_t segment; // Code segment selector
uint8_t reserved; // Always 0
uint8_t flags; // Gate type and flags
uint16_t base_high; // Upper 16 bits of handler address
};
struct idt_ptr {
uint16_t limit; // Size of IDT - 1
uint32_t base; // Address of IDT
};void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags) {
idt[num].base_low = base & 0xFFFF;
idt[num].base_high = (base >> 16) & 0xFFFF;
idt[num].segment = sel;
idt[num].reserved = 0;
idt[num].flags = flags;
}During kernel initialization:
void idt_init(void) {
// Set up IDT pointer
idtp.limit = (sizeof(struct idt_entry) * 256) - 1;
idtp.base = (uint32_t)&idt;
// Clear all entries
for(int i = 0; i < 256; i++) {
idt_set_gate(i, 0, 0, 0);
}
// Set up exception handlers (vectors 0-14)
idt_set_gate(0, (uint32_t)isr0, 0x08, 0x8E);
// ... more exception handlers
// Set up IRQ handlers (vectors 32-47)
idt_set_gate(32, (uint32_t)irq0, 0x08, 0x8E);
// ... more IRQ handlers
// Load IDT
load_idt(&idtp);
}ISRs are low-level assembly routines that handle the context switch from user/kernel code to interrupt handler code.
All ISRs follow the same pattern:
isr0: ; Example: Division by Zero
cli ; Disable interrupts
push byte 0 ; Push dummy error code (this exception doesn't have one)
push byte 0 ; Push interrupt number
jmp isr_common_stub
irq1: ; Example: Keyboard IRQ
cli
push byte 0
push byte 33 ; Interrupt number (32 + IRQ number)
jmp irq_common_stubThe common stub handles state preservation:
isr_common_stub:
pusha ; Save all general-purpose registers
push ds ; Save segment registers
push es
push fs
push gs
; Load kernel data segment
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
push esp ; Push pointer to register structure
call isr_handler ; Call C handler
add esp, 4 ; Clean up parameter
pop gs ; Restore segment registers
pop fs
pop es
pop ds
popa ; Restore general-purpose registers
add esp, 8 ; Clean up error code and interrupt number
iret ; Return from interruptThe registers structure passed to C handlers:
struct registers {
uint32_t ds, es, fs, gs; // Segment registers
uint32_t edi, esi, ebp, esp, ebx, edx, ecx, eax; // General registers
uint32_t int_no, err_code; // Interrupt number and error code
uint32_t eip, cs, eflags, useresp, ss; // Execution state
};cupid-os implements a flexible IRQ handler system that allows dynamic registration of device drivers.
typedef void (*irq_handler_t)(struct registers* regs);
void irq_install_handler(int irq, irq_handler_t handler);
void irq_uninstall_handler(int irq);void irq_handler(struct registers* r) {
int irq = r->int_no - 32; // Convert vector to IRQ number
if (irq_handlers[irq]) {
irq_handlers[irq](r); // Call registered handler
}
pic_send_eoi(irq); // Send EOI to PIC
}void keyboard_isr(struct registers* regs) {
uint8_t scancode = inb(KEYBOARD_DATA_PORT);
keyboard_enqueue_scancode(scancode);
pic_send_eoi(IRQ_KEYBOARD);
}The interrupt system is initialized in this order:
- PIC Initialization: Remap IRQs to avoid conflicts
- IDT Setup: Create IDT entries for exceptions and IRQs
- ISR Installation: Load IDT into processor
- IRQ Handler Setup: Install default handlers
- Device Initialization: Register device-specific handlers
void kernel_main() {
// ... other initialization
pic_init(); // Set up PIC
idt_init(); // Set up IDT and load it
irq_init(); // Set up IRQ subsystem
// Install device handlers
keyboard_init();
timer_init();
// Enable interrupts
__asm__ volatile("sti");
// ... start shell
}- Cause: Invalid IDT entry or corrupted stack
- Debug: Check GDT setup, IDT loading, stack pointer
- Cause: PIC not properly initialized
- Debug: Verify ICW sequences, check masks
- Cause: Wrong vector number or flags
- Debug: Check IDT entries, verify interrupt numbers
- Cause: ISR doesn't return or sends wrong EOI
- Debug: Add debug prints, check EOI timing
void debug_isr(struct registers* r) {
print("ISR called: ");
print_int(r->int_no);
print("\n");
}void dump_registers(struct registers* r) {
print("EIP: "); print_hex(r->eip);
print(" CS: "); print_hex(r->cs);
print(" EFL: "); print_hex(r->eflags);
print("\n");
}uint8_t pic_get_mask(int pic) {
uint16_t port = (pic == 0) ? PIC1_DATA : PIC2_DATA;
return inb(port);
}- Keep ISRs short: Do minimal work, defer processing
- Avoid function calls: Inline critical operations
- Minimize stack usage: Be careful with local variables
-
Disable interrupts sparingly: Use
cli/sticarefully - Prioritize IRQs: Handle critical interrupts first
- Use interrupt levels: If hardware supports it
-
Compiler barriers: Use
volatilefor shared variables - CPU barriers: Consider memory fence instructions if needed
- Choose vector: Find unused interrupt vector (48-255)
- Create ISR: Add assembly stub and C handler
- Set IDT gate: Register handler in IDT
- Handle interrupt: Implement device-specific logic
- Implement handler: Create IRQ handler function
-
Register handler: Use
irq_install_handler() - Initialize device: Set up hardware, enable interrupts
- Handle data: Process interrupts in handler
- Interrupt prioritization: Implement priority schemes
- Nested interrupts: Allow interrupts during handlers
- Interrupt threads: Move handler processing to threads
- Intel Architecture Manuals: Detailed interrupt specifications
- OSDev Wiki: PIC programming and IDT setup guides
- 8259A Datasheet: PIC hardware specifications
For more information about system components:
- Bootloader - System startup process
- Drivers - Device driver implementations
- Memory Management - Memory subsystem details
Getting Started
Shell & Scripting
Compilers
- CupidC Compiler
- CupidC Language Reference
- CupidC 2D Graphics Library
- CupidASM Assembler
- Toolchain Bootstrap
- Floating Point
Programs & Docs
GUI
Kernel
Filesystems
Hardware