New system calls for xv6, MIT's small Unix-like teaching kernel for RISC-V. Written for the Operating Systems course at Ben-Gurion University in 2025 (assignment 1).
The work is in the kernel's process code, kernel/proc.c and kernel/sysproc.c, plus the syscall plumbing that connects user programs to it. Every other file matches the xv6 the course started from, so git diff cb0971f shows exactly what I added.
memsize() returns how many bytes of memory the calling process is using (its sz).
Exit messages. exit(status, msg) now takes a message, kept in the process table entry. wait(&status, msg) copies the child's message back into the parent's memory. The xv6 shell prints it, so a program can say something on its way out. Messages hold up to 31 characters (MAXEXITMSG is 32 bytes with the null). A longer one is cut to fit, and a null or unreadable pointer becomes an empty message, because exit must never fail. Every user program in the tree was updated to the new signatures.
forkn(n, pids) creates n children (1 to NFORKN, which is 16) in one call:
- The parent gets
0back, plus every child's PID inpids. - Child
igetsiback (1 ton), so each child knows which one it is without any extra coordination. - It's all or nothing. Every child is prepared first and only marked runnable once all of them exist. If one fails, or
pidscan't be written, the ones already created are freed and the call returns-1.
waitall(&n, statuses) blocks until every child has exited, then returns how many there were and all their exit statuses at once.
| Program | What it shows |
|---|---|
helloworld |
The basics: a user program that prints and exits |
goodbye |
An exit message: the shell prints Goodbye World xv6 when it ends |
memsize_test |
memsize before and after a 20 KB malloc, and after free |
bigarray |
forkn + waitall: sums 65,536 integers across 6 children |
syscalltests |
Tests for all four calls, including their error paths |
bigarray fills an array with 0 to 65,535 and calls forkn(6, pids). Each child sums its own slice and returns the partial sum as its exit status. The parent collects all six with waitall and adds them up. The total should be 2,147,450,880, the sum of 0 to 65,535, which just fits in a 32-bit int.
syscalltests runs each test in its own child. A failing test exits with status 1 and uses its exit message to say why, so the tests rely on the feature they check. They cover:
memsizegrowing and shrinking exactly withsbrk- Exit messages that are normal, too long, null, or a bad pointer, and
waitwith a bad message pointer forknwithnout of range or a badpidspointer, leaving no children behindforkn(16): distinct PIDs, and each index from 1 to 16 used once- All or nothing: with only 10 free process slots,
forkn(16)fails andforkn(10)still succeeds, so the partial children were freed - 20 rounds of
forkn(16)in a row, which would run out of slots if any leaked waitallwith no children, and with children made by plainfork
GitHub Actions builds the kernel and runs syscalltests and xv6's own usertests -q in QEMU on every push, with .github/run-tests.sh.
- Why
memsizedoesn't drop afterfree. xv6'smallocgrows the process withsbrk, butfreeonly hands memory back to the user-space allocator, never to the kernel. The process stays the size it grew to. - Why the message is copied twice. The child's message lives in its own address space, which is gone by the time the parent asks. So
exitcopies it into the kernel's process table, andwaitcopies it out into the parent withcopyout. - Why
forknwaits before making children runnable. If each child ran the moment it was created, a later failure would leave some children already running with no clean way to undo them. Preparing them all first keeps the call all or nothing. The children also get their parent only at the end, underwait_lock, so nowaitcan see a child that might still be thrown away.
The repo includes a devcontainer with the RISC-V toolchain and QEMU. Open it in VS Code and choose "Reopen in Container", or install gcc-riscv64-linux-gnu and qemu-system-misc yourself (on Windows, WSL works). Then:
make qemuAt the xv6 prompt, run syscalltests, helloworld, goodbye, memsize_test or bigarray. Quit QEMU with Ctrl-A, then X.
To run the full test suite the way CI does:
bash .github/run-tests.sh| File | Change |
|---|---|
kernel/proc.c |
forkn, waitall, and exit messages in exit / wait |
kernel/proc.h |
exit_msg field in the process struct |
kernel/param.h |
MAXEXITMSG and NFORKN |
kernel/sysproc.c |
sys_memsize, sys_forkn, sys_waitall, new sys_exit / sys_wait arguments |
kernel/syscall.c, kernel/syscall.h |
Syscall numbers 22 to 24 and their dispatch entries |
kernel/defs.h |
Kernel prototypes for the new and changed functions |
user/user.h, user/usys.pl |
User-space declarations and stubs |
user/sh.c |
Prints a finished command's exit message |
user/*.c |
The five programs above, and every existing program moved to the new exit / wait |
The original xv6 README and its credits are in README, and the MIT license is in LICENSE.