xv6 Structure

The xv6 codebase is organised into four distinct parts:

Makefile

kernel/   builds the OS kernel

user/     builds xv6 user programs

mkfs/     builds fs.img using those user programs

QEMU      boots kernel + fs.img

Kernel Reading Order

OrderFileGenerated objectKindPurpose
1kernel/types.hHeaderBasic integer/type aliases.
2kernel/param.hHeaderKernel-wide size limits.
3kernel/memlayout.hHeaderPhysical/virtual memory map.
4kernel/riscv.hHeaderRISC-V registers, paging, interrupt helpers.
5kernel/defs.hHeaderCross-file kernel declarations.
6kernel/kernel.ldLinker scriptKernel memory layout at link time.
7kernel/entry.Skernel/entry.oAssemblyFirst code after QEMU jump.
8kernel/start.ckernel/start.oCEarly CPU setup before main.
9kernel/main.ckernel/main.oCKernel initialization order.
10kernel/spinlock.hHeaderSpinlock structure.
11kernel/spinlock.ckernel/spinlock.oCShort critical-section locking.
12kernel/printf.ckernel/printf.oCKernel printing and panic.
13kernel/string.ckernel/string.oCBasic memory/string helpers.
14kernel/kalloc.ckernel/kalloc.oCPhysical page allocator.
15kernel/vm.hHeadersbrk allocation mode constants.
16kernel/vm.ckernel/vm.oCPage tables and virtual memory.
17kernel/proc.hHeaderProcess, CPU, trapframe structures.
18kernel/proc.ckernel/proc.oCProcesses, scheduling, sleep/wakeup.
19kernel/swtch.Skernel/swtch.oAssemblyLow-level context switch.
20kernel/trampoline.Skernel/trampoline.oAssemblyUser/kernel trap transition code.
21kernel/kernelvec.Skernel/kernelvec.oAssemblyKernel-mode trap vector.
22kernel/trap.ckernel/trap.oCTrap, syscall, interrupt handling.
23kernel/syscall.hHeaderSyscall number definitions.
24kernel/syscall.ckernel/syscall.oCSyscall dispatch and argument fetching.
25kernel/sysproc.ckernel/sysproc.oCProcess-related syscalls.
26kernel/uart.ckernel/uart.oCLow-level serial device driver.
27kernel/console.ckernel/console.oCConsole input/output layer.
28kernel/plic.ckernel/plic.oCExternal interrupt controller.
29kernel/virtio.hHeaderVirtio disk protocol definitions.
30kernel/virtio_disk.ckernel/virtio_disk.oCVirtual disk driver.
31kernel/buf.hHeaderDisk buffer structure.
32kernel/bio.ckernel/bio.oCBuffer cache and LRU block reuse.
33kernel/fs.hHeaderOn-disk filesystem format.
34kernel/sleeplock.hHeaderSleeping lock structure.
35kernel/sleeplock.ckernel/sleeplock.oCLocks that sleep while waiting.
36kernel/file.hHeaderIn-memory file/inode/device structs.
37kernel/fs.ckernel/fs.oCInodes, directories, path lookup.
38kernel/log.ckernel/log.oCFilesystem transaction log.
39kernel/file.ckernel/file.oCOpen-file table and file operations.
40kernel/fcntl.hHeaderFile open flags.
41kernel/stat.hHeaderFile metadata structure.
42kernel/sysfile.ckernel/sysfile.oCFile-related syscalls.
43kernel/pipe.ckernel/pipe.oCPipes for process communication.
44kernel/elf.hHeaderELF executable file format.
45kernel/exec.ckernel/exec.oCLoad and run user programs.

User-Space Reading Order

OrderFileKindPurpose
1user/user.hHeaderUser-visible syscall and library declarations.
2user/usys.plGeneratorGenerates user syscall stubs.
3user/usys.SGenerated assemblyUser-side syscall wrappers using ecall.
4user/ulib.cUser libraryBasic user-space helper functions.
5user/printf.cUser libraryUser-space formatted printing.
6user/umalloc.cUser librarySimple user-space memory allocator.

User Programs

OrderFileKindPurpose
1user/init.cUser programFirst user process.
2user/sh.cUser programxv6 shell.
3user/ls.cUser programList directory contents.
4user/cat.cUser programPrint file contents.
5user/echo.cUser programPrint arguments.
6user/grep.cUser programSearch text.
7user/wc.cUser programCount lines, words, bytes.
8user/mkdir.cUser programCreate directories.
9user/rm.cUser programRemove files.
10user/ln.cUser programCreate hard links.
11user/kill.cUser programKill a process.
12user/stressfs.cTest programStress filesystem behavior.
13user/forktest.cTest programStress process creation.
14user/grind.cTest programStress processes/filesystem/concurrency.
15user/usertests.cTest programBroad xv6 test suite.

mkfs and Filesystem Image

mkfs is a host-side tool that runs on the build machine before xv6 boots. It packs compiled user binaries into fs.img, the virtual disk QEMU presents to xv6.

OrderFile / ArtifactKindPurpose
1kernel/fs.hShared format headerDefines xv6 on-disk filesystem layout.
2mkfs/mkfs.cHost toolCreates fs.img using xv6 filesystem format.
3user/_init etc.RISC-V binariesCompiled user programs inserted into fs.img.
4fs.imgDisk imageVirtual disk passed to xv6 by QEMU.

Full Build-to-Boot Pipeline

OrderStepRuns where?Purpose
1MakefileHostCoordinates the build.
2Build kernel filesHost cross-compilerProduces kernel/kernel.
3Build user support filesHost cross-compilerProduces user runtime objects.
4Build user programsHost cross-compilerProduces user/_init, user/_sh, etc.
5Build mkfs/mkfs.cHost compilerProduces host executable mkfs/mkfs.
6Run mkfs/mkfsHostPacks user binaries into fs.img.
7Start QEMUHostCreates virtual RISC-V machine.
8Run kernel/kernelQEMU/RISC-VBoots xv6 kernel.
9Run /initxv6 user modeStarts first user process.
10Run /shxv6 user modeStarts shell.

Runtime flow:

QEMU

kernel/entry.S

kernel/start.c

kernel/main.c

disk + filesystem init

kernel/exec.c loads /init

user/init.c starts /sh

user/sh.c runs commands

Makefile

The Makefile coordinates three separate builds and then launches QEMU.

Build Pipeline

StepInputLinker ScriptOutput
Kernel buildkernel/*.c + kernel/*.Skernel/kernel.ldkernel/kernel
User builduser/*.c + usys.Suser/user.lduser/_init etc.
mkfs (Host)mkfs/mkfs.cmkfs/mkfs
Filesystem imagemkfs/mkfs + user/_init etc.fs.img
Bootkernel/kernel + fs.imgQEMU launches xv6

Linking all kernel object files with kernel.ld produces three files:

FileContent
kernel/kernelLinked kernel binary loaded by QEMU
kernel/kernel.asmMixed source/disassembly for inspection
kernel/kernel.symAddress-to-symbol map for debugging

Every user program links against a small runtime library:

ObjectSourceRole
ulib.oulib.cString helpers and syscall wrappers
usys.ogenerated usys.SSyscall stubs (ecall wrappers)
printf.oprintf.cUser-space printf
umalloc.oumalloc.cUser-space malloc/free

Notes:

  • usys.S is generated by running usys.pl through Perl where each stub loads the syscall number into a7 and executes ecall.
  • User programs are named with a leading underscore (user/_init) to avoid clashing with host tools. mkfs strips it when packing into fs.img.
  • forktest omits printf.o and umalloc.o to stay small enough to max out the process table.

These are the compiled user programs packed into fs.img by mkfs:

ProgramPurpose
_initFirst user process started by the kernel
_shxv6 shell
_lsList directory contents
_catPrint file contents
_echoPrint arguments
_grepSearch text
_wcCount lines, words, bytes
_mkdirCreate directories
_rmRemove files
_lnCreate hard links
_killKill a process
_zombieDemonstrate zombie process behavior
_forktestStress process creation
_stressfsStress filesystem writes
_usertestsBroad xv6 test suite
_grindStress processes, filesystem, and concurrency
_logstressStress filesystem logging
_forphan / _dorphanTest orphaned process behavior

Toolchain

ToolRole
$(CC)Compiles C and preprocessed .S assembly
$(LD)Links object files into binaries
$(OBJDUMP)Generates .asm and .sym files for inspection
gcc (host)Compiles mkfs/mkfs.c — runs on build machine, not RISC-V

Compiler Flags

FlagPurpose
-Wall -WerrorWarnings as errors
-OBasic optimization
-ggdbGDB-friendly debug info
-gdwarf-2DWARF v2 debug format
-fno-omit-frame-pointerKeep frame pointers for stack traces
-march=rv64gcTarget 64-bit RISC-V with standard extensions
-mcmodel=medanyAddressing for code linked at 0x80000000, not near zero
-MDEmit .d dependency files for incremental builds
-ffreestandingNo hosted C environment assumptions
-nostdlibDo not link standard library or startup files
-fno-commonCatch duplicate global definitions at link time
-fno-builtin-*Prevent GCC substituting xv6’s own memcpy, printf etc. with libc versions
-fno-stack-protectorNo stack canary — kernel has no runtime support for it
-fno-pie -no-pieFixed-address binaries; xv6 does not use position-independent code
-I.Include headers relative to project root

Linker Flags

FlagPurpose
-z max-page-size=4096Align ELF segments to 4 KiB, matching xv6’s page size.

QEMU Launch

SpecValue
Machinevirt (generic RISC-V virtual board)
CPUriscv64
Cores3
RAM128M
Kernelkernel/kernel
Diskfs.img via virtio-blk
DisplayNone (-nographic, serial console only)

The Linker Script

The kernel/kernel.ld tells the linker how to lay out the final kernel binary in memory. It defines where code goes, in what order, and what symbols the rest of the kernel can use to find section boundaries.

Memory Layout

The linker arranges the kernel into sections in this order, starting at 0x80000000:

SectionContentsNotes
.textKernel code_entry placed first, then all other code
.rodataRead-only dataConstants, string literals, aligned to 16 bytes
.dataInitialized globalsNon-zero globals, stored in the binary
.bssZero-initialized globalsNot stored in binary; kernel zeroes at startup

.text:

  • Executable code only, read and execute permissions.
  • _entry lands at exactly 0x80000000.
  • Trampoline is carved out at the end, aligned to a 4 KiB page boundary
  • user-kernel transitions and must be mapped at the same virtual address in every page table.
  • trampoline.S declares .section trampsec, which the linker places here.

.rodata:

  • Read-only.
  • Aligned to 16 bytes.
  • Contains string literals and constant arrays that must not be modified at runtime.
  • Separate from .data so the OS can enforce read-only page permissions.

.data:

  • Read-write.
  • Aligned to 16 bytes.
  • Holds initialized globals with non-zero starting values, embedded in the binary and copied into RAM at load time.

.bss:

  • Read-write.
  • Aligned to 16 bytes.
  • Holds zero-initialized globals.
  • Not stored in the binary: the linker records only the size and the kernel zeroes the region at startup.

Note:

The 16-byte alignment across data sections ensures efficient memory access. On a 64-bit RISCV system, 16 bytes covers two 64-bit registers, which compilers exploit for multi-word loads and stores. Unaligned access can cause hardware exceptions or significant slowdowns on some architectures.

Symbols

The linker script exports two symbols the kernel uses at runtime:

SymbolMeaning
etextAddress of the end of the text section
endAddress of the end of the entire kernel image
_trampolineStart address of the trampoline page

The .rodata

Size: 2,080 bytes.

Contents:

  • Lock debug names: passed to initlock() and initsleeplock() at startup.
  • Panic and error messages: invariant violation and error detection strings across the kernel.
  • Boot messages: printed during initialization and secondary hart startup.
  • Format strings: used in trap handling and process dumps.
  • Path strings: "/" and "/init" used during first process setup.
  • Compiler-promoted immutable tables: digit lookup, process state names, syscall dispatch table.

Note: No const globals exist in xv6 everything here was placed by the compiler.

Permissions: xv6 maps this range as PTE_R | PTE_W in the kernel page table, so read-only is by convention only.

The .data

Size: 24 bytes almost nothing.

Contents:

  • Thenextpid = 1 in proc.c is the only meaningful non-zero global.
  • A static local first in kalloc.c to detect the first kfree call.

Why so small: most kernel state is zero-initialized and lives in .bss; string data lives in .rodata.

The .bss

Size: 103,224 bytes the bulk of xv6’s kernel state.

Contents: all global structs declared without explicit initializers. Not stored in the binary; the kernel zeroes this region at startup.

SymbolSizeFirst used byPurpose
stack032 KiBentry.S / start.cBoot stack for all CPUs before main
cons168 Bconsoleinit()Console input buffer and lock
tx_lock24 Bconsoleinit()UART transmit spinlock
tx_chan4 Bconsoleinit()UART transmit sleep channel
tx_busy4 Bconsoleinit()UART transmit busy flag
pr24 Bprintfinit()printf serialization lock
panicked4 Bprintfinit()Flag set when kernel has panicked
panicking4 Bprintfinit()Flag set while panic is in progress
kmem32 Bkinit()Physical page free list and its lock
kernel_pagetable8 Bkvminit()Pointer to the kernel page table
proc23 KiBprocinit()Process table 64 process slots
cpus1 KiBprocinit()Per-CPU state (current process, scheduler context, lock depth)
wait_lock24 Bprocinit()Condition lock for parent/child wait coordination
pid_lock24 Bprocinit()Protects nextpid counter
tickslock24 Btrapinit()Serializes access to the ticks counter
ticks4 Btrapinit()Wall-clock tick counter incremented by timer interrupts
bcache34 KiBbinit()Buffer cache 30 LRU disk block buffers
sb32 Biinit()Superblock filesystem geometry and metadata
itable6.7 KiBiinit()In-memory inode table
log168 Biinit()Filesystem transaction log state
ftable4 KiBfileinit()Open file table shared across all processes
devsw160 Bfileinit()Device switch table mapping major numbers to read/write handlers
disk320 Bvirtio_disk_init()Virtio disk driver state and descriptor ring
initproc8 Buserinit()Pointer to the init process
started4 BAfter CPU 0 initSignals secondary CPUs that CPU 0 has finished initialization

The Free Memory

Starts at end (linker symbol at the end of .bss) and extends to PHYSTOP (0x88000000).

  • Not part of the kernel binary — raw physical RAM managed at runtime.
  • kalloc.c tracks it as a free list of 4 KiB pages.
  • kinit() walks from end to PHYSTOP and adds each page to the free list.
  • kalloc() pops a page from the free list; kfree() pushes one back.
Allocationkalloc() sitekfree() sitePagesWhat gets a page
Kernel page tablekvmmake()Never1021 root + 3 L1 + 98 L0 nodes for the Sv39 tree
Kernel stacksproc_mapstacks()Never64One per process slot, called inside kvmmake()
Virtio ringsvirtio_disk_init()Never3Descriptor ring, available ring, used ring for disk DMA
Trapframeallocproc()freeproc()1 per processPer-process trap register save area
User page tableproc_pagetable()proc_freepagetable()1 per processRoot page table, called inside allocproc()
User memoryuvmalloc()uvmdealloc() / uvmfree()variesUser process address space pages during exec and sbrk
Intermediate page table nodeswalk() with alloc=1freewalk()variesL1 and L0 pages as user page tables are built
Fork copyuvmcopy()uvmfree() on child exit1 per mapped pagePhysical page copies for child during fork
Page faultvmfault()uvmfree() on process exit1 per faultOn-demand page on fault
Pipe bufferpipealloc()pipeclose()1 per pipePipe kernel buffer
exec argvsys_exec()After copy / on errorvariesTemporary argument string pages during exec