326 lines
		
	
	
	
		
			9.3 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			326 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; maybe just give the code in the repo</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.
 | |
| 
 | |
|   
 | |
| </body>
 | |
| </html>
 | |
| 
 | |
| 
 | |
|   
 | |
| 
 | |
|   
 | 
