typedefstructsegment { int virtualAddr; /* location of segment in virt addr space */ int inFileAddr; /* location of segment in this file */ intsize; /* size of segment */ } Segment;
typedefstructnoffHeader { int noffMagic; /* should be NOFFMAGIC */ Segment code; /* executable code segment */ Segment initData; /* initialized data segment */ Segment uninitData; /* uninitialized data segment -- should be zero'ed before use */ } NoffHeader;
There are only three kinds of segment in Nachos file header: executable code, initialized data and uninitialized data. And the noffMagic is a special number to be recognized as a Nachos file.
Start Process
In main.cc in threads directory, we see:
if (!strcmp(*argv, "-x")) { // run a user program ASSERT(argc > 1); StartProcess(*(argv + 1)); argCount = 2; }
StartProcess is in progtest.cc in userprog directory:
voidStartProcess(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; space->print(); 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" }
Firstly it opens an executable file, then gives it address space, make the current thread to execute it, initialize all registers, load page table, then run.
For details, we should look into every function.
addrspace.cc
In userprog directory. See the constructor:
AddrSpace::AddrSpace(OpenFile *executable) { NoffHeader noffH; unsignedint i, size; executable->ReadAt((char *)&noffH, sizeof(noffH), 0); if ((noffH.noffMagic != NOFFMAGIC) && (WordToHost(noffH.noffMagic) == NOFFMAGIC)) SwapHeader(&noffH); ASSERT(noffH.noffMagic == NOFFMAGIC); // how big is address space? size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStackSize; // we need to increase the size to leave room for the stack numPages = divRoundUp(size, PageSize); size = numPages * PageSize; ASSERT(numPages <= NumPhysPages); // check we're not trying to run anything too big // at least until we have virtual memory DEBUG('a', "Initializing address space, num pages %d, size %d\n", numPages, size); // first, set up the translation pageTable = new TranslationEntry[numPages]; for (i = 0; i < numPages; i++) { pageTable[i].virtualPage = i; // for now, virtual page # = phys page # pageTable[i].physicalPage = i; pageTable[i].valid = TRUE; pageTable[i].use = FALSE; pageTable[i].dirty = FALSE; pageTable[i].readOnly = FALSE; // if the code segment was entirely on a separate page, // we could set its pages to be read-only } // zero out the entire address space, to zero the unitialized data segment and the stack segment bzero(machine->mainMemory, size); // then, copy in the code and data segments into memory if (noffH.code.size > 0) { DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", noffH.code.virtualAddr, noffH.code.size); executable->ReadAt(&(machine->mainMemory[noffH.code.virtualAddr]), noffH.code.size, noffH.code.inFileAddr); } if (noffH.initData.size > 0) { DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", noffH.initData.virtualAddr, noffH.initData.size); executable->ReadAt(&(machine->mainMemory[noffH.initData.virtualAddr]), noffH.initData.size, noffH.initData.inFileAddr); } }
First it reads the file header, and make sure it’s big endian and it’s a NOFF file by the NOFF magic number. Then it calculates the size of address space, including the stack, after which the page numbers can be decided. Then it creates the page tables, initializing all the values in each page table. Currently the virtual page is the same as the physical page.
Then it clear all the address space. After that, copy the code and data into memory.
Page table
The last step we see the page table, the code of translation from virtual page to physical page is in translate in machine directory:
classTranslationEntry { public: int virtualPage; // The page number in virtual memory. int physicalPage; // The page number in real memory (relative to the start of "mainMemory" bool valid; // If this bit is set, the translation is ignored. // (In other words, the entry hasn't been initialized.) bool readOnly; // If this bit is set, the user program is not allowed // to modify the contents of the page. bool use; // This bit is set by the hardware every time the // page is referenced or modified. bool dirty; // This bit is set by the hardware every time the page is modified. };
ExceptionType Machine::Translate(int virtAddr, int* physAddr, intsize, bool writing){ int i; unsignedint vpn, offset; TranslationEntry *entry; unsignedint pageFrame; DEBUG('a', "\tTranslate 0x%x, %s: ", virtAddr, writing ? "write" : "read"); // check for alignment errors if (((size == 4) && (virtAddr & 0x3)) || ((size == 2) && (virtAddr & 0x1))) { DEBUG('a', "alignment problem at %d, size %d!\n", virtAddr, size); return AddressErrorException; } // we must have either a TLB or a page table, but not both! ASSERT(tlb == NULL || pageTable == NULL); ASSERT(tlb != NULL || pageTable != NULL); // calculate the virtual page number, and offset within the page, from the virtual address vpn = (unsigned) virtAddr / PageSize; offset = (unsigned) virtAddr % PageSize; if (tlb == NULL) { // => page table => vpn is index into table if (vpn >= pageTableSize) { DEBUG('a', "virtual page # %d too large for page table size %d!\n", virtAddr, pageTableSize); return AddressErrorException; } elseif (!pageTable[vpn].valid) { DEBUG('a', "virtual page # %d too large for page table size %d!\n", virtAddr, pageTableSize); return PageFaultException; } entry = &pageTable[vpn]; } else { for (entry = NULL, i = 0; i < TLBSize; i++) if (tlb[i].valid && ((unsignedint)tlb[i].virtualPage == vpn)) { entry = &tlb[i]; // FOUND! break; } if (entry == NULL) { // not found DEBUG('a', "*** no valid TLB entry found for this virtual page!\n"); return PageFaultException; // really, this is a TLB fault, // the page may be in memory, but not in the TLB } } if (entry->readOnly && writing) { // trying to write to a read-only page DEBUG('a', "%d mapped read-only at %d in TLB!\n", virtAddr, i); return ReadOnlyException; } pageFrame = entry->physicalPage; // if the pageFrame is too big, there is something really wrong! // An invalid translation was loaded into the page table or TLB. if (pageFrame >= NumPhysPages) { DEBUG('a', "*** frame %d > %d!\n", pageFrame, NumPhysPages); return BusErrorException; } entry->use = TRUE; // set the use, dirty bits if (writing) entry->dirty = TRUE; *physAddr = pageFrame * PageSize + offset; ASSERT((*physAddr >= 0) && ((*physAddr + size) <= MemorySize)); DEBUG('a', "phys addr = 0x%x\n", *physAddr); return NoException; }
It decides whether to use TLB or page table. Get page entry, then get physical page frame, make current page entry in use, if writing, make it dirty which means it’s modified, then get the physical address.
Registers
voidAddrSpace::InitRegisters(){ int i; for (i = 0; i < NumTotalRegs; i++) machine->WriteRegister(i, 0); // Initial program counter -- must be location of "Start" machine->WriteRegister(PCReg, 0); // Need to also tell MIPS where next instruction is, because of branch delay possibility machine->WriteRegister(NextPCReg, 4); // Set the stack register to the end of the address space, where we allocated the stack; // but subtract off a bit, to make sure we don't accidentally reference off the end! machine->WriteRegister(StackReg, numPages * PageSize - 16); DEBUG('a', "Initializing stack register to %d\n", numPages * PageSize - 16); } voidAddrSpace::RestoreState(){ machine->pageTable = pageTable; machine->pageTableSize = numPages; }
Run
voidMachine::Run(){ Instruction *instr = new Instruction; // storage for decoded instruction if(DebugIsEnabled('m')) printf("Starting thread \"%s\" at time %d\n", currentThread->getName(), stats->totalTicks); interrupt->setStatus(UserMode); for (;;) { OneInstruction(instr); interrupt->OneTick(); if (singleStep && (runUntilTime <= stats->totalTicks)) Debugger(); } }
It runs the instructions one by one from registers. Below is how it’s fetched:
boolMachine::ReadMem(int addr, intsize, int *value){ int data; ExceptionType exception; int physicalAddress; DEBUG('a', "Reading VA 0x%x, size %d\n", addr, size); exception = Translate(addr, &physicalAddress, size, FALSE); if (exception != NoException) { machine->RaiseException(exception, addr); return FALSE; } switch (size) { case1: data = machine->mainMemory[physicalAddress]; *value = data; break; case2: data = *(unsigned short *) &machine->mainMemory[physicalAddress]; *value = ShortToHost(data); break; case4: data = *(unsignedint *) &machine->mainMemory[physicalAddress]; *value = WordToHost(data); break; default: ASSERT(FALSE); } DEBUG('a', "\tvalue read = %8.8x\n", *value); return (TRUE); }
First it translate virtual address to physical address, then read in main memory.
To do
test
In test directory, do below to halt.c :
intmain(){ int i, j, k; k = 3; i = 2; j = i - 1; k = i - j + k; Halt(); /* not reached*/ }
if (executable == NULL) { printf("Unable to open file %s\n", filename); return; } space = new AddrSpace(executable); currentThread->space = space; // here space->print();
Run nachos –d m –x ../test/halt.noff , we can see: