This lab will familiarize you with the implementation of system calls and
upcalls (or signals). In particular, you will implement new system calls
Before you start coding, read §4, Traps and system calls of the xv6 book, and related source files:
kernel/trampoline.S: the assembly involved in changing from user space to kernel space and back
kernel/trap.c: code handling all interrupts
To start the lab, fetch the latest versions and create a new branch for your solution:
It will be important to understand a bit of RISC-V assembly.
There is a file
user/call.c in your xv6 repo.
make fs.img compiles it and
also produces a readable assembly version of the program in
Read the code in
user/call.asm for the functions
main. Please see the assignments page for a link to the RISC-V ISA manual; however, there are many other resources online that can also provide useful information to understand RISC-V assembly.
We have included some questions below for you to think about. Note that you don’t need to submit answers to the questions in this lab. Do answer them for yourself though!
main’s call to
f in the assembly code for
Where is the call to
g? (Hint: the compiler may inline functions.)
ra just after the
In the following code,
what is going to be printed after
(note: the answer is not a specific value.)
Why does this happen?
For debugging it is often useful to have a backtrace:
a list of the function calls on the stack above the point at which the error occurred.
This is implemented by the
backtrace() function in
Suppose that we insert a call to
sleep from your lab util solution, which calls
Your output should be as follows:
If you run make qemu-trace instead of make qemu, you should see something that looks like a Linux kernel panic:
Read the source code of
How does this function walk up the stack and print the saved return address in each stack frame?
kernel/riscv.h). These number are helpful for
backtrace() to terminate its loop.
In this exercise you’ll add a feature to xv6 that periodically alerts a process
as it uses CPU time. This might be useful for compute-bound processes that want
to limit how much CPU time they chew up, or for processes that want to compute
but also want to take some periodic action. More generally, you’ll be
implementing a primitive form of user-level interrupt/fault handlers. Your
solution is correct if it passes
some tests results in kernel printing usertrap messages (e.g.,
unexpected scause...), which can be ignored if test prints “OK”.
is essentially a way to make sure that your implementation didn’t break any
other existing behavior of the kernel.
You should add a new
sigalarm(interval, handler) system call. If an
sigalarm(n, fn), then after every
n “ticks” of CPU time
that the program consumes, the kernel should cause application function
be called. When
fn returns, the application should resume where it left off.
A tick is a fairly arbitrary unit of time in xv6, determined by how often a
hardware timer generates interrupts. If an application calls
the kernel should stop generating periodic alarm calls.
You’ll find a file
user/alarmtest.c in your xv6 repository. Add it to the
Makefile. It won’t compile correctly until you’ve added
sigreturn system calls (see below).
sigalarm(2, periodic) in
test0 to ask the kernel to force
a call to
periodic() every 2 ticks, and then spins for a while. You can see
the assembly code for
user/alarmtest.asm, which may be handy
for debugging. Your solution is correct, when
alarmtest produces output like
usertests also runs correctly:
When you’re done, your solution will not involve a lot of code, but it may be
tricky to get it right. We’ll test your code with the version of
in the original repository. You can modify
alarmtest.c to help you debug, but
make sure to revert to the original
alarmtest.c before submitting your
solution or running the grading scripts on your end.
Get started by modifying the kernel to jump to the alarm handler
in user space, which will cause
test0 to print “alarm!”. Don’t worry
yet what happens after the “alarm!” output; it’s OK for now if your
program crashes after printing “alarm!”. Here are some hints:
Makefile to cause
be compiled as an xv6 user program.
user/usys.pl (which generates
kernel/syscall.c to allow
alarmtest to invoke the
sigreturn system calls.
sys_sigreturn should just return zero.
sys_sigalarm() should store the alarm interval and the
pointer to the handler function in new fields in the
struct proc for this, too. You
can initialize proc fields in
usertrap(); you should add some code here to keep track of the ticks that passed while a process was active on the CPU.
periodic is at address 0).
usertrap() so that when a process’s alarm interval is met then the signal handler of the process is executed. An important question to think about is: When a trap on RISC-V returns to user space, what determines the instruction address at which user-space code resumes execution?
alarmtest prints “alarm!”.
Chances are that
alarmtest crashes in
test1 after it prints
“alarm!”, or that
alarmtest (eventually) prints “test1 failed”, or
alarmtest exits without printing “test1 passed”. To fix this,
you must ensure that, when the alarm handler is done, control returns
to the instruction at which the user program was originally interrupted
by the timer interrupt. You must ensure that the register contents
are restored to the values they held at the time of the interrupt,
so that the user program can continue undisturbed after the alarm.
Finally, you should “re-arm” the alarm counter after each time it
goes off, so that the handler is called periodically.
As a starting point, we’ve made a design decision for you: user
alarm handlers are required to call the
sigreturn system call when
they have finished. Have a look at
alarmtest.c for an
example. This means that you can add code to
that cooperate to cause the user process to resume properly after
it has handled the alarm.
usertrap save enough state in
struct proc when the timer
goes off that
sigreturn can correctly return to the interrupted
test2 checks for this behavior.
Once you pass
usertests to make sure you
didn’t break any other parts of the kernel.