RustForge

foundry

Foundry OS: the operating system built on the Rustbelt kernel

authorTyler Fowler <tyler@localhost>2026-10-30 03:30:09 -0700
commitd478ebf57a05d03aaa6ac392ee6b3d12edc8c543
parentf856a3aa4c884f7c0d06d17dfccaecef3b2dd9ec
treebrowse files at this commit
Stage 11: core polish (0.11.0)

SMP bring-up on 4 CPUs (ACPI MADT, local APIC, INIT/SIPI trampoline,
per-CPU GDT/TSS and swapgs data), a multi-core scheduler with
sleep/wake, wait queues and idle CPUs woken by IPI, kernel stacks with
guard pages and frame-exact cleanup of threads and processes, a user
stack guard zone, sysinfo/overflow programs, tests/smp.rs and
tests/guard_pages.rs, README learner section and roadmap.
Aoverflow.rs+29 -0
Msh.rs+2 -2
Mspin.rs+3 -1
Asysinfo.rs+48 -0
Mulib.rs+72 -3

5 files changed, 154 insertions, 6 deletions

@@ -0,0 +1,29 @@+//! A buggy program: a function that calls itself forever. Every call needs+//! a little more stack, so the stack grows down until it runs into the+//! *guard zone* below it, which is never mapped. The CPU raises a page+//! fault there and the kernel kills just this program, saying+//! "killed: stack overflow", instead of letting the stack quietly grow over+//! other memory.+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++/// Uses 1 KiB of stack per call, then calls itself again.+fn recurse(depth: u64) -> u64 {+    let mut buf = [0u8; 1024];+    // `black_box` stops the compiler from removing the array or turning+    // the recursion into a loop.+    buf[0] = depth as u8;+    let buf = core::hint::black_box(buf);+    recurse(depth + 1) + buf[0] as u64+}++fn main() -> i64 {+    println!("[overflow] pid {}: recursing until the stack runs out...", ulib::getpid());+    let n = recurse(0);+    println!("[overflow] returned {} -- this must never be printed!", n);+    1+}
@@ -36,8 +36,8 @@ const HELP: &str = "\ Rustbelt shell commands   builtins   cd [dir]  pwd  echo [text]  ver  drives  alias [name=value]              history  jobs  wait [pid]  cls / clear  help  exit [code]-  programs   ls [-l]  cat  wc  grep [-i] [-v]  mkdir  rm  touch  cp  sleep-  (bin dirs) hello  demo  evil  spin  child  sh+  programs   ls [-l]  cat  wc  grep [-i] [-v]  mkdir  rm  touch  cp  sleep  sysinfo+  (bin dirs) hello  demo  evil  spin [rounds]  overflow  child  sh   run [--trace] prog args   run a program (--trace prints its syscalls)   start prog args           run it in the background (or: prog args &)   a | b | c                 pipes;  > file  >> file  < file  redirection
@@ -1,5 +1,6 @@ //! A CPU hog: busy-loops without ever making a syscall in between. The timer //! interrupt still preempts it, so everything else keeps running.+//! `spin` does 3 rounds of 30 million additions; `spin <n>` does n rounds. #![no_std] #![no_main] @@ -9,8 +10,9 @@ mod ulib;  fn main() -> i64 {     println!("[spin] pid {}: spinning without syscalls...", ulib::getpid());+    let rounds: u64 = ulib::arg(1).and_then(|a| a.parse().ok()).unwrap_or(3);     let mut count: u64 = 0;-    for round in 1..=3 {+    for round in 1..=rounds {         for _ in 0..30_000_000 {             count = core::hint::black_box(count + 1);         }
@@ -0,0 +1,48 @@+//! `sysinfo`: how many CPUs there are, how busy each one is, and how many+//! threads, processes and memory frames are in use.+//!+//! The load is measured, not guessed: each CPU counts its ~10 ms timer+//! ticks, and how many of them interrupted real work rather than its idle+//! thread. We read those counters twice, a quarter of a second apart; busy ticks+//! divided by all ticks in between is that CPU's load.+#![no_std]+#![no_main]++#[macro_use]+#[path = "ulib.rs"]+mod ulib;++fn main() -> i64 {+    let before = ulib::sysinfo();+    ulib::sleep_ms(250); // sleeping, not spinning: we don't count as load+    let after = ulib::sysinfo();++    let seconds = after.uptime_ticks * 10 / 182; // the PIT ticks ~18.2 times a second+    println!(+        "{} CPU{} online (this program ran on CPU {}), up {} s",+        after.cpus,+        if after.cpus == 1 { "" } else { "s" },+        after.this_cpu,+        seconds+    );+    println!(+        "{} threads, {} processes, {} KiB of memory frames in use",+        after.threads,+        after.processes,+        after.frames_in_use * 4+    );+    println!("CPU load over the last quarter second:");+    for cpu in 0..(after.cpus as usize).min(16) {+        let ticks = after.ticks[cpu] - before.ticks[cpu];+        let busy = after.busy_ticks[cpu] - before.busy_ticks[cpu];+        let percent = if ticks == 0 { 0 } else { busy * 100 / ticks };+        // A 20-character bar: one '#' per 5%.+        let mut bar = [b'.'; 20];+        for c in bar.iter_mut().take((percent / 5) as usize) {+            *c = b'#';+        }+        let bar = core::str::from_utf8(&bar).unwrap_or("");+        println!("  CPU{} [{}] {:3}%", cpu, bar, percent);+    }+    0+}
@@ -41,6 +41,7 @@ pub const SYS_READLINK: u64 = 89; pub const SYS_GETDENTS64: u64 = 217; pub const SYS_SPAWN: u64 = 500; pub const SYS_MOUNTS: u64 = 501;+pub const SYS_SYSINFO: u64 = 502;  pub const O_CREAT: u64 = 0o100; pub const O_TRUNC: u64 = 0o1000;@@ -199,6 +200,28 @@ pub fn uname(buf: &mut [u8; 6 * 65]) -> [&str; 6] { pub fn mounts(buf: &mut [u8]) -> i64 {     syscall3(SYS_MOUNTS, buf.as_mut_ptr() as u64, buf.len() as u64, 0) }+/// What `sysinfo` reports (same layout as the kernel's syscall::SysInfo).+/// `ticks[cpu]` counts that CPU's ~10 ms timer ticks, `busy_ticks[cpu]` the+/// ones where it was running a real thread rather than idling: sample twice+/// and subtract to get a CPU's load.+#[repr(C)]+#[derive(Clone, Copy, Default)]+pub struct SysInfo {+    pub cpus: u64,+    pub this_cpu: u64,+    pub threads: u64,+    pub processes: u64,+    pub frames_in_use: u64,+    pub uptime_ticks: u64,+    pub ticks: [u64; 16],+    pub busy_ticks: [u64; 16],+}+/// Ask the kernel for CPU, thread, process and memory counts.+pub fn sysinfo() -> SysInfo {+    let mut info = SysInfo::default();+    syscall3(SYS_SYSINFO, &mut info as *mut SysInfo as u64, 0, 0);+    info+} /// Current directory into `buf`; returns its length (or -errno). pub fn getcwd(buf: &mut [u8]) -> i64 {     syscall3(SYS_GETCWD, buf.as_mut_ptr() as u64, buf.len() as u64, 0)@@ -355,10 +378,54 @@ impl fmt::Write for Stdout {     } } +/// Collects the pieces of one `print!`/`println!` so the whole line goes to+/// the kernel in a single `write`. With several CPUs, two programs printing+/// at the same moment would otherwise have their pieces mixed up mid-line.+/// Flushes when full and when dropped (at the end of the macro).+pub struct LineWriter {+    fd: i64,+    buf: [u8; 256],+    len: usize,+}++impl LineWriter {+    pub fn new(fd: i64) -> LineWriter {+        LineWriter { fd, buf: [0; 256], len: 0 }+    }+    pub fn flush(&mut self) {+        if self.len > 0 {+            write_all(self.fd, &self.buf[..self.len]);+            self.len = 0;+        }+    }+}++impl fmt::Write for LineWriter {+    fn write_str(&mut self, s: &str) -> fmt::Result {+        if self.len + s.len() > self.buf.len() {+            self.flush();+        }+        if s.len() > self.buf.len() {+            write_all(self.fd, s.as_bytes()); // too big to buffer: send as is+        } else {+            self.buf[self.len..self.len + s.len()].copy_from_slice(s.as_bytes());+            self.len += s.len();+        }+        Ok(())+    }+}++impl Drop for LineWriter {+    fn drop(&mut self) {+        self.flush();+    }+}+ macro_rules! print {     ($($arg:tt)*) => {{         use core::fmt::Write;-        let _ = write!($crate::ulib::Stdout, $($arg)*);+        let mut out = $crate::ulib::LineWriter::new(1);+        let _ = write!(out, $($arg)*);     }}; } @@ -384,14 +451,16 @@ impl fmt::Write for Fd { macro_rules! eprintln {     ($($arg:tt)*) => {{         use core::fmt::Write;-        let _ = writeln!($crate::ulib::Stderr, $($arg)*);+        let mut out = $crate::ulib::LineWriter::new(2);+        let _ = writeln!(out, $($arg)*);     }}; }  macro_rules! println {     ($($arg:tt)*) => {{         use core::fmt::Write;-        let _ = writeln!($crate::ulib::Stdout, $($arg)*);+        let mut out = $crate::ulib::LineWriter::new(1);+        let _ = writeln!(out, $($arg)*);     }}; }