[ built, not watched ]

Build real expertise,
one guided system at a time.

Real expertise matters more than ever, with AI and because of it. the research →

Kernels, filesystems, databases — built from scratch on your own machine, with an expert mentor guiding every step of the path.

Free to start · no credit card · Rust for Beginners is free

idt.rs — os-rust
idt.rspic.rsmain.rs
81/// IRQ0 (timer): count the tick and acknowledge the PIC.
82extern "x86-interrupt" fn timer_handler(_frame: InterruptStackFrame) {
83 TICKS.fetch_add(1, Ordering::Relaxed);
84 super::pic::eoi(0);
85}
86
87/// IRQ1 (keyboard): read the scancode from the PS/2 data port and acknowledge.
88extern "x86-interrupt" fn keyboard_handler(_frame: InterruptStackFrame) {
89 let _scancode = unsafe { super::port::inb(0x60) };
90 super::pic::eoi(1);
91}
TERMINALPROBLEMSOUTPUTbash · os-rust
$ sboot test 07-interrupts  ⋮ the build preface, and your toolchain's own output  [PASS] kernel boots (1 pt)  [FAIL] timer IRQ fires after sti (ticks accumulate) (2 pt)         your kernel booted and ran as far as "[PASS] resumed", then stopped         emitting markers. It did not reset, so it is halted or looping         rather than faulting.          A hang here (no output, timeout) usually means exactly one timer           interrupt fired and then silence — the classic missing-EOI symptom.  [PASS] no in-kernel assertion failures on the serial log (1 pt)  [PASS] remaps the 8259 PIC (1 pt)  [PASS] keyboard IRQ handler reads the PS/2 data port (1 pt)   score: 4/6  ⋮ 2 lines — the keep-going verdict, and the stuck? sign-off$ 

[ meet Guru — your mentor ]

Here to guide you on your journey.

Expert review on every line you write.

[ on a pass — never on a fail ][ re-read when your diff changes ]
Guru· claude-opus-5 review · your diff vs the startersubmissions #1 · #2

Revise recommended — one of these would bite you.

The checks passed; the findings below are the layer the tests cannot see.

[ 1 would bite you ][ 1 craft ]
✓ checks passed · 18/18 pts · submission #2 · grade recorded
os/kernel/src/fs/simplefs.rs+209 −0
@@ -0,0 +1,209 @@ new file
⋮ lines 1–121 — the on-disk records, mount, and the inode table
122+/// Read from a file's data blocks into `buf`, stopping at end of file.
123+fn read_bytes(&self, inode: &Inode, offset: u64, buf: &mut [u8])
124+ -> Result<usize, FsError> {
⋮ lines 125–129 — the EOF clamp and the `done` / `block` locals
130+ while done < want {
131+ let pos = offset + done as u64;
132+ if pos >= size { break; }
Guru[ craft ]simplefs.rs:132

`if pos >= size { break; }` can never fire — `want` is already capped at `size - offset` above.

A dead guard makes a reader hunt for the path that needs it, and there isn't one. The clamp above is the live defence; this one is decoration.

try: Delete it — or state the invariant honestly: `debug_assert!(pos < size);`

133+ let index = (pos / BLOCK_SIZE as u64) as usize;
134+ let within = (pos % BLOCK_SIZE as u64) as usize;
135+ let n = core::cmp::min(BLOCK_SIZE - within, want - done);
136+ self.dev.read_block(inode.direct[index] as u64, &mut block)?;
Guru[ would bite you ]simplefs.rs:136

When `inode.direct[index]` is 0 — a hole — this reads device block 0: the superblock.

It passes because nothing in this image is sparse yet. The first write past a file's end changes that: reads in the gap hand back superblock bytes as file contents — a real bug hiding behind a green check.

try: A hole reads as zeroes: `if ptr == 0 { buf[done..done + n].fill(0) }` — only a nonzero slot touches the device.

⋮ lines 137–209 — the copy into `buf`, the loop close, and write_bytes

[ when you’re stuck ]

Bring the broken build. Leave with it running.

Ask about the check that is failing and you get the one question that moves you — never the answer pasted in. Underneath it, on every ask, a written hint Guru does not need to be online for.

[ 01 ]

Stuck? Ask Guru. Understand what broke, and keep moving.

[ before it passes ][ has no answer key ]
Guru· socratic — guides, never hands the fix
youburst doesn’t pass — the front of the 64 bytes arrives and the tail is gone. my write_byte is only this:
pub fn write_byte(b: u8) {
    out8(COM1, b);
}
what am I missing?
GuruYour two lines do exactly what they say — the missing part is upstream of them. out8(COM1, b) hands a byte over whether or not the UART has room for one, and your init switched on a queue in front of the wire. So: where does the UART tell you it has room again? Your own run answers half of it — count how many of the 64 arrived before the tail stopped.
youso I add a wait before out8?
GuruThat’s the shape of it. Two things to settle: which LSR bit means “ready for the next byte” (the register table in this lab’s canon has it in one row), and whether you wait before you hand the byte over or after. Then prove it — send exactly 16 bytes, then 17. If 16 arrives whole and 17 loses its last, you have found where the loss happens, and you found it.
[ 02 ]

Or take a hint, on your own machine

[ one hint per ask ][ answered offline ]
~/kernel-in-rust/os — local
$ sboot hint 01-first-lighthint — 01-first-light · fb.background # your failing check [hint 1 of 2 · fb.background] Nothing reached the screen at the point we sampled. The mostcommon cause is that your fill loop never actually ran — thescaffold calls gfx::main once and halts, so if fill() isn'tcalled from main, the framebuffer stays whatever the bootloaderleft. Confirm main calls fill before anything else. run it again for the next rung (2 of 2 — the debug ladder).$ 

[ the paths ]

Choose the expertise you want to build first.

Start a path and you finish with a system you built — every prerequisite already on it, in an order already worked out.

Path 01 · the language

Rust, from First Line to Systems

Start with no Rust and finish writing the code the rest of the stack is built on.

Start free
Rust for Beginners:Build a Word GameRust for Systems:Read a SQLite FileAsync Rust:Build a Runtime
Path 02 · the machine

Operating Systems in Rust

Boot your own kernel, then teach it memory, scheduling, a filesystem, and a shell that answers you.

Start free
Rust for Kernelsx86-64 Essentials:Probe the MachineKernel in Rust:Own the MachineBuild an OS from Scratch
Path 03 · the metal

Operating Systems in C

The same machine, in the language it was written in — boot your own kernel in C and own every byte you hand the hardware.

Start free
x86-64 Essentials:Probe the MachineKernel in C:Own the Machine
Path 04 · the data

Storage & Databases

Build the engine under the query — pages, B-trees, and a log that survives the crash you cause on purpose.

Get notified
Build a Key-Value StoreBuild a SQL Engine
Drawing board

Linux Kernel

Drawing board

AI & LLMs

1 of 6

[ inside one lab ]

Read a little. Build the real thing.

Every lab: concept, canon, build — with Guru beside you, and a grade that comes from running it.

[ 01 ] concept

A quick read — enough to start

One page on the idea you are about to build. Short on purpose: it gets you to the keyboard.

[ 02 ] canon

The canon, linked — the piece worth reading

The actual chapter, paper or datasheet, in the original, with the part you need named.

[ 03 ] build

Now you build it — Guru beside you

You write it into a real system, and it is graded by running it.

sboot test [ on your machine ]

Instant feedback on the published checks — and it never quotes our tests, only your own kernel.

~/os-rust/os — local
$ sboot test 07-interrupts── build: `cargo xtask build` — your toolchain's own output follows   [PASS] kernel boots (1 pt)  [FAIL] timer IRQ fires after sti (ticks accumulate) (2 pt)         your kernel booted and ran as far as "[PASS] resumed", then         stopped emitting markers. It did not reset, so it is halted         or looping rather than faulting.          A hang here (no output, timeout) usually means exactly one           timer interrupt fired and then silence — the classic           missing-EOI symptom.  [PASS] no in-kernel assertion failures on the serial log (1 pt)  [PASS] remaps the 8259 PIC (1 pt)  [PASS] keyboard IRQ handler reads the PS/2 data port (1 pt)   score: 4/6  ❌ keep going — see the failing checks above.stuck?  sboot hint · sourceboot.com/courses/os-rust/stages/07-interrupts#stuck$ 

sboot submit [ official ]

The official run. Your machine builds it and boots it once; we judge that run and record it — a grade earned by code that actually ran.

sourceboot — official grade
$ sboot submit 07-interrupts── checking 07-interrupts locally first (one build, one run)── build: `cargo xtask build` — your toolchain's own output follows ── local check passed (6/6)── packaging os/ (source only)── submitted (238 KB) — graded on the server   [PASS] kernel boots (1 pt)  [PASS] timer IRQ fires after sti (ticks accumulate) (2 pt)  [PASS] no in-kernel assertion failures on the serial log (1 pt)  [PASS] remaps the 8259 PIC (1 pt)  [PASS] keyboard IRQ handler reads the PS/2 data port (1 pt)  score: 6/6   ✅ official grade: 6/6 — recorded  Submitted → sourceboot.com/courses/os-rust/stages/07-interrupts?submission=…#review  read your review, then press Complete there — that's what finishes the lab$ 

[ your path is open ]

Pick a path and start free.

Build something that actually runs before you decide anything.

[ built, not watched ]

Every completion is a system that actually ran — an official run of your own code, on your path for good.

[ paths, not projects ]

Prerequisites live on the path itself, so everything you need arrives in the place you need it.

[ your machine, your workspace ]

What you build is a git repo on your own machine from the first commit — yours to keep, with none of our grading inside it.

Get the updates instead:

New paths, new labs, and the ones that just went live. No noise.