Lab 3: Introduction to xv6, Pre-Lab Reading &
System Calls
Pre-Lab reading exercises
1. Make & Makefiles. Make is a Unix/Linux build tool that drives compilation via
user-defined Makefiles. Read any two short guides end-to-end to understand targets,
dependencies, variables, and pattern rules.
• Makefile tutorial (Colby)
• Makefile quick guide (RIP Tutorial)
2. GDB (GNU Debugger). Learn basic commands: run, break, next, step, finish,
backtrace, print, watch.
• GDB quick guide (Tutorialspoint)
Pre-Lab exercise: Xv6 installation (preferably on your own sys-
tem)
xv6 is a teaching OS from MIT that captures core Unix ideas in a small codebase. Please
use a recent Ubuntu LTS (18.04/20.04/22.04) if possible.
Ubuntu / Debian (recommended)
Install prerequisites:
sudo apt-get update
sudo apt -y install build-essential gdb coreutils util-linux sysstat procps wget tar q
Fetch xv6 and build:
wget [Link]
tar -xf [Link]
cd xv6-public
make
Run xv6 (text mode):
make qemu-nox
If the boot completes successfully you should see:
1
init: starting sh
$
Try a command:
$ ls
Exit QEMU: press Ctrl+A, release, then press X.
Windows 10/11
Install WSL (Windows Subsystem for Linux) from Microsoft’s guide, then follow
the Ubuntu steps inside WSL.
• WSL Installation (Microsoft)
Windows 7/8 or macOS
Use an Ubuntu VM or dual-boot. After Ubuntu is available, follow the Ubuntu steps
above.
• Dual-boot overview
• Ubuntu in VirtualBox
FAQ / Troubleshooting
• If make qemu-nox says “Couldn’t find a working QEMU executable”, try:
sudo apt install qemu-system-x86
• On bare-metal Ubuntu (not VM), you can also try:
sudo apt install qemu-kvm
• To quit QEMU monitor in no-GUI mode, see: [Link]/a/1211516
Exercises
The goal of these xv6 exercises is to extend OS functionality while learning how to
compile, run, and modify a small Unix-like kernel. You will touch the system call layer,
basic process management, user programs, and simple instrumentation. Typical files you
may read/modify include: proc.c, proc.h, syscall.c, syscall.h, sysproc.c, defs.h,
user.h, usys.S.
Notes.
• user.h lists user-visible syscall prototypes.
• usys.S contains stubs that invoke the trap for each syscall.
• syscall.h maps syscall names to numbers.
• syscall.c parses args and dispatches to implementations.
• sysproc.c implements process-related syscalls you add.
2
• proc.h defines struct proc
• proc.c covers process syscalls and the scheduler.
Q1. Adding user programs in xv6
Task
You have a basic user-space prompt after make qemu or make qemu-nox. We will:
1. Examine the provided sample program testcase.c (prints “Hello, world!”) and the
modified Makefile entries that build it. Build and run:
$ ./testcase
Hello, world!
2. Explore sources of several built-in user programs (cat.c, ls.c, wc.c, echo.c, grep.c).
Notice minor syntax/ABI differences from standard Linux userland where applicable.
Execute them inside xv6 to confirm behavior.
3. Modify the xv6 shell (sh.c) to change the prompt (e.g., Prompt>). Search for the
current $ prompt and replace appropriately.
4. Complete cmd.c: it should fork(); the child exec()s a program provided via com-
mand line; the parent wait()s for completion. Example:
$ ./cmd ls
$ ./cmd echo hello
Use the provided argument setup in cmd.c and implement the core logic where marked.
5. Update the Makefile to build and install your new/modified user programs. Rebuild
and test.
Hints / References
• Build/run: make, make qemu-nox. Add programs in the UPROGS section of the Makefile.
• User-space examples: user/ directory (e.g., cat.c, echo.c).
• Shell: sh.c (prompt string and command loop).
Q2. New system calls in xv6
Task
Add a syscall int helloYou(char *name); that prints name to the console from kernel
mode (use cprintf). Steps:
1. Study an existing syscall with multiple arguments (e.g., open in sysfile.c) and the
arg helpers argint, argptr, argstr in syscall.c.
2. Wire up your syscall end-to-end:
• Add a prototype to user.h.
3
• Add a stub in usys.S.
• Assign a syscall number in syscall.h.
• Register the handler in the table in syscall.c.
• Implement the kernel-side function in sysproc.c (or call into proc.c if needed).
3. Test with helloyou.c (provided or self-written), e.g.,
$ helloyou Alice
Hints / References
• Arg parsing and dispatch: syscall.c, syscall.h, usys.S.
• Process syscalls: sysproc.c.
• Printing in kernel mode: cprintf.
Q3. Exploring System Calls in xv6
Objective
See how user programs enter the kernel, where system calls are dispatched, and how to
instrument usage.
Task
Modify the kernel so that every time any system call is invoked by a process, a
per-process counter is incremented. When a process exits, print the total number of sys-
tem calls it executed in the format: [pid=#] syscalls=#. Track (at least) these syscalls:
fork, exit, wait, pipe, read, write, close, kill, exec, open, mknod, unlink,
fstat, link, mkdir, chdir, getpid, sbrk, sleep, uptime, dup. Create a small
user program usyscount.c that performs some I/O and exits to trigger the final print.
Help / References
• Where to look in code: syscall.c (dispatch), syscall.h (numbers), usys.S
(stubs), proc.c/proc.h (per-process data), sysproc.c.
• xv6 book (MIT): Traps and system calls chapter (the syscall() path).
• OSTEP: “System Calls” overview.
Q4. Measuring Average System Call Latency
Objective
Measure the overhead of kernel entry/exit using xv6’s timer ticks and simple per-process
accounting.
4
Task
Write latency.c that calls getpid() in a loop (e.g., 1000 times). Add minimal instru-
mentation so each process maintains a running total of time spent inside system calls
(using timer ticks) and a count of system calls. On process exit, print the average syscall
latency in ticks: [pid=#] avg_syscall_ticks=#. Keep the fast path lean (avoid heavy
work in the syscall hot path).
Help / References
• Where to look in code: trap.c (timer interrupt / ticks), syscall.c (entry/exit
hook points), architecture headers (riscv.h or x86.h) for reading time if needed.
• xv6 book (MIT): Timer interrupts and clock ticks.
• OSTEP: Timing and measurement best practices.