Exec
StartProcess
Thread::Fork
AddrSpace
Shadow Page Table
AddrSpace::AddrSpace(char * name, OpenFile* executable)
AddrSpace::mapPages()
AddrSpace::loadpage(int vpn, int ppn)
Next Assignment
/* 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);
What should Exec return? Is the address space identifier sufficient?
I would expect to get the identity of the newly created nachos child.
In Unix, this would be the process id of the new child, an integer.
The new nachos child will be represented in the kernel by a Thread object.
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. 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; } space = new AddrSpace(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.
We will need to change AddrSpace's constructor as the current constructor loads any user program with virtual addresses equal to physical addresses.
Instead of modifying the current constructor, I suggest you just add another constructor:
AddrSpace::AddrSpace(char * name, OpenFile* executable)
StartProcess would need to pass the executable file name as well as the OpenFile object for it.
The new constructor for AddrSpace can begin as the original constructor. It should calculate the number of pages for the executable as before.
Since paging out is not yet being implemented, the constructor should also use the bitmap for physical page to check if there are enough free frames to load this program.
However, it should not load the program into memory in the same way as before.
Later with paging, we will need additional information about each page that is not part of the Page Table. In particular, we must keep track of whether the page is in a swap file or in the original executable file.
The shadow page table is a table of entries like this:
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; };
The AddrSpace class should then add:
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 };
Note that the constructor for PageEntry marks each entry with inSwapFile set to false.
This member function should calculate for each virtual page, the inFileAddr and the size for pages in the code and initialized data segments. All other pages should be set initially to size 0 and inFileAddr = 0.
How?
Why?
The information to load virtual page vpn is in the shadow page table. If this page has code, or code + data, or data:
pAddr = ppn * PageSize; exeFile->ReadAt(&(machine->mainMemory[pAddr]), shadowPageTable[vpn].size, shadowPageTable[vpn].inFileAddr);
where exeFile is the OpenFile object for the executable file. (Later we will also need swapFile->ReadAt...)
Setting the size to 0 for the uninitialized data (and stack) pages means that the statement above will not read anything. To handle this case we could first zero out the selected page before executing the statement above.
memset(&(machine->mainMemory[pAddr]), 0, PageSize);
We need to keep track of the physical pages.
Here is the existing BitMap class again:
class BitMap { public: BitMap(int nitems); // Initialize a bitmap, with "nitems" bits // initially, all bits are cleared. ~BitMap(); // De-allocate bitmap void Mark(int which); // Set the "nth" bit void Clear(int which); // Clear the "nth" bit bool Test(int which); // Is the "nth" bit set? int Find(); // Return the # of a clear bit, and as a side // effect, set the bit. // If no bits are clear, return -1. int NumClear(); // Return the number of clear bits virtual void Print(); // Print contents of bitmap // These aren't needed until FILESYS, when we will need to read and // write the bitmap to a file void FetchFrom(OpenFile *file); // fetch contents from disk void WriteBack(OpenFile *file); // write contents to disk private: int numBits; // number of bits in the bitmap int numWords; // number of words of bitmap storage // (rounded up if numBits is not a // multiple of the number of bits in // a word) unsigned int *map; // bit storage };
The BitMap class is a general purpose class. It can be adapted for the particular purpose of tracking physical pages. Perhaps something like this:
#include "bitmap.h" #include "thread.h" class CoreMapEntry { public: CoreMapEntry() { stillValid = false; owner = NULL; virtualPage = -1; locked = false; } bool stillValid; AddrSpace *owner; int virtualPage; bool locked; }; class CoreMap : public BitMap { public: CoreMapEntry *theMap; CoreMap(int nPages) : BitMap(nPages) { theMap = new CoreMapEntry[nPages]; } virtual ~CoreMap() { delete theMap; } virtual int findVictimFrame(AddrSpace *t); virtual void Print(); CoreMapEntry& operator[](int i) { ASSERT(0<= i); return theMap[i]; } };
Recall that machine, fileSystem, currentThread and such global pointers to singleton objects are declared in threads/system.h. E.g.,
// File: system.h
...
extern Thread *currentThread; // the thread holding the CPU
extern Thread *threadToBeDestroyed; // the thread that just finished
extern Scheduler *scheduler; // the ready list
extern Interrupt *interrupt; // interrupt status
extern Statistics *stats; // performance metrics
extern Timer *timer; // the hardware alarm clock
#ifdef USER_PROGRAM
#include "machine.h"
extern Machine* machine; // user program memory and registers
#include "coremap.h"
extern CoreMap *frameMap;
#endif
...
These global singleton objects are defined and initialized in the file threads/system.cc. E.g.,
// File: system.cc ... Thread *currentThread; // the thread we are running now Thread *threadToBeDestroyed; // the thread that just finished ... #ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB Machine *machine; // user program memory and registers #endif ... #ifdef USER_PROGRAM // Added CoreMap *frameMap; // End #endif void Initialize(int argc, char **argv) { ... currentThread = new Thread("main"); currentThread->setStatus(RUNNING); interrupt->Enable(); CallOnUserAbort(Cleanup); // if user hits ctl-C #ifdef USER_PROGRAM machine = new Machine(debugUserProg); // this must come first #endif ... #ifdef USER_PROGRAM // Added ... frameMap = new CoreMap(NumPhysPages); // End #endif }
Modify AddrSpace and StartProcess as above. You might want to save backup copies or use RCS first. You don't have to implement Exec yet.
After making the changes, try running a user program again.