CSC546 - Operating Systems


Topics

Exec

StartProcess

Thread::Fork

AddrSpace

Shadow Page Table

AddrSpace::AddrSpace(char * name, OpenFile* executable)

AddrSpace::mapPages()

AddrSpace::loadpage(int vpn, int ppn)

Next Assignment


Exec Overview

/* 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.


Exec Implementation

How is a new nachos process created?


Thread::Fork

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.


Exec Detail Outline

#include "syscall.h"
int main()
{
  Exec("simplewrite");
  Exit(0);
}

Steps in implementing the Exec system call:

  1. Copy the name of the executable from the user program's address space.

  2. 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.

  3. 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.)

  4. 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.

  5. Return the address of the thread, cast as an int, as the return value in register 2. Adjust the PC registers.

StartProcess

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.


AddrSpace

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.


Shadow Page Table

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.


AddrSpace::mapPages()

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?


AddrSpace::loadPage(int vpn, int ppn)

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);

BitMap

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
};

CoreMap

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];
    }

};

Where to Declare the BitMap

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
...

Where to Initialize

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
}

Next Assignment

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.