These suggestions were written by a previous CS170 TA when sample code was not made available. They may be useful for some of you and you DO NOT have to read these suggestions.
Thread::Fork()
spawns a new kernel thread that uses the same
AddrSpace
as the thread that spawned it. If that address space is
duplicated and not shared, it is no longer a kernel thread but a
Forked Process. If that AddrSpace
instead contains code and data
loaded in from a separate file, it is no longer a forked process,
but an executed process. The details of Fork()
and Exec() are
covered in steps 1) and 7) below.
code/userprog/exception.cc
, put function calls for each system call.
just print debug statements in these functions for now, so you can see
when system calls get executed. The function may need to return or
take an argument. Because the argument lies in user space, you
will need to transfer it over using machine register reads and
writes. A sample stub illustrating this is shown below.
case SC_Join: { int result = myJoin(machine->ReadRegister(4)); machine->WriteRegister(2, result); break; }
Fork()
, Yeild()
, Exec()
, Join()
and Exit()
are
implemented in that order, you will not have to worry about the
call you are currently working on depending upon unimplemented
calls.
ExceptionHandler
function needs to do
when executing a system call is increment the program counter. Write a
helper function to do this - it needs to update PCreg
, NextPCreg
, and
PrevPCreg
. They should all incriment by 32 bits (4 bytes).
Fork()
. Fork will create a new kernel thread and set
it's AddrSpace to be a duplicate of the CurrentThread's space. It
sets then Yields()
. The new thread runs a dummy function that will
will copy back the machine registers, PC and return registers saved
from before the yield was performed. You did save the PC, return
and other machine registers didn't you?
Fork()
will not work completely
until the completion of step 4. Don't get stuck on step 1), steps
2-4 are much more important.
getPage()
allocates the first clear page and 2) clearPage(int i)
takes the index of a page and frees
it. You can use a bitmap (in code/userprog/bitmap.*
) with one bit
per page to track allocation or use your own integer array, which
ever you prefer.
AddrSpace
(code/userprog/addrspace.*
) to use the memory
manager. first, modify the page table constructors to use pages
allocated by your memory manager for the physical page index. The
later modification will come in step 4.
AddrSpace::Translate
function, which converts a virtual
address to a physical address. It does so by breaking the virtual
address into a page table index and an offset. It then looks up
the physical page in the page table entry given by the page
table index and obtains the final physical address by combining
the physical page address with the page offset. It might help to
pass a pointer to the space you would like the physical address to
be stored in as a paramter. This will allow the function to
return a boolean TRUE or FALSE depending on whether or not the
virtual address was valid. If confused, consult the text book on memory management and page table or
Machine::Translate()
in machine/translate.cc
AddrSpace::ReadFile
function, which loads the code and
data segments into the translated memory, instead of at position 0
like the code in the AddrSpace
constructor already does. This is
needed not only for Exec()
but for the initial startup of the
machine when executing any test program with virtual memory.
system.h
). All of your user-level file I/O
must go through the diskBuffer
. Be sure to to under or over run
the buffer during the copy. Also be sure not to write too much of
the file into memeory. You can use the following prototype for
the function.
int AddrSpace::ReadFile(int virtAddr, OpenFile* file, int size, int fileAddr) {You will also need to use the functions:
File::ReadAt(buff,size,addr)
and bcopy(src,dst,num)
as well as the memory locations at
machine->mainMemory[physAddr].
At this point, test programs should work the same as before. That is,
the halt program and other system calls will still operate the way they
did before you modified AddrSpace
. Also Fork()
should be
working. Test your implementation appropriately.
Yield()
. Given that forked processes are almost the same
as kernel threads, this one should be trivial.
Exec()
. Exec is creating a new process (kernel thread)
with new code and data segments loaded from the OpenFile
object
constructed from the filename passed in by the user. In order
to get that file name you will have to write a function that
copies over the string from user space. This function will start
copying memory from the physical address pointed to by the
virtual address in machine->ReadRegister(4)
. It should go until
it hits a NULL
byte.
Exec(someTestFile)
from someOtherTestFile.c
(in the test/
dir).
Join()
. Join should force the current running thread to
wait for some process to finish. The PCB manager can keep track of
who is waiting for who using a condition variable for each PCB.
Exit(int status)
. This function should set set the
status in the PCB being exited. It should also force any threads
waiting on the exiting process to wake up.
~cs170/nachos-projtest/proj2
that don't rely on part 2. If
you have time, test using some programs to do crazy or malicious
things.
We will be using NACHOS files exclusivly in part 2. This will be good
preparation for project 3. NACHOS Files are similar to Unix files
except they are stored on avirtual disk that is implemented as one big
Unix file. The interface to Create, Open, Close, WriteAt
and ReadAt
to those files are defined in filesys/filesys.cc
, filesys/openfile.cc
and filesys/filehdr.h
. You may want to look through those files when
getting ready to call these functions for the first time.
SysOpenFile
object class that contains a pointer to the
file system's OpenFile
object as well as the systems (int)FileID
and
(char *)fileName
for that file and the number of user processes
accessing currently it. Declare an array of SysOpenFile
objects for
use by all system calls implemented in part 2.
UserOpenFile
object class that contains the (char *)fileName
,
an index into the global SysOpenFile
table and an integer offset
represeting a processes current position in that file.
AddrSpace
's PCB
to contain an array of
OpenUserFiles
. Limit
the number to something reasonable, but greater than 20. Write a
method (in PCBManager
) that returns an OpenUserFile
object given the
fileName.
Create(char *fileName)
. This is a straight forward call
that should simply get the fileName from user space then use
fileSystem->Create(fileName,0)
to create a new instance of an
OpenFile object. Until a user opens the file for IO it is not
necessary to do anything further.
Open(char *fileName)
;. This function will use an OpenFile
object created previously by fileSystem->Open
(fileName). Once you have this object, check to see if it is already
open by some other process in the global SysOpenFile
table. If so,
incriment the userOpens
count. If not, create a new SysOpenFile
and
store it's pointer in the global table at the next open slot. You
can obtain the FileID
by looking up the name in your
SysOpenFile
table.
OpenUserFile
object (given a
SysOpenFile
object) and store it in the currentThread
's PCB
's
OpenUserFile
array.
FileID
to the user.
MainMem
and a buffer given a
staring virtual address and a size. It should operate in the same way
AddrSpace::ReadFile
writes into the main memory one diskBuffer
at a
time.
ReadFile
into a "helper"
function called userReadWrite()
that is general enough that both
ReadFile()
, myRead()
and myWrite()
can call. It need only be
parameterized by the type of operation to be performed (Read or
Write).
Write()
, It will read from the MainMem
addressed by
the virtual addresses. It writes into the given (empty) buffer.
Write()
, will then put that buffer into an OpenFile
. When called
by Read()
, It will write into MainMem
the data in the given (full)
buffer that Read()
read from an OpenFile.
Write(char *buffer, int size, OpenFileId (int)id)
; First
you will need to get the arguments from the user by reading registers
4-6. If the OpenFileID
is == ConsoleOutput (syscall.h)
, then you
call PutChar() in console.cc to print the buffer content.
Otherwise, grab a handle to the OpenFile
object from
the user's openfile list pointing to the global file list. Why can't
you just go directly to the global file list?... becuase the user may
not have opened that file before trying to write to it. Once you have
the OpenFile
object, you should fill up a buffer
using your userReadWrite()
function. Then simply call
OpenFileObject->Write(myOwnBuffer, size)
;
Read(char *buffer, int size, OpenFileId id)
; Get the
arguments from the user in the same way you did for Write()
. If the
OpenFileID
== ConsoleInput (syscall.h)
, use a routine
( GetChar()
in console.cc) to read into a buffer one character at a time.
Otherwise, grab a handle to the OpenFile object in the same way you
did for Write()
and use OpenFileObject->ReadAt(myOwnBuffer,size,pos)
to
put n characters into your buffer. pos is the position listed in the
UserOpenFile
object that represents the place in the current file you
are writing to. The number read is returned from
ReadAt()
.
userReadWrite()
function. Finally,
return the number of bytes written.
~cs170/nachoos-projtest/proj2
Now that both
parts are finished all test programs should work.