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) --- shell/src/lib.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 2 deletions(-) (limited to 'shell/src') diff --git a/shell/src/lib.rs b/shell/src/lib.rs index 28eb737..97fa6c2 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs @@ -83,9 +83,11 @@ fn term_print(s: &str) { } fn term_println(s: &str) { - term_print(s); + gop_print!("{}", s); gop_println!(); - rsnix::serial::write_str("\r\n"); + // One locked call (line + CRLF) so a tick can't split the pair and + // let a background task's serial line interleave mid-line. + rsnix::serial::write_line(s); } fn term_err(msg: &str) { @@ -696,6 +698,11 @@ write `{} TIL `", "touch" => cmd_touch(&parsed), "rm" => cmd_rm(&parsed), "edit" => cmd_edit(&parsed), + "bg" => cmd_bg(&parsed), + "jobs" => cmd_jobs(&parsed), + "sleep" => cmd_sleep(&parsed), + "kill" => cmd_kill(&parsed), + "demo" => cmd_demo(&parsed), _ => term_err(&alloc::format!("unknown command: {}", cmd)), } } @@ -718,6 +725,11 @@ fn cmd_help(_p: &Parsed) { date — show current date/time (RTC) pci — list PCI devices rescan — re-scan for hotplugged/removed disks (also runs automatically) + bg — spawn a background task (spinner, ticker, counter, boom, overflow) + jobs — list tasks (id, name, state, ticks) + sleep — block the shell for ms; background tasks keep running + kill — stop a background task (id from jobs) + demo — spawn spinner + ticker + counter ls — list a directory cat — print files (cat USE a TO b copies a→b; cat TIL END is a heredoc) klog — kernel boot log (cat /dev/klog; WRITING persists it) @@ -1562,6 +1574,145 @@ fn cmd_rescan(_p: &Parsed) { } } +// --------------------------------------------------------------------------- +// Multitasking: bg / jobs / sleep / kill / demo +// +// Background tasks are headless: they may sleep, yield, and log to +// serial/klog only. Only the shell touches the disk, filesystem, and +// terminal (the ADR 0003 invariant). +// --------------------------------------------------------------------------- + +fn cmd_bg(p: &Parsed) { + let Some(name) = p.args.first() else { + term_err("bg: needs a task name — spinner, ticker, counter, boom, overflow"); + return; + }; + let entry: fn() = match name.as_str() { + "spinner" => task_spinner, + "ticker" => task_ticker, + "counter" => task_counter, + "boom" => task_boom, + "overflow" => task_overflow, + _ => { + term_err(&alloc::format!( + "bg: unknown task '{}' (spinner, ticker, counter, boom, overflow)", + name + )); + return; + } + }; + match rsnix::sched::spawn(entry, name) { + Some(id) => term_println(&alloc::format!("bg: spawned task {} ({})", id, name)), + None => term_err("bg: task table full"), + } +} + +fn cmd_jobs(p: &Parsed) { + let mut pipe = Pipeline::new(&p.filters, &p.output); + let lines = rsnix::sched::jobs(); + if lines.is_empty() { + let _ = pipe.line("jobs: no tasks"); + } + for l in lines { + if !pipe.line(&l) { + break; + } + } + pipe.end(); +} + +fn cmd_sleep(p: &Parsed) { + let ms: u64 = match p.args.first().and_then(|s| s.parse().ok()) { + Some(m) if m > 0 => m, + _ => { + term_err("sleep: needs a duration in ms (e.g. sleep 2000)"); + return; + } + }; + term_println(&alloc::format!( + "sleep: blocking shell for {} ms — background tasks keep running", + ms + )); + rsnix::sched::sleep_ms(ms); + term_println(&alloc::format!("sleep: woke after {} ms", ms)); +} + +fn cmd_kill(p: &Parsed) { + let id: usize = match p.args.first().and_then(|s| s.parse().ok()) { + Some(id) => id, + None => { + term_err("kill: needs a task id (see jobs)"); + return; + } + }; + match rsnix::sched::kill(id) { + Ok(()) => term_println(&alloc::format!("kill: task {} marked done", id)), + Err(e) => term_err(&alloc::format!("kill: {}", e)), + } +} + +fn cmd_demo(_p: &Parsed) { + let mut n = 0; + for (entry, name) in [ + (task_spinner as fn(), "spinner"), + (task_ticker as fn(), "ticker"), + (task_counter as fn(), "counter"), + ] { + if rsnix::sched::spawn(entry, name).is_some() { + n += 1; + } + } + term_println(&alloc::format!( + "demo: spawned {} task(s) — spinner 250ms, ticker 500ms, counter 1000ms (try jobs)", + n + )); +} + +// --- Demo task bodies (headless: sleep + serial/klog only) --- + +fn task_spinner() { + let mut n: u64 = 0; + loop { + rsnix::serial_println!("spinner: {}", n); + n += 1; + rsnix::sched::sleep_ms(250); + } +} + +fn task_ticker() { + let mut n: u64 = 0; + loop { + rsnix::serial_println!("ticker: {}", n); + n += 1; + rsnix::sched::sleep_ms(500); + } +} + +fn task_counter() { + let mut n: u64 = 0; + loop { + rsnix::serial_println!("counter: {}", n); + n += 1; + rsnix::sched::sleep_ms(1000); + } +} + +fn task_boom() { + panic!("boom: deliberate task panic"); +} + +/// Recurses until the guard page faults — the task should die with a +/// "stack overflow" panic while the shell keeps running. +fn task_overflow() { + #[allow(unconditional_recursion)] + fn recurse(d: u64) { + let keep = [0u8; 512]; + core::hint::black_box(&keep); + recurse(d + 1); + } + recurse(0); +} + fn cmd_pci(p: &Parsed) { let devices = rsnix::pci::probe(); let mut pipe = Pipeline::new(&p.filters, &p.output); -- cgit v1.2.3