an interactive playground

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 ↓
Chapter 1

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.

You & Your Programs browser · game · editor · this webpage
requests (system calls) ↓    results ↑
The Operating System  xv6 lives here scheduler · memory manager · file system · drivers
commands ↓    raw signals ↑
The Hardware CPU · memory (RAM) · disk · devices

Hover or tap any layer to see what it does.

Chapter 2

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…)
Memory (the program)
    CPU
    Fetch
    Decode
    Execute
    Press Step to run one stage at a time, or Run to watch the heartbeat.
    PC0program counter
    ACC0accumulator
    Output

    This tiny CPU understands 4 instructions. Watch PC (which line is next) and ACC (the CPU's scratch value) change with every step.

    Chapter 3 · Big Idea #1

    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."

    Processes waiting to run
    The single physical CPU
    idle
    What it produces (one shared output stream)

    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.

    Chapter 4 · Big Idea #2

    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.

    Each thread adds 1 to the counter 200 times.
    Correct total should be 400.
    Thread A
    idle
    shared counter
    0
    Thread B
    idle

    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.

    Chapter 5 · Big Idea #3

    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.

    RAM volatile
    Disk persistent

    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.

    Chapter 6

    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.

    USER MODE limited powers
    your program
    KERNEL MODE full powers

    Watch the program cross into the kernel only for the privileged moment, then come right back.

    Chapter 7

    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.

    Address space (the process's private memory)
    Codethe instructions
    Dataglobals
    Heap ↓malloc() / free() grow this
    ↕ free space ↕
    Stack ↑function calls, locals
    Registers (the CPU's live state for this process)
    PC / IP which instruction is next
    SP top of the stack
    FP base of current frame
    Why this matters: to pause a process and run another, the OS saves all of this, then restores it later. That save/restore is a context switch. See exactly where xv6 stores it ↓
    Chapter 8

    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
    parentpid 4
    RUNNING
    program: sh
    child
    (none yet)
    What just happened
    A single shell process is running, waiting for you to type a command. Press Next step to type ls and watch how the shell runs it.
    Terminal
    $

    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.

    Chapter 9

    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.

    NEWbeing created
    READYcould run, waiting its turn
    RUNNINGon the CPU right now
    BLOCKEDwaiting on I/O
    ZOMBIEdone, awaiting cleanup
    Current state: new
    History

    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).

    Chapter 10

    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.

    Process A RUNNING
    saved context (in memory)
    Physical CPU live registers
    running: A
    Process B READY
    saved context (in memory)

    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!).

    Chapter 11 · The payoff

    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.

    
            
    👆 Click a highlighted field on the left to learn what it does.

    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. ↓

    Chapter 12 · The Real Deal

    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?

    The origin story

    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.

    Old vs. new: two architectures

    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.

    Why this demo uses the x86 version

    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.

    Run it back ↑