From 8f05ea44f69cb796e7b1f581ba1b8045c98d615b Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 1 Aug 2026 17:38:57 +0200 Subject: rsnix: preemptive kernel tasks (ADR 0003) — sched, IST fault stacks, 176-byte frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sched: 16-slot static table, shell=task 0, idle=task 1, spawn with guard page + 8 KiB stack, RR on the 100 Hz tick, int 0x60 yield, sleep_ms, lazy reap, jobs/bg/sleep/kill shell commands - gdt: own GDT mirroring UEFI selectors + TSS with IST1/IST2 (guard-page overflows die gracefully instead of triple-faulting) - interrupts: isr_dispatch returns next frame in rax; IntFrame grows rsp/ss fields — QEMU's 64-bit iretq always pops all five slots from a 176-byte frame, so synthetic task frames must carry rsp=top+ss (a 160-byte frame made the first iretq pop rsp/ss from the unmapped page beyond the frame) - panic handler: task panics abort the task, shell keeps running - main: sched::init moved after page allocator + adopted tables (the idle's stack+guard need both; it was silently not spawning) --- rsnix/src/serial.rs | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) (limited to 'rsnix/src/serial.rs') diff --git a/rsnix/src/serial.rs b/rsnix/src/serial.rs index 484a114..2267225 100644 --- a/rsnix/src/serial.rs +++ b/rsnix/src/serial.rs @@ -60,8 +60,17 @@ pub fn try_get_byte() -> Option { } } -/// Write a string to serial. +/// Write a string to serial, holding the scheduler lock for the whole +/// write so a line can't be interleaved with another task's output. pub fn write_str(s: &str) { + crate::sched::lock(); + write_raw(s); + crate::sched::unlock(); +} + +/// Raw serial write — no scheduler lock. Callers must hold the lock +/// (klog::emit does) or be in a context where preemption is impossible. +pub fn write_raw(s: &str) { for &b in s.as_bytes() { if b == b'\n' { put_byte(b'\r'); @@ -70,6 +79,16 @@ pub fn write_str(s: &str) { } } +/// One scheduler-locked call: the line plus its CRLF, atomic with +/// respect to context switches (used by the shell's term_println, where +/// the line and its newline must not be split by a tick). +pub fn write_line(s: &str) { + crate::sched::lock(); + write_raw(s); + write_raw("\r\n"); + crate::sched::unlock(); +} + // --------------------------------------------------------------------------- // fmt::Write implementation // --------------------------------------------------------------------------- @@ -96,17 +115,17 @@ macro_rules! serial_print { /// here, so the ring captures the whole boot sequence for free. /// User-facing shell output must NOT use this macro (shell output would /// pollute the boot log); it writes to the serial port directly. +/// +/// The whole line is formatted, written, and logged under one scheduler +/// lock (klog::emit), so lines from different tasks never interleave. #[macro_export] macro_rules! serial_println { () => { - $crate::serial::write_str("\r\n"); - $crate::klog::append_fmt(core::format_args!("")); + $crate::klog::emit(core::format_args!("")) + }; + ($($arg:tt)*) => { + $crate::klog::emit(core::format_args!($($arg)*)) }; - ($($arg:tt)*) => {{ - $crate::serial_print!($($arg)*); - $crate::serial::write_str("\r\n"); - $crate::klog::append_fmt(core::format_args!($($arg)*)); - }}; } // --------------------------------------------------------------------------- -- cgit v1.2.3