From b8212e21277ca9153666bb891c4779f7c4c609d1 Mon Sep 17 00:00:00 2001 From: pyao12 Date: Sun, 31 May 2026 12:11:57 +0800 Subject: [PATCH 1/4] [feat] Simple Multitask --- Makefile | 14 ++- include/scheduler.h | 39 ++++++ kernel/main.cpp | 38 +++++- kernel/scheduler/context_switch.S | 30 +++++ kernel/scheduler/scheduler.cpp | 189 ++++++++++++++++++++++++++++++ 5 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 include/scheduler.h create mode 100644 kernel/scheduler/context_switch.S create mode 100644 kernel/scheduler/scheduler.cpp diff --git a/Makefile b/Makefile index c60b38c..5ea0ff7 100644 --- a/Makefile +++ b/Makefile @@ -17,9 +17,11 @@ BOOT_OBJ = build/boot.o KERNEL_CPP = kernel/entry.cpp kernel/main.cpp kernel/serial.cpp kernel/fs.cpp \ kernel/memory/heap.cpp kernel/memory/pmm.cpp \ + kernel/scheduler/scheduler.cpp \ graphics/context.cpp graphics/draw.cpp \ fonts/pixel_font.cpp -KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o) +KERNEL_ASM = kernel/scheduler/context_switch.S +KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o) $(KERNEL_ASM:%.S=build/%.o) EFI_TOP_C = $(wildcard efi/lib/*.c) EFI_TOP_S = $(wildcard efi/lib/*.S) @@ -44,6 +46,7 @@ all: _bd $(EFI_OBJ) $(BOOT_OBJ) $(KERNEL_OBJ) _bd: @mkdir -p build/graphics build/kernel build/fonts build/kernel/memory \ + build/kernel/scheduler \ build/efi/lib build/efi/lib/x86_64 build/efi/lib/runtime build/efi/gnuefi $(EFI_CRT0_OBJ): efi/gnuefi/crt0-efi-x86_64.S | _bd @@ -86,6 +89,15 @@ build/kernel/memory/%.o: kernel/memory/%.cpp | _bd @echo "Compile CPP $<" @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ +build/kernel/scheduler/%.o: kernel/scheduler/%.cpp | _bd + @echo "Compile CPP $<" + @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ + +build/kernel/scheduler/%.o: kernel/scheduler/%.S | _bd + @echo "Compile AS $<" + @gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \ + -fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@ + build/graphics/%.o: graphics/%.cpp | _bd @echo "Compile CPP $<" @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ diff --git a/include/scheduler.h b/include/scheduler.h new file mode 100644 index 0000000..71ea7e1 --- /dev/null +++ b/include/scheduler.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +#define TASK_STACK_SIZE (PAGE_SIZE * 4) // 16 KB kernel stack per task +#define TASK_MAX 32 +#define TASK_NAME_LEN 32 + +typedef enum { + TASK_STATE_READY, + TASK_STATE_RUNNING, + TASK_STATE_TERMINATED, +} task_state_t; + +typedef struct task { + UINT64 rsp; // saved stack pointer (for context switch) + UINT32 id; + task_state_t state; + char name[TASK_NAME_LEN]; + void* stack_base; // base address of kernel stack + struct task* next; // circular linked list +} task_t; + +// Create a new task. Returns task pointer or NULL on failure. +task_t* task_create(const char* name, void (*entry)(void)); + +// Yield CPU to next ready task (cooperative) +void yield(void); + +// Start the scheduler — does not return. Picks first READY task and runs it. +void scheduler_run(void); + +// Called by a task when it finishes — marks as TERMINATED and yields +void task_exit(void); + +// Get current running task +task_t* scheduler_current(void); diff --git a/kernel/main.cpp b/kernel/main.cpp index afac7a3..38b41cf 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include extern EFI_SYSTEM_TABLE *ST; @@ -105,7 +106,40 @@ extern "C" void kernel_main() { } pf_print("Welcome to Sylva OS!\n"); - serial_write(" Kernel prepared well, start loop.\n"); + serial_write(" Kernel prepared well.\n"); - while (1) ASM ("hlt"); // 《30天》看多了 (doge + // --- Multitasking demo --- + serial_write("Sylva: creating tasks...\n"); + + // Task A: prints a message 3 times, yielding between each + task_create("taskA", []() { + for (int i = 0; i < 3; i++) { + serial_write("[taskA] running iteration "); + serial_write_hex(i); + serial_write("\n"); + yield(); + } + serial_write("[taskA] done\n"); + }); + + // Task B: prints a message 5 times + task_create("taskB", []() { + for (int i = 0; i < 5; i++) { + serial_write("[taskB] hello from taskB #"); + serial_write_hex(i); + serial_write("\n"); + yield(); + } + serial_write("[taskB] done\n"); + }); + + // Task C: short task + task_create("taskC", []() { + serial_write("[taskC] quick task\n"); + yield(); + serial_write("[taskC] finished\n"); + }); + + serial_write("Sylva: starting scheduler\n"); + scheduler_run(); // never returns } \ No newline at end of file diff --git a/kernel/scheduler/context_switch.S b/kernel/scheduler/context_switch.S new file mode 100644 index 0000000..9b14b1b --- /dev/null +++ b/kernel/scheduler/context_switch.S @@ -0,0 +1,30 @@ +.intel_syntax noprefix + +// void context_switch(UINT64* old_rsp, UINT64 new_rsp) +// rdi = &old_rsp (pointer to save current RSP) +// rsi = new_rsp (value of new stack pointer) +// +// Saves/restores callee-saved registers only. +// On first switch into a new task, ret lands on task_entry_trampoline. +.global context_switch +context_switch: + push rbx + push rbp + push r12 + push r13 + push r14 + push r15 + + mov [rdi], rsp // save current RSP + mov rsp, rsi // switch to new stack + + pop r15 + pop r14 + pop r13 + pop r12 + pop rbp + pop rbx + + ret + +.section .note.GNU-stack,"",@progbits diff --git a/kernel/scheduler/scheduler.cpp b/kernel/scheduler/scheduler.cpp new file mode 100644 index 0000000..686f87a --- /dev/null +++ b/kernel/scheduler/scheduler.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include + +// Assembly: context_switch(UINT64* old_rsp, UINT64 new_rsp) +extern "C" void context_switch(UINT64* old_rsp, UINT64 new_rsp); + +static task_t g_tasks[TASK_MAX]; +static UINT32 g_task_count = 0; +static task_t* g_current = NULL; +static task_t* g_task_list = NULL; // circular linked list head + +// Trampoline: first thing a new task runs after context_switch. +// The entry function pointer is stored in the task's name field +// (we repurpose a slot — actually we store it in a simple global array). +static void (*g_task_entries[TASK_MAX])(void); + +extern "C" void task_entry_trampoline() { + task_t* cur = scheduler_current(); + if (cur && g_task_entries[cur->id]) { + g_task_entries[cur->id](); // call the user function + } + task_exit(); // clean up when done +} + +task_t* task_create(const char* name, void (*entry)(void)) { + if (g_task_count >= TASK_MAX) { + serial_write("SCHED: task limit reached\n"); + return NULL; + } + + UINT32 id = g_task_count++; + task_t* task = &g_tasks[id]; + + // Store entry function for the trampoline + g_task_entries[id] = entry; + + // Allocate kernel stack + UINTN stack_pages = TASK_STACK_SIZE / PAGE_SIZE; + void* stack = pmm_alloc_pages(stack_pages); + if (!stack) { + serial_write("SCHED: stack alloc failed for task "); + serial_write(name); + serial_write("\n"); + return NULL; + } + + // Fill task struct + task->id = id; + task->state = TASK_STATE_READY; + task->stack_base = stack; + + // Copy name + const char* s = name; + char* d = task->name; + for (int i = 0; i < TASK_NAME_LEN - 1 && *s; i++) { + *d++ = *s++; + } + *d = '\0'; + + // Set up initial stack for first context_switch into this task. + // Stack grows downward. context_switch will pop 6 regs then ret. + // + // Layout (high addr -> low addr): + // [stack + TASK_STACK_SIZE] <- top + // return addr = task_entry_trampoline (ret goes here) + // rbx = 0 + // rbp = 0 + // r12 = 0 + // r13 = 0 + // r14 = 0 + // r15 = 0 <- RSP points here initially + // + UINT64* sp = (UINT64*)((UINT8*)stack + TASK_STACK_SIZE); + + // Push return address (task_entry_trampoline) + *--sp = (UINT64)task_entry_trampoline; + + // Push callee-saved registers (all zero) + *--sp = 0; // rbx + *--sp = 0; // rbp + *--sp = 0; // r12 + *--sp = 0; // r13 + *--sp = 0; // r14 + *--sp = 0; // r15 + + task->rsp = (UINT64)sp; + + // Insert into circular linked list + if (g_task_list == NULL) { + task->next = task; // points to itself (single element circle) + g_task_list = task; + } else { + // Insert after current tail (g_task_list is the "last" in circle) + task->next = g_task_list->next; + g_task_list->next = task; + g_task_list = task; // new tail + } + + serial_write("SCHED: created task '"); + serial_write(task->name); + serial_write("' id="); + serial_write_hex(id); + serial_write("\n"); + + return task; +} + +void yield(void) { + if (g_current == NULL || g_task_list == NULL) return; + + task_t* cur = g_current; + task_t* next = cur->next; + + // Skip terminated tasks + while (next->state == TASK_STATE_TERMINATED && next != cur) { + next = next->next; + } + if (next->state == TASK_STATE_TERMINATED) return; // all terminated + + if (next == cur) return; // only one task, nothing to do + + cur->state = TASK_STATE_READY; + next->state = TASK_STATE_RUNNING; + g_current = next; + + context_switch(&cur->rsp, next->rsp); +} + +void scheduler_run(void) { + if (g_task_list == NULL) { + serial_write("SCHED: no tasks to run\n"); + return; + } + + // Find first READY task + task_t* start = g_task_list->next; // head of circle + task_t* t = start; + do { + if (t->state == TASK_STATE_READY) { + break; + } + t = t->next; + } while (t != start); + + if (t->state != TASK_STATE_READY) { + serial_write("SCHED: no READY tasks\n"); + return; + } + + g_current = t; + t->state = TASK_STATE_RUNNING; + + serial_write("SCHED: starting task '"); + serial_write(t->name); + serial_write("'\n"); + + // First context switch — no old RSP to save (we're still in scheduler_run) + // Just switch to the task's stack directly. + // We need a dummy old_rsp to satisfy the API, but we never return here. + UINT64 dummy_rsp; + context_switch(&dummy_rsp, t->rsp); + + // We only return here when ALL tasks are terminated + serial_write("SCHED: all tasks finished\n"); + while (1) ASM ("hlt"); +} + +void task_exit(void) { + if (g_current == NULL) return; + + serial_write("SCHED: task '"); + serial_write(g_current->name); + serial_write("' exited\n"); + + g_current->state = TASK_STATE_TERMINATED; + + // Yield to next task — we won't come back + yield(); + + // Should never reach here + while (1) ASM ("hlt"); +} + +task_t* scheduler_current(void) { + return g_current; +} -- 2.43.0 From 4ff227bc75b09b57daa95361b1644d5c41af065a Mon Sep 17 00:00:00 2001 From: pyao12 Date: Sun, 31 May 2026 18:13:16 +0800 Subject: [PATCH 2/4] [fix] Scheduler overwrote task stats --- kernel/scheduler/scheduler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/scheduler/scheduler.cpp b/kernel/scheduler/scheduler.cpp index 686f87a..8d37c3d 100644 --- a/kernel/scheduler/scheduler.cpp +++ b/kernel/scheduler/scheduler.cpp @@ -122,7 +122,8 @@ void yield(void) { if (next == cur) return; // only one task, nothing to do - cur->state = TASK_STATE_READY; + if (cur->state != TASK_STATE_TERMINATED) + cur->state = TASK_STATE_READY; next->state = TASK_STATE_RUNNING; g_current = next; -- 2.43.0 From daccb0a763d446757a43409f5221051f1fb89da2 Mon Sep 17 00:00:00 2001 From: pyao12 Date: Sun, 31 May 2026 18:35:25 +0800 Subject: [PATCH 3/4] [feat] 100Hz task switch --- Makefile | 15 +- include/gdt.h | 61 ++++ include/idt.h | 48 +++ include/pic.h | 20 + include/pit.h | 18 + include/scheduler.h | 5 + kernel/entry.cpp | 1 + kernel/interrupt/gdt.cpp | 71 ++++ kernel/interrupt/idt.cpp | 66 ++++ kernel/interrupt/idt_helpers.S | 33 ++ kernel/interrupt/isr.S | 589 ++++++++++++++++++++++++++++++ kernel/interrupt/pic.cpp | 71 ++++ kernel/interrupt/pit.cpp | 48 +++ kernel/main.cpp | 60 ++- kernel/scheduler/context_switch.S | 14 +- kernel/scheduler/scheduler.cpp | 88 +++-- 16 files changed, 1172 insertions(+), 36 deletions(-) create mode 100644 include/gdt.h create mode 100644 include/idt.h create mode 100644 include/pic.h create mode 100644 include/pit.h create mode 100644 kernel/interrupt/gdt.cpp create mode 100644 kernel/interrupt/idt.cpp create mode 100644 kernel/interrupt/idt_helpers.S create mode 100644 kernel/interrupt/isr.S create mode 100644 kernel/interrupt/pic.cpp create mode 100644 kernel/interrupt/pit.cpp diff --git a/Makefile b/Makefile index 5ea0ff7..13e7c50 100644 --- a/Makefile +++ b/Makefile @@ -18,9 +18,11 @@ BOOT_OBJ = build/boot.o KERNEL_CPP = kernel/entry.cpp kernel/main.cpp kernel/serial.cpp kernel/fs.cpp \ kernel/memory/heap.cpp kernel/memory/pmm.cpp \ kernel/scheduler/scheduler.cpp \ + kernel/interrupt/gdt.cpp kernel/interrupt/idt.cpp \ + kernel/interrupt/pic.cpp kernel/interrupt/pit.cpp \ graphics/context.cpp graphics/draw.cpp \ fonts/pixel_font.cpp -KERNEL_ASM = kernel/scheduler/context_switch.S +KERNEL_ASM = kernel/scheduler/context_switch.S kernel/interrupt/isr.S kernel/interrupt/idt_helpers.S KERNEL_OBJ = $(KERNEL_CPP:%.cpp=build/%.o) $(KERNEL_ASM:%.S=build/%.o) EFI_TOP_C = $(wildcard efi/lib/*.c) @@ -46,7 +48,7 @@ all: _bd $(EFI_OBJ) $(BOOT_OBJ) $(KERNEL_OBJ) _bd: @mkdir -p build/graphics build/kernel build/fonts build/kernel/memory \ - build/kernel/scheduler \ + build/kernel/scheduler build/kernel/interrupt \ build/efi/lib build/efi/lib/x86_64 build/efi/lib/runtime build/efi/gnuefi $(EFI_CRT0_OBJ): efi/gnuefi/crt0-efi-x86_64.S | _bd @@ -98,6 +100,15 @@ build/kernel/scheduler/%.o: kernel/scheduler/%.S | _bd @gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \ -fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@ +build/kernel/interrupt/%.o: kernel/interrupt/%.cpp | _bd + @echo "Compile CPP $<" + @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ + +build/kernel/interrupt/%.o: kernel/interrupt/%.S | _bd + @echo "Compile AS $<" + @gcc -Iinclude -Iefi/inc -ffreestanding -fno-stack-protector -fno-stack-check \ + -fshort-wchar -mno-red-zone -fcf-protection=none -c $< -o $@ + build/graphics/%.o: graphics/%.cpp | _bd @echo "Compile CPP $<" @g++ $(KERNEL_CXXFLAGS) -c $< -o $@ diff --git a/include/gdt.h b/include/gdt.h new file mode 100644 index 0000000..78cc6cb --- /dev/null +++ b/include/gdt.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +// GDT selectors +#define GDT_NULL 0x00 +#define GDT_KERNEL_CODE 0x08 +#define GDT_KERNEL_DATA 0x10 +#define GDT_USER_CODE 0x18 +#define GDT_USER_DATA 0x20 +#define GDT_TSS 0x28 + +// GDT entry +struct gdt_entry { + UINT16 limit_low; + UINT16 base_low; + UINT8 base_mid; + UINT8 access; + UINT8 granularity; + UINT8 base_high; +} __attribute__((packed)); + +// GDT pointer +struct gdt_ptr { + UINT16 limit; + UINT64 base; +} __attribute__((packed)); + +// TSS (Task State Segment) — 64-bit mode uses only RSP0 and IST1-7 +struct tss { + UINT32 reserved0; + UINT64 rsp0; + UINT64 rsp1; + UINT64 rsp2; + UINT64 reserved1; + UINT64 ist1; + UINT64 ist2; + UINT64 ist3; + UINT64 ist4; + UINT64 ist5; + UINT64 ist6; + UINT64 ist7; + UINT64 reserved2; + UINT16 reserved3; + UINT16 iopb_offset; +} __attribute__((packed)); + +// TSS descriptor in GDT (system segment, 16 bytes) +struct tss_descriptor { + UINT16 limit_low; + UINT16 base_low; + UINT8 base_mid; + UINT8 access; + UINT8 granularity; + UINT8 base_high; + UINT32 base_upper; + UINT32 reserved; +} __attribute__((packed)); + +void gdt_init(void); +void gdt_set_kernel_stack(UINT64 stack); // sets TSS.RSP0 diff --git a/include/idt.h b/include/idt.h new file mode 100644 index 0000000..7f1d7a2 --- /dev/null +++ b/include/idt.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +// IDT entry (64-bit gate descriptor) +struct idt_entry { + UINT16 offset_low; + UINT16 selector; + UINT8 ist; // IST index (bits 0-2), reserved (bits 3-6), type (bit 7) + UINT8 type_attr; // P (bit 7), DPL (bits 5-6), 0 (bit 4), type (bits 0-3) + UINT16 offset_mid; + UINT32 offset_high; + UINT32 reserved; +} __attribute__((packed)); + +struct idt_ptr { + UINT16 limit; + UINT64 base; +} __attribute__((packed)); + +// Interrupt frame saved by ISR stubs +struct trap_frame { + // Pushed by ISR stub (general-purpose registers) + UINT64 r11; + UINT64 r10; + UINT64 r9; + UINT64 r8; + UINT64 rdi; + UINT64 rsi; + UINT64 rdx; + UINT64 rcx; + UINT64 rax; + // Pushed by ISR stub (metadata) + UINT64 vector; + UINT64 error_code; + // Pushed by CPU (interrupt frame) + UINT64 rip; + UINT64 cs; + UINT64 rflags; + UINT64 rsp; + UINT64 ss; +} __attribute__((packed)); + +// Handler function type +typedef void (*isr_handler_t)(trap_frame*); + +void idt_init(void); +void idt_set_handler(UINT8 vector, isr_handler_t handler); diff --git a/include/pic.h b/include/pic.h new file mode 100644 index 0000000..928d6e6 --- /dev/null +++ b/include/pic.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +// 8259 PIC ports +#define PIC1_CMD 0x20 +#define PIC1_DATA 0x21 +#define PIC2_CMD 0xA0 +#define PIC2_DATA 0xA1 + +// PIC vectors +#define PIC_IRQ_BASE 0x20 // IRQ 0 mapped to vector 0x20 (32) + +// EOI signal +#define PIC_EOI 0x20 + +void pic_init(void); +void pic_send_eoi(UINT8 irq); +void pic_mask_irq(UINT8 irq); +void pic_unmask_irq(UINT8 irq); diff --git a/include/pit.h b/include/pit.h new file mode 100644 index 0000000..6c473c8 --- /dev/null +++ b/include/pit.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +// PIT I/O ports +#define PIT_CHANNEL0_DATA 0x40 +#define PIT_COMMAND_PORT 0x43 + +// PIT frequency +#define PIT_BASE_FREQ 1193182 // 1.193182 MHz +#define PIT_TICK_HZ 100 // 100 Hz tick (10ms per tick) + +// Timer tick callback type +typedef void (*timer_callback_t)(void); + +void pit_init(void); +void pit_set_tick_handler(timer_callback_t handler); +UINT64 pit_get_ticks(void); diff --git a/include/scheduler.h b/include/scheduler.h index 71ea7e1..2165d78 100644 --- a/include/scheduler.h +++ b/include/scheduler.h @@ -7,6 +7,7 @@ #define TASK_STACK_SIZE (PAGE_SIZE * 4) // 16 KB kernel stack per task #define TASK_MAX 32 #define TASK_NAME_LEN 32 +#define TIME_SLICE_DEFAULT 5 // 5 ticks = 50ms at 100 Hz typedef enum { TASK_STATE_READY, @@ -21,6 +22,7 @@ typedef struct task { char name[TASK_NAME_LEN]; void* stack_base; // base address of kernel stack struct task* next; // circular linked list + UINT32 time_slice; // remaining ticks before preemption } task_t; // Create a new task. Returns task pointer or NULL on failure. @@ -37,3 +39,6 @@ void task_exit(void); // Get current running task task_t* scheduler_current(void); + +// Timer tick handler — called by PIT IRQ 0, drives preemption +void scheduler_tick(void); diff --git a/kernel/entry.cpp b/kernel/entry.cpp index 6e13f77..616824e 100644 --- a/kernel/entry.cpp +++ b/kernel/entry.cpp @@ -8,6 +8,7 @@ extern "C" void kernel_main(); extern "C" void _start(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable) { (void)ImageHandle; ST = SystemTable; + ASM("cli"); // disable interrupts until IDT is ready kernel_main(); while (1) ASM ("hlt"); } diff --git a/kernel/interrupt/gdt.cpp b/kernel/interrupt/gdt.cpp new file mode 100644 index 0000000..f74da9a --- /dev/null +++ b/kernel/interrupt/gdt.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +static gdt_entry g_gdt[7]; // 5 segments + TSS (2 entries) +static gdt_ptr g_gdt_ptr; +static tss g_tss; + +extern "C" void lgdt_and_reload(void* gdt_ptr); +extern "C" void tss_load(UINT16 selector); + +static void gdt_set_entry(UINT32 index, UINT32 base, UINT32 limit, + UINT8 access, UINT8 granularity) { + g_gdt[index].limit_low = limit & 0xFFFF; + g_gdt[index].base_low = base & 0xFFFF; + g_gdt[index].base_mid = (base >> 16) & 0xFF; + g_gdt[index].access = access; + g_gdt[index].granularity = granularity | ((limit >> 16) & 0x0F); + g_gdt[index].base_high = (base >> 24) & 0xFF; +} + +static void tss_set_descriptor(UINT32 index) { + UINT64 base = (UINT64)&g_tss; + UINT32 limit = sizeof(tss) - 1; + + g_gdt[index].limit_low = limit & 0xFFFF; + g_gdt[index].base_low = base & 0xFFFF; + g_gdt[index].base_mid = (base >> 16) & 0xFF; + g_gdt[index].access = 0x89; + g_gdt[index].granularity = 0x00; + g_gdt[index].base_high = (base >> 24) & 0xFF; + + g_gdt[index + 1].limit_low = (limit >> 16) & 0xFFFF; + g_gdt[index + 1].base_low = (base >> 32) & 0xFFFF; + g_gdt[index + 1].base_mid = (base >> 48) & 0xFF; + g_gdt[index + 1].access = 0; + g_gdt[index + 1].granularity = 0; + g_gdt[index + 1].base_high = 0; +} + +void gdt_init(void) { + serial_write("GDT: initializing\n"); + + for (int i = 0; i < 7; i++) { + g_gdt[i] = {0}; + } + + gdt_set_entry(0, 0, 0, 0, 0); + gdt_set_entry(1, 0, 0xFFFFF, 0x9A, 0xA0); + gdt_set_entry(2, 0, 0xFFFFF, 0x92, 0xC0); + gdt_set_entry(3, 0, 0xFFFFF, 0xFA, 0xA0); + gdt_set_entry(4, 0, 0xFFFFF, 0xF2, 0xC0); + tss_set_descriptor(5); + + g_tss = {}; + g_tss.iopb_offset = sizeof(tss); + + g_gdt_ptr.limit = sizeof(g_gdt) - 1; + g_gdt_ptr.base = (UINT64)&g_gdt[0]; + + serial_write("GDT: loading GDT + reloading segments\n"); + lgdt_and_reload(&g_gdt_ptr); + serial_write("GDT: loading TSS\n"); + tss_load(GDT_TSS); + + serial_write("GDT: done\n"); +} + +void gdt_set_kernel_stack(UINT64 stack) { + g_tss.rsp0 = stack; +} diff --git a/kernel/interrupt/idt.cpp b/kernel/interrupt/idt.cpp new file mode 100644 index 0000000..70ae035 --- /dev/null +++ b/kernel/interrupt/idt.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include + +// Defined in isr.S — 256 ISR stubs +extern "C" void* isr_stub_table[256]; + +static idt_entry g_idt[256]; +static idt_ptr g_idt_ptr; +static isr_handler_t g_handlers[256] = {0}; + +void idt_set_handler(UINT8 vector, isr_handler_t handler) { + g_handlers[vector] = handler; +} + +// Called from isr.S common handler +extern "C" void isr_dispatch(trap_frame* frame) { + UINT8 vector = (UINT8)frame->vector; + + if (g_handlers[vector]) { + g_handlers[vector](frame); + } else if (vector < 32) { + serial_write("IDT: unhandled exception #"); + serial_write_hex(vector); + serial_write(" error="); + serial_write_hex(frame->error_code); + serial_write("\n"); + while (1) ASM("hlt"); + } +} + +// IDT helpers (defined in idt_helpers.S) +extern "C" void idt_load(UINT64 base, UINT16 limit); + +static void idt_set_entry(UINT8 vector, UINT64 handler_addr) { + g_idt[vector].offset_low = handler_addr & 0xFFFF; + g_idt[vector].selector = 0x08; // kernel code segment + g_idt[vector].ist = 0; + g_idt[vector].type_attr = 0x8E; // present, DPL=0, 64-bit interrupt gate + g_idt[vector].offset_mid = (handler_addr >> 16) & 0xFFFF; + g_idt[vector].offset_high = (handler_addr >> 32) & 0xFFFFFFFF; + g_idt[vector].reserved = 0; +} + +void idt_init(void) { + serial_write("IDT: initializing 256 entries\n"); + + // Clear IDT + for (int i = 0; i < 256; i++) { + g_idt[i] = {0}; + g_handlers[i] = NULL; + } + + // Install all 256 ISR stubs + for (int i = 0; i < 256; i++) { + idt_set_entry(i, (UINT64)isr_stub_table[i]); + } + + // Load IDT + g_idt_ptr.limit = sizeof(g_idt) - 1; + g_idt_ptr.base = (UINT64)&g_idt[0]; + idt_load(g_idt_ptr.base, g_idt_ptr.limit); + + serial_write("IDT: loaded\n"); +} diff --git a/kernel/interrupt/idt_helpers.S b/kernel/interrupt/idt_helpers.S new file mode 100644 index 0000000..c21660b --- /dev/null +++ b/kernel/interrupt/idt_helpers.S @@ -0,0 +1,33 @@ +.intel_syntax noprefix + +.global idt_load +idt_load: + sub rsp, 10 + mov [rsp], si + mov [rsp + 2], rdi + lidt [rsp] + add rsp, 10 + ret + +.global lgdt_and_reload +lgdt_and_reload: + lgdt [rdi] + push 0x08 + mov rax, offset .reload_cs + push rax + retfq +.reload_cs: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov fs, ax + mov gs, ax + mov ss, ax + ret + +.global tss_load +tss_load: + ltr di + ret + +.section .note.GNU-stack,"",@progbits diff --git a/kernel/interrupt/isr.S b/kernel/interrupt/isr.S new file mode 100644 index 0000000..d68b0e5 --- /dev/null +++ b/kernel/interrupt/isr.S @@ -0,0 +1,589 @@ +.intel_syntax noprefix + +// ISR stub table — 256 entries +// For exceptions without error code, we push a dummy 0 +// For exceptions with error code, the CPU already pushed it + +// Macros for generating ISR stubs +.macro ISR_NOERR num +.global isr_stub_\num +isr_stub_\num: + push 0 // dummy error code + push \num // vector number + jmp isr_common +.endm + +.macro ISR_ERR num +.global isr_stub_\num +isr_stub_\num: + // error code already on stack from CPU + push \num // vector number + jmp isr_common +.endm + +// === Exceptions 0-31 === +ISR_NOERR 0 // #DE Divide Error +ISR_NOERR 1 // #DB Debug +ISR_NOERR 2 // NMI +ISR_NOERR 3 // #BP Breakpoint +ISR_NOERR 4 // #OF Overflow +ISR_NOERR 5 // #BR Bound Range Exceeded +ISR_NOERR 6 // #UD Invalid Opcode +ISR_NOERR 7 // #NM Device Not Available +ISR_ERR 8 // #DF Double Fault +ISR_NOERR 9 // Coprocessor Segment Overrun (reserved) +ISR_ERR 10 // #TS Invalid TSS +ISR_ERR 11 // #NP Segment Not Present +ISR_ERR 12 // #SS Stack-Segment Fault +ISR_ERR 13 // #GP General Protection +ISR_ERR 14 // #PF Page Fault +ISR_NOERR 15 // Reserved +ISR_NOERR 16 // #MF x87 FPU Error +ISR_ERR 17 // #AC Alignment Check +ISR_NOERR 18 // #MC Machine Check +ISR_NOERR 19 // #XM SIMD Exception +ISR_NOERR 20 // Virtualization Exception +ISR_ERR 21 // #CP Control Protection +ISR_NOERR 22 +ISR_NOERR 23 +ISR_NOERR 24 +ISR_NOERR 25 +ISR_NOERR 26 +ISR_NOERR 27 +ISR_NOERR 28 +ISR_ERR 29 // Hypervisor Injection +ISR_ERR 30 // #SX Security Exception +ISR_NOERR 31 // Reserved + +// === Hardware IRQs 32-255 === +ISR_NOERR 32 // PIT timer (IRQ 0) +ISR_NOERR 33 // Keyboard (IRQ 1) +ISR_NOERR 34 // Cascade (IRQ 2) +ISR_NOERR 35 // COM2 (IRQ 3) +ISR_NOERR 36 // COM1 (IRQ 4) +ISR_NOERR 37 // LPT2 (IRQ 5) +ISR_NOERR 38 // Floppy (IRQ 6) +ISR_NOERR 39 // LPT1 / Spurious (IRQ 7) +ISR_NOERR 40 // CMOS RTC (IRQ 8) +ISR_NOERR 41 // ACPI (IRQ 9) +ISR_NOERR 42 // Open (IRQ 10) +ISR_NOERR 43 // Open (IRQ 11) +ISR_NOERR 44 // PS/2 Mouse (IRQ 12) +ISR_NOERR 45 // FPU (IRQ 13) +ISR_NOERR 46 // Primary ATA (IRQ 14) +ISR_NOERR 47 // Secondary ATA (IRQ 15) + +// Vectors 48-255: software / unused +ISR_NOERR 48 +ISR_NOERR 49 +ISR_NOERR 50 +ISR_NOERR 51 +ISR_NOERR 52 +ISR_NOERR 53 +ISR_NOERR 54 +ISR_NOERR 55 +ISR_NOERR 56 +ISR_NOERR 57 +ISR_NOERR 58 +ISR_NOERR 59 +ISR_NOERR 60 +ISR_NOERR 61 +ISR_NOERR 62 +ISR_NOERR 63 +ISR_NOERR 64 +ISR_NOERR 65 +ISR_NOERR 66 +ISR_NOERR 67 +ISR_NOERR 68 +ISR_NOERR 69 +ISR_NOERR 70 +ISR_NOERR 71 +ISR_NOERR 72 +ISR_NOERR 73 +ISR_NOERR 74 +ISR_NOERR 75 +ISR_NOERR 76 +ISR_NOERR 77 +ISR_NOERR 78 +ISR_NOERR 79 +ISR_NOERR 80 +ISR_NOERR 81 +ISR_NOERR 82 +ISR_NOERR 83 +ISR_NOERR 84 +ISR_NOERR 85 +ISR_NOERR 86 +ISR_NOERR 87 +ISR_NOERR 88 +ISR_NOERR 89 +ISR_NOERR 90 +ISR_NOERR 91 +ISR_NOERR 92 +ISR_NOERR 93 +ISR_NOERR 94 +ISR_NOERR 95 +ISR_NOERR 96 +ISR_NOERR 97 +ISR_NOERR 98 +ISR_NOERR 99 +ISR_NOERR 100 +ISR_NOERR 101 +ISR_NOERR 102 +ISR_NOERR 103 +ISR_NOERR 104 +ISR_NOERR 105 +ISR_NOERR 106 +ISR_NOERR 107 +ISR_NOERR 108 +ISR_NOERR 109 +ISR_NOERR 110 +ISR_NOERR 111 +ISR_NOERR 112 +ISR_NOERR 113 +ISR_NOERR 114 +ISR_NOERR 115 +ISR_NOERR 116 +ISR_NOERR 117 +ISR_NOERR 118 +ISR_NOERR 119 +ISR_NOERR 120 +ISR_NOERR 121 +ISR_NOERR 122 +ISR_NOERR 123 +ISR_NOERR 124 +ISR_NOERR 125 +ISR_NOERR 126 +ISR_NOERR 127 +ISR_NOERR 128 +ISR_NOERR 129 +ISR_NOERR 130 +ISR_NOERR 131 +ISR_NOERR 132 +ISR_NOERR 133 +ISR_NOERR 134 +ISR_NOERR 135 +ISR_NOERR 136 +ISR_NOERR 137 +ISR_NOERR 138 +ISR_NOERR 139 +ISR_NOERR 140 +ISR_NOERR 141 +ISR_NOERR 142 +ISR_NOERR 143 +ISR_NOERR 144 +ISR_NOERR 145 +ISR_NOERR 146 +ISR_NOERR 147 +ISR_NOERR 148 +ISR_NOERR 149 +ISR_NOERR 150 +ISR_NOERR 151 +ISR_NOERR 152 +ISR_NOERR 153 +ISR_NOERR 154 +ISR_NOERR 155 +ISR_NOERR 156 +ISR_NOERR 157 +ISR_NOERR 158 +ISR_NOERR 159 +ISR_NOERR 160 +ISR_NOERR 161 +ISR_NOERR 162 +ISR_NOERR 163 +ISR_NOERR 164 +ISR_NOERR 165 +ISR_NOERR 166 +ISR_NOERR 167 +ISR_NOERR 168 +ISR_NOERR 169 +ISR_NOERR 170 +ISR_NOERR 171 +ISR_NOERR 172 +ISR_NOERR 173 +ISR_NOERR 174 +ISR_NOERR 175 +ISR_NOERR 176 +ISR_NOERR 177 +ISR_NOERR 178 +ISR_NOERR 179 +ISR_NOERR 180 +ISR_NOERR 181 +ISR_NOERR 182 +ISR_NOERR 183 +ISR_NOERR 184 +ISR_NOERR 185 +ISR_NOERR 186 +ISR_NOERR 187 +ISR_NOERR 188 +ISR_NOERR 189 +ISR_NOERR 190 +ISR_NOERR 191 +ISR_NOERR 192 +ISR_NOERR 193 +ISR_NOERR 194 +ISR_NOERR 195 +ISR_NOERR 196 +ISR_NOERR 197 +ISR_NOERR 198 +ISR_NOERR 199 +ISR_NOERR 200 +ISR_NOERR 201 +ISR_NOERR 202 +ISR_NOERR 203 +ISR_NOERR 204 +ISR_NOERR 205 +ISR_NOERR 206 +ISR_NOERR 207 +ISR_NOERR 208 +ISR_NOERR 209 +ISR_NOERR 210 +ISR_NOERR 211 +ISR_NOERR 212 +ISR_NOERR 213 +ISR_NOERR 214 +ISR_NOERR 215 +ISR_NOERR 216 +ISR_NOERR 217 +ISR_NOERR 218 +ISR_NOERR 219 +ISR_NOERR 220 +ISR_NOERR 221 +ISR_NOERR 222 +ISR_NOERR 223 +ISR_NOERR 224 +ISR_NOERR 225 +ISR_NOERR 226 +ISR_NOERR 227 +ISR_NOERR 228 +ISR_NOERR 229 +ISR_NOERR 230 +ISR_NOERR 231 +ISR_NOERR 232 +ISR_NOERR 233 +ISR_NOERR 234 +ISR_NOERR 235 +ISR_NOERR 236 +ISR_NOERR 237 +ISR_NOERR 238 +ISR_NOERR 239 +ISR_NOERR 240 +ISR_NOERR 241 +ISR_NOERR 242 +ISR_NOERR 243 +ISR_NOERR 244 +ISR_NOERR 245 +ISR_NOERR 246 +ISR_NOERR 247 +ISR_NOERR 248 +ISR_NOERR 249 +ISR_NOERR 250 +ISR_NOERR 251 +ISR_NOERR 252 +ISR_NOERR 253 +ISR_NOERR 254 +ISR_NOERR 255 + +// === Common ISR handler === +// Stack layout on entry (from ISR stub): +// [rsp+0x00] vector (pushed by stub) +// [rsp+0x08] error_code (pushed by stub or CPU) +// [rsp+0x10] rip (pushed by CPU) +// [rsp+0x18] cs (pushed by CPU) +// [rsp+0x20] rflags (pushed by CPU) +// [rsp+0x28] rsp (pushed by CPU) +// [rsp+0x30] ss (pushed by CPU) + +isr_common: + // Save all general-purpose registers to form trap_frame + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rdx + push rcx + push rax + + // Now RSP points to trap_frame struct, pass as argument + mov rdi, rsp + call isr_dispatch + + // Restore registers + pop rax + pop rcx + pop rdx + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + + // Skip vector + error_code (16 bytes) + add rsp, 16 + + iretq + +// === ISR stub table (array of pointers) === +.section .data +.global isr_stub_table +isr_stub_table: + .quad isr_stub_0 + .quad isr_stub_1 + .quad isr_stub_2 + .quad isr_stub_3 + .quad isr_stub_4 + .quad isr_stub_5 + .quad isr_stub_6 + .quad isr_stub_7 + .quad isr_stub_8 + .quad isr_stub_9 + .quad isr_stub_10 + .quad isr_stub_11 + .quad isr_stub_12 + .quad isr_stub_13 + .quad isr_stub_14 + .quad isr_stub_15 + .quad isr_stub_16 + .quad isr_stub_17 + .quad isr_stub_18 + .quad isr_stub_19 + .quad isr_stub_20 + .quad isr_stub_21 + .quad isr_stub_22 + .quad isr_stub_23 + .quad isr_stub_24 + .quad isr_stub_25 + .quad isr_stub_26 + .quad isr_stub_27 + .quad isr_stub_28 + .quad isr_stub_29 + .quad isr_stub_30 + .quad isr_stub_31 + .quad isr_stub_32 + .quad isr_stub_33 + .quad isr_stub_34 + .quad isr_stub_35 + .quad isr_stub_36 + .quad isr_stub_37 + .quad isr_stub_38 + .quad isr_stub_39 + .quad isr_stub_40 + .quad isr_stub_41 + .quad isr_stub_42 + .quad isr_stub_43 + .quad isr_stub_44 + .quad isr_stub_45 + .quad isr_stub_46 + .quad isr_stub_47 + .quad isr_stub_48 + .quad isr_stub_49 + .quad isr_stub_50 + .quad isr_stub_51 + .quad isr_stub_52 + .quad isr_stub_53 + .quad isr_stub_54 + .quad isr_stub_55 + .quad isr_stub_56 + .quad isr_stub_57 + .quad isr_stub_58 + .quad isr_stub_59 + .quad isr_stub_60 + .quad isr_stub_61 + .quad isr_stub_62 + .quad isr_stub_63 + .quad isr_stub_64 + .quad isr_stub_65 + .quad isr_stub_66 + .quad isr_stub_67 + .quad isr_stub_68 + .quad isr_stub_69 + .quad isr_stub_70 + .quad isr_stub_71 + .quad isr_stub_72 + .quad isr_stub_73 + .quad isr_stub_74 + .quad isr_stub_75 + .quad isr_stub_76 + .quad isr_stub_77 + .quad isr_stub_78 + .quad isr_stub_79 + .quad isr_stub_80 + .quad isr_stub_81 + .quad isr_stub_82 + .quad isr_stub_83 + .quad isr_stub_84 + .quad isr_stub_85 + .quad isr_stub_86 + .quad isr_stub_87 + .quad isr_stub_88 + .quad isr_stub_89 + .quad isr_stub_90 + .quad isr_stub_91 + .quad isr_stub_92 + .quad isr_stub_93 + .quad isr_stub_94 + .quad isr_stub_95 + .quad isr_stub_96 + .quad isr_stub_97 + .quad isr_stub_98 + .quad isr_stub_99 + .quad isr_stub_100 + .quad isr_stub_101 + .quad isr_stub_102 + .quad isr_stub_103 + .quad isr_stub_104 + .quad isr_stub_105 + .quad isr_stub_106 + .quad isr_stub_107 + .quad isr_stub_108 + .quad isr_stub_109 + .quad isr_stub_110 + .quad isr_stub_111 + .quad isr_stub_112 + .quad isr_stub_113 + .quad isr_stub_114 + .quad isr_stub_115 + .quad isr_stub_116 + .quad isr_stub_117 + .quad isr_stub_118 + .quad isr_stub_119 + .quad isr_stub_120 + .quad isr_stub_121 + .quad isr_stub_122 + .quad isr_stub_123 + .quad isr_stub_124 + .quad isr_stub_125 + .quad isr_stub_126 + .quad isr_stub_127 + .quad isr_stub_128 + .quad isr_stub_129 + .quad isr_stub_130 + .quad isr_stub_131 + .quad isr_stub_132 + .quad isr_stub_133 + .quad isr_stub_134 + .quad isr_stub_135 + .quad isr_stub_136 + .quad isr_stub_137 + .quad isr_stub_138 + .quad isr_stub_139 + .quad isr_stub_140 + .quad isr_stub_141 + .quad isr_stub_142 + .quad isr_stub_143 + .quad isr_stub_144 + .quad isr_stub_145 + .quad isr_stub_146 + .quad isr_stub_147 + .quad isr_stub_148 + .quad isr_stub_149 + .quad isr_stub_150 + .quad isr_stub_151 + .quad isr_stub_152 + .quad isr_stub_153 + .quad isr_stub_154 + .quad isr_stub_155 + .quad isr_stub_156 + .quad isr_stub_157 + .quad isr_stub_158 + .quad isr_stub_159 + .quad isr_stub_160 + .quad isr_stub_161 + .quad isr_stub_162 + .quad isr_stub_163 + .quad isr_stub_164 + .quad isr_stub_165 + .quad isr_stub_166 + .quad isr_stub_167 + .quad isr_stub_168 + .quad isr_stub_169 + .quad isr_stub_170 + .quad isr_stub_171 + .quad isr_stub_172 + .quad isr_stub_173 + .quad isr_stub_174 + .quad isr_stub_175 + .quad isr_stub_176 + .quad isr_stub_177 + .quad isr_stub_178 + .quad isr_stub_179 + .quad isr_stub_180 + .quad isr_stub_181 + .quad isr_stub_182 + .quad isr_stub_183 + .quad isr_stub_184 + .quad isr_stub_185 + .quad isr_stub_186 + .quad isr_stub_187 + .quad isr_stub_188 + .quad isr_stub_189 + .quad isr_stub_190 + .quad isr_stub_191 + .quad isr_stub_192 + .quad isr_stub_193 + .quad isr_stub_194 + .quad isr_stub_195 + .quad isr_stub_196 + .quad isr_stub_197 + .quad isr_stub_198 + .quad isr_stub_199 + .quad isr_stub_200 + .quad isr_stub_201 + .quad isr_stub_202 + .quad isr_stub_203 + .quad isr_stub_204 + .quad isr_stub_205 + .quad isr_stub_206 + .quad isr_stub_207 + .quad isr_stub_208 + .quad isr_stub_209 + .quad isr_stub_210 + .quad isr_stub_211 + .quad isr_stub_212 + .quad isr_stub_213 + .quad isr_stub_214 + .quad isr_stub_215 + .quad isr_stub_216 + .quad isr_stub_217 + .quad isr_stub_218 + .quad isr_stub_219 + .quad isr_stub_220 + .quad isr_stub_221 + .quad isr_stub_222 + .quad isr_stub_223 + .quad isr_stub_224 + .quad isr_stub_225 + .quad isr_stub_226 + .quad isr_stub_227 + .quad isr_stub_228 + .quad isr_stub_229 + .quad isr_stub_230 + .quad isr_stub_231 + .quad isr_stub_232 + .quad isr_stub_233 + .quad isr_stub_234 + .quad isr_stub_235 + .quad isr_stub_236 + .quad isr_stub_237 + .quad isr_stub_238 + .quad isr_stub_239 + .quad isr_stub_240 + .quad isr_stub_241 + .quad isr_stub_242 + .quad isr_stub_243 + .quad isr_stub_244 + .quad isr_stub_245 + .quad isr_stub_246 + .quad isr_stub_247 + .quad isr_stub_248 + .quad isr_stub_249 + .quad isr_stub_250 + .quad isr_stub_251 + .quad isr_stub_252 + .quad isr_stub_253 + .quad isr_stub_254 + .quad isr_stub_255 + +.section .note.GNU-stack,"",@progbits diff --git a/kernel/interrupt/pic.cpp b/kernel/interrupt/pic.cpp new file mode 100644 index 0000000..6fb5d27 --- /dev/null +++ b/kernel/interrupt/pic.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +static inline void outb(UINT16 port, UINT8 val) { + ASM("outb %0, %1" : : "a"(val), "Nd"(port)); +} + +static inline UINT8 inb(UINT16 port) { + UINT8 ret; + ASM("inb %1, %0" : "=a"(ret) : "Nd"(port)); + return ret; +} + +static void pic_wait(void) { + // Small delay for slow PICs + ASM("jmp 1f\n\t1: jmp 1f\n\t1:"); +} + +void pic_init(void) { + serial_write("PIC: remapping 8259 PIC\n"); + + // Save masks + UINT8 mask1 = inb(PIC1_DATA); + UINT8 mask2 = inb(PIC2_DATA); + + // ICW1: begin initialization, ICW4 needed + outb(PIC1_CMD, 0x11); pic_wait(); + outb(PIC2_CMD, 0x11); pic_wait(); + + // ICW2: vector offset + outb(PIC1_DATA, PIC_IRQ_BASE); // IRQ 0-7 → vector 0x20-0x27 + pic_wait(); + outb(PIC2_DATA, PIC_IRQ_BASE + 8); // IRQ 8-15 → vector 0x28-0x2F + pic_wait(); + + // ICW3: cascading + outb(PIC1_DATA, 0x04); pic_wait(); // slave on IRQ 2 + outb(PIC2_DATA, 0x02); pic_wait(); // cascade identity + + // ICW4: 8086 mode + outb(PIC1_DATA, 0x01); pic_wait(); + outb(PIC2_DATA, 0x01); pic_wait(); + + // Restore masks + outb(PIC1_DATA, mask1); + outb(PIC2_DATA, mask2); + + serial_write("PIC: remapped IRQ 0-7 → 0x20-0x27\n"); +} + +void pic_send_eoi(UINT8 irq) { + if (irq >= 8) { + outb(PIC2_CMD, PIC_EOI); + } + outb(PIC1_CMD, PIC_EOI); +} + +void pic_mask_irq(UINT8 irq) { + UINT16 port = (irq < 8) ? PIC1_DATA : PIC2_DATA; + UINT8 shift = irq % 8; + UINT8 mask = inb(port) | (1 << shift); + outb(port, mask); +} + +void pic_unmask_irq(UINT8 irq) { + UINT16 port = (irq < 8) ? PIC1_DATA : PIC2_DATA; + UINT8 shift = irq % 8; + UINT8 mask = inb(port) & ~(1 << shift); + outb(port, mask); +} diff --git a/kernel/interrupt/pit.cpp b/kernel/interrupt/pit.cpp new file mode 100644 index 0000000..1389506 --- /dev/null +++ b/kernel/interrupt/pit.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include + +static inline void outb(UINT16 port, UINT8 val) { + ASM("outb %0, %1" : : "a"(val), "Nd"(port)); +} + +static volatile UINT64 g_ticks = 0; +static timer_callback_t g_tick_handler = NULL; + +extern "C" void pit_irq_handler(void) { + g_ticks++; + if (g_tick_handler) { + g_tick_handler(); + } +} + +void pit_init(void) { + serial_write("PIT: initializing channel 0 at "); + serial_write_hex(PIT_TICK_HZ); + serial_write(" Hz\n"); + + UINT32 divisor = PIT_BASE_FREQ / PIT_TICK_HZ; + + // Command byte: channel 0, lobyte/hibyte, rate generator, binary + outb(PIT_COMMAND_PORT, 0x36); + + // Send divisor (low byte first, then high byte) + outb(PIT_CHANNEL0_DATA, (UINT8)(divisor & 0xFF)); + outb(PIT_CHANNEL0_DATA, (UINT8)((divisor >> 8) & 0xFF)); + + // Unmask IRQ 0 (timer) + pic_unmask_irq(0); + + serial_write("PIT: divisor = "); + serial_write_hex(divisor); + serial_write("\n"); +} + +void pit_set_tick_handler(timer_callback_t handler) { + g_tick_handler = handler; +} + +UINT64 pit_get_ticks(void) { + return g_ticks; +} diff --git a/kernel/main.cpp b/kernel/main.cpp index 38b41cf..c0849cd 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -8,11 +8,14 @@ #include #include #include +#include +#include +#include +#include extern EFI_SYSTEM_TABLE *ST; inline void init_gop() { - // 初始化 GOP EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP; uefi_call_wrapper(ST->BootServices->SetWatchdogTimer, 4, 0, 0, 0, NULL); @@ -21,7 +24,6 @@ inline void init_gop() { } inline void init_serial() { - // 初始化串行驱动 EFI_SERIAL_IO_PROTOCOL *SerialIo = NULL; EFI_GUID gEfiSerialIoProtocolGuid = EFI_SERIAL_IO_PROTOCOL_GUID; EFI_HANDLE *SerialHandles = NULL; @@ -45,6 +47,26 @@ inline void init_serial() { } } +// External: PIT IRQ handler defined in pit.cpp +extern "C" void pit_irq_handler(void); + +// PIC IRQ handler — dispatches IRQ 0 (timer) +static void irq_handler(trap_frame* frame) { + UINT8 vector = (UINT8)frame->vector; + UINT8 irq = vector - PIC_IRQ_BASE; + + switch (irq) { + case 0: // PIT timer + pit_irq_handler(); + break; + default: + break; + } + + // Send EOI to PIC + pic_send_eoi(irq); +} + extern "C" void kernel_main() { init_gop(); init_serial(); @@ -53,7 +75,7 @@ extern "C" void kernel_main() { uefi_call_wrapper(ST->ConOut->OutputString, 2, ST->ConOut, (CHAR16*)L"Kernel is running!\n"); uefi_call_wrapper(ST->ConOut->ClearScreen, 1, ST->ConOut); - serial_write("\n\n"); // 防止和前面串了serial.log看不清 + serial_write("\n\n"); // init memory managers serial_write("Sylva: init PMM...\n"); @@ -108,27 +130,46 @@ extern "C" void kernel_main() { pf_print("Welcome to Sylva OS!\n"); serial_write(" Kernel prepared well.\n"); + // --- Interrupt infrastructure --- + serial_write("Sylva: init GDT...\n"); + gdt_init(); + + serial_write("Sylva: init IDT...\n"); + idt_init(); + + serial_write("Sylva: init PIC...\n"); + pic_init(); + + // Register IRQ handler (vector 0x20 = PIC_IRQ_BASE + 0) + idt_set_handler(PIC_IRQ_BASE + 0, irq_handler); + + serial_write("Sylva: init PIT...\n"); + pit_init(); + pit_set_tick_handler(scheduler_tick); + + // Enable interrupts + ASM("sti"); + serial_write("Sylva: interrupts enabled\n"); + // --- Multitasking demo --- serial_write("Sylva: creating tasks...\n"); - // Task A: prints a message 3 times, yielding between each + // Task A: prints messages — preemption handles time slicing task_create("taskA", []() { for (int i = 0; i < 3; i++) { serial_write("[taskA] running iteration "); serial_write_hex(i); serial_write("\n"); - yield(); } serial_write("[taskA] done\n"); }); - // Task B: prints a message 5 times + // Task B: prints messages task_create("taskB", []() { for (int i = 0; i < 5; i++) { serial_write("[taskB] hello from taskB #"); serial_write_hex(i); serial_write("\n"); - yield(); } serial_write("[taskB] done\n"); }); @@ -136,10 +177,9 @@ extern "C" void kernel_main() { // Task C: short task task_create("taskC", []() { serial_write("[taskC] quick task\n"); - yield(); serial_write("[taskC] finished\n"); }); - serial_write("Sylva: starting scheduler\n"); + serial_write("Sylva: starting preemptive scheduler\n"); scheduler_run(); // never returns -} \ No newline at end of file +} diff --git a/kernel/scheduler/context_switch.S b/kernel/scheduler/context_switch.S index 9b14b1b..0d24914 100644 --- a/kernel/scheduler/context_switch.S +++ b/kernel/scheduler/context_switch.S @@ -5,7 +5,19 @@ // rsi = new_rsp (value of new stack pointer) // // Saves/restores callee-saved registers only. -// On first switch into a new task, ret lands on task_entry_trampoline. +// The caller-saved registers are already saved on the task's stack +// by the ISR stub (for preemptive switching) or are not in use +// (for the initial switch). +// +// For preemptive switching: +// - Timer IRQ fires, ISR stub pushes rax-r11 + trap_frame +// - C code runs (isr_dispatch → timer handler → scheduler) +// - context_switch saves/restores callee-saved regs + switches RSP +// - On return, C code unwinds, isr_common pops rax-r11 + iretq +// +// For new tasks: +// - task_create sets up a fake trap_frame on the stack +// - context_switch pops callee-saved regs, returns into task_entry_trampoline .global context_switch context_switch: push rbx diff --git a/kernel/scheduler/scheduler.cpp b/kernel/scheduler/scheduler.cpp index 8d37c3d..1eed468 100644 --- a/kernel/scheduler/scheduler.cpp +++ b/kernel/scheduler/scheduler.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -13,16 +15,14 @@ static task_t* g_current = NULL; static task_t* g_task_list = NULL; // circular linked list head // Trampoline: first thing a new task runs after context_switch. -// The entry function pointer is stored in the task's name field -// (we repurpose a slot — actually we store it in a simple global array). static void (*g_task_entries[TASK_MAX])(void); extern "C" void task_entry_trampoline() { task_t* cur = scheduler_current(); if (cur && g_task_entries[cur->id]) { - g_task_entries[cur->id](); // call the user function + g_task_entries[cur->id](); } - task_exit(); // clean up when done + task_exit(); } task_t* task_create(const char* name, void (*entry)(void)) { @@ -34,7 +34,6 @@ task_t* task_create(const char* name, void (*entry)(void)) { UINT32 id = g_task_count++; task_t* task = &g_tasks[id]; - // Store entry function for the trampoline g_task_entries[id] = entry; // Allocate kernel stack @@ -47,10 +46,10 @@ task_t* task_create(const char* name, void (*entry)(void)) { return NULL; } - // Fill task struct task->id = id; task->state = TASK_STATE_READY; task->stack_base = stack; + task->time_slice = TIME_SLICE_DEFAULT; // Copy name const char* s = name; @@ -73,12 +72,13 @@ task_t* task_create(const char* name, void (*entry)(void)) { // r14 = 0 // r15 = 0 <- RSP points here initially // + // When preempted by timer IRQ, the ISR stub saves a full trap_frame + // on the task's stack — that layout is only created by hardware+ISR. + // UINT64* sp = (UINT64*)((UINT8*)stack + TASK_STACK_SIZE); - // Push return address (task_entry_trampoline) *--sp = (UINT64)task_entry_trampoline; - // Push callee-saved registers (all zero) *--sp = 0; // rbx *--sp = 0; // rbp *--sp = 0; // r12 @@ -90,13 +90,12 @@ task_t* task_create(const char* name, void (*entry)(void)) { // Insert into circular linked list if (g_task_list == NULL) { - task->next = task; // points to itself (single element circle) + task->next = task; g_task_list = task; } else { - // Insert after current tail (g_task_list is the "last" in circle) task->next = g_task_list->next; g_task_list->next = task; - g_task_list = task; // new tail + g_task_list = task; } serial_write("SCHED: created task '"); @@ -108,28 +107,71 @@ task_t* task_create(const char* name, void (*entry)(void)) { return task; } +// Find next READY task in the circular list, starting from g_current->next +static task_t* find_next_ready(void) { + if (g_current == NULL || g_task_list == NULL) return NULL; + + task_t* next = g_current->next; + task_t* start = next; + + do { + if (next->state == TASK_STATE_READY) { + return next; + } + next = next->next; + } while (next != start); + + return NULL; // no READY tasks +} + void yield(void) { if (g_current == NULL || g_task_list == NULL) return; task_t* cur = g_current; - task_t* next = cur->next; + task_t* next = find_next_ready(); - // Skip terminated tasks - while (next->state == TASK_STATE_TERMINATED && next != cur) { - next = next->next; - } - if (next->state == TASK_STATE_TERMINATED) return; // all terminated - - if (next == cur) return; // only one task, nothing to do + if (next == NULL || next == cur) return; if (cur->state != TASK_STATE_TERMINATED) cur->state = TASK_STATE_READY; next->state = TASK_STATE_RUNNING; + next->time_slice = TIME_SLICE_DEFAULT; g_current = next; context_switch(&cur->rsp, next->rsp); } +// Timer tick handler — called from PIT IRQ 0 +void scheduler_tick(void) { + if (g_current == NULL) return; + + // Decrement time slice + if (g_current->time_slice > 0) { + g_current->time_slice--; + } + + // If time slice expired, preempt + if (g_current->time_slice == 0) { + task_t* cur = g_current; + task_t* next = find_next_ready(); + + if (next == NULL || next == cur) { + // No other task ready, or only this task — reload time slice + cur->time_slice = TIME_SLICE_DEFAULT; + return; + } + + // Preempt + cur->state = TASK_STATE_READY; + cur->time_slice = TIME_SLICE_DEFAULT; + next->state = TASK_STATE_RUNNING; + next->time_slice = TIME_SLICE_DEFAULT; + g_current = next; + + context_switch(&cur->rsp, next->rsp); + } +} + void scheduler_run(void) { if (g_task_list == NULL) { serial_write("SCHED: no tasks to run\n"); @@ -137,7 +179,7 @@ void scheduler_run(void) { } // Find first READY task - task_t* start = g_task_list->next; // head of circle + task_t* start = g_task_list->next; task_t* t = start; do { if (t->state == TASK_STATE_READY) { @@ -153,14 +195,14 @@ void scheduler_run(void) { g_current = t; t->state = TASK_STATE_RUNNING; + t->time_slice = TIME_SLICE_DEFAULT; serial_write("SCHED: starting task '"); serial_write(t->name); serial_write("'\n"); - // First context switch — no old RSP to save (we're still in scheduler_run) - // Just switch to the task's stack directly. - // We need a dummy old_rsp to satisfy the API, but we never return here. + // First context switch — switch to the task's stack + // This will never return (until all tasks terminate) UINT64 dummy_rsp; context_switch(&dummy_rsp, t->rsp); -- 2.43.0 From 2464bcc765eca421e934c99267b40e9ff9d1b419 Mon Sep 17 00:00:00 2001 From: pyao12 Date: Sun, 31 May 2026 19:15:31 +0800 Subject: [PATCH 4/4] [fix] preemptive scheduler not interleaving tasks --- kernel/main.cpp | 12 ++++++++---- kernel/scheduler/scheduler.cpp | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/kernel/main.cpp b/kernel/main.cpp index c0849cd..bbea2fb 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -55,6 +55,10 @@ static void irq_handler(trap_frame* frame) { UINT8 vector = (UINT8)frame->vector; UINT8 irq = vector - PIC_IRQ_BASE; + // Send EOI BEFORE handling, so PIC can deliver new interrupts + // immediately after a context switch inside the handler. + pic_send_eoi(irq); + switch (irq) { case 0: // PIT timer pit_irq_handler(); @@ -62,9 +66,6 @@ static void irq_handler(trap_frame* frame) { default: break; } - - // Send EOI to PIC - pic_send_eoi(irq); } extern "C" void kernel_main() { @@ -156,10 +157,11 @@ extern "C" void kernel_main() { // Task A: prints messages — preemption handles time slicing task_create("taskA", []() { - for (int i = 0; i < 3; i++) { + for (int i = 0; i < 10; i++) { serial_write("[taskA] running iteration "); serial_write_hex(i); serial_write("\n"); + for (volatile int j = 0; j < 50000000; j++) {} } serial_write("[taskA] done\n"); }); @@ -170,6 +172,7 @@ extern "C" void kernel_main() { serial_write("[taskB] hello from taskB #"); serial_write_hex(i); serial_write("\n"); + for (volatile int j = 0; j < 50000000; j++) {} } serial_write("[taskB] done\n"); }); @@ -177,6 +180,7 @@ extern "C" void kernel_main() { // Task C: short task task_create("taskC", []() { serial_write("[taskC] quick task\n"); + for (volatile int j = 0; j < 50000000; j++) {} serial_write("[taskC] finished\n"); }); diff --git a/kernel/scheduler/scheduler.cpp b/kernel/scheduler/scheduler.cpp index 1eed468..09a8b64 100644 --- a/kernel/scheduler/scheduler.cpp +++ b/kernel/scheduler/scheduler.cpp @@ -18,6 +18,7 @@ static task_t* g_task_list = NULL; // circular linked list head static void (*g_task_entries[TASK_MAX])(void); extern "C" void task_entry_trampoline() { + ASM("sti"); task_t* cur = scheduler_current(); if (cur && g_task_entries[cur->id]) { g_task_entries[cur->id](); -- 2.43.0