The second phase of Nachos is to implement system calls and support multiprogramming. As in the first assignment, we give you some of the code you need; your job is to complete the system and enhance it. This document first describes the features and files in Nachos that you need to focus on for this assignment.
This lab is quite challenging and we strongly encourage you to form teams of two.
The first step is to read and understand the part of the system we have
written for you. Our code can run a single user-level `C' program at a
time. As a test case, we've provided
you with a trivial user program, `halt'; all halt does is to turn around
and ask the operating system to shut the machine down. Run the program
`nachos -x ../test/halt'. As before, trace
what happens as the user program gets loaded, runs, and invokes a system
call.
The files for this assignment are:
addrspace.h & addrspace.cc, (found in the userprog directory)
create and destroy address spaces for user programs.
syscall.h (found in the userprog directory)
the system call interface. This file contains the
signatures of the system calls you must implement and
the codes that represent each system call when ExceptionHandler
is called.
exception.cc (found in the userprog directory)
The handler for system calls and other user-level
exceptions, such as page faults. In the code we supply,
only the `halt' system call is supported. This file
explains how
parameters are passed to system calls, where return
results should be placed, and other useful hints on
implementing the system calls.
filesys.h, openfile.h (found in the filesys directory)
a stub defining the Nachos file system routines.
For this assignment, we have implemented the Nachos
file system by directly making the corresponding
calls to the UNIX file system. This mechanism
prevents you from needing to write the file
system as well.
machine.h (found in the machine directory)
Emulates the part of the machine that executes user
programs: main memory, processor registers, etc.
You need to be able to read and write registers
to retrieve the parameters from system calls and return
values from system calls.
console.h & console.cc (found in the machine directory)
emulates a terminal device using UNIX files. A terminal
is (i) byte oriented, (ii) incoming bytes can be
read and written at the same time, and (iii) bytes
arrive asynchronously (as a result of user keystrokes),
without being explicitly requested. You will call
the methods provided here from your SynchConsole
class.
system.cc (found in the threads directory)
The file that initializes global variables when
Nachos starts and cleans up before Nachos halts. If you
add any new global variables, they should be initialized
in the Initialize method in system.cc.
bitmap.h & bitmap.cc (found in the userprog directory)
Routines for manipulating bitmaps (this might be
useful for keeping track of physical page frames)
translate.h & translate.cc (found in the machine directory)
Translation table routines. In the code we supply
to run "halt", we assume that every virtual address is
the same as its physical address -- this restricts
us to running one user program at a time. You will
generalize this to allow multiple user programs
to be run concurrently. We will not ask you to
implement virtual memory support until in assignment
3; for now, every page must be in physical
memory.
progtest.cc (found in the userprog directory)
Test routines for running user programs. Look at
StartProcess to help understand what you need to do
to implement the Exec system call.
timer.h (found in the machine directory)
Defines a Timer class which implements time slices.
machine.h & machine.cc (found in the machine directory)
Emulates the part of the machine that executes user
programs: main memory, processor registers, etc.
mipssim.h & mipssim.cc (found in the machine directory)
Emulates the integer instruction set of a MIPS R2/3000
processor.
So far, all the code you have written for Nachos has been part of the operating system kernel. In a real operating system, the kernel not only uses its procedures internally, but allows user-level programs to access some of its routines them via ``system calls''.
In this assignment we are giving you a simulated CPU that models a real CPU. In fact, the simulated CPU is the same as the real CPU (a MIPS chip), but we cannot just run user programs as regular UNIX processes, because we want complete control over how many instructions are executed at a time, how the address spaces work, and how interrupts and exceptions (including system calls) are handled. We recommend that you spend some time going through machine.cc and skimming through mipsim.cc to understand the line between kernel code that's run directly on the delux machine and user code that is being simulated in nachos.
Our simulator can run normal programs compiled from C -- see the Makefile in the `test' subdirectory for an example. The compiled programs must be linked with some special flags, then converted into Nachos format, using the program ``coff2noff'' (which we supply). The only caveat is that floating point operations are not supported.
Building
Problems 1 & 2 (System Calls and Exception Handling)
In the first part of this assignment, you will implement system calls
and exception handling for Nachos. You will be working with the simulated
CPU that is provided by Nachos. We use a simulated CPU, because we want
complete control over how many instructions are executed at a time, how
the address spaces work, and how interrupts and exceptions (including system
calls) are handled. The Nachos CPU simulates a MIPS chip. The simulator
can run normal C programs (except that floating point operations are not
supported and the only system calls supported are the ones that you implement).
At present, Nachos is a uniprogramming operating system; it can run a single
C program at a time. As a test case, we've provided you with a trivial
user program,
halt(in
the test directory); all halt does is to turn around and
ask the operating system to shut the machine down. Run the program
nachos -x ../test/haltfrom the userprog directory. As before, trace what happens as the user program gets loaded, runs, and invokes a system call.
In the assignment, we tell you to put output statements in your code at various places. To do this, use the DEBUG procedure call, passing '4' as the first argument, as in:
DEBUG ('4', "Allocating frame %d.\n", frame);
To see this output run nachos with the -d 4 flag. This should facilitate
debugging on your part and will also facilitate testing and grading. These
statements must be present to
receive full credit for a portion of the assignment. Feel free to add
additional statements that you feel help clarify how your code is working.
(Error messages that must be
reported to the nachos user, such as when a system call is given bad
parameter values, should be output with normal printf statements so that
they always appear.)
See the Makefile in the `test' subdirectory for an example of how to generate an executable program for Nachos. Among other things, this Makefile can generate the executable for halt. Briefly, C programs are compiled and linked using gcc and gld (with special flags) and are then converted to Nachos object file format (noff) using coff2noff, a utility provided by Nachos. Since Nachos runs ordinary C programs, you can make your own test programs that make the system calls you are implementing. Remember that you cannot use system calls or library routines that use system calls in your test programs. This means you cannot put printf statements in your test programs, for example.
You will be working mostly in the userprog directory.
Note that you will need to "bullet-proof" the Nachos kernel from user program errors -- there should be nothing a user program can do to crash the operating system (with the exception of explicitly asking the system to halt). Therefore, your system call implementations should do whatever error checking is necessary to ensure that they will succeed or will cause the user's program to fail, but not nachos.
Problem 3 (Multiprogramming)
In this assignment, you will implement multiprogramming for Nachos.
You will test your implementation by running user programs on Nachos. At
present, Nachos can run a single user-level C program at a time. You will
start a user program by running Nachos with the "-x" flag as for the last
lab. From the
application program perspective, multiprogramming is accomplished by
having a C program "exec" other programs. That is, there is no user shell
where multiple application programs are started.
Before beginning this assignment, do the following:
1. In machine/machine.h, change
#define NumPhysPages
32
to
#define NumPhysPages
48
2.In vm/Makefile, delete -DUSE_TLB from the line:
DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS_STUB -DVM -DUSE_TLB
3.Rebuild the makefile (gmake depend in the vm directory).
4.Recompile nachos (gmake nachos in the vm directory).
The Assignment
Problem 1
(5 points each) Implement system calls that interface to the file system.
The signatures of the system calls are defined in syscall.h.
Your implementation goes in ExceptionHandler in exception.cc. (See how
halt is defined in syscall.h and implemented in exception.cc.)
Your system call implementations should check the parameters passed in to make sure they are legal and then call the equivalent kernel routines already implemented in filesys/filesys.h and filesys/openfile.h. Note that we are using the stub implementation of the file system for this assignment.
Create
Create a
file. Remember to copy the filename from the user specified string to a
kernel string. The
file exception.cc contains
the function UserToKernelString which can be used for this purpose.
Open
Open a file. Keep
track of the ids of the opened files so that exit can close them. You should
allow
up to 16 files to be open
at one time. Remember to copy the filename from the user specified string
to a kernel string.
Close
Close a file. Remember
to update the list of opened files.
Read
Read from a file.
Remember to copy the contents from the kernel buffer to the user buffer
after
reading. The file exception.cc
contains a function, KernelToUser, which can be used for this
purpose.
Write
Write to a file.
Remember to copy the contents from the user buffer to the kernel buffer
before
writing. The file exception.cc
contains a function, UserToKernel, which can be used for this
purpose. Make sure that
your implementation allows read and write from the console.
Problem 2
(10 points each) Impement the Exec, Join and Exit System Calls
Problem 3
(35 points) Implement multiprogramming with time-slicing. The code we have given you is restricted to running one user program at a time. You will need to:
1. Come up with a way of allocating
physical memory frames so that multiple programs can be loaded
into memory at once
(bitmap.h might come in handy, here.)
2. Provide a way of copying data to/from
the kernel from/to the user's virtual address space (now that
the addresses the
user program sees are not the same as the ones the kernel sees)
3. Use timer interrupts to force threads
to yield after a certain number of ticks. Note that scheduler.cc
now saves and restores user
machine state on context switches.
Implement a timer so that
each user process gives up the CPU after its timeslice expires. In
threads/system.cc, there
is currently code to create a timer if the program is called with the -rs
flag.
You just need to change
this so that the timer is always created. All other aspects of the timer
are
already implemented. (The
length of the time slice is defined by TimerTicks defined in
machine/stats.h.)
Tip :
You must allow each AddrSpace to have its own page table so that multiple
programs may be running at the same time. This has already been done for
the most part; just use the pageTable that is owned by each address space.
In this assignment, you will only need to modify the page tables that each
address space already has. You do not need to deal with the TLB at all.
The machine will know how to find your page tables, so do not change their
format. All you need to do is correctly populate the page table with the
translations made when loading the program. Your page table must allow
arbitrary translations. You will probably find a system-wide bitmap helpful
to determine which physical pages are free. When you load the program in
AddrSpace, instead of putting it at a physical page equal to the virtual
page, you will find a free page in physical memory to map to that virtual
page (with the page table). Be sure to free the physical memory when the
address space is done.
You do not need to implement demand paging. If there is not enough memory to meet a process's needs, you can simply display an error message and abort the process. Remember to free the memory when the process exits. Output a DEBUG message for each page loaded into memory identifying the virtual page number and the physical page frame.
Currently, Nachos zeros the entire main memory when an address space is created using a call to bzero. You need to be sure that you only zero the pages actually allocated to that process.
// Start changes put your changes here // End changesYou should put all your modified files in a lab3 subdirectory of your cs377 directory.
USERPROG_H = ../userprog/addrspace.h\ ../userprog/bitmap.h\ ../userprog/SynchConsole.h\ ../filesys/filesys.h\ ../filesys/openfile.h\ ../machine/console.h\ ../machine/machine.h\ ../machine/mipssim.h\ ../machine/translate.h USERPROG_C = ../userprog/addrspace.cc\ ../userprog/bitmap.cc\ ../userprog/exception.cc\ ../userprog/progtest.cc\ ../userprog/SynchConsole.cc\ ../machine/console.cc\ ../machine/machine.cc\ ../machine/mipssim.cc\ ../machine/translate.cc USERPROG_O = addrspace.o bitmap.o exception.o progtest.o console.o machine.o \ mipssim.o translate.o SynchConsole.oIf you add a .h file in the userprog directory, you must add the name to the USERPROG_H list. If you add a .cc file, you must add the corresponding names to the USERPROG_C and USERPROG_O lists. If your new .h file is included in one or more files in a different directory, you should also put the .h file on the other directory's h list. Be sure to run gmake depend before gmake nachos.
/* Program to test I/O */ /* Reads, writes to stdin and stdout; opens a file and writes stuff to it. */ #include "syscall.h" #define numloop 500 int main() { int i; char *ch = "A"; OpenFileId fd; char buffer[24]; i= Read(buffer,2,ConsoleInput); /* read from console */ Write(buffer,2,ConsoleOutput); /* write the same thing to console */ fd = Open("pa.c.bak"); /* open a file */ i = Read(buffer,7,fd); /* Read from it */ Write(buffer,i,ConsoleOutput); // write chars just read from // file on the screen Close(fd); /* Write a bunch of A's on the screen */ for (i=0;i<numloop;i++) Write(ch,1,ConsoleOutput); Exit(0); }Here are the changes made to the Makefile in the test directory in order to compile the user program printa.c:
all: halt shell matmult sort printa printa.o: printa.c $(CC) $(CFLAGS) -c printa.c printa: printa.o start.o $(LD) $(LDFLAGS) start.o printa.o -o printa.coff ../bin/coff2noff printa.coff printaIn order to compile your user programs, make appropriate changes to the Makefile in the test directory and type make all at the prompt while in the test directory.
Testing MultiProgramming Using Exec and Join
Here are some test programs for you to use. You should make up your own test programs to more thoroughly test your code.
test_ExecJoin.c
and test_Child.c
are a pair of programs that result in the creation of multiple processes.
Creation stops when Nachos runs out of physical memory, then the
threads all finish in the reverse order from the
order they started in.
test_ExecJoin_II.c, test_Child_IIa.c, and test_Child_IIb.c operate in parallel creating and writing to three different files.
test_SynchConsole.c,
test_SynchConsoleA.c,
test_SynchConsoleB.c,
and test_SynchConsoleC.c
are processes operating in parallel, writing to the console.