Discussion
Section: Wednesday, November 10th, 1999
Getting
Started with Lab 3
GENERAL TIPS:
For each system call, you will need to sanity
check the parameters, list all the things that can possibly go wrong (never
assume that your program will be well-behaved; we will
deliberately test your implementations against
malicious user programs) and design a way to take evasive action. A good
chunk of real OS code does this sort of bullet-proofing.
Put checks everywhere! Just because a Syscall routine gets a file ID
doesn't mean the file exists. Also use ASSERTs to verify that what you
think must be true is. Both of these techniques help you narrow where problems
exist.
Test your code as you go along. Make up your own test cases for debug.
Put debug print statements in the nachos code (exception.cc mostly) to
see intermediate values because the user code is highly restricted in what
it can use for IO (which is basically the console, but you can't use the
console for debug until you've debugged the code).
Code for any of these routines should not exceed 25 or so lines (except
for ExceptionHandler()) and many routines can be done in a dozen lines.
If you are greatly exceeding this metric then you are probably doing something
wrong. However, if you put a lot of checks and printouts then you
may have more.
This assignment is rather long.
If I were you I would start NOW (i.e if I hadn't already started last week)
!
The
ExceptionHandler
All exceptions are caught by ExceptionHandler
(defined in exception.cc) which calls the proper syscall handlers
-
use a switch statement to handle which
type of exception has happened
-
the kind of possible exceptions are
listed as an enum ExceptionType in machine.h
-
don't forget a case to handle if the
Exception is not one of the allowed types
-
for the non-syscall exceptions, just
print and error and do a SyscallExit with a -1 as the parameter
-
in the SyscallException you can use
another switch to find the type of SyscallException (SC_Exit, SC_Create,
SC_Close, etc...)
-
you can get the type by setting the
switch variable of the SyscallException case statement to machine->ReadRegister(2)
For more info on this read:
Salsa:
System Calls and Exception Handling
Roadmap:
System Calls and ExceptionHandling
NOTE: Break your syscalls into
functions . One long ExceptionHandler function is unacceptable coding
style. ExceptionHandler should call SyscallExit, SyscallJoin, and SyscallExec
as well as all of the other Syscall Functions. You WILL loose points
if you implement one large ExceptionHandler function . Read the values
out of machine->readRegister(4), machine->readRegister(5), machine->readRegister(6)
and send them as parameters to the functions.
Your exception handler should look something like this :
void
ExceptionHandler(ExceptionType which)
{
int type, arg1, arg2,......;
// Figure out a way of finding the type of syscall exception, arguments,
etc.
switch(which)
{
case SyscallException:
switch(type)
{
case SC_Create:
SyscallCreate( arguments, etc) ;
break ;
case SC_Open:
.
.
}
case PageFaultException:
.
.
}
}
PASSING
ARGUMENTS TO SYSCALLS IS TRICKY!
Values are passed to the ExceptionHandler
via registers as integers. You must cast the register values to the appropriate
types in your SyscallXXX invocations.
RETURNING
A VALUE IS TRICKY!
The kernel is communicating with
the user process via machine registers. See the top comments in exception.cc
that states values are returned in reg 2. Read up in machine.cc on ReadRegister
and WriteRegister. Register read and write examples are in the shell code
of exception.cc.
You need
to Implement some of the System Calls
-
there are 5 system calls that you must
implement for the first part of this assignment
-
you can find routines Halt, Create,
Open, Read, Write, Close, Exit and Length prototyped in syscall.h
-
somewhat unexpectedly, you will be implementing
them in exception.cc (not syscall.cc)
-
SyscallCreate
-
SyscallOpen
-
SyscallClose
-
SyscallRead
-
SyscallWrite
Getting
Started with the Syscall Implementations...
-
read syscall.h comments closely (they
give a good idea of what needs to be done)
-
in test/halt.c, a call to Halt() will
make a call to ExceptionHandler(int which)
-
passing an integer with the value SC_Halt
-
SyscallCreate, SyscallOpen, SyscallRead,
etc. will be called by the ExceptionHandler() case that they correspond
to
-
you must increment the PC before returning
from each of these calls
Tips
for Create()
-
SyscallCreate is a wrapper around the
call of Create from the global variable file System (defined in system.cc)
-
you have to pass a name to the fileSystem->Create()
method
-
since the call is in KERNEL space, but
the pointer is in USER space you must first create a copy of the name by
allocating storage for, and copying the text
-
this buffers kernel calls from user
space for protection
-
ALL data transferred between USER/KERNEL
or KERNEL/USER *MUST* be copied
-
things may work if you don't but you
will lose points for ignoring protection
-
see the routine UserToKernelString(name)
in exception.cc
-
delete this storage when done
-
this basic process is used in most of
the SyscallXXX routines
-
you must increment the PC before returning
Tips
for Open()
opens a file if it exists, or, if
it doesn't, creates it and then opens it
-
check that the maximum number of open
files allowed are not already open
-
use fileSystem calls (i.e., fileSystem->Open)
to get the OpenFile*
-
you have to assign a unique ID to the
file which gets saved with the file ptr on your file list
-
this ID is also returned to the user
by the function at the end
-
one way of generating an ID is to have
a routine that takes a start value that gets set to one more than the maximum
ID currently in the list of open files
-
an open file is then added to the file
list
files->SortedInsert(openFile, id)
Tips
for Close()
close() finds a file by ID and removes
it from the list of files
-
use files->SortedRemove(id) to get your
Openfile*
-
remove the file
-
don't forget to check if the ID is valid
Tips
for Write()
-
writing to a console is different than
writing to a file
-
the console output ID is ALWAYS ConsoleOutput,
(1)
-
the equivalent in Unix is STDOUT
-
this can be skipped until you implement
a console, but be aware of the difference
-
non-console file write is done via the
OpenFile routine Write
-
you have to find the openfile pointer
by ID and pass along the data buffer and size
-
Remember, the buffer pointer parameter
points to USER space
Console
-
you will need a global Console structure
(see machine/console.h)
-
this is the standard input/output device
which is your screen
-
look at machine/console.cc
-
trick is to make sure characters are
ready before you can read them and the device is not busy before you can
write them. Basically you have to synchronize to the console
I hope this sounds familiar to the
producer/consumer problem .
Tips
for Read()
-
follow the tips for Write()
-
console input ID is ALWAYS ConsoleInput,
(0) and is equivalent to STDIN in Unix
Tips
for SyscallExec()
-
forks and executes a new program in a new address space
-
it starts out by converting name to a string in kernel memory if the thread
that called exec is in userspace
-
a unique ProcessID is then obtained
-
opens the executable file and sends it to a new address space
AddrSpace
*aspace = new AddrSpace(...pass appropriate arguments here...)
-
if any check fails, for instance if the file doesn't exist, or there is
not enough memory, -1 is returned
-
the new process is inserted into a list of children for the parent (this
is used later in SyscallJoin and SyscallExit)
-
then a new process entry is made in the linked list of processes
-
the data in this entry is initialized
-
finally a new thread is made and the process can execute via a call to
Fork() passing as one of its parameters "ExecProcess"
//----------------------------------------------------------------------
// ExecProcess
//
This is called by SyscallExec to actually run the new process.
//
It is a forked kernel thread which begins by setting up it's
//
address space and initializes things. It then calls machine->Run
//
which causes it to execute.
//
//----------------------------------------------------------------------
void ExecProcess(int execFileName)
{
DEBUG('4', "Starting
process %s\n", (char *) execFileName);
currentThread->space->InitRegisters();
currentThread->space->RestoreState();
// load page table register
machine->Run();
// jump to the user progam
ASSERT(FALSE);
// machine->Run never returns;
// the address space exits
// by doing the syscall "exit"
}
-
the ProcessID is returned to the calling function
Tips
for SyscallJoin()
-
used to join and retrieve the exit value of a processes child
-
the first thing it does is go through the calling process's child list
to ensure that the child on which the calling process wishes to join is
indeed it's child
-
if it isn't, -1 is returned
-
the child is then removed from the childlist
-
the next thing that occurs is the process entry of the child is found and
a semaphore is used to make sure that the status variable is set to the
child's exit value before using it
-
in other words, the semaphore is used to make sure the child actually exited
-
when this is true, the value is taken from status
-
then the child's process entry is deleted and this function returns the
status
Tips
for SyscallExit()
-
what a user process calls to die
-
a process calls SyscallExit() passing in one int argument (process status)
indicating it's return status
1.this function
releases the address space of the process and joins (calling SyscallJoin)
on any children of the process that were never joined on (thus freeing
up
'zombies')
2.all open files
are closed (as before)
3.function then
puts the exit status in processPtr->status
4.semaphore
is used to prevent the parent from joining before this function is called
5.then the thread
dies gracefully using currentThread->Finish()
Errata in the Given Code
Use the following code for UserToKernel and KernelToUser. The distributed
code uses ReadMem/WriteMem, but they appear to have problems. The other
method (also given in the distribution but commented out) works. Comment/Uncomment
appropriately.
//-----------------------------------------------------------
// UserToKernel
// Copies a buffer of length from user
virtual address to a kernel
// structure.
//-----------------------------------------------------------
char *UserToKernel(char *source, int length)
{
int current=0;
int phyAddr;
char *dest;
if(!source)
return (int)0;
dest=new char[length];
while(current<length){
phyAddr=GetPhysAddrInKernel((int)source++, FALSE);
if(!phyAddr)
return 0;
dest[current++]=machine->mainMemory[phyAddr];
}
return dest;
}
//-------------------------------------------------------
// KernelToUser
// Copies a buffer of length from kernel
space to user space
//-------------------------------------------------------
void KernelToUser(char *dest, char *source, int length)
{
int current=0;
int phyAddr;
while(current<length){
phyAddr=GetPhysAddrInKernel((int)dest++, TRUE);
// dest won't get deleted if address is invalid
machine->mainMemory[phyAddr]=source[current++];
}
}