Current Assignment
Exec
Thread 'name' Data Member
Next Assignment
Unix fork and wait
Join
Last Required Assignment
Not Required
The original Nachos code loads every user program into Mips physical memory locations which are the same as the compiler generated virtual addresses.
For this assignment the original Nachos code needs to be modified and extended to load a user program into available page frames instead so that two or more user programs can be loaded simultaneously into the Mips memory.
After the modifications have been made, you will need to retest nachos to make sure it can still execute a single user program using the -x option as before.
This will not yet test having 2 or more user programs in the Mips memory, but it is an essential requirement before doing so.
In more detail, put coremap.h and coremap.cc files in the userprog directory. Then in code/Makefile.common:
Declare frameMap in threads/system.h:
#ifdef USER_PROGRAM extern CoreMap *frameMap; #endif
Define frameMap in threads/system.cc:
#ifdef USER_PROGRAM CoreMap *frameMap; #endif
Allocate an instance of CoreMap and assign it to frameMap in the Initialize routine also in threads/system.cc
void Initialize(int argc, char **argv)
{
...
#ifdef USER_PROGRAM
frameMap = new CoreMap(NumPhysPages);
#endif
class PageEntry
{
public:
PageEntry()
{
inSwapFile = false;
inFileAddr = 0;
size = 0;
}
bool inSwapFile; // true if page is in swapFile
int inFileAddr;
// number of bytes to fetch from file if inSwapFile is false
// If 0, then set all bytes to 0.
int size;
};
Put the file, pageentry.h, with this definition in the userprog directory.
Modify the code/Makefile.common file to add pageentry.h to the USERPROG_H definition.
TranslationEntry *pageTable; // Assume linear page table translation
// for now!
unsigned int numPages; // Number of pages in the virtual
// address space
PageEntry *shadowPageTable; // Shadow page table
OpenFile *exeFile; // The Executable file loaded
// in this address space.
OpenFile *swapFile; // The Swap file for the
// program loaded in this
// address space.
};
void AddrSpace::mapPages(const NoffH&)
This function should set the shadowPageTable entries for each page. This means calculating the inFileAddr (offset in the executable file) of this page and the size (number of bytes to read from the file).
You will have to insert the following include into addrspace.h
#include "noff.h"
and comment out the same include in addrspace.cc. It should not be necessary to comment out this include. However, the header file noff.h does not protect itself from being included twice. Since addrspace.cc already includes addrspace.h (which now will include noff.h), it must not also include noff.h.
Note that the uninitialized data segment will contain no bytes in the executable file since all bytes in this segment are understood to be 0.
The noff format generated by the compiler has the code segment followed immediately by the initialized data segment. Next comes the uninitialized data segment, and finally the stack segment.
Unless the code segment size happens to be an exact multiple of the page size, this means that the last code page will not be completely full of code. In general, therefore, this last code page will also contain the beginning of the initialized data segment.
If the last page of the initialized data segment is not full, then the size (number of bytes to read from the executable file) for that page will be less than the page size.
So shadowPageTable[i].size will be equal to:
AddrSpace::loadPage(int vnp, int ppn);
which uses the shadow page table to load virtual page, vpn, into physical page frame ppn. The shadow page table will contain all the information necessary to know how to load the page either from the executable file (if this is the first time the page is loaded) or from the swap file. (For this assignment the swap file will not be used since the program code is not swapped out.)
In particular, the shadow page table will have the file offset (inFileAddr) and the number of bytes to read (size).
The OpenFile::ReadAt function can then be used to read the needed bytes from the file for the requested page.
When a page is loaded, the page table should be updated. The valid bit should be set and the physical frame for this page should be set.
AddrSpace::AddrSpace(char * name, OpenFile* executable);
This constructor should
This should be changed to check if the number of pages in the current program is <= the number of free physical pages. E.g.,
numPages <= frameMap->NumClear()
After recompiling, try to execute some Mips user programs using the -x nachos commandline option.
/* A unique identifier for an executing user program (address space) */ typedef int SpaceId; /* Run the executable, stored in the Nachos file "name", and return the * address space identifier */ SpaceId Exec(char *name);
For convenience, you could simply return the address of the Thread object (cast as an integer). This will provide uniqueness and allow easy location of the child Thread object by the kernel.
How is a new nachos process created?
Thread *t;
t = new Thread(name); // where name can be the name of the executable
// file.
Note that creating the Thread object doesn't allocate an address space or make the thread READY to run. It just allocates the Thread object that the nachos kernel will use to to keep track of the new thread.
t->Fork( (void (*)(int)) StartProcess, (int) savedExeName);
Recall the code for Thread::Fork
void
Thread::Fork(VoidFunctionPtr func, int arg)
{
DEBUG('t', "Forking thread \"%s\" with func = 0x%x, arg = %d\n",
name, (int) func, arg);
StackAllocate(func, arg);
IntStatus oldLevel = interrupt->SetLevel(IntOff);
scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts
// are disabled!
(void) interrupt->SetLevel(oldLevel);
}
Note that the first argument to Thread::Fork cannot be the user program. It has to be a nachos kernel routine (like StartProcess).
But the sole purpose of StartProcess (exeName) is to load the mips executable file passed to StartProcess into the Mips memory and to execute it.
#include "syscall.h"
int main()
{
Exec("simplewrite");
Exit(0);
}
Steps in implementing the Exec system call:
Copy the name of the executable from the user program's address space.
Open that file and read the noff header, checking for the noff magic. (See AddrSpace constructor). If this file does not begin with noffmagic, then it is not a valid Mips executable file. Return -1 in register 2. Close the file. Adjust PC registers.
Create a new Thread object, t, to represent the new child nachos process. (Make sure the name of the Thread object is allocated space and saved in the Thread object.)
Call
t->Fork( (void (*)(int))StartProcess, (int) userProgName);
to run StartProcess in a separate nachos thread from the calling thread.
Note that StartProcess will be passed the parameter userProgName and will load the program into Mips memory and begin executing it.
Here is StartProcess (again):
void
StartProcess(char *filename)
{
OpenFile *executable = fileSystem->Open(filename);
AddrSpace *space;
if (executable == NULL) {
printf("Unable to open file %s\n", filename);
return;
}
// Use the new constructor
space = new AddrSpace(filename, executable);
currentThread->space = space;
delete executable; // close file
space->InitRegisters(); // set the initial register values
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"
}
We can try to make this function the basis of each nachos user process as we implement multiprogramming.
The Exec system call cannot simply pass a local character array to Thread::Fork:
void do_Exec()
{
char buf[256];
int bytes; ...
...
t->Fork( (void (*)(int)) StartProcess, (int) buf);
Since buf will go away when do_Exec returns, but StartProcess will not execute until Thread t is scheduled.
Here is the Thread class and constructor:
class Thread {
private:
// NOTE: DO NOT CHANGE the order of these first two members.
// THEY MUST be in this position for SWITCH to work.
// the current stack pointer
int* stackTop;
// all registers except for stackTop
int machineState[MachineStateSize];
public:
Thread(char* debugName); // initialize a Thread
~Thread(); // deallocate a Thread
// NOTE -- thread being deleted
// must not be running when delete
// is called
...
void Fork(VoidFunctionPtr func, int arg);
...
char* getName() { return (name); }
...
private:
// some of the private data for this class is listed above
int* stack; // Bottom of the stack
// NULL if this is the main thread
// (If NULL, don't deallocate stack)
ThreadStatus status; // ready, running or blocked
char* name;
...
};
Here is the Thread constructor:
Thread::Thread(char* threadName)
{
name = threadName;
stackTop = NULL;
stack = NULL;
status = JUST_CREATED;
#ifdef USER_PROGRAM
space = NULL;
#endif
}
This constructor should create storage the name and then copy threadName to name, not just assign the pointer.
Exec can use the "permanent" character string 'name' stored in the Thread object and holding the executable file name to pass to Thread::Fork instead of a local variable which is not permanent and will disappear as soon as the Exec system call returns.
Make the change to Thread class and provide an initial implementation of Exec.
I will provide suggested steps.
Test Exec (and multiprogramming) by writing a user program that runs another user program.
input: return code for parent process
output: none
{
ignore all signals;
close all open files;
free regions, memory associated with process;
make process state zombie;
assign parent process ID of all child processes to be init process (1);
if any children were zombie, send death of child signal to init;
send death of child signal to parent process;
context switch;
}
|
input: address of variable to store status of exiting process
output: child ID, child exit code
{
if (waiting process has no child processes)
return(error);
for(;;)
{
if ( waiting process has zombie child)
{
pick arbitrary zombie child;
free child process table entry;
return(child ID, child exit code);
}
if (process has no children)
{
return error;
}
sleep at interruptible priority (event child process exits);
}
}
|
A modified version of the Thread class (which I will provide) follows. The purpose of these modifications is to facilitate Unix style implementation of Nachos Exit and Join.
The Join system call will need to block the calling Nachos process until notification that one of its children has terminated. Nachos semaphores will be used to block the process.
When a Nachos process exits, all its terminated children can be completely removed. So a list of all children is needed. This is why the list.h include file is added.
Here are the additional include files needed in thread.h:
// Additional include files
#include "list.h"
#include "synch.h" //
class Semaphore; // Forward declaration; needed for synch.cc
// since synch.cc includes synch.h includess thread.h
...
class Thread {
private:
...
public:
...
// Additions
ThreadStatus getStatus() { return status; }
void setParent(Thread *t) { parent = t; }
Thread * getParent() { return parent; }
void addChild(Thread *t) { children.Append(t); t->setParent(this);}
void releaseChildren();
void releaseChild(Thread *t);
Thread * getChild(int id);
bool isZombie() const { return _zombie; }
void setZombie(bool b) { _zombie = b; }
int getExitStatus() const { return exitStatus; }
void setExitStatus(int val) { exitStatus = val;}
void Wait();
void Signal();
// End of Additions
private:
// Additions
List children;
Thread * parent;
Semaphore *joinSem;
int exitStatus;
bool _zombie;
// End additions.
};
Here are the implementations of the Wait and Signal member functions added to Thread:
void Thread::Wait()
{
joinSem->P();
}
void Thread::Signal()
{
joinSem->V();
}
This will be to use the new Thread modified class (which I will provide) and to fully implement Join and Exit (with modifications to Exec).
You should then be able to write an appropriate "nachos" shell program to allow interactive execution of Mips programs.
Most of the frame work is set for paging; i.e., swapping pages out to disk and implementing virtual memory. I will provide an outline of steps for this.
(You can do this over your summer break.)