xv6, demystified
An operating system sounds scary. It is not. By the time you scroll to the bottom, you will see how a CPU runs instructions, how one chip pretends to be many, how programs share, fight, and forget — and exactly what a process is inside the tiny teaching OS called xv6.
Nothing to install. Everything below is clickable. Poke it.
Let's go ↓What even is an operating system?
Imagine a giant, busy restaurant kitchen full of expensive equipment: ovens (the CPU), counters (memory), and freezers (the disk). You are a customer. You do not walk into the kitchen and cook. You hand your order to a waiter, and the waiter deals with all the chaos for you.
The operating system is that waiter. It is software, not hardware — a layer that sits between your programs and the physical machine, so no program has to talk directly to the CPU, memory, and disks. You just say "I want to run," and the OS handles the messy details.
Hover or tap any layer to see what it does.
Everything an OS does boils down to three big ideas. The whole rest of this page is just these three, up close:
Virtualization
Make one real thing (a CPU, a stick of RAM) look like many, so every program feels like it owns the whole machine.
Concurrency
Juggle many tasks at once — and survive the chaos when they touch the same data at the same time.
Persistence
Remember things after the power goes off, by carefully writing to disk through a file system.
The heartbeat: Fetch → Decode → Execute
Underneath everything, a CPU does one boring thing billions of times a second. It grabs the next instruction, figures out what it means, and does it. Then again. Forever. That loop is the heartbeat of every program you have ever run.
- Fetch — grab the next instruction from memory
- Decode — figure out what it's asking for
- Execute — actually do it (add, load, jump…)
This tiny CPU understands 4 instructions. Watch PC (which line is next) and ACC (the CPU's scratch value) change with every step.
Virtualization: one CPU pretending to be many
Here's the magic trick. You have, say, one CPU. But you run a browser, music, a download, and a game — all "at the same time." How?
The OS switches between programs insanely fast — runs program A for a sliver of time, freezes it, runs B for a sliver, freezes it, runs C… So fast that to you it looks simultaneous. That's the same trick as Netflix splitting one big server so a thousand people each feel like they have their own. The illusion of "many" from "one."
Drag the sliders. A small time slice = output letters interleave finely (feels parallel, more switching overhead). A big slice = each program hogs the CPU in long bursts. This choice — who runs and when — is called a scheduling policy.
Concurrency: when sharing goes wrong
When two helpers (threads) work inside the same program, they share the same memory. That's powerful — and dangerous. Watch what happens when both try to add to the same counter.
Adding 1 looks like a single step, but the CPU really does three:
load the value → add one → store it back. If the two threads
interleave those tiny steps, they overwrite each other and lose counts. An
atomic operation (protected by a lock) forces one thread to finish
all three steps before the other starts.
Correct total should be 400.
Run it with the lock off a few times — you'll often land below 400 (lost updates). Flip the lock on and it's always exactly right. That's why operating systems give us locks.
Persistence: remembering after the power dies
Memory (RAM) is volatile — yank the power and it forgets everything instantly.
The disk is persistent — it remembers even when off. To save something for real,
a program asks the OS's file system to write it to disk, using system calls like
open, write, and close.
Make a note, keep it only in RAM, then pull the plug. Then try again but save to disk first.
The lesson: anything that only lived in RAM is gone after a crash. Only what reached the disk survives. Real file systems add tricks like journaling so a crash mid-write doesn't corrupt your data.
User mode vs Kernel mode (the trap)
Your programs run in user mode — limited powers, like a guest with no keys to the server room. They can't touch hardware directly. When a program needs something privileged (read a file, do I/O), it makes a system call, which traps into the OS and flips the CPU into kernel mode — full powers. The OS does the dangerous part safely, then does a return-from-trap back to user mode.
Watch the program cross into the kernel only for the privileged moment, then come right back.
So… what is a process?
A program is a lifeless file sitting on disk. A process is that program brought to life and running. To capture a running program, the OS tracks its machine state: the memory it can touch (its address space) and the contents of the CPU's registers.
How a process is born: fork() & exec()
Where do new processes even come from? In UNIX (and xv6), you don't create one from thin air.
You clone yourself with fork(), then the clone becomes a different program
with exec(). This odd two-step dance is exactly how your shell runs every command you type.
- fork() — make a near-identical copy (child) of the current process
- exec() — replace the child's program with a brand-new one (same pid)
- wait() — parent pauses until the child finishes, then cleans it up
ls and watch how the shell runs it.
The mind-bender: fork() returns twice — it returns the child's pid to the parent, and
0 to the child. That's how each copy knows who it is (if (fork()==0) { ...child... }). And
exec() never returns on success — there's no old program left to return to.
The life of a process: states & transitions
A process is not always running. Mostly it's waiting its turn, or waiting for slow I/O. It moves between a handful of states. Click the transition buttons and drive a process through its life.
Only valid moves light up. Schedule: ready → running. Deschedule: running → ready (your time slice ended). I/O sends you to blocked; when it finishes you go back to ready (not straight to running — you still wait your turn).
The magic trick up close: the context switch
Back in Chapter 3 the OS "switched between programs insanely fast." Here's the actual mechanism. A CPU has only one set of real registers. To pause process A and run process B, the OS must save A's registers into A's memory, then load B's saved registers into the CPU. That swap — registers out, registers in — is a context switch.
Watch the values: the CPU's live registers get copied into the running process's saved slot, then the
other process's saved values get copied into the CPU. In xv6 this happens in a function literally called
swtch(), and the saved values live in the context field of struct proc (next chapter!).
The real xv6 struct proc
Everything you just learned lives inside one C struct in the actual xv6 kernel. The OS keeps an array of these — one per process — and that array is the process list. Click any field to see which idea from this page it represents.
This is the real xv6 struct proc (from proc.h), lightly trimmed. Real operating systems — Linux, macOS, Windows — track all of this too, just with far more fields. Want to see the rest of the actual code? Keep going. ↓
Enough pretending. Here's the actual xv6 source code.
Funny thing about everything above: we wrote JavaScript to simulate an operating system, running inside a browser, which is itself a program running on a real OS. Cute. But xv6 is the real article — raw C and x86 assembly that boots on bare metal and actually is the machine's boss.
Below is the genuine source (straight from MIT's xv6-public kernel). Every tab is the real code behind
a toy you just played with. The punchline you've been waiting for: the context switch is hand-written
assembly, and the lock that saved your counter is one atomic instruction.
📖 Wait — what is xv6, exactly?
xv6 is a tiny, Unix-like teaching operating system written at MIT in 2006 for their OS course (then 6.828). It's a modern re-implementation of Ken Thompson & Dennis Ritchie's Unix Version 6 (V6) from 1975 — same spirit and structure, but rewritten in clean ANSI C instead of 1970s pre-K&R C. The whole point: it's only ~9,000 lines, so a student can read the entire kernel in a weekend.
The original xv6 targets Intel x86 — that's
xv6-public,
and it's the version you see in these tabs. Around 2019 MIT ported it to RISC-V for the
renamed course 6.1810; that newer edition is
xv6-riscv.
The ideas are identical across both — only the architecture-specific bits differ
(register names, the swtch.S assembly, and pgdir vs pagetable).
We use x86 here because it's the version the OSTEP textbook uses (e.g. its
struct proc figure), so the code matches what most readers are studying from.
So: same OS, two flavors. x86 (xv6-public, what's below, matches OSTEP) ·
RISC-V (xv6-riscv, the current MIT 6.1810 course).
Pick a file above.
Want the whole thing? It's about 9,000 lines of C — small enough to read end-to-end in a weekend, which is exactly why it's the world's favorite teaching OS. Grab it at github.com/mit-pdos/xv6-public.
You made it. 🎉
You now know what an OS is (the waiter), the CPU's heartbeat (fetch–decode–execute), the three big ideas
(virtualization, concurrency, persistence), how the kernel protects the machine (the trap), what a
process really is (machine state), how it lives and dies (states), where xv6 keeps all of it
(struct proc) — and now what the real kernel code looks like. That's the foundation the
entire field is built on.