n
| CPSC 331 | Operating Systems | Spring 2026 |
This project has several goals:
Your primary task is to add a system call to xv6 as described in the project README. You'll also write a small user program. There is very little code to write; the crux of the assignment is gaining understanding of several aspects of xv6.
Review the course policy on academic integrity.
Certain uses of AI are permitted on this assignment. AI use is not
required.
The basic rule: you may use the code completion features of Copilot but may not use features (such as the coding agent) where code is generated from English language prompts. It is also essential that you understand and think critically about any code suggested by Copilot, both to help develop your C programming skills and because while the code suggestions are often uncannily on target, they are not always exactly what you want.
Using the code explanation features of Copilot Chat is permitted, though be careful that this doesn't spill over into code generation.
Review the policy on late work.
Revise and resubmit applies for this assignment. Review the details of how revise and resubmit works.
To hand in your project:
Make sure that you have included a comment at the beginning of your program containing your name and a description of the program, and that you have auto-formatted it.
Copy your entire initial-xv6 directory to your handin directory (/classes/cs331/handin/username).
Check that the handin was successful and that the directory structure is correct: your handin folder /classes/cs331/handin/username should contain a folder initial-xv6 which in turn contains a src directory.
Do the steps outlined in reddish boxes below before you start writing code.
Copy the initial-xv6 directory (and all of its contents) from /classes/cs331 to your workspace folder (~/cs331/workspace). Make sure that you end up with the initial-xv6 directory inside your workspace folder, don't just copy its contents.
Copy xv6-edit-makefile.sh from /classes/cs331/tester to your tester folder (~/cs331/workspace/tester). (Copying the whole tester folder was part of the setup for project 1. This step just grabs an updated version of one of the files in that folder.)
Very important! One of the things the autoformatter can do is reorganize the #include directives in your code. While useful in some circumstances, this will break the xv6 code. To turn off sorting includes:
From the initial-xv6/src directory, use make to perform various tasks:
make # compile make qemu # compile anything needed, then run using qemu make clean # remove all compiled elements to force a rebuild from scratch on the next make
make qemu will open a new window with the xv6 shell. Type ls in the qemu window to see the programs available to run (i.e. the available commands). Exit qemu by closing the qemu window — there is no exit command.
Note: if you click in the qemu window, it will grab the focus. (You'll see the message "Press Ctrl-Alt-G to release grab" displayed in the title bar if this happens.) On some keyboards this specifically means the right control and right alt keys — if you have two control and/or alt keys and the one you try doesn't work, try the other one.
From the initial-xv6/src directory, use make to perform various testing-related tasks:
make test # compile and run the tests make test-clean # remove compiled elements related to running the tests
You can also run
./test-getreadcount.sh
from the top-level initial-xv6 directory as described in the project README.
The provided test cases cover everything; you do not need to create additional tests. Note that the tester setup is a bit delicate and test 1 (hello) may not succeed if getreadcount hasn't been implemented yet — test hello by running it in xv6 as described in that section below, and hold off on running the provided tests until you are ready to test getreadcount as well.
System calls in xv6 were discussed in class. (See the posted slides.) For additional reference:
The following provide more detailed explanations of trap and system call handling in xv6. This is of relevance to this project, but these readings go into greater depth than is needed to complete the project. Check them out if you want to know more, or if you have questions not resolved by the links above and the materials from class.
To search the source code for a particular string, you can utilize the "Find in Folder" feature of VSCode or the command line tool grep.
Find in Folder: In the Explorer panel in VSCode, either right-click on the folder to search or click on the folder to search and press shift-alt-F. Type the search string in the box at the top of the Search panel. To only search certain files within the subdirectory, edit the "files to include" line — ./initial-xv6/src will search only source files for this project and not your entire workspace, for example.
grep: The typical usage is
grep [-i] pattern files
where pattern and files are replaced by the search pattern and the files to search, respectively. -i is an optional switch which makes the search case-insensitive. Multiple files can be specified by listing the filenames separated by spaces. You can get fancier with the pattern (look up grep in the man pages for more information) but a simple string is sufficient in many cases. For example, run
grep "xv6" README
while in the initial/src directory to get all the lines of README containing the string "xv6" (without the quotes).
grep "getpid" */*.?
will print lines containing "getpid" from all files with a single-character extension in a subdirectory of the current directory — this will search header (.h), C source (.c), and assembly language (.S) files, which are likely the ones you want to search.
The following resources provide additional background about xv6 that is not needed for this project, but which may be of interest for the big picture of what is going on:
You have two tasks: add a system call to xv6 as described in the project README, and create a small user program.
To do this:
Work through the sections below, in order. For each, read through the whole section to see what it contains before getting started writing code. There are a number of specific directions and hints that will make things a great deal easier if you are aware of them rather than just launching straight into trying to achieve the specifications.
Create a system utility hello which prints out greetings. It should take the recipient of the greeting as a command line parameter and print out hello <recipient>. For example, when invoked as
hello world
it should print out
hello world
Additional specifications:
Follow naming conventions and the organization of the provided xv6 code: name the program file hello.c and put it in the user subdirectory.
To do this:
Start by writing a standard C program that prints the greeting as specified, then read through the "Notes on writing xv6 programs" below to modify it for xv6. (Don't try to compile or run your program yet!)
Update the provided Makefile (in the initial-xv6/src directory): locate the definition of UPROGS in initial-xv6/Makefile and add hello. (To make things easier to read, the definition of UPROGS has been spread out over multiple lines. So that make still interprets this as a single line, make sure that the character \ appears at the end of every line but the last.)
If you followed the naming conventions and correctly updated the Makefile, you should be able to compile xv6 with
make
(Be in the same directory as the Makefile when you run make.)
Boot xv6 by starting up qemu with
make qemu
Once qemu starts up, try
$ ls
to see the available programs and files. (Note: the $ prompt is included above to indicate that you are typing this in the qemu window rather than the Linux terminal. Don't actually type the $, just type ls.) hello should be listed.
Test your hello program with
$ hello world
(or the recipient of your choice). Hold off on running the provided tests for now — test 1 should pass if hello is implemented correctly, but the tester setup is a bit delicate and if you haven't implemented the getreadcount system call yet (test 2), even test 1 may not succeed.
Notes on writing xv6 programs:
You'll need two #includes in nearly every xv6 program: types.h, which defines some important types, and user.h, which contains the headers for the available system calls and other library routines. Be sure to include them in that order!
You are writing a C program for xv6 rather than Linux, which means you can only use the xv6 standard libraries, not the full set of C standard libraries. Headers for the xv6 system call and user library routines are defined in include/user.h. Unfortunately, you can't look them up in the man pages to find out more — the main source of documentation is the xv6 source code itself, and that can be rather minimal. To locate the right bit of source code, you can use grep or Find in Folder to determine which file(s) you need, then, if you are looking for a subroutine definition, use the xv6 file index can help you browse the file(s) identified for the relevant definition.
xv6's printf behaves like the C standard dprintf — the first parameter is the file descriptor for where the output is to be printed. (printf.c file reference) xv6 uses the standard values (0 for standard input, 1 for standard output, and 2 for standard error) but does not define named constants — use 0, 1, 2 directly.
You must call exit() to return from main in order to allow the OS to properly clean up the terminating process. (return or simply letting main end may lead to strange and undesirable results.) Unlike in Linux, there is no parameter to exit().
Implement the getreadcount system call as described in the README.
Hints/suggestions:
Recall from class that you need three things to implement a system call: a library routine (assembly code) that traps to the OS, a handler (C) that does the work of the system call, and an entry in the system call table. See the slides to find out what files those things go in. You can also find the places you need to modify by searching for a similar already-existing system call such as getpid (as suggested in the project README) — use Find in Folder or grep to search the .c, .h, and .S files in subdirectories of src for occurrences of "getpid".
The sys_ system call handlers are found in both kernel/sysfile.c and kernel/sysproc.c. Which is the appropriate place for sys_getreadcount? (One way to help you decide is to look at the system calls defined in each — which set does getreadcount seem more like?)
For the "do the work of the system call" part, the description of getreadcount says to return the value of a counter which is incremented every time read() is called. There are a bunch of things to figure out here:
For starters, where should that counter be stored? You can get a hint by looking at sys_getpid — proc->pid refers to a variable proc and a field pid in a struct. (See pages 34-35 in for more about structs.)
Next, where is that struct defined? Definitions of types are often in header files (filenames ending with .h), so you can use Find in Folder or grep to search all of the .h files in the include subdirectory for "pid".
When you find a promising file, take a look at the source code — what struct does the pid field belong to, and what is that struct's purpose? Is that a place that makes sense for storing the read counter?
Once you add readcount as a field of struct proc (like pid) you can write the body of sys_getreadcount. But don't forget that you also need to initialize and update the counter. Where do those things go? For the initialization, a hint is to look at where something else in struct proc is initialized (since we've been using pid as an example of a potentially similar thing — it is also a piece of information associated with a process), such as pid. Use Find in Folder or grep to search for "->pid" to look for places where pid is assigned a value. (If you use grep, you'll need to escape the leading dash so grep doesn't think you are specifying an option — use the pattern "\->pid" instead.) When you've found what you are looking for, look around at the context a bit — what are the subroutines in kernel/proc.c for? What do you suppose allocproc's job is?