foundry
Foundry OS: the operating system built on the Rustbelt kernel
| author | Tyler Fowler <tyler@localhost> | 2026-10-30 03:30:09 -0700 |
|---|---|---|
| commit | d478ebf57a05d03aaa6ac392ee6b3d12edc8c543 | |
| parent | f856a3aa4c884f7c0d06d17dfccaecef3b2dd9ec | |
| tree | browse 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.
| A | overflow.rs | +29 -0 |
| M | sh.rs | +2 -2 |
| M | spin.rs | +3 -1 |
| A | sysinfo.rs | +48 -0 |
| M | ulib.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)*); }}; }