rust-kernel
Rustbelt: a hobby x86_64 kernel written in Rust
| author | Edward <edward@localhost> | 2026-10-31 17:22:26 -0700 |
|---|---|---|
| commit | a91bf2c1317b4b7dc0c293615e379acf56ae0183 | |
| parent | fc5e78aac8e1fcad62e645581d205f6b5b391b7a | |
| tree | browse files at this commit | |
Stage 18 prep: fixes from Clarke's 0.17.0 review
- timer_getoverrun / si_overrun: while a POSIX timer's signal is still
pending, further expirations add to the overrun count (they used to
overwrite it). The count is latched when the signal is delivered:
getoverrun reports the last delivered signal's overrun (0 before the
first delivery), and counting starts over for the next one.
- timerd starts at boot, so the first timer no longer costs 16 frames
after the headless report's "before" snapshot (report is flat again).
- coretest statfs: stat/lstat/fstat of mount points (/tmp, /dev,
/mnt/disk1) report the mounted fs's st_dev (and ext2's root ino 2).
These already passed: the probe's "/tmp has /'s st_dev" came from
printing st.st_dev in the same printf call as a later stat("/", &st)
argument (C evaluates arguments in unspecified order).| M | src/main.rs | +4 -0 |
| M | src/signal.rs | +14 -3 |
| M | src/timer.rs | +67 -19 |
| M | user/coretest.rs | +38 -0 |
4 files changed, 123 insertions, 22 deletions
@@ -109,6 +109,10 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { kprintln!("[net] no virtio-net card (start QEMU with -device virtio-net-pci)"); } + // --- Timers (timer.rs): start timerd now, so its one-off cost (a+ // kernel stack: 16 frames) isn't counted as the shell's (stage 18) --+ rustbelt::timer::start();+ // --- The shell (user/sh.rs), run by its own kernel thread --------------- thread::spawn("shell", shell_supervisor);
@@ -566,7 +566,8 @@ pub fn deliver_outcome(frame: &mut TrapFrame) -> Option<bool> { let (sig, info) = ts.pending.take(allowed).or_else(|| s.pending.take(allowed))?; Some((sig, info, s.action(sig), ts.reason.take())) });- let Some((sig, info, action, reason)) = next else { break };+ let Some((sig, mut info, action, reason)) = next else { break };+ crate::timer::delivered(p.pid, &mut info); // a POSIX timer's: latch its overrun count match action.handler { SIG_IGN if sig != SIGKILL && sig != SIGSTOP => continue, SIG_IGN | SIG_DFL => match default_action(sig) {@@ -1090,11 +1091,21 @@ pub fn pending_blocked(t: &Task) -> u64 { /// rt_sigtimedwait's dequeue: take a pending signal of `set` (the /// thread's own first, then the process's), without running any handler. pub fn take_waited(t: &Task, set: u64) -> Option<(u32, SigInfo)> {- without_interrupts(|| {+ let (sig, mut info) = without_interrupts(|| { let mut s = t.process.signals.lock(); let mut ts = t.sig.lock(); ts.pending.take(set).or_else(|| s.pending.take(set))- })+ })?;+ crate::timer::delivered(t.process.pid, &mut info);+ Some((sig, info))+}++/// Is POSIX timer `id`'s signal `sig` still pending in `p` (for the+/// process or one of its threads)? timer.rs counts overruns meanwhile.+pub fn timer_pending(p: &Process, sig: u32, id: i32) -> bool {+ let is_it = |pending: &Pending| pending.info.iter().any(|(s, i)| *s == sig && i.code == crate::timer::SI_TIMER && i.fields[0] as u32 == id as u32);+ let tasks = p.tasks();+ without_interrupts(|| is_it(&p.signals.lock().pending) || tasks.iter().any(|t| is_it(&t.sig.lock().pending))) } /// Is the process's queue of pending signals full (sigqueue: EAGAIN)?
@@ -10,7 +10,8 @@ //! sleep. Why a thread and not the timer interrupt? Sending a signal takes //! locks and may wake threads; doing that from normal thread context keeps //! the interrupt handler tiny and follows the locking rules in thread.rs.-//! timerd is started the first time anyone arms a timer.+//! timerd is started at boot (stage 18; it used to start the first time+//! anyone armed a timer). //! //! Resolution: timerd sleeps in PIT ticks (~55 ms), so a timer fires up //! to ~110 ms late, never early. A periodic timer whose signals come late@@ -63,8 +64,12 @@ struct Timer { /// SIGEV_THREAD_ID's thread. tid: u64, sigval: u64,- /// Expirations missed at the last firing (timer_getoverrun).+ /// Expirations counted since the signal was sent, while it is still+ /// pending (stage 18 fix: this *adds up*; it used to be overwritten). overrun: u64,+ /// What timer_getoverrun reports: `overrun` as it was when the last+ /// signal was delivered (0 before the first delivery), like Linux.+ overrun_last: u64, } static TIMERS: Mutex<Vec<Timer>> = Mutex::new(Vec::new());@@ -77,11 +82,19 @@ fn with_table<R>(f: impl FnOnce(&mut Vec<Timer>) -> R) -> R { without_interrupts(|| f(&mut TIMERS.lock())) } -/// Tell timerd to look again (starting it the first time).-fn poke() {+/// Start timerd. The kernel does this at boot (stage 18: before, the+/// first timer started it, so its stack showed up in the frame count of+/// whatever program armed a timer first). Calling it again does nothing.+pub fn start() { if !STARTED.swap(true, Ordering::AcqRel) { crate::thread::spawn("timerd", timerd); }+}++/// Tell timerd to look again (starting it, if nobody did yet: the test+/// kernels in tests/ don't run the boot code).+fn poke() {+ start(); CHANGED.store(true, Ordering::Release); CHANGED_QUEUE.wake_all(); }@@ -103,20 +116,33 @@ fn timerd() { if at > now { continue; }- if timer.interval > 0 {- let missed = (now - at) / timer.interval;- timer.overrun = missed;- timer.next = Some(at + (missed + 1) * timer.interval);- } else {- timer.overrun = 0;- timer.next = None;- }- due.push(*timer);+ // Periods that went by since `at` without a wake-up of+ // ours (timerd sleeps in ~55 ms ticks): expirations too.+ let missed = if timer.interval > 0 { (now - at) / timer.interval } else { 0 };+ timer.next = (timer.interval > 0).then(|| at + (missed + 1) * timer.interval);+ due.push((*timer, missed)); } t.iter().filter_map(|t| t.next).min() });- for timer in due {- fire(&timer);+ for (timer, missed) in due {+ // A POSIX timer whose last signal hasn't been delivered yet+ // (blocked, say) doesn't send another: like Linux, it counts+ // the expirations as overruns, which the delivery reports.+ // (Checked outside the table lock: signal locks come first.)+ let posix = timer.id != ITIMER_ID && timer.notify != SIGEV_NONE;+ let still_pending = posix && process::get(timer.pid).is_some_and(|p| signal::timer_pending(&p, timer.signo, timer.id));+ let send = with_table(|t| {+ let Some(x) = t.iter_mut().find(|x| x.pid == timer.pid && x.id == timer.id) else { return false };+ if still_pending {+ x.overrun = x.overrun.saturating_add(1 + missed);+ } else {+ x.overrun = missed;+ }+ !still_pending+ });+ if send {+ fire(&timer);+ } } let changed = || CHANGED.swap(false, Ordering::AcqRel).then_some(()); match next {@@ -139,7 +165,7 @@ fn fire(timer: &Timer) { SigInfo::from_pid(timer.signo, 0) // SI_KERNEL, like Linux's itimers } else { let mut info = SigInfo::new(timer.signo, SI_TIMER);- info.fields[0] = timer.id as u32 as u64 | (timer.overrun.min(i32::MAX as u64)) << 32; // si_tid, si_overrun+ info.fields[0] = timer.id as u32 as u64; // si_tid; si_overrun is filled in at delivery (`delivered`) info.fields[1] = timer.sigval; // si_value info };@@ -183,6 +209,7 @@ pub fn set_itimer(pid: u64, value: u64, interval: u64) -> (u64, u64) { tid: 0, sigval: 0, overrun: 0,+ overrun_last: 0, }); } old.map_or((0, 0), |x| read(&x, now))@@ -214,7 +241,7 @@ pub fn create(pid: u64, clock: u64, ev: SigEvent) -> Result<i32, Errno> { let id = (0..).find(|i| !ids.contains(i)).unwrap(); // No sigevent: SIGALRM with the timer id as the value (Linux's default). let sigval = ev.sigval.unwrap_or(id as u64);- t.push(Timer { pid, id, clock, next: None, interval: 0, signo: ev.signo, notify: ev.notify, tid: ev.tid, sigval, overrun: 0 });+ t.push(Timer { pid, id, clock, next: None, interval: 0, signo: ev.signo, notify: ev.notify, tid: ev.tid, sigval, overrun: 0, overrun_last: 0 }); Ok(id) }) }@@ -227,6 +254,7 @@ pub fn settime(pid: u64, id: i32, absolute: bool, value: u64, interval: u64) -> let timer = t.iter_mut().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).ok_or(Errno::EINVAL)?; let old = read(timer, now); timer.overrun = 0;+ timer.overrun_last = 0; timer.interval = interval; timer.next = match (value, absolute) { (0, _) => None,@@ -248,9 +276,29 @@ pub fn gettime(pid: u64, id: i32) -> Result<(u64, u64), Errno> { with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| read(x, now)).ok_or(Errno::EINVAL)) } -/// timer_getoverrun: periods missed at the last expiry.+/// timer_getoverrun: the overrun count of the last *delivered* signal. pub fn getoverrun(pid: u64, id: i32) -> Result<u64, Errno> {- with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| x.overrun.min(i32::MAX as u64)).ok_or(Errno::EINVAL))+ with_table(|t| t.iter().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID).map(|x| x.overrun_last.min(i32::MAX as u64)).ok_or(Errno::EINVAL))+}++/// A signal was just taken off a pending set to be delivered (or picked up+/// by sigwaitinfo). If it is a POSIX timer's, latch that timer's overrun+/// count: it goes into si_overrun and becomes what timer_getoverrun says,+/// and counting starts again from 0. Call without signal locks held.+pub fn delivered(pid: u64, info: &mut SigInfo) {+ if info.code != SI_TIMER {+ return;+ }+ let id = info.fields[0] as u32 as i32;+ let overrun = with_table(|t| {+ let x = t.iter_mut().find(|x| x.pid == pid && x.id == id && id != ITIMER_ID)?;+ x.overrun_last = x.overrun;+ x.overrun = 0;+ Some(x.overrun_last.min(i32::MAX as u64))+ });+ if let Some(n) = overrun {+ info.fields[0] = id as u32 as u64 | n << 32; // si_tid, si_overrun+ } } /// timer_delete.
@@ -361,6 +361,20 @@ fn statfs(c: &mut Checks) { close(w); // st_dev tells file systems apart. c.check(st("/tmp").dev != st("/mnt/disk1").dev && st("/mnt/disk1/").dev == st("/mnt/disk1/lost+found").dev, "st_dev: one per file system");+ // Stage 18 fix check (Clarke): a mount point itself reports the+ // *mounted* file system's st_dev and root inode, not the directory it+ // covers. find -xdev, du -x and cp -x compare st_dev with this.+ write_file("/tmp/devchk", b"x");+ c.check(st("/tmp").dev == st("/tmp/devchk").dev && st("/tmp").dev != st("/").dev, "stat(\"/tmp\"): the mounted ramfs's st_dev, not /'s");+ c.check(st("/dev").dev == st("/dev/null").dev && st("/dev").dev != st("/").dev, "stat(\"/dev\"): devfs's st_dev");+ c.check(st("/mnt/disk1").dev == st("/mnt/disk1/lost+found").dev && st("/mnt/disk1").ino == 2, "stat(\"/mnt/disk1\"): ext2's st_dev and its root inode (2)");+ let mut ls = Stat::default();+ c.check(lstat("/tmp", &mut ls) == 0 && ls.dev == st("/tmp/devchk").dev, "lstat of a mount point: the mounted fs's st_dev too");+ let fd = open("/mnt/disk1", O_RDONLY | O_DIRECTORY);+ let mut fs_ = Stat::default();+ c.check(fstat(fd, &mut fs_) == 0 && fs_.dev == st("/mnt/disk1/lost+found").dev && fs_.ino == 2, "fstat of an fd opened on a mount point: same");+ close(fd);+ unlink("/tmp/devchk"); // statx write_file("/tmp/sx.txt", b"twelve bytes"); let mut sx = [0u8; 256];@@ -535,6 +549,7 @@ fn sockets(c: &mut Checks) { static ALRM: AtomicI64 = AtomicI64::new(0); static TCODE: AtomicI64 = AtomicI64::new(0); static TVAL: AtomicU64 = AtomicU64::new(0);+static TOVR: AtomicI64 = AtomicI64::new(-1); extern "C" fn on_alarm(_sig: i32) { ALRM.fetch_add(1, Ordering::SeqCst);@@ -544,6 +559,7 @@ extern "C" fn on_timer(_sig: i32, info: *const SigInfo, _ctx: *mut u8) { unsafe { TCODE.store((*info).code as i64, Ordering::SeqCst); TVAL.store(*((info as *const u8).add(24) as *const u64), Ordering::SeqCst);+ TOVR.store(*((info as *const u8).add(20) as *const i32) as i64, Ordering::SeqCst); // si_overrun } ALRM.fetch_add(1, Ordering::SeqCst); }@@ -638,6 +654,28 @@ fn timers(c: &mut Checks) { c.check(old[1] == 50_000_000, "disarming returns the old interval"); c.check(syscall3(SYS_TIMER_DELETE, id as u64, 0, 0) == 0, "timer_delete"); c.check(syscall3(SYS_TIMER_DELETE, id as u64, 0, 0) == -EINVAL, "timer_delete again: EINVAL");+ // Overruns (stage 18 fix, found by Clarke): a 20 ms timer whose signal+ // is blocked for 0.5 s expires ~25 times but sends one signal; the+ // other ~24 add up as overruns. timer_getoverrun reports the count of+ // the last *delivered* signal: 0 while it is still pending.+ ALRM.store(0, Ordering::SeqCst);+ TOVR.store(-1, Ordering::SeqCst);+ let r = syscall3(SYS_TIMER_CREATE, 1, sev.as_ptr() as u64, &mut id as *mut i32 as u64);+ let old_mask = sigprocmask(SIG_BLOCK, sigbit(SIGUSR1)) as u64;+ let spec = [0u64, 20_000_000, 0, 20_000_000]; // every 20 ms+ syscall6(SYS_TIMER_SETTIME, id as u64, 0, spec.as_ptr() as u64, 0, 0, 0);+ sleep_through(500);+ c.check(r == 0 && syscall3(SYS_TIMER_GETOVERRUN, id as u64, 0, 0) == 0, "overrun: 0 before the first signal is delivered");+ sigprocmask(SIG_SETMASK, old_mask); // delivered now+ let first = TOVR.load(Ordering::SeqCst);+ let got = syscall3(SYS_TIMER_GETOVERRUN, id as u64, 0, 0);+ c.check(ALRM.load(Ordering::SeqCst) == 1 && (18..=30).contains(&first), &format!("blocked 0.5 s: one signal, si_overrun {} (Linux: ~24)", first));+ c.check(got == first, &format!("timer_getoverrun = the delivered signal's si_overrun ({})", got));+ c.check(wait_for(2, 1000), "unblocked: the next signal comes");+ let next = TOVR.load(Ordering::SeqCst);+ // (Not 0: timerd and signal delivery both work in ~55 ms ticks.)+ c.check((0..=10).contains(&next), &format!("...with a fresh count ({}), not the old one added", next));+ syscall3(SYS_TIMER_DELETE, id as u64, 0, 0); // SIGEV_NONE: nothing is sent, the time just runs down. sev[1] = 1u64 << 32; // notify = SIGEV_NONE syscall3(SYS_TIMER_CREATE, 0, sev.as_ptr() as u64, &mut id as *mut i32 as u64);