0%

The Expansion of Nachos Address Space

Actually, we only need to modify the addrspace code, but it cannot be tested directly, so there is an Exec system call.

New codes

Create a file named exec.c , and bar.c in test directory.

// exec.c
#include "syscall.h"
int main() {
Halt();
}

// bar.c
#include "syscall.h"
int main() {
Exec("../test/exec.noff");
Halt();
}

Then Modify Makefile in test directory:

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

On a context switch, save any machine state:

void AddrSpace::SaveState() {
pageTable = machine->pageTable;
numPages = machine->pageTableSize;
}

System Call

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
void Machine::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:

void ExceptionHandler(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();
}
else if (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.

bar.c

In start.s :

	.globl Exec
.ent Exec
Exec:
addiu $2,$0,SC_Exec
syscall
j $31
.end Exec

Register 2 = Register 0 + SC_Exec , then a system call, then jump to $31 , which means go back, then the end.

Use below to generate a .s file of bar.c :

/usr/local/mips/bin/decstation-ultrix-gcc -I ../userprog -I ../threads -S bar.c
	.file	1 "bar.c"
gcc2_compiled.:
__gnu_compiled_c:
.rdata
.align 2
$LC0:
.ascii "../test/exec.noff\000"
.text
.align 2
.globl main
.ent main
main:
.frame $fp,24,$31 # vars= 0, regs= 2/0, args= 16, extra= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
subu $sp,$sp,24
sw $31,20($sp)
sw $fp,16($sp)
move $fp,$sp
jal __main
la $4,$LC0
jal Exec
jal Halt
$L1:
move $sp,$fp
lw $31,20($sp)
lw $fp,16($sp)
addu $sp,$sp,24
j $31
.end main

From la $4,$LC0 we can see that the Register 4 stores the argument. So the code in exception.cc is :

else if (type == SC_Exec) {
int address = machine->ReadRegister(4);
char filename[50];
int it = 0;
while(1) {
machine->ReadMem(address+it, 1, (int*)&filename[it]);
if(filename[it]=='\0') {
break;
}
it++;
}
printf("[%d %d], SC_Exec\n", which, type);
printf("%s\n", filename);
DEBUG('a', "Shutdown, initiated by user program.\n");
interrupt->Halt();
}

Then make clean, make , run ./nachos -d m -x ../test/bar.noff :

Exception: syscall
[1 2], SC_Exec
../test/exec.noff
Machine halting!

RaiseException

In mipssim.cc :

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.

AdvancePC

To solve the problem, we can manually forward PC.

In exception.cc in userprog directory, add :

void advancePC() {
machine->WriteRegister(PrevPCReg, machine->ReadRegister(PCReg));
machine->WriteRegister(PCReg, machine->ReadRegister(NextPCReg));
machine->WriteRegister(NextPCReg, machine->ReadRegister(NextPCReg) + 4);
}

To use it :

else if (type == SC_Exec) {
int address = machine->ReadRegister(4);
char filename[50];
int it = 0;
while(1) {
machine->ReadMem(address+it, 1, (int*)&filename[it]);
if(filename[it]=='\0') {
break;
}
it++;
}
printf("[%d %d], SC_Exec\n", which, type);
printf("%s\n", filename);
advancePC();
}

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!

It works normally.