Programming Assignment #1
(100 points total)
Add System Calls to XV6
Due 11:45PM 6/3/2025 (firm)[ This is a BACKUP server. Do NOT use unless the primary server is DOWN. ]
[ This content is protected and may not be shared, uploaded, or distributed. ]This spec is private (i.e., only for students who took or are taking CSCI 350 at USC). You do not have permissions to display this spec at a public place (such as a public bitbucket/github). You also do not have permissions to display the code you write to implementation this spec at a public place since your code was written to implement a private spec. (If a prospective employer asks you to post your code, please tell them that you do not have permissions to do so; but you can send them a private copy and ask them to keep it private.)
Shortcuts:
You won't be adding much source code; rather, the goal is to get you comfortable with the workflow so that future assignments requiring more extensive changes to the codebase won't be too intimidating!
Your assignment is to build the xv6 operating system and boot it on an x86 emulator QEMU. You will add a new system call called `trace`, which turns console-based system call tracing for the calling process on and off. The system call also reports the total number of system calls called by the process since it started. You will also create a date system call which will be called via date.c. This will print out the date and time in a recognizable format. Finally, you will answer a few questions about this assignment and the xv6 operating system and submit.
Please read the entire document to ensure you have no trouble in setting up the dev environment and building a runnable version of xv6. Some important xv6 resources:
Please understand that the grader is only allowed to grade your assignments on a standard 32-bit Ubuntu 16.04 system, a.k.a, the "standard" system. Therefore, you must test your code on the "standard" system. The only way you will get points for your assignments is by passing tests on the "standard" system. You won't get any partial credit if your code can run on another system. We cannot give you any points based on your effort.
Please note that if you are using a virtual machine hypervisor to run your "standard" system, please set the number of CPUs to be one. Someone has reported that running with two CPUs would confuse xv6 since you can also set the number of CPUs in the xv6 Makefile. Unfortunately, I cannot verify if this really works of not. The bottomline is that if you are using a virtual machine hypervisor to run your "standard" system, you may want to set the number of CPUs to either one or two, whichever that works best. On AWS Free Tier, you can only have one CPU if you don't want to pay (and that's the system the grader will have).
Make sure you watch the lecture that covers the PA1 slides.
cat /etc/os-release
pwd
mkdir cs350
cd cs350
mkdir pa1
cd pa1
wget --user=USERNAME --password=PASSWORD http://bourbon.usc.edu/cs350-m25/programming/pa1/xv6-pa1-src.tar.gz
where USERNAME and PASSWORD are the username and password used to access protected content from our class web site.
After you have downloaded the xv6-pa1-src.tar.gz file, do:
tar xvf xv6-pa1-src.tar.gz
cd xv6-pa1-src
Now you are all set up!
make qemu
The emulator (QEMU) will start but it will echo on the terminal too. Use make qemu-nox. Doing so avoids the
use of X windows and is generally fast and easy. However, quitting is not so easy; to quit, you
have to know the shortcuts provided by the emulator. Type Ctrl+a
by x to exit the emulation. There are a few other commands like this available; to see them,
type Ctrl+a followed by an h.
When you see the xv6 command prompt `$ `, you can type the following to run the ls user space program:
ls
This program will do a directory listing of the files in the current working directory (in this case, it's `/`, the root directory).
You can try running other programs:
echo Hello
cat README
Here is a Youtube video that demonstrates xv6 installation procedure.
make qemu-nox-gdb
This will start qemu but qemu will wait for gdb to make a connection. In the 2nd terminal, type `gdb` to start the gdb debugger.
When you get the gdb prompt, type the following (don't type the `(gdb) ` shown on the left side because it's showing you the gdb prompt):
(gdb) source .gdbinit
Set any breakpoints you want in the GDB window. Then type `continue` (or just `c`) to start xv6.
Where can you set breakpoints? You can set a breakpoint either on a line number of a function name.
Please take a look at the xv6 source code. For now, let's set a breakpoint
at the beginning of the `exec()` function in the OS kernel. In the source code listing, it's line 6610.
Although in the `exec.c` file, it's on line 11. So, you can do either:
(gdb) break exec
or (the `list` gdb command list the source code):
(gdb) list exec.c:11
(gdb) break exec.c:11
Type `continue` (or just `c`) in the gdb window to continue and xv6 will start running.
When you hit the breakpoint, you can type `where` to see the call stack, or type `where full`
to see the call stack with all the variables in each stack frame. You can use the `print` gdb command
to print out the value of a variables, add more breakpoints, type `next` to single-step,
type `step` to step into a function, type `quit` to quit gdb, type `c` to continue, etc.
Please see a gdb tutorial for other things you can do in gdb.
Please note that the source code you see in the xv6 source code is the OS kernel source code. What if you want to set a breakpoint in a user space program, such as the `ls` program? The executable code for the `ls` program is `_ls`. So, you need to switch gdb from debugging the `kernel` to `_ls` by typing the following (the `delete` command would delete all current breakpoints in the kernel):
(gdb) delete
(gdb) symbol-file _ls
By reading the source code of `ls.c`, you can see that there is a function called ls() and you can
set a breakpoint in that function by typing:
(gdb) break ls
If you continue and then type `ls` in the first terminal windows, you should hit this breakpoint.
Reading the source code of the ls() in `ls.c`, you would see that the first thing it does
is to call the open() function. You should set a breakpoint there and continue:
(gdb) break open
(gdb) cont
When you get to open(), you would notice that things look a bit different than regular C code.
That's because open() is a system call. An application program doesn't know how to
open a file. It needs to ask the OS kernel to open a file. There is a context switch at a system call
where you switch from the user space context to the kernel space context. We will talk about
how this is done in class. For now, you just need to know that open() will reach the corresponding
kernel version of the open(), which is called sys_open(). In xv6, if a system call is
called foo(), the corresponding kernel version of the foo() system call will be sys_foo().
To set a breakpoint in sys_open(), you need to switch to debug the kernel:
(gdb) delete
(gdb) symbol-file kernel
Then you can do something like the following to set a breakpoint in `sys_open()` to continue:
(gdb) break sys_open
(gdb) cont
Once you get inside sys_open(), you would notice that the kernel code is not much different from user space code!
Gdb is designed to mainly debug C code. Gdb is really not comfortable with switching between different contexts and things can get pretty delicate when you are switching between user space and kernel code. So, please be super careful when you are doing that. One little mistake and you may have to start from the beginning again!
Also, single-step gdb commands (i.e., `next` and `step`) may not work as expected when a context switch is involved. If you know that a context switch will happen, you should set a breakpoint and use the `cont` gdb command to get there.
How do you get back to your user space code? In this case, you need to get back to where you made the open() system call. Open `ls.c` and you should see that you called open() on line 33. In gdb, you should do the following to see the code around there:
(gdb) delete
(gdb) symbol-file _ls
(gdb) list ls.c:33
(gdb) list
You should see that when open() returns, you should be on either line 34 or 38. So, you would do:
(gdb) break ls.c:34
(gdb) break ls.c:38
(gdb) cont
Advanced Debugging (Optional)
If you really want to know exactly how this context switching work, i.e., how the open() system call reaches the sys_open() kernel function, you can switch to debugging assembly code in user space to see exactly what happens at the open() system call. This is not required at this point, but if you really want to know, here's what you can do. When you are at the open() system call, switch to the assembly code debugging mode in gdb by doing the following:
(gdb) layout asm
You can then use gdb commands such as "ni" and si" to single-step at the aseembly code level.
You can set a breakpoint at a machine instruction. For example, to break at virtual address 0x8010560a, do:
(gdb) break *0x8010560a
To return back to source code level debugging, do:
(gdb) layout src
To collapse the source code panel, do:
(gdb) tui disable
To create assembly code of your kernel do:
objdump --disassemble --section=".text" kernel > kernel.txt
Then you can open kernel.txt with your favorite text editor to read assembly code. If you want to also see the corresponding C source code, you can do:
objdump --disassemble --section=".text" -S kernel > kernel.txt
The above stuff is optional for now.
Hopefully, you won't need to do that in this class.
One day, when you have a really tough bug and
the only way to debug your code is to debug context switching at the assembly code leve,
then you need to come back here and review the above stuff.
Your assignment is to add a new system call called trace. Its syntax is:
int trace(int)
When called with a non-zero parameter, e.g., trace(1), system call tracing is turned on for that process.
Each system call from that process will be printed to the console in a user-friendly format showing:
- the process ID
- the process name
- the system call number
- the system call name
Calling trace(0) turns tracing off for that process. System calls will no longer be printed to the console.
In all cases, the trace system call also returns the total number of system calls that the process has made since it started. Hence, you can write code such as:
printf("total system calls so far = %d\n", trace(0));
How To Add A System Call?
You need to touch several files to add a system call in xv6. Look at the implementation of existing system calls for guidance on how to add a new one. The files that you need to edit to add a new system call include:user.h
This contains the user-side function prototypes of system calls as well as utility library functions (stat(), strcpy(), printf(), etc.).syscall.h
This file contains symbolic definitions of system call numbers. You need to define a unique number for your system call. Be sure that the numbers are consecutive. That is, there are no missing number in the sequence. These numbers are indices into a table of pointers defined in syscall.c (see next item).syscall.c
This file contains entry code for system call processing. The syscall(void) function is the entry function for all system calls. Each system call is identified by a unique integer, which is placed in the processor’s eax register. The syscall() function checks the integer to ensure that it is in the appropriate range and then calls the corresponding function that implements that call by making an indirect function call to a function in the syscalls[] table. You need to ensure that the kernel function that implements your system call is in the proper sequence in the syscalls array.usys.SWhen you implement your trace call, you’ll need to retrieve the incoming parameter. The file syscall.c defines a few helper functions to do this. The functions argint, argptr, and argstr retrieve the nth system call argument, as either an integer, pointer, or a string. argint uses the esp register to locate the argument: esp points at the return address for the system call stub.
This file contains macros for the assembler code for each system call. This is user code (it will be part of a user-level program) that is used to make a system call. The macro simply places the system call number into the eax register and then invokes the system call. You need to add a macro entry for your system call here.sysproc.c
This is a collection of process-related system calls. The functions in this file are called from syscall. You can add your new function to this file.proc.h
Per-process state is stored in a proc structure: struct proc in proc.h. You’ll need to extend that structure to keep track of the metrics required by this assignment.proc.c
This contains code for initializing the proc structure. Since you have modified proc.h, you’ll also need to find where the proc structure is allocated so that you can ensure that the elements are initialized appropriately.Create a new system call called trace(int). This turns console-based system call logging on and off for only the calling process.
Extend the proc structure (the process control block) in proc.h to keep track of whether tracing for the process is on or off. Be sure that the elements are cleared (initialized) whenever a new process is created.
In implementing your system call, you’ll need to access the single parameter passed by trace. Use the helper functions defined in syscall.c (argint, argptr, and argstr). Take a look at how other system calls are implemented in xv6. For example, getpid is a simple system call that takes no arguments and returns the current process’ process ID; kill and sleep are examples of system calls that takes a single integer parameter.
Testing Your Code For The trace System Call
A test program called test_project1.c is part of your xv6 code base. Modify your Makefile file to include a new user level program:
UPROGS=\
...
_test_project1\
EXTRA=\
...
test_project1.c\
Compile and run the program. The expected output should look like the following:
xv6...
cpu1: starting 1
cpu0: starting 0
sb: size 1000 nblocks 941 ninodes 200 nlog 30 logstart 2 inodestart 32 bmap start 58
init: starting sh
$ test_project1
pid: 3 [test_project1] syscall(1=fork)
pid: 3 [test_project1] syscall(1=fork)
pid: 3 [test_project1] syscall(3=wait)
pid: 3 [test_project1] syscall(3=wait)
pid: 3 [test_project1] syscall(11=getpid)
pid: 3 [test_project1] syscall(22=trace)
total syscalls so far: 10
total syscalls so far: 40
$
Please take a look at the grading guidelines. You also need to get
`grade_pa1.c` compiled so that the grader can run all the tests to grade this part of the assignment.
A user-level program called date.c is part of your xv6 code base. You should modify your Makefile file to get it compiled.
Your strategy for making a date system call should be to clone all of the pieces of code that are specific to some existing system call, for example the uptime() system call. You should grep for "uptime" in all the source files, using "grep -n uptime *.[chS]".
When you're done, typing `date` to an xv6 shell prompt should print the current UTC time by running your `date.c` program. Running `date` should produce a printout that looks like the following (of course, the actual date and time should correspond to the current date and time plus 6 hours (or plus 7 hours), assuming that your computer's clock is displaying the USC time zone):
$ date
Current date: 6:56:7, 5/14/2025
The grading guidelines is the only grading procedure we will use to grade your program. No other grading procedure will be used. Please note that the grader may use a different set of test programs and commandline arguments when grading your submission. (We may make minor changes if we discover bugs in the script or things that we forgot to test.) It is strongly recommended that you run your code through the commands in the grading guidelines.
make pa1-submit
and the following command should run:
tar cvzf pa1-submit.tar.gz \
Makefile \
pa1-README.txt \
proc.c \
proc.h \
syscall.c \
syscall.h \
sysproc.c \
user.h \
usys.S
Please note that you are not permitted to change any other existing files.
If you submit another existing file, it will be deleted before grading.
If you give instructions to the grader to modify another existing file (other than Config.mk), it will disregarded.
A pa1-README.txt template file is provided here. You must save it as your pa1-README.txt file and follow the instructions in it to fill it out with appropriate information and include it in your submission. You must not delete a single line from w1-README.txt. Please make sure that you satisfy all the README requirements.
To check the content of pa1-submit.tar.gz, you can use the following command:
tar tvzf pa1-submit.tar.gz
Please read the output of the above command carefully to see what files were included in pa1-submit.tar.gz
and what are their file sizes and make sure that they make sense.
Please enter your USC e-mail address and your submission PIN below. Then click on the Browse button and locate and select your submission file (i.e., pa1-submit.tar.gz). Then click on the Upload button to submit your pa1-submit.tar.gz. (Be careful what you click! Do NOT submit the wrong file!) If you see an error message, please read the dialogbox carefully and fix what needs to be fixed and repeat the procedure. If you don't know your submission PIN, please visit this web site to have your PIN e-mailed to your USC e-mail address.
When this web page was last loaded, the time at the submission server (merlot.usc.edu) was 28Nov2025-01:56:07. Reload this web page to see the current time on merlot.usc.edu.
If the command is executed successfully and if everything checks out, a ticket will be issued to you to let you know "what" and "when" your submission made it to the Bistro server. The next web page you see would display such a ticket and the ticket should look like the sample shown in the submission web page (of course, the actual text would be different, but the format should be similar). Make sure you follow the Verify Your Ticket instructions to verify the SHA1 hash of your submission to make sure what you did not accidentally submit the wrong file. Also, an e-mail (showing the ticket) will be sent to your USC e-mail address. Please read the ticket carefully to know exactly "what" and "when" your submission made it to the Bistro server. If there are problems, please contact the instructor.
It is extreme important that you also verify your submission after you have submitted pa1-submit.tar.gz electronically to make sure that every you have submitted is everything you wanted us to grade. If you don't verify your submission and you ended up submit the wrong files, please understand that due to our fairness policy, there's absolutely nothing we can do.
Finally, please be familiar with the Electronic Submission Guidelines
and information on the bsubmit web page.