From e7a0d2e6b14643e9190337e98c04060be369d6c2 Mon Sep 17 00:00:00 2001 From: RarDog Date: Wed, 2 Sep 2026 16:54:19 +0300 Subject: [PATCH] feat(sched): implement preemptive time-slicing multitasking via PIT 8254 timer ISR (Vector 32) --- kernel/asm/timer_isr.S | 49 ++++++++++++ kernel/src/arch/idt.rs | 19 +++++ kernel/src/drivers/mod.rs | 1 + kernel/src/drivers/timer.rs | 74 ++++++++++++++++++ kernel/src/main.rs | 28 +++---- kernel/src/sched/mod.rs | 151 ++++++++++++++++++++++-------------- 6 files changed, 248 insertions(+), 74 deletions(-) create mode 100644 kernel/asm/timer_isr.S create mode 100644 kernel/src/drivers/timer.rs diff --git a/kernel/asm/timer_isr.S b/kernel/asm/timer_isr.S new file mode 100644 index 0000000..ab6c929 --- /dev/null +++ b/kernel/asm/timer_isr.S @@ -0,0 +1,49 @@ +/* opencoreC - Timer Preemptive Interrupt Trampoline (Vector 32 / 0x20) */ +.global timer_isr_entry +.extern timer_interrupt_handler + +.section .text +timer_isr_entry: + /* 1. Save all general-purpose registers to form a complete interrupt context frame */ + push rax + push rbx + push rcx + push rdx + push rsi + push rdi + push rbp + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + + /* 2. Pass current saved stack pointer in RDI */ + mov rdi, rsp + call timer_interrupt_handler + + /* 3. Switch stack pointer to returned value (RAX contains next thread's RSP) */ + mov rsp, rax + + /* 4. Restore all general-purpose registers */ + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rbp + pop rdi + pop rsi + pop rdx + pop rcx + pop rbx + pop rax + + /* 5. Return from interrupt (IRETQ restores RIP, CS, RFLAGS, RSP, SS) */ + iretq diff --git a/kernel/src/arch/idt.rs b/kernel/src/arch/idt.rs index ad4405c..48804a1 100644 --- a/kernel/src/arch/idt.rs +++ b/kernel/src/arch/idt.rs @@ -1,6 +1,8 @@ //! Interrupt Descriptor Table (IDT) and Exception Handling use crate::kprintln; +use crate::sched::SCHEDULER; +use crate::drivers::timer::send_eoi; use core::mem::size_of; #[repr(C, packed)] @@ -49,6 +51,7 @@ static mut IDT: [IdtEntry; 256] = [IdtEntry::missing(); 256]; extern "C" { fn default_isr_stub(); + fn timer_isr_entry(); } // Global assembly ISR stub for default exception handling @@ -85,6 +88,10 @@ pub unsafe fn init() { (*idt_ptr.add(i)).set_handler(stub_addr, 0, 0); } + // Set Timer Interrupt (Vector 32 / 0x20) + let timer_addr = timer_isr_entry as *const () as usize as u64; + (*idt_ptr.add(32)).set_handler(timer_addr, 0, 0); + let descriptor = IdtDescriptor { limit: (size_of::<[IdtEntry; 256]>() - 1) as u16, base: idt_ptr as u64, @@ -97,3 +104,15 @@ pub unsafe fn init() { pub extern "C" fn raw_exception_handler(stack_ptr: *const u64) { kprintln!("[CPU EXCEPTION] Trap triggered at stack frame: {:p}", stack_ptr); } + +#[no_mangle] +pub extern "C" fn timer_interrupt_handler(saved_rsp: u64) -> u64 { + unsafe { + // Send End of Interrupt to controller + send_eoi(); + + // Perform preemptive scheduling quantum check + let sched = &mut *core::ptr::addr_of_mut!(SCHEDULER); + sched.preempt(saved_rsp) + } +} diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs index b1fc0cf..30fc88c 100644 --- a/kernel/src/drivers/mod.rs +++ b/kernel/src/drivers/mod.rs @@ -1 +1,2 @@ pub mod serial; +pub mod timer; diff --git a/kernel/src/drivers/timer.rs b/kernel/src/drivers/timer.rs new file mode 100644 index 0000000..302aad2 --- /dev/null +++ b/kernel/src/drivers/timer.rs @@ -0,0 +1,74 @@ +//! System Timer & PIC (Programmable Interval Timer 8254 on Vector 32) + +use crate::kprintln; + +const PIC1_COMMAND: u16 = 0x20; +const PIC1_DATA: u16 = 0x21; +const PIC2_COMMAND: u16 = 0xA0; +const PIC2_DATA: u16 = 0xA1; + +const PIT_CHANNEL0_DATA: u16 = 0x40; +const PIT_COMMAND_MODE: u16 = 0x43; + +const PIC_EOI: u8 = 0x20; + +#[inline] +unsafe fn outb(port: u16, val: u8) { + core::arch::asm!("out dx, al", in("dx") port, in("al") val, options(nomem, nostack, preserves_flags)); +} + +#[inline] +unsafe fn io_wait() { + outb(0x80, 0); +} + +/// Send End of Interrupt to PIC +pub unsafe fn send_eoi() { + outb(PIC1_COMMAND, PIC_EOI); +} + +/// Remap PIC offsets to 32 (0x20) and 40 (0x28) +unsafe fn remap_pic() { + // ICW1: Start initialization in cascade mode + outb(PIC1_COMMAND, 0x11); + io_wait(); + outb(PIC2_COMMAND, 0x11); + io_wait(); + + // ICW2: Vector offsets (Master = 32, Slave = 40) + outb(PIC1_DATA, 0x20); + io_wait(); + outb(PIC2_DATA, 0x28); + io_wait(); + + // ICW3: Cascade wiring + outb(PIC1_DATA, 0x04); + io_wait(); + outb(PIC2_DATA, 0x02); + io_wait(); + + // ICW4: 8086 mode + outb(PIC1_DATA, 0x01); + io_wait(); + outb(PIC2_DATA, 0x01); + io_wait(); + + // Mask all IRQs except IRQ 0 (Timer) + outb(PIC1_DATA, 0xFE); // Enable only IRQ0 (bit 0 = 0) + outb(PIC2_DATA, 0xFF); // Disable all on Slave +} + +/// Initialize PIT to tick at target Hz (e.g. 100 Hz = 10 ms quantum) +pub unsafe fn init(target_hz: u32) { + remap_pic(); + + let divisor = (1193182 / target_hz) as u16; + + // Channel 0, lobyte/hibyte, Rate Generator (Mode 2) + outb(PIT_COMMAND_MODE, 0x36); + outb(PIT_CHANNEL0_DATA, (divisor & 0xFF) as u8); + outb(PIT_CHANNEL0_DATA, ((divisor >> 8) & 0xFF) as u8); + + kprintln!("[TIMER] PIT 8254 initialized at {} Hz ({} ms time slice quantum).", + target_hz, 1000 / target_hz); +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 0323d37..ed98101 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -17,6 +17,7 @@ use sched::SCHEDULER; core::arch::global_asm!(include_str!("../asm/context.S")); core::arch::global_asm!(include_str!("../asm/syscall_entry.S")); core::arch::global_asm!(include_str!("../asm/ring3_enter.S")); +core::arch::global_asm!(include_str!("../asm/timer_isr.S")); #[no_mangle] pub extern "C" fn _start() -> ! { @@ -60,21 +61,18 @@ pub extern "C" fn _start() -> ! { mm::init(memmap_resp); } - // 6. Initialize IPC & Capability System + // 6. Initialize Preemptive Hardware Timer (PIT 8254 @ 100 Hz) + kprintln!("[BOOT] Configuring PIT 8254 timer for Preemptive Multi-Tasking..."); + unsafe { + drivers::timer::init(100); + } + + // 7. Initialize IPC & Capability System ipc::init(); - // 7. Initialize Scheduler + // 8. Initialize Scheduler sched::init(); - // 8. Self-Test Fast-Path IPC Ping - kprintln!("[TEST] Executing Kernel Fast-Path IPC Self-Test..."); - let (status, resp_op, val) = ipc::fastpath::handle_fastpath_call( - ipc::fastpath::CAP_KERNEL_CONTROL, - ipc::fastpath::IPC_OP_PING, - 42, 0, 0, 0 - ); - kprintln!("[TEST] IPC Fast-Path Ping Result: status={}, opcode={:#x}, val={}", status, resp_op, val); - // 9. Inspect and Load Initial Userspace Processes unsafe { let mod_resp = MODULE_REQUEST.response; @@ -102,7 +100,7 @@ pub extern "C" fn _start() -> ! { proc.pml4_paddr, proc.kernel_stack_top, ); - kprintln!("[BOOT] Registered Thread '{}' with TID {:?}", name, tid); + kprintln!("[BOOT] Registered Preemptible Thread '{}' with TID {:?}", name, tid); } Err(err) => { kprintln!("[ERROR] Failed to load module [{}]: {}", i, err); @@ -110,8 +108,10 @@ pub extern "C" fn _start() -> ! { } } - kprintln!("[BOOT] Starting Multi-Tasking & IPC Rendezvous..."); + kprintln!("[BOOT] Enabling Interrupts & Starting Preemptive Scheduler..."); kprintln!("-------------------------------------------------------"); + + // Pick first ready thread and switch to it with interrupts enabled (*sched_ptr).schedule(); } else { kprintln!("[BOOT] No userspace modules detected. Staying in Ring 0 idle loop."); @@ -122,7 +122,7 @@ pub extern "C" fn _start() -> ! { loop { unsafe { - core::arch::asm!("hlt"); + core::arch::asm!("sti; hlt"); } } } diff --git a/kernel/src/sched/mod.rs b/kernel/src/sched/mod.rs index 0fd4da3..0db78f9 100644 --- a/kernel/src/sched/mod.rs +++ b/kernel/src/sched/mod.rs @@ -11,27 +11,34 @@ extern "C" { fn switch_to(prev_rsp: *mut u64, next_rsp: u64); } +const DEFAULT_QUANTUM_TICKS: u32 = 5; // 5 ticks @ 100Hz = 50ms time slice + pub struct Scheduler { pub threads: [Thread; MAX_THREADS], pub current_tid: usize, pub next_tid: u64, + pub quantum_remaining: u32, + pub ticks: u64, } pub static mut SCHEDULER: Scheduler = Scheduler { threads: [const { Thread::empty() }; MAX_THREADS], current_tid: 0, next_tid: 1, + quantum_remaining: DEFAULT_QUANTUM_TICKS, + ticks: 0, }; impl Scheduler { pub fn init(&mut self) { - // Slot 0 is reserved for Kernel Idle/Bootstrap thread self.threads[0].id = 0; self.threads[0].name = "kernel_idle"; self.threads[0].state = ThreadState::Running; self.current_tid = 0; self.next_tid = 1; - kprintln!("[SCHED] Scheduler initialized (Multi-Thread TCB table ready)."); + self.quantum_remaining = DEFAULT_QUANTUM_TICKS; + self.ticks = 0; + kprintln!("[SCHED] Preemptive Multi-Tasking Scheduler initialized (Quantum: {} ticks).", DEFAULT_QUANTUM_TICKS); } pub fn create_user_thread( @@ -55,23 +62,33 @@ impl Scheduler { self.threads[i].user_rsp = user_rsp; self.threads[i].user_rip = entry_rip; - // Setup initial kernel stack for IRETQ return + // Build initial interrupt frame for timer_isr_entry / iretq return: + // [kstack - 5]: SS (0x1B) + // [kstack - 4]: RSP (user_rsp) + // [kstack - 3]: RFLAGS (0x202) + // [kstack - 2]: CS (0x23) + // [kstack - 1]: RIP (entry_rip) + // [kstack - 20..-6]: 15 GPRs (rax, rbx, rcx, rdx, rsi, rdi, rbp, r8..r15) unsafe { let kstack = kernel_stack_top as *mut u64; - let stack_ptr = kstack.sub(16); - - *stack_ptr.add(0) = 0; // r15 - *stack_ptr.add(1) = 0; // r14 - *stack_ptr.add(2) = 0; // r13 - *stack_ptr.add(3) = 0; // r12 - *stack_ptr.add(4) = 0; // rbx - *stack_ptr.add(5) = 0; // rbp - *stack_ptr.add(6) = initial_user_trampoline as *const () as usize as u64; // RIP for switch_to + let frame_ptr = kstack.sub(20); - self.threads[i].context.rsp = stack_ptr as u64; + // GPRs (all 0) + for j in 0..15 { + *frame_ptr.add(j) = 0; + } + + // IRETQ Frame + *frame_ptr.add(15) = entry_rip; // RIP + *frame_ptr.add(16) = 0x23; // CS (User Code) + *frame_ptr.add(17) = 0x0202; // RFLAGS (IF enabled) + *frame_ptr.add(18) = user_rsp; // RSP (User Stack) + *frame_ptr.add(19) = 0x1B; // SS (User Data) + + self.threads[i].context.rsp = frame_ptr as u64; } - kprintln!("[SCHED] Created User Thread [TID {}] '{}' (RIP: {:#x}, RSP: {:#x})", + kprintln!("[SCHED] Created Preemptible Thread [TID {}] '{}' (RIP: {:#x}, RSP: {:#x})", tid, name, entry_rip, user_rsp); return Some(tid); } @@ -109,14 +126,72 @@ impl Scheduler { if self.threads[self.current_tid].state == ThreadState::Running { self.threads[self.current_tid].state = ThreadState::Ready; } + self.quantum_remaining = DEFAULT_QUANTUM_TICKS; self.schedule(); } + /// Preemptive tick called directly from timer ISR handler + pub fn preempt(&mut self, saved_rsp: u64) -> u64 { + self.ticks += 1; + + if self.quantum_remaining > 0 { + self.quantum_remaining -= 1; + if self.quantum_remaining > 0 { + return saved_rsp; // Keep running current thread + } + } + + // Time slice quantum expired -> Preempt current thread! + self.quantum_remaining = DEFAULT_QUANTUM_TICKS; + + let prev_idx = self.current_tid; + self.threads[prev_idx].context.rsp = saved_rsp; + if self.threads[prev_idx].state == ThreadState::Running { + self.threads[prev_idx].state = ThreadState::Ready; + } + + // Find next Ready thread (Round-Robin) + let mut next_idx = (prev_idx + 1) % MAX_THREADS; + let mut found = false; + for _ in 0..MAX_THREADS { + if self.threads[next_idx].state == ThreadState::Ready { + found = true; + break; + } + next_idx = (next_idx + 1) % MAX_THREADS; + } + + if !found { + // Restore previous thread if it's still runnable + if self.threads[prev_idx].state == ThreadState::Ready { + self.threads[prev_idx].state = ThreadState::Running; + } + return saved_rsp; + } + + self.current_tid = next_idx; + self.threads[next_idx].state = ThreadState::Running; + + let next_pml4 = self.threads[next_idx].pml4_paddr; + let next_kstack = self.threads[next_idx].kernel_stack_top; + let next_rsp = self.threads[next_idx].context.rsp; + + unsafe { + if next_pml4 != 0 { + (*core::ptr::addr_of_mut!(VMM)).load_cr3(next_pml4); + } + if next_kstack != 0 { + set_kernel_stack(next_kstack); + } + } + + next_rsp + } + pub fn schedule(&mut self) { let prev_idx = self.current_tid; let mut next_idx = (prev_idx + 1) % MAX_THREADS; - // Find next Ready thread (Round-Robin) let mut found = false; for _ in 0..MAX_THREADS { if self.threads[next_idx].state == ThreadState::Ready { @@ -130,7 +205,7 @@ impl Scheduler { if self.threads[prev_idx].state == ThreadState::Running { return; } - next_idx = 0; // idle thread + next_idx = 0; } if prev_idx == next_idx && self.threads[prev_idx].state == ThreadState::Running { @@ -166,47 +241,3 @@ pub fn init() { (*core::ptr::addr_of_mut!(SCHEDULER)).init(); } } - -#[no_mangle] -pub extern "C" fn initial_user_trampoline() { - let (entry_rip, user_rsp) = unsafe { - let sched = &mut *core::ptr::addr_of_mut!(SCHEDULER); - let curr = sched.get_current_mut(); - (curr.user_rip, curr.user_rsp) - }; - - unsafe { - // IRETQ to Ring 3 - core::arch::asm!( - "mov ax, 0x1B", - "mov ds, ax", - "mov es, ax", - "mov fs, ax", - "mov gs, ax", - "push 0x1B", // User SS - "push {user_rsp}", // User RSP - "push 0x0202", // RFLAGS (IF enabled) - "push 0x23", // User CS - "push {entry_rip}", // User RIP - "xor rax, rax", - "xor rbx, rbx", - "xor rcx, rcx", - "xor rdx, rdx", - "xor rsi, rsi", - "xor rdi, rdi", - "xor rbp, rbp", - "xor r8, r8", - "xor r9, r9", - "xor r10, r10", - "xor r11, r11", - "xor r12, r12", - "xor r13, r13", - "xor r14, r14", - "xor r15, r15", - "iretq", - user_rsp = in(reg) user_rsp, - entry_rip = in(reg) entry_rip, - options(noreturn) - ); - } -}