## in line 53 targets = exec bar halt shell matmult sort
## in line 74 INCDIR = -I../lab7-8 -I../userprog -I../threads
make.
Then in userprog directory, run below:
./nachos -d m -x ../test/bar.noff
Then we got:
Exception: syscall Unexpected user mode exception 1 2 Assertion failed: line 61, file "exception.cc" Aborted (core dumped)
This is because when executing Exec("../test/exec.noff"); , the system should load ../test/exec.noff to memory, and allocate address space for it, but in the last experiment we know that in the construction of AddrSpace we just simply project virtual page to physical page with the same index, so when loading a new program the project of pages will be covered and destroyed.
Modify codes
AddrSpace
In AddrSpace, we need to note which pages are taken, so add a bitmap in addrspace.h in private area:
static BitMap* userMap;
Don’t forget to include "bitmap.h" . Then assign it in addrspace.cc :
BitMap* AddrSpace::userMap = new BitMap(NumPhysPages);
Modify the constructor of AddrSpace to change the projection of virtual pages and physical pages:
// make sure we have enough space ASSERT(userMap->NumClear() >= numPages); for (i = 0; i < numPages; i++) { pageTable[i].virtualPage = i; // for now, virtual page # = phys page # pageTable[i].physicalPage = userMap->Find(); 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 }
After that, when fetching a noff file, coping its data and putting to machine, we should know where the physical address really is, so we should locate the page and its offset:
// then, copy in the code and data segments into memory if (noffH.code.size > 0) { // calculate where the physical page locates int pagePosition = pageTable[noffH.code.virtualAddr / PageSize].physicalPage * PageSize; // and the offset int offset = noffH.code.virtualAddr % PageSize; DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", pagePosition + offset, noffH.code.size); executable->ReadAt(&(machine->mainMemory[pagePosition + offset]), noffH.code.size, noffH.code.inFileAddr); } if (noffH.initData.size > 0) { int pagePosition = pageTable[noffH.initData.virtualAddr / PageSize].physicalPage * PageSize; int offset = noffH.initData.virtualAddr % PageSize; DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", pagePosition + offset, noffH.initData.size); executable->ReadAt(&(machine->mainMemory[pagePosition + offset]), noffH.initData.size, noffH.initData.inFileAddr); }
Also the destructor:
AddrSpace::~AddrSpace() { for (int i = 0; i < numPages; i++) { userMap->Clear(pageTable[i].physicalPage); } delete [] pageTable; }
The system call is only allowed in kernel mode, so when it comes to a system call, the system will throw an exception to switch to kernel mode to handle this.
So when does this happen?
When fetching and executing instructions, we see something in Machine::OneInstruction(Instruction *instr) :
case OP_SYSCALL: RaiseException(SyscallException, 0); return;
Look into RaiseException :
// Transfer control to the Nachos kernel from user mode, because // the user program either invoked a system call, or some exception // occured (such as the address translation failed). // // "which" -- the cause of the kernel trap // "badVaddr" -- the virtual address causing the trap, if appropriate voidMachine::RaiseException(ExceptionType which, int badVAddr){ DEBUG('m', "Exception: %s\n", exceptionNames[which]); // ASSERT(interrupt->getStatus() == UserMode); registers[BadVAddrReg] = badVAddr; DelayedLoad(0, 0); // finish anything in progress interrupt->setStatus(SystemMode); ExceptionHandler(which); // interrupts are enabled at this point interrupt->setStatus(UserMode); }
It falls into system mode, then handles exception, after which goes back to user mode.
Let’s see ExceptionHandler , which is in exception.cc in userprog directory:
voidExceptionHandler(ExceptionType which){ int type = machine->ReadRegister(2); if ((which == SyscallException) && (type == SC_Halt)) { DEBUG('a', "Shutdown, initiated by user program.\n"); interrupt->Halt(); } else { printf("Unexpected user mode exception %d %d\n", which, type); ASSERT(FALSE); } }
The result of the system call, if any, must be put back into register 2.
The type is the type of system call, which currently only recognize SC_Halt . So let’s add exec to this:
if (which == SyscallException) { if (type == SC_Halt) { DEBUG('a', "Shutdown, initiated by user program.\n"); interrupt->Halt(); } elseif (type == SC_Exec) { DEBUG('a', "Shutdown, initiated by user program.\n"); // how to handle this? } else { printf("Unexpected user mode exception [type] %d %d\n", which, type); ASSERT(FALSE); } } else { printf("Unexpected user mode exception %d %d\n", which, type); ASSERT(FALSE); }
But we don’t know how to handle the system call currently, so there’s more to do.
case OP_SYSCALL: RaiseException(SyscallException, 0); return;
The return shows that when an exception is trapped, it’s handled and after that it returns to the main process then executes the instruction again. So it will cause an endless loop.
What if we don’t use advancePC() ? Here is the result:
.................. ../test/exec.noff At PC = 0x34: SYSCALL Exception: syscall [1 2], SC_Exec ../test/exec.noff At PC = 0x34: SYSCALL Exception: syscall [1 2], SC_Exec ../test/exec.noff At PC = 0x34: SYSCALL Exception: syscall [1 2], SC_Exec ../test/exec.noff At PC = 0x34: SYSCALL Exception: syscall [1 2], SC_Exec ../test/exec.noff ..................
An endless loop. If we use it:
Exception: syscall [1 2], SC_Exec ../test/exec.noff At PC = 0x38: JR r0,r31 At PC = 0x3c: SLL r0,r0,0 At PC = 0xf0: JAL 4 At PC = 0xf4: SLL r0,r0,0 At PC = 0x10: ADDIU r2,r0,0 At PC = 0x14: SYSCALL Exception: syscall [1 0], SC_Halt Machine halting!