Skip to content

Commit e1a1d3d

Browse files
committed
fix: implementation and optimization of context switching and scheduler
1 parent eba22e7 commit e1a1d3d

File tree

8 files changed

+1062
-0
lines changed

8 files changed

+1062
-0
lines changed

kernel/src/interrupts/handler.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ pub extern "x86-interrupt" fn timer_interrupt_handler(stack_frame: InterruptStac
164164
registry.handle(context);
165165
}
166166

167+
// Call scheduler timer tick for preemptive multitasking
168+
crate::process::scheduler::timer_tick();
169+
167170
apic::end_of_interrupt();
168171
}
169172

kernel/src/main.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ pub extern "C" fn kernel_main() -> ! {
7979
proka_kernel::drivers::init_devices(); // Initialize devices
8080
proka_kernel::libs::time::init(); // Init time system
8181
proka_kernel::libs::initrd::load_initrd(); // Load initrd
82+
83+
// Initialize scheduler
84+
proka_kernel::process::scheduler::init();
85+
8286
x86_64::instructions::interrupts::enable(); // Enable interrupts
8387

8488
#[allow(unused_parens)]
@@ -115,10 +119,15 @@ pub extern "C" fn kernel_main() -> ! {
115119
proka_kernel::libs::pci::print_all_pci_devices();
116120
proka_kernel::drivers::usb::init();
117121

122+
// Run scheduler tests before shell
123+
proka_kernel::process::scheduler_test::run_tests();
124+
118125
let shell = proka_kernel::libs::shell::Shell::new();
119126
shell.run("keyboard");
120127

128+
// Enter idle loop - scheduler will switch to other threads
121129
loop {
130+
x86_64::instructions::interrupts::enable();
122131
x86_64::instructions::hlt();
123132
}
124133
}

kernel/src/memory/frame/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,19 @@ unsafe impl FrameAllocator<Size4KiB> for LockedFrameAllocator {
200200
}
201201
}
202202

203+
impl LockedFrameAllocator {
204+
/// Allocate multiple contiguous frames
205+
pub fn alloc_frames(&self, count: usize) -> PhysFrame {
206+
self.allocate_contiguous(count)
207+
.expect("Failed to allocate frames")
208+
}
209+
210+
/// Get the start address of a frame
211+
pub fn start_address(&self, frame: PhysFrame) -> usize {
212+
frame.start_address().as_u64() as usize
213+
}
214+
}
215+
203216
/// Format byte count to human-readable string
204217
pub fn format_bytes(bytes: usize) -> alloc::string::String {
205218
const UNITS: &[&str] = &["B", "KiB", "MiB", "GiB", "TiB"];
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//! Context switching for Proka Kernel
2+
//!
3+
//! Based on Redox OS context switching implementation.
4+
//! Uses ret instruction to jump to new thread via stack.
5+
6+
use super::thread::Context;
7+
use core::mem::offset_of;
8+
9+
/// Initialize a new thread's context for its first run
10+
///
11+
/// Sets up the stack so that when context switch "returns",
12+
/// it jumps to the entry point
13+
pub fn init_context(
14+
ctx: &mut Context,
15+
entry_point: usize,
16+
stack_top: usize,
17+
is_kernel_thread: bool,
18+
) {
19+
// Stack grows down.
20+
// System V ABI: stack must be 16-byte aligned before call.
21+
// Call pushes 8-byte return address.
22+
// So at function entry, (rsp + 8) is 16-byte aligned, i.e., rsp % 16 == 8.
23+
24+
// We use stack_top - 16 for initial RSP.
25+
// We write entry_point at stack_top - 16.
26+
// When ret pops it, RSP becomes stack_top - 8.
27+
// Since stack_top is 4096-aligned, (stack_top - 8) % 16 == 8.
28+
let stack_ptr = (stack_top - 16) as *mut u64;
29+
30+
// SAFETY: stack_ptr is valid (allocated by allocate_kernel_stack)
31+
unsafe {
32+
core::ptr::write(stack_ptr, entry_point as u64);
33+
}
34+
35+
// Set up context
36+
ctx.rip = 0; // Not used - we use stack-based return
37+
ctx.rsp = (stack_top - 16) as u64; // Stack pointer points to entry_point
38+
ctx.rflags = 0x202; // IF flag set (interrupts enabled)
39+
40+
if is_kernel_thread {
41+
ctx.cs = 0x08; // Kernel code segment
42+
ctx.ss = 0x10; // Kernel data segment
43+
} else {
44+
ctx.cs = 0x1B | 3;
45+
ctx.ss = 0x23 | 3;
46+
}
47+
48+
// All registers start at zero
49+
ctx.rax = 0;
50+
ctx.rbx = 0;
51+
ctx.rcx = 0;
52+
ctx.rdx = 0;
53+
ctx.rsi = 0;
54+
ctx.rdi = 0;
55+
ctx.rbp = 0;
56+
ctx.r8 = 0;
57+
ctx.r9 = 0;
58+
ctx.r10 = 0;
59+
ctx.r11 = 0;
60+
ctx.r12 = 0;
61+
ctx.r13 = 0;
62+
ctx.r14 = 0;
63+
ctx.r15 = 0;
64+
}
65+
66+
/// Context switch - save old context and restore new context
67+
///
68+
/// # Safety
69+
/// Must be called with interrupts disabled.
70+
///
71+
/// This function switches to the new context by:
72+
/// 1. Saving callee-saved registers to old context
73+
/// 2. Loading callee-saved registers from new context
74+
/// 3. Switching stack pointer
75+
/// 4. Returning (which pops the new RIP from the new stack)
76+
#[unsafe(naked)]
77+
pub unsafe extern "C" fn switch_context(_old_ctx: *mut Context, _new_ctx: *const Context) {
78+
use Context as Cx;
79+
80+
core::arch::naked_asm!(
81+
// System V AMD64 ABI:
82+
// - Parameters in rdi (old_ctx), rsi (new_ctx)
83+
// - Callee-saved: rbx, r12-r15, rbp, rsp
84+
85+
// Save callee-saved registers to old context
86+
"mov [rdi + {off_rbx}], rbx",
87+
"mov [rdi + {off_r12}], r12",
88+
"mov [rdi + {off_r13}], r13",
89+
"mov [rdi + {off_r14}], r14",
90+
"mov [rdi + {off_r15}], r15",
91+
"mov [rdi + {off_rbp}], rbp",
92+
93+
// Save RSP (current stack pointer)
94+
"mov [rdi + {off_rsp}], rsp",
95+
96+
// Save RFLAGS
97+
"pushfq",
98+
"pop QWORD PTR [rdi + {off_rflags}]",
99+
100+
// Load callee-saved registers from new context
101+
"mov rbx, [rsi + {off_rbx}]",
102+
"mov r12, [rsi + {off_r12}]",
103+
"mov r13, [rsi + {off_r13}]",
104+
"mov r14, [rsi + {off_r14}]",
105+
"mov r15, [rsi + {off_r15}]",
106+
"mov rbp, [rsi + {off_rbp}]",
107+
108+
// Switch stack pointer FIRST so that if interrupts are enabled by popfq,
109+
// they use the new thread's stack.
110+
"mov rsp, [rsi + {off_rsp}]",
111+
112+
// Load RFLAGS
113+
"push QWORD PTR [rsi + {off_rflags}]",
114+
"popfq",
115+
116+
// Return - this pops the return address from the new stack
117+
"ret",
118+
119+
120+
off_rbx = const(offset_of!(Cx, rbx)),
121+
off_r12 = const(offset_of!(Cx, r12)),
122+
off_r13 = const(offset_of!(Cx, r13)),
123+
off_r14 = const(offset_of!(Cx, r14)),
124+
off_r15 = const(offset_of!(Cx, r15)),
125+
off_rbp = const(offset_of!(Cx, rbp)),
126+
off_rsp = const(offset_of!(Cx, rsp)),
127+
off_rflags = const(offset_of!(Cx, rflags)),
128+
);
129+
}
130+
131+
/// First context switch from boot to the first thread
132+
///
133+
/// # Safety
134+
/// This function does not return. It switches to the new context and
135+
/// never comes back (until that thread yields).
136+
#[unsafe(naked)]
137+
pub unsafe extern "C" fn first_context_switch(_new_ctx: *const Context) -> ! {
138+
use Context as Cx;
139+
140+
core::arch::naked_asm!(
141+
// Load callee-saved registers from new context
142+
"mov rbx, [rdi + {off_rbx}]",
143+
"mov r12, [rdi + {off_r12}]",
144+
"mov r13, [rdi + {off_r13}]",
145+
"mov r14, [rdi + {off_r14}]",
146+
"mov r15, [rdi + {off_r15}]",
147+
"mov rbp, [rdi + {off_rbp}]",
148+
149+
// Switch stack pointer
150+
"mov rsp, [rdi + {off_rsp}]",
151+
152+
// Load RFLAGS
153+
"push QWORD PTR [rdi + {off_rflags}]",
154+
"popfq",
155+
156+
// Return to start the new thread
157+
"ret",
158+
159+
160+
off_rbx = const(offset_of!(Cx, rbx)),
161+
off_r12 = const(offset_of!(Cx, r12)),
162+
off_r13 = const(offset_of!(Cx, r13)),
163+
off_r14 = const(offset_of!(Cx, r14)),
164+
off_r15 = const(offset_of!(Cx, r15)),
165+
off_rbp = const(offset_of!(Cx, rbp)),
166+
off_rsp = const(offset_of!(Cx, rsp)),
167+
off_rflags = const(offset_of!(Cx, rflags)),
168+
);
169+
}

kernel/src/process/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1+
pub mod context_switch;
2+
pub mod scheduler;
3+
pub mod scheduler_test;
14
pub mod task;
5+
pub mod thread;

0 commit comments

Comments
 (0)