feat(sched): implement preemptive time-slicing multitasking via PIT 8254 timer ISR (Vector 32)

This commit is contained in:
RarDog
2026-09-02 16:54:19 +03:00
parent 448b0e710e
commit e7a0d2e6b1
6 changed files with 248 additions and 74 deletions
+49
View File
@@ -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
+19
View File
@@ -1,6 +1,8 @@
//! Interrupt Descriptor Table (IDT) and Exception Handling //! Interrupt Descriptor Table (IDT) and Exception Handling
use crate::kprintln; use crate::kprintln;
use crate::sched::SCHEDULER;
use crate::drivers::timer::send_eoi;
use core::mem::size_of; use core::mem::size_of;
#[repr(C, packed)] #[repr(C, packed)]
@@ -49,6 +51,7 @@ static mut IDT: [IdtEntry; 256] = [IdtEntry::missing(); 256];
extern "C" { extern "C" {
fn default_isr_stub(); fn default_isr_stub();
fn timer_isr_entry();
} }
// Global assembly ISR stub for default exception handling // 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); (*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 { let descriptor = IdtDescriptor {
limit: (size_of::<[IdtEntry; 256]>() - 1) as u16, limit: (size_of::<[IdtEntry; 256]>() - 1) as u16,
base: idt_ptr as u64, base: idt_ptr as u64,
@@ -97,3 +104,15 @@ pub unsafe fn init() {
pub extern "C" fn raw_exception_handler(stack_ptr: *const u64) { pub extern "C" fn raw_exception_handler(stack_ptr: *const u64) {
kprintln!("[CPU EXCEPTION] Trap triggered at stack frame: {:p}", stack_ptr); 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)
}
}
+1
View File
@@ -1 +1,2 @@
pub mod serial; pub mod serial;
pub mod timer;
+74
View File
@@ -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);
}
+14 -14
View File
@@ -17,6 +17,7 @@ use sched::SCHEDULER;
core::arch::global_asm!(include_str!("../asm/context.S")); 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/syscall_entry.S"));
core::arch::global_asm!(include_str!("../asm/ring3_enter.S")); core::arch::global_asm!(include_str!("../asm/ring3_enter.S"));
core::arch::global_asm!(include_str!("../asm/timer_isr.S"));
#[no_mangle] #[no_mangle]
pub extern "C" fn _start() -> ! { pub extern "C" fn _start() -> ! {
@@ -60,21 +61,18 @@ pub extern "C" fn _start() -> ! {
mm::init(memmap_resp); 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(); ipc::init();
// 7. Initialize Scheduler // 8. Initialize Scheduler
sched::init(); 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 // 9. Inspect and Load Initial Userspace Processes
unsafe { unsafe {
let mod_resp = MODULE_REQUEST.response; let mod_resp = MODULE_REQUEST.response;
@@ -102,7 +100,7 @@ pub extern "C" fn _start() -> ! {
proc.pml4_paddr, proc.pml4_paddr,
proc.kernel_stack_top, proc.kernel_stack_top,
); );
kprintln!("[BOOT] Registered Thread '{}' with TID {:?}", name, tid); kprintln!("[BOOT] Registered Preemptible Thread '{}' with TID {:?}", name, tid);
} }
Err(err) => { Err(err) => {
kprintln!("[ERROR] Failed to load module [{}]: {}", i, 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!("-------------------------------------------------------"); kprintln!("-------------------------------------------------------");
// Pick first ready thread and switch to it with interrupts enabled
(*sched_ptr).schedule(); (*sched_ptr).schedule();
} else { } else {
kprintln!("[BOOT] No userspace modules detected. Staying in Ring 0 idle loop."); kprintln!("[BOOT] No userspace modules detected. Staying in Ring 0 idle loop.");
@@ -122,7 +122,7 @@ pub extern "C" fn _start() -> ! {
loop { loop {
unsafe { unsafe {
core::arch::asm!("hlt"); core::arch::asm!("sti; hlt");
} }
} }
} }
+91 -60
View File
@@ -11,27 +11,34 @@ extern "C" {
fn switch_to(prev_rsp: *mut u64, next_rsp: u64); 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 struct Scheduler {
pub threads: [Thread; MAX_THREADS], pub threads: [Thread; MAX_THREADS],
pub current_tid: usize, pub current_tid: usize,
pub next_tid: u64, pub next_tid: u64,
pub quantum_remaining: u32,
pub ticks: u64,
} }
pub static mut SCHEDULER: Scheduler = Scheduler { pub static mut SCHEDULER: Scheduler = Scheduler {
threads: [const { Thread::empty() }; MAX_THREADS], threads: [const { Thread::empty() }; MAX_THREADS],
current_tid: 0, current_tid: 0,
next_tid: 1, next_tid: 1,
quantum_remaining: DEFAULT_QUANTUM_TICKS,
ticks: 0,
}; };
impl Scheduler { impl Scheduler {
pub fn init(&mut self) { pub fn init(&mut self) {
// Slot 0 is reserved for Kernel Idle/Bootstrap thread
self.threads[0].id = 0; self.threads[0].id = 0;
self.threads[0].name = "kernel_idle"; self.threads[0].name = "kernel_idle";
self.threads[0].state = ThreadState::Running; self.threads[0].state = ThreadState::Running;
self.current_tid = 0; self.current_tid = 0;
self.next_tid = 1; 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( pub fn create_user_thread(
@@ -55,23 +62,33 @@ impl Scheduler {
self.threads[i].user_rsp = user_rsp; self.threads[i].user_rsp = user_rsp;
self.threads[i].user_rip = entry_rip; 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 { unsafe {
let kstack = kernel_stack_top as *mut u64; let kstack = kernel_stack_top as *mut u64;
let stack_ptr = kstack.sub(16); let frame_ptr = kstack.sub(20);
*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
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); tid, name, entry_rip, user_rsp);
return Some(tid); return Some(tid);
} }
@@ -109,14 +126,72 @@ impl Scheduler {
if self.threads[self.current_tid].state == ThreadState::Running { if self.threads[self.current_tid].state == ThreadState::Running {
self.threads[self.current_tid].state = ThreadState::Ready; self.threads[self.current_tid].state = ThreadState::Ready;
} }
self.quantum_remaining = DEFAULT_QUANTUM_TICKS;
self.schedule(); 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) { pub fn schedule(&mut self) {
let prev_idx = self.current_tid; let prev_idx = self.current_tid;
let mut next_idx = (prev_idx + 1) % MAX_THREADS; let mut next_idx = (prev_idx + 1) % MAX_THREADS;
// Find next Ready thread (Round-Robin)
let mut found = false; let mut found = false;
for _ in 0..MAX_THREADS { for _ in 0..MAX_THREADS {
if self.threads[next_idx].state == ThreadState::Ready { if self.threads[next_idx].state == ThreadState::Ready {
@@ -130,7 +205,7 @@ impl Scheduler {
if self.threads[prev_idx].state == ThreadState::Running { if self.threads[prev_idx].state == ThreadState::Running {
return; return;
} }
next_idx = 0; // idle thread next_idx = 0;
} }
if prev_idx == next_idx && self.threads[prev_idx].state == ThreadState::Running { 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(); (*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)
);
}
}