323 lines
9.3 KiB
HTML
323 lines
9.3 KiB
HTML
<html>
|
|
<head>
|
|
<title>Lab: system calls</title>
|
|
<link rel="stylesheet" href="homework.css" type="text/css" />
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Lab: system calls</h1>
|
|
|
|
This lab makes you familiar with the implementation of system calls.
|
|
In particular, you will implement a new system
|
|
calls: <tt>sigalarm</tt> and <tt>sigreturn</tt>.
|
|
|
|
<b>Note: before this lab, it would be good to have recitation section on gdb and understanding assembly</b>
|
|
|
|
<h2>Warmup: system call tracing</h2>
|
|
|
|
<p>In this exercise you will modify the xv6 kernel to print out a line
|
|
for each system call invocation. It is enough to print the name of the
|
|
system call and the return value; you don't need to print the system
|
|
call arguments.
|
|
|
|
<p>
|
|
When you're done, you should see output like this when booting
|
|
xv6:
|
|
|
|
<pre>
|
|
...
|
|
fork -> 2
|
|
exec -> 0
|
|
open -> 3
|
|
close -> 0
|
|
$write -> 1
|
|
write -> 1
|
|
</pre>
|
|
|
|
<p>
|
|
That's init forking and execing sh, sh making sure only two file descriptors are
|
|
open, and sh writing the $ prompt. (Note: the output of the shell and the
|
|
system call trace are intermixed, because the shell uses the write syscall to
|
|
print its output.)
|
|
|
|
<p> Hint: modify the syscall() function in kernel/syscall.c.
|
|
|
|
<p>Run the programs you wrote in the lab and inspect the system call
|
|
trace. Are there many system calls? Which systems calls correspond
|
|
to code in the applications you wrote above?
|
|
|
|
<p>Optional: print the system call arguments.
|
|
|
|
<h2>RISC-V assembly</h2>
|
|
|
|
<p>For the alarm system call it will be important to understand RISC-V
|
|
assembly. Since in later labs you will also read and write assembly,
|
|
it is important that you familiarize yourself with RISC_V assembly.
|
|
|
|
<p>Add a file user/call.c with the following content, modify the
|
|
Makefile to add the program to the user programs, and compile (make
|
|
fs.img). The Makefile also produces a binary and a readable
|
|
assembly a version of the program in the file user/call.asm.
|
|
<pre>
|
|
#include "kernel/param.h"
|
|
#include "kernel/types.h"
|
|
#include "kernel/stat.h"
|
|
#include "user/user.h"
|
|
|
|
int g(int x) {
|
|
return x+3;
|
|
}
|
|
|
|
int f(int x) {
|
|
return g(x);
|
|
}
|
|
|
|
void main(void) {
|
|
printf(1, "%d %d\n", f(8)+1, 13);
|
|
exit();
|
|
}
|
|
</pre>
|
|
|
|
<p>Read through call.asm and understand it. The instruction manual
|
|
for RISC-V is in the doc directory (doc/riscv-spec-v2.2.pdf). Here
|
|
are some questions that you should answer for yourself:
|
|
|
|
<ul>
|
|
<li>Which registers contain arguments to functions? Which
|
|
register holds 13 in the call to <tt>printf</tt>? Which register
|
|
holds the second one? Which register holds the second one? Etc.
|
|
|
|
<li>Where is the function call to <tt>f</tt> and <tt>g</tt>
|
|
in <tt>main</tt>? (Hint: compiler may inline functions.)
|
|
|
|
<li>At what address is the function <tt>printf</tt> located?
|
|
|
|
<li>What value is in the register <tt>ra</tt> in the <tt>jalr</tt>
|
|
to <tt>printf</tt> in <tt>main</tt>?
|
|
</ul>
|
|
|
|
|
|
<h2>alarm</h2>
|
|
|
|
<p>
|
|
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; you could use something similar
|
|
to handle page faults in the application, for example.
|
|
|
|
<p>
|
|
You should add a new <tt>sigalarm(interval, handler)</tt> system call.
|
|
If an application calls <tt>sigalarm(n, fn)</tt>, then after every
|
|
<tt>n</tt> "ticks" of CPU time that the program consumes, the kernel
|
|
will cause application function
|
|
<tt>fn</tt> to be called. When <tt>fn</tt> returns, the application
|
|
will 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.
|
|
|
|
<p>
|
|
You should put the following example program in <tt>user/alarmtest.c</tt>:
|
|
|
|
<b>XXX Insert the final program here</b>
|
|
<pre>
|
|
#include "kernel/param.h"
|
|
#include "kernel/types.h"
|
|
#include "kernel/stat.h"
|
|
#include "kernel/riscv.h"
|
|
#include "user/user.h"
|
|
|
|
void test0();
|
|
void test1();
|
|
void periodic();
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
test0();
|
|
test1();
|
|
exit();
|
|
}
|
|
|
|
void test0()
|
|
{
|
|
int i;
|
|
printf(1, "test0 start\n");
|
|
alarm(2, periodic);
|
|
for(i = 0; i < 1000*500000; i++){
|
|
if((i % 250000) == 0)
|
|
write(2, ".", 1);
|
|
}
|
|
alarm(0, 0);
|
|
printf(1, "test0 done\n");
|
|
}
|
|
|
|
void
|
|
periodic()
|
|
{
|
|
printf(1, "alarm!\n");
|
|
}
|
|
|
|
void __attribute__ ((noinline)) foo(int i, int *j) {
|
|
if((i % 2500000) == 0) {
|
|
write(2, ".", 1);
|
|
}
|
|
*j += 1;
|
|
}
|
|
|
|
void test1() {
|
|
int i;
|
|
int j;
|
|
|
|
printf(1, "test1 start\n");
|
|
j = 0;
|
|
alarm(2, periodic);
|
|
for(i = 0; i < 1000*500000; i++){
|
|
foo(i, &j);
|
|
}
|
|
if(i != j) {
|
|
printf(2, "i %d should = j %d\n", i, j);
|
|
exit();
|
|
}
|
|
printf(1, "test1 done\n");
|
|
}
|
|
</pre>
|
|
|
|
The program calls <tt>sigalarm(2, periodic1)</tt> in <tt>test0</tt> to
|
|
ask the kernel to force a call to <tt>periodic()</tt> every 2 ticks,
|
|
and then spins for a while. After you have implemented
|
|
the <tt>sigalarm()</tt> system call in the kernel,
|
|
<tt>alarmtest</tt> should produce output like this for <tt>test0</tt>:
|
|
|
|
<b>Update output for final usertests.c</b>
|
|
<pre>
|
|
$ alarmtest
|
|
alarmtest starting
|
|
.....alarm!
|
|
....alarm!
|
|
.....alarm!
|
|
......alarm!
|
|
.....alarm!
|
|
....alarm!
|
|
....alarm!
|
|
......alarm!
|
|
.....alarm!
|
|
...alarm!
|
|
...$
|
|
</pre>
|
|
<p>
|
|
|
|
<p>
|
|
(If you only see one "alarm!", try increasing the number of iterations in
|
|
<tt>alarmtest.c</tt> by 10x.)
|
|
|
|
<p>The main challenge will be to arrange that the handler is invoked
|
|
when the process's alarm interval expires. In your usertrap, when a
|
|
process's alarm interval expires, you'll want to cause it to execute
|
|
its handler. How can you do that? You will need to understand in
|
|
details how system calls work (i.e., the code in kernel/trampoline.S
|
|
and kernel/trap.c). Which register contains the address where
|
|
systems calls return to?
|
|
|
|
<p>Your solution will be few lines of code, but it will be tricky to
|
|
write the right lines of code. Common failure scenarios are: the
|
|
user program crashes or doesn't terminate. You can see the assembly
|
|
code for the alarmtest program in alarmtest.asm, which will be handy
|
|
for debugging.
|
|
|
|
<h2>Test0: invoke handler</h2>
|
|
|
|
<p>To get started, the best strategy is to first pass test0, which
|
|
will force you to handle the main challenge above. Here are some
|
|
hints how to pass test0:
|
|
|
|
<ul>
|
|
|
|
<li>You'll need to modify the Makefile to cause <tt>alarmtest.c</tt>
|
|
to be compiled as an xv6 user program.
|
|
|
|
<li>The right declaration to put in <tt>user/user.h</tt> is:
|
|
<pre>
|
|
int sigalarm(int ticks, void (*handler)());
|
|
</pre>
|
|
|
|
<li>Update kernel/syscall.h and user/usys.S (update usys.pl to update
|
|
usys.S) to allow <tt>alarmtest</tt> to invoke the sigalarm system
|
|
call.
|
|
|
|
<li>Your <tt>sys_sigalarm()</tt> should store the alarm interval and
|
|
the pointer to the handler function in new fields in the <tt>proc</tt>
|
|
structure; see <tt>kernel/proc.h</tt>.
|
|
|
|
<li>You'll need to keep track of how many ticks have passed since the
|
|
last call (or are left until the next call) to a process's alarm
|
|
handler; you'll need a new field in <tt>struct proc</tt> for this
|
|
too. You can initialize <tt>proc</tt> fields in <tt>allocproc()</tt>
|
|
in <tt>proc.c</tt>.
|
|
|
|
<li>Every tick, the hardware clock forces an interrupt, which is handled
|
|
in <tt>usertrap()</tt>; you should add some code here.
|
|
|
|
<li>You only want to manipulate a process's alarm ticks if there's a a
|
|
timer interrupt; you want something like
|
|
<pre>
|
|
if(which_dev == 2) ...
|
|
</pre>
|
|
|
|
<li>Only invoke the process's alarm function, if the process has a
|
|
timer outstanding. Note that the address of the user's alarm
|
|
function might be 0 (e.g., in alarmtest.asm, <tt>periodic</tt> is at
|
|
address 0).
|
|
|
|
<li>It will be easier to look at traps with gdb if you tell qemu to
|
|
use only one CPU, which you can do by running
|
|
<pre>
|
|
make CPUS=1 qemu
|
|
</pre>
|
|
|
|
</ul>
|
|
|
|
<h2>test1(): resume interrupted code</h2>
|
|
|
|
<p>Test0 doesn't tests whether the handler returns correctly to
|
|
interrupted instruction in test0. If you didn't get this right, it
|
|
is likely that test1 will fail (the program crashes or the program
|
|
goes into an infinite loop).
|
|
|
|
<p>A main challenge is to arrange that when the handler returns, it
|
|
returns to the instruction where the program was interrupted. Which
|
|
register contains the return address of a function? When the kernel
|
|
receives an interrupt, which register contains the address of the
|
|
interrupted instruction?
|
|
|
|
<p>Your solution is likely to require you to save and restore
|
|
registers---what registers do you need to save and restore to resume
|
|
the interrupted code correctly? (Hint: it will be many). There are
|
|
several ways to do this, but one convenient way is to add another
|
|
system call <tt>sigreturn</tt> that the handler calls when it is
|
|
done. Your job is to arrange that <tt>sigreturn</tt> returns to the
|
|
interrupted code.
|
|
|
|
Some hints:
|
|
<ul>
|
|
<li>Add the <tt>sigreturn</tt> system call, following the changes
|
|
you made to support <tt>sigalarm</tt>.
|
|
|
|
<li>Save enough state when the timer goes in the <tt>struct
|
|
proc</tt> so that <tt>sigreturn</tt> can return to the
|
|
interrupted code.
|
|
|
|
<li>Prevent re-entrant calls to the handler----if a handler hasn't
|
|
returned yet, don't call it again.
|
|
<ul>
|
|
|
|
<p>Once you pass <tt>test0</tt> and <tt>test1</tt>, run usertests to
|
|
make sure you didn't break any other parts of the kernel.
|
|
|
|
|
|
|
|
|
|
|