Guided tours
Each tour follows one path through the running system, from the first instruction to the hardware and back, across C, assembly and the linker script. At every step you see the real code, the state of the machine (which hart, which mode, which stack, which page table, which locks are held, and whether interrupts can arrive) and what the other CPUs might be doing at that moment.
Start here: a learning path
1From make qemu to a disk image and a kernel2Power-on to main, on every hart at once3main: one hart builds the kernel, the others wait4From the first process to the shell prompt5Life of a system call11From a timer tick to a context switch15Spinlocks from the hardware up16sleep and wakeup, and the lost-wakeup problem12One scheduler per hart20fork21exit, wait and zombies22exec24The kernel page table and turning paging on25A user address space29A disk read, end to end31The log: begin_op, commit and group commit34Path lookup35Creating and naming files39Pipes40Capstone: the shell running ls | wc41Every transition: mode, stack and page table45One complete time slice on three harts50noff and intena through a sleep, a yield and an interrupt
Boot and build
1From make qemu to a disk image and a kernelBefore xv6 can run a single instruction, two files must exist: kernel/kernel, the
program the three CPUs will execute, and fs.img, the disk they will read init and the
shell from. You type make qemu, about a hundred…✓2Power-on to main, on every hart at onceQEMU has built a machine with three CPUs (harts), copied kernel/kernel into
RAM at 0x80000000, and released all three harts at the same instant. None of them knows
it is not alone. They run the same instructions,…✓3main: one hart builds the kernel, the others waitThree harts have just arrived in main, in supervisor mode, each on its own boot stack,
with paging and interrupts off (Tour 2: Power-on to main, on every hart at once). Line 13 splits them. Hart 0 goes off to…✓4From the first process to the shell promptAt the end of main, the kernel is built but nothing is running. There is no init
program in memory, no shell, no user code at all: just one entry in the process table,
marked RUNNABLE, with an empty address space.…✓
Traps and system calls
5Life of a system callYou type echo hi at the xv6 shell, and two letters appear on your screen. Between those
two events, a user program asks the kernel for help, the CPU changes privilege mode twice,
two page tables take turns, a process…✓6System-call arguments and user pointersYou type cat README. Before cat can print a single byte, it must ask the kernel to
open a file, and to do that it hands the kernel a pointer: the address of the
string "README" in its own memory. That…✓7The trampoline and the trapframeTour 5: Life of a system call crossed the user/kernel border twice and moved on quickly. This tour stays at
the border. It is 83 instructions long, written in assembly, and runs while the hart
belongs to nobody: no…✓8Traps taken inside the kernelIn Tour 5: Life of a system call a trap came from user mode: the program asked for help, and the
trampoline page switched page tables, saved 31 registers and found a kernel stack. This
tour is about the other kind of…✓9Device interrupts and the PLICYou type cat README. To print the first line, cat needs the file’s first data block,
block 48 of the disk, and nobody has read it since boot. The kernel hands the request to
the disk and puts cat to sleep. Some time…✓10Exceptions and faultsA system call is a trap the program asks for. An exception is a trap the program did
not ask for: it touched memory it has no page for, wrote to its own code, or executed an
instruction it is not allowed to execute.…✓
Time and scheduling
11From a timer tick to a context switchA program that never makes a system call never asks the kernel for anything. So how does
the kernel ever get the CPU back from it? This tour answers that by following one tick of
the clock on hart 1, from the moment…✓12One scheduler per hartxv6 has no central scheduler that hands out work. Instead every hart runs its own copy of
the same loop, scheduler, forever, and all three loops walk the same table of
64 processes, proc[], looking for one marked…✓13swtch and the lock handed across a context switchEverywhere else in xv6, the thread that acquires a spinlock releases it. A context switch
breaks that rule on purpose. When a process gives up its CPU, it acquires its own
p->lock, and the scheduler releases it.…✓14pause(n) and the tick counterxv6 has exactly one notion of time: ticks, a counter that goes up by one about ten times
a second. Only hart 0 increments it. Every process that wants to wait for time to pass,
through the pause system call, sleeps…✓
Concurrency primitives
15Spinlocks from the hardware upTwo harts want the same page of memory at the same moment. There is one list of free pages
in the whole machine, kmem.freelist, and if both harts pop it at once they can
both walk away with the same page. Two…✓16sleep and wakeup, and the lost-wakeup problemYou type cat README | wc at the shell. Two programs start at almost the same moment on
two different harts. wc asks the pipe for data before cat has
written anything, so wc must wait. A few microseconds later cat, on…✓17Sleep-locksYou type ls | grep README. The shell’s child forks two processes, and a moment later both
call exec at almost the same instant, on two different harts: ls (pid 4)
on hart 1 and grep (pid 5) on hart 2. To load a…✓18Lock ordering: how xv6 avoids deadlockOne lock can never deadlock a correct program. Two locks can. If hart 1 holds lock A and
waits for B while hart 2 holds B and waits for A, both wait forever, and soon every other
hart that touches A or B joins them.…✓19Memory ordering across hartsWhen you read a C program you assume that its statements happen in the order you wrote
them. On one hart that assumption is safe: whatever the compiler and the CPU do behind
the scenes, a hart always sees its own…✓
Processes
20forkYou type ls at the $ prompt and press Enter. Before a single line of ls runs, the
shell has to make a second copy of itself: a new process with its own memory, its own
kernel stack and its own process ID, but the…✓21exit, wait and zombiesls has printed its listing. On hart 2 it calls exit(0). On hart 0, the shell has been
asleep inside wait(0) since it forked ls. In the next few microseconds the two must
meet: the dying process has to tell its…✓22execThe shell’s child (pid 3) is a copy of the shell (Tour 20: fork). It has parsed the command
line, found the single word ls, and now calls exec("ls", argv). When that call
succeeds, the process is no longer…✓23killkill 5 sounds like an order: stop process 5, now. In xv6 it is closer to a note left
on the victim’s desk. kkill sets a flag, p->killed, and if the victim is
asleep, nudges it awake. That is all. The victim itself…✓
Memory
24The kernel page table and turning paging onUntil now, every address the kernel used went straight to physical memory. That cannot
last: user processes need their own private views of memory, kernel stacks need guard
pages, and the trampoline must appear at…✓25A user address spaceThe shell sh (pid 2) believes it owns a private memory that starts at address 0 and
runs up to 256 GiB. Its code is at 0x0, its command buffer at 0x2020, its stack near
0x5000. Every other process believes the same…✓26sbrk, eager and lazy, and page faultsA program that needs more memory asks the kernel to move the end of its address space
up. xv6 offers two ways to do it. sbrk(n) is eager: the kernel allocates and maps
every page right away. sbrklazy(n) is lazy: the…✓27The physical page allocatorEvery page table, every page of user memory, every kernel stack, trapframe and pipe buffer
in xv6 comes from one place: a linked list of free 4096-byte pages managed by
kalloc and kfree in kernel/kalloc.c. The whole…✓28Crossing the user/kernel boundary in memoryYou type ls README. To print one line, ls makes two system calls that carry pointers
across the user/kernel boundary in opposite directions: open("README", …) hands the
kernel the address of a string in…✓
File system
29A disk read, end to endYou type cat README. The first 512 bytes cat asks for live in disk block 48: in a
fresh fs.img from this build, README is inode 2, its data blocks are 48, 49 and 50, and
it is 2,441 bytes long (read straight out of…✓30The buffer cachexv6 keeps exactly 30 disk blocks in memory at a time. Every file-system operation, on
every hart, goes through those 30 buffers: reading a directory, allocating a block,
updating an inode, writing the log. The buffer…✓31The log: begin_op, commit and group commitAppending to a file changes several disk blocks: the free-block bitmap, the new data
blocks, and the inode that points to them. If the power fails after some of those writes
and before others, the disk is left…✓32Crash recoveryYou type echo hi > f on a freshly made disk, and the power goes out. Not before the
command, not after it: in the middle of the kernel writing its changes to the disk. When
the machine comes back, is there a file…✓33The life of an inodeYou type cat README &; rm README. Two programs start at once: cat opens README and
starts printing it, and on another hart rm deletes it. And yet cat prints the whole
file, to the last line. Only when cat closes…✓34Path lookupA program says open("/a/b/c"). The disk has no idea what /a/b/c means. It knows inode
numbers and blocks. Somewhere between the two, the kernel must walk from the root
directory to a, from a to b, from b to…✓35Creating and naming filesecho hi > newfile creates a file. That sounds like one action, but on the disk it is
three: an inode must be found and marked used, a directory entry must be written
that names it, and the directory’s own inode is…✓36Reading and writing a filewc README reads 2441 bytes, 512 at a time. echo hi > f writes three bytes, two of
them in one write and the newline in another, and the second lands right after the
first. Both look trivial from user space. In the…✓
Devices and putting it all together
37A keystroke's journeyThe shell has printed $ and is waiting. You press l, then s, then Enter. Within a
fraction of a millisecond each letter appears on your screen, and after Enter the shell
has the line "ls\n" in its buffer.…✓38Output to the console from three hartsThere is one screen and three harts. When several programs print at once, whose bytes go
out first, and how finely can they be mixed? The answer in xv6 is precise, and it is
decided by locks: a sleep lock that…✓39Pipesls | wc prints 24 96 608: ls produced 608 bytes and wc counted them, yet ls
never knew wc existed, and neither program ever touched a file. Between them sat a
pipe: 512 bytes of kernel memory, two counters, one…✓40Capstone: the shell running ls | wcYou type seven characters and press Enter:✓
The dance of privilege
41Every transition: mode, stack and page tableFreeze xv6 at any instruction, on any hart, and ask three questions. Which privilege
mode? User, supervisor or machine. Which stack? Whatever memory sp points into.
Which page table? Whatever satp names. If you can…✓42One hart's stacks, from power-on to the first user instructionEvery instruction a CPU executes runs with some value in sp, and that value is always a
claim: “this is my stack.” This tour follows the claim on hart 0 from the moment QEMU
powers it on, when sp is simply 0, to the…✓43A system call, CSR by CSRTour 5: Life of a system call followed echo hi’s write(1, "hi", 2) through the kernel’s layers. This tour
follows the same system call with a different lens: the control and status registers
(CSRs) and the…✓44One interrupt, three landing sitesA timer interrupt is the same event every time: on some hart, the clock passes
stimecmp. But where it lands depends entirely on what that hart was doing. There are
exactly three possibilities in xv6, and they differ…✓45One complete time slice on three hartsThis tour follows one complete time slice on one hart, from start to finish, and at
every step it answers the master question (Mode, stack and page table: the master question): which mode,
which stack, which page…✓46Boot: returning from a trap that never happenedA RISC-V hart can lower its privilege in exactly one way: by returning from a trap.
mret returns from a machine-mode trap, sret from a supervisor-mode trap. There is no
“enter supervisor mode” instruction. So to get…✓47Where a suspended process livesA process that is not running is not anywhere in the CPU. No hart holds its registers, no
pc points into its code. Yet some hart can pick it up at any moment and continue it as if
nothing had happened. So everything…✓48Breaking the invariantsThe transitions of this group work because a handful of rules hold at every instant: a lock
here, interrupts off there, a page mapped twice, a register copied before it can be lost.
The code states some of them as…✓
Locks and interrupt state
49Every lock in one ls | wcTour 40: Capstone: the shell running ls | wc drove ls | wc from the keyboard to the answer. This tour drives it again and
counts every lock on the way: every acquire of a spinlock, every
acquiresleep of a sleep-lock,…✓50noff and intena through a sleep, a yield and an interruptEvery hart keeps three small facts about interrupts. SIE is one bit in sstatus: may an
interrupt be taken right now? noff (mycpu()->noff) counts how many push_off calls
are still open. intena (mycpu()->intena)…✓51The lock-order graph, measuredTour 18: Lock ordering: how xv6 avoids deadlock derived xv6’s lock order by reading the code: find each place that holds two
locks, check that it agrees with the others. This tour does the opposite. We let the…✓52Breaking the lock rulesxv6’s locking rests on a short list of rules: never acquire a lock you already hold; never
sleep holding a spinlock; keep interrupts off while you hold one; let intena travel with the
thread; register for a wakeup…✓