init: init nachos hw01, should pass jenkins os_group_20_hw job but fail on os_group_20_ta job

This commit is contained in:
TA
2024-09-19 18:59:13 +08:00
commit 6ad2fa368f
267 changed files with 71977 additions and 0 deletions

44
code/machine/callback.h Normal file
View File

@@ -0,0 +1,44 @@
// callback.h
// Data structure to allow an object to register a "callback".
// On an asynchronous operation, the call to start the operation
// returns immediately. When the operation completes, the called
// object must somehow notify the caller of the completion.
// In the general case, the called object doesn't know the type
// of the caller.
//
// We implement this using virtual functions in C++. An object
// that needs to register a callback is set up as a derived class of
// the abstract base class "CallbackObj". When we pass a
// pointer to the object to a lower level module, that module
// calls back via "obj->CallBack()", without knowing the
// type of the object being called back.
//
// Note that this isn't a general-purpose mechanism,
// because a class can only register a single callback.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
//
#ifndef CALLBACK_H
#define CALLBACK_H
#include "copyright.h"
// Abstract base class for objects that register callbacks
class CallBackObj {
public:
virtual void CallBack() = 0;
protected:
CallBackObj() {}; // to prevent anyone from creating
// an instance of this class. Only
// allow creation of instances of
// classes derived from this class.
virtual ~CallBackObj() {};
};
#endif

174
code/machine/console.cc Normal file
View File

@@ -0,0 +1,174 @@
// console.cc
// Routines to simulate a serial port to a console device.
// A console has input (a keyboard) and output (a display).
// These are each simulated by operations on UNIX files.
// The simulated device is asynchronous, so we have to invoke
// the interrupt handler (after a simulated delay), to signal that
// a byte has arrived and/or that a written byte has departed.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "console.h"
#include "main.h"
#include "stdio.h"
//----------------------------------------------------------------------
// ConsoleInput::ConsoleInput
// Initialize the simulation of the input for a hardware console device.
//
// "readFile" -- UNIX file simulating the keyboard (NULL -> use stdin)
// "toCall" is the interrupt handler to call when a character arrives
// from the keyboard
//----------------------------------------------------------------------
ConsoleInput::ConsoleInput(char *readFile, CallBackObj *toCall)
{
if (readFile == NULL)
readFileNo = 0; // keyboard = stdin
else
readFileNo = OpenForReadWrite(readFile, TRUE); // should be read-only
// set up the stuff to emulate asynchronous interrupts
callWhenAvail = toCall;
incoming = EOF;
// start polling for incoming keystrokes
kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
}
//----------------------------------------------------------------------
// ConsoleInput::~ConsoleInput
// Clean up console input emulation
//----------------------------------------------------------------------
ConsoleInput::~ConsoleInput()
{
if (readFileNo != 0)
Close(readFileNo);
}
//----------------------------------------------------------------------
// ConsoleInput::CallBack()
// Simulator calls this when a character may be available to be
// read in from the simulated keyboard (eg, the user typed something).
//
// First check to make sure character is available.
// Then invoke the "callBack" registered by whoever wants the character.
//----------------------------------------------------------------------
void
ConsoleInput::CallBack()
{
char c;
int readCount;
ASSERT(incoming == EOF);
if (!PollFile(readFileNo)) { // nothing to be read
// schedule the next time to poll for a packet
kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
} else {
// otherwise, try to read a character
readCount = ReadPartial(readFileNo, &c, sizeof(char));
if (readCount == 0) {
// this seems to happen at end of file, when the
// console input is a regular file
// don't schedule an interrupt, since there will never
// be any more input
// just do nothing....
}
else {
// save the character and notify the OS that
// it is available
ASSERT(readCount == sizeof(char));
incoming = c;
kernel->stats->numConsoleCharsRead++;
}
callWhenAvail->CallBack();
}
}
//----------------------------------------------------------------------
// ConsoleInput::GetChar()
// Read a character from the input buffer, if there is any there.
// Either return the character, or EOF if none buffered.
//----------------------------------------------------------------------
char
ConsoleInput::GetChar()
{
char ch = incoming;
if (incoming != EOF) { // schedule when next char will arrive
kernel->interrupt->Schedule(this, ConsoleTime, ConsoleReadInt);
}
incoming = EOF;
return ch;
}
//----------------------------------------------------------------------
// ConsoleOutput::ConsoleOutput
// Initialize the simulation of the output for a hardware console device.
//
// "writeFile" -- UNIX file simulating the display (NULL -> use stdout)
// "toCall" is the interrupt handler to call when a write to
// the display completes.
//----------------------------------------------------------------------
ConsoleOutput::ConsoleOutput(char *writeFile, CallBackObj *toCall)
{
if (writeFile == NULL)
writeFileNo = 1; // display = stdout
else
writeFileNo = OpenForWrite(writeFile);
callWhenDone = toCall;
putBusy = FALSE;
}
//----------------------------------------------------------------------
// ConsoleOutput::~ConsoleOutput
// Clean up console output emulation
//----------------------------------------------------------------------
ConsoleOutput::~ConsoleOutput()
{
if (writeFileNo != 1)
Close(writeFileNo);
}
//----------------------------------------------------------------------
// ConsoleOutput::CallBack()
// Simulator calls this when the next character can be output to the
// display.
//----------------------------------------------------------------------
void
ConsoleOutput::CallBack()
{
putBusy = FALSE;
kernel->stats->numConsoleCharsWritten++;
callWhenDone->CallBack();
}
//----------------------------------------------------------------------
// ConsoleOutput::PutChar()
// Write a character to the simulated display, schedule an interrupt
// to occur in the future, and return.
//----------------------------------------------------------------------
void
ConsoleOutput::PutChar(char ch)
{
ASSERT(putBusy == FALSE);
WriteFile(writeFileNo, &ch, sizeof(char));
putBusy = TRUE;
kernel->interrupt->Schedule(this, ConsoleTime, ConsoleWriteInt);
}

90
code/machine/console.h Normal file
View File

@@ -0,0 +1,90 @@
// console.h
// Data structures to simulate the behavior of a terminal
// I/O device. A terminal has two parts -- a keyboard input,
// and a display output, each of which produces/accepts
// characters sequentially.
//
// The console hardware device is asynchronous. When a character is
// written to the device, the routine returns immediately, and an
// interrupt handler is called later when the I/O completes.
// For reads, an interrupt handler is called when a character arrives.
//
// In either case, the serial line connecting the computer
// to the console has limited bandwidth (like a modem!), and so
// each character takes measurable time.
//
// The user of the device registers itself to be called "back" when
// the read/write interrupts occur. There is a separate interrupt
// for read and write, and the device is "duplex" -- a character
// can be outgoing and incoming at the same time.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef CONSOLE_H
#define CONSOLE_H
#include "copyright.h"
#include "utility.h"
#include "callback.h"
// The following two classes define the input (and output) side of a
// hardware console device. Input (and output) to the device is simulated
// by reading (and writing) to the UNIX file "readFile" (and "writeFile").
//
// Since input (and output) to the device is asynchronous, the interrupt
// handler "callWhenAvail" is called when a character has arrived to be
// read in (and "callWhenDone" is called when an output character has been
// "put" so that the next character can be written).
//
// In practice, usually a single hardware thing that does both
// serial input and serial output. But conceptually simpler to
// use two objects.
class ConsoleInput : public CallBackObj {
public:
ConsoleInput(char *readFile, CallBackObj *toCall);
// initialize hardware console input
~ConsoleInput(); // clean up console emulation
char GetChar(); // Poll the console input. If a char is
// available, return it. Otherwise, return EOF.
// "callWhenAvail" is called whenever there is
// a char to be gotten
void CallBack(); // Invoked when a character arrives
// from the keyboard.
private:
int readFileNo; // UNIX file emulating the keyboard
CallBackObj *callWhenAvail; // Interrupt handler to call when
// there is a char to be read
char incoming; // Contains the character to be read,
// if there is one available.
// Otherwise contains EOF.
};
class ConsoleOutput : public CallBackObj {
public:
ConsoleOutput(char *writeFile, CallBackObj *toCall);
// initialize hardware console output
~ConsoleOutput(); // clean up console emulation
void PutChar(char ch); // Write "ch" to the console display,
// and return immediately. "callWhenDone"
// will called when the I/O completes.
void CallBack(); // Invoked when next character can be put
// out to the display.
private:
int writeFileNo; // UNIX file emulating the display
CallBackObj *callWhenDone; // Interrupt handler to call when
// the next char can be put
bool putBusy; // Is a PutChar operation in progress?
// If so, you can't do another one!
};
#endif // CONSOLE_H

268
code/machine/disk.cc Normal file
View File

@@ -0,0 +1,268 @@
// disk.cc
// Routines to simulate a physical disk device; reading and writing
// to the disk is simulated as reading and writing to a UNIX file.
// See disk.h for details about the behavior of disks (and
// therefore about the behavior of this simulation).
//
// Disk operations are asynchronous, so we have to invoke an interrupt
// handler when the simulated operation completes.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "disk.h"
#include "debug.h"
#include "sysdep.h"
#include "main.h"
// We put a magic number at the front of the UNIX file representing the
// disk, to make it less likely we will accidentally treat a useful file
// as a disk (which would probably trash the file's contents).
const int MagicNumber = 0x456789ab;
const int MagicSize = sizeof(int);
const int DiskSize = (MagicSize + (NumSectors * SectorSize));
//----------------------------------------------------------------------
// Disk::Disk()
// Initialize a simulated disk. Open the UNIX file (creating it
// if it doesn't exist), and check the magic number to make sure it's
// ok to treat it as Nachos disk storage.
//
// "toCall" -- object to call when disk read/write request completes
//----------------------------------------------------------------------
Disk::Disk(CallBackObj *toCall)
{
int magicNum;
int tmp = 0;
DEBUG(dbgDisk, "Initializing the disk.");
callWhenDone = toCall;
lastSector = 0;
bufferInit = 0;
sprintf(diskname,"DISK_%d",kernel->hostName);
fileno = OpenForReadWrite(diskname, FALSE);
if (fileno >= 0) { // file exists, check magic number
Read(fileno, (char *) &magicNum, MagicSize);
ASSERT(magicNum == MagicNumber);
} else { // file doesn't exist, create it
fileno = OpenForWrite(diskname);
magicNum = MagicNumber;
WriteFile(fileno, (char *) &magicNum, MagicSize); // write magic number
// need to write at end of file, so that reads will not return EOF
Lseek(fileno, DiskSize - sizeof(int), 0);
WriteFile(fileno, (char *)&tmp, sizeof(int));
}
active = FALSE;
}
//----------------------------------------------------------------------
// Disk::~Disk()
// Clean up disk simulation, by closing the UNIX file representing the
// disk.
//----------------------------------------------------------------------
Disk::~Disk()
{
Close(fileno);
}
//----------------------------------------------------------------------
// Disk::PrintSector()
// Dump the data in a disk read/write request, for debugging.
//----------------------------------------------------------------------
static void
PrintSector (bool writing, int sector, char *data)
{
int *p = (int *) data;
if (writing)
cout << "Writing sector: " << sector << "\n";
else
cout << "Reading sector: " << sector << "\n";
for (unsigned int i = 0; i < (SectorSize/sizeof(int)); i++) {
cout << p[i] << " ";
}
cout << "\n";
}
//----------------------------------------------------------------------
// Disk::ReadRequest/WriteRequest
// Simulate a request to read/write a single disk sector
// Do the read/write immediately to the UNIX file
// Set up an interrupt handler to be called later,
// that will notify the caller when the simulator says
// the operation has completed.
//
// Note that a disk only allows an entire sector to be read/written,
// not part of a sector.
//
// "sectorNumber" -- the disk sector to read/write
// "data" -- the bytes to be written, the buffer to hold the incoming bytes
//----------------------------------------------------------------------
void
Disk::ReadRequest(int sectorNumber, char* data)
{
int ticks = ComputeLatency(sectorNumber, FALSE);
ASSERT(!active); // only one request at a time
ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors));
DEBUG(dbgDisk, "Reading from sector " << sectorNumber);
Lseek(fileno, SectorSize * sectorNumber + MagicSize, 0);
Read(fileno, data, SectorSize);
if (debug->IsEnabled('d'))
PrintSector(FALSE, sectorNumber, data);
active = TRUE;
UpdateLast(sectorNumber);
kernel->stats->numDiskReads++;
kernel->interrupt->Schedule(this, ticks, DiskInt);
}
void
Disk::WriteRequest(int sectorNumber, char* data)
{
int ticks = ComputeLatency(sectorNumber, TRUE);
ASSERT(!active);
ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors));
DEBUG(dbgDisk, "Writing to sector " << sectorNumber);
Lseek(fileno, SectorSize * sectorNumber + MagicSize, 0);
WriteFile(fileno, data, SectorSize);
if (debug->IsEnabled('d'))
PrintSector(TRUE, sectorNumber, data);
active = TRUE;
UpdateLast(sectorNumber);
kernel->stats->numDiskWrites++;
kernel->interrupt->Schedule(this, ticks, DiskInt);
}
//----------------------------------------------------------------------
// Disk::CallBack()
// Called by the machine simulation when the disk interrupt occurs.
//----------------------------------------------------------------------
void
Disk::CallBack ()
{
active = FALSE;
callWhenDone->CallBack();
}
//----------------------------------------------------------------------
// Disk::TimeToSeek()
// Returns how long it will take to position the disk head over the correct
// track on the disk. Since when we finish seeking, we are likely
// to be in the middle of a sector that is rotating past the head,
// we also return how long until the head is at the next sector boundary.
//
// Disk seeks at one track per SeekTime ticks (cf. stats.h)
// and rotates at one sector per RotationTime ticks
//----------------------------------------------------------------------
int
Disk::TimeToSeek(int newSector, int *rotation)
{
int newTrack = newSector / SectorsPerTrack;
int oldTrack = lastSector / SectorsPerTrack;
int seek = abs(newTrack - oldTrack) * SeekTime;
// how long will seek take?
int over = (kernel->stats->totalTicks + seek) % RotationTime;
// will we be in the middle of a sector when
// we finish the seek?
*rotation = 0;
if (over > 0) // if so, need to round up to next full sector
*rotation = RotationTime - over;
return seek;
}
//----------------------------------------------------------------------
// Disk::ModuloDiff()
// Return number of sectors of rotational delay between target sector
// "to" and current sector position "from"
//----------------------------------------------------------------------
int
Disk::ModuloDiff(int to, int from)
{
int toOffset = to % SectorsPerTrack;
int fromOffset = from % SectorsPerTrack;
return ((toOffset - fromOffset) + SectorsPerTrack) % SectorsPerTrack;
}
//----------------------------------------------------------------------
// Disk::ComputeLatency()
// Return how long will it take to read/write a disk sector, from
// the current position of the disk head.
//
// Latency = seek time + rotational latency + transfer time
// Disk seeks at one track per SeekTime ticks (cf. stats.h)
// and rotates at one sector per RotationTime ticks
//
// To find the rotational latency, we first must figure out where the
// disk head will be after the seek (if any). We then figure out
// how long it will take to rotate completely past newSector after
// that point.
//
// The disk also has a "track buffer"; the disk continuously reads
// the contents of the current disk track into the buffer. This allows
// read requests to the current track to be satisfied more quickly.
// The contents of the track buffer are discarded after every seek to
// a new track.
//----------------------------------------------------------------------
int
Disk::ComputeLatency(int newSector, bool writing)
{
int rotation;
int seek = TimeToSeek(newSector, &rotation);
int timeAfter = kernel->stats->totalTicks + seek + rotation;
#ifndef NOTRACKBUF // turn this on if you don't want the track buffer stuff
// check if track buffer applies
if ((writing == FALSE) && (seek == 0)
&& (((timeAfter - bufferInit) / RotationTime)
> ModuloDiff(newSector, bufferInit / RotationTime))) {
DEBUG(dbgDisk, "Request latency = " << RotationTime);
return RotationTime; // time to transfer sector from the track buffer
}
#endif
rotation += ModuloDiff(newSector, timeAfter / RotationTime) * RotationTime;
DEBUG(dbgDisk, "Request latency = " << (seek + rotation + RotationTime));
return(seek + rotation + RotationTime);
}
//----------------------------------------------------------------------
// Disk::UpdateLast
// Keep track of the most recently requested sector. So we can know
// what is in the track buffer.
//----------------------------------------------------------------------
void
Disk::UpdateLast(int newSector)
{
int rotate;
int seek = TimeToSeek(newSector, &rotate);
if (seek != 0)
bufferInit = kernel->stats->totalTicks + seek + rotate;
lastSector = newSector;
DEBUG(dbgDisk, "Updating last sector = " << lastSector << " , " << bufferInit);
}

92
code/machine/disk.h Normal file
View File

@@ -0,0 +1,92 @@
// disk.h
// Data structures to emulate a physical disk. A physical disk
// can accept (one at a time) requests to read/write a disk sector;
// when the request is satisfied, the CPU gets an interrupt, and
// the next request can be sent to the disk.
//
// Disk contents are preserved across machine crashes, but if
// a file system operation (eg, create a file) is in progress when the
// system shuts down, the file system may be corrupted.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef DISK_H
#define DISK_H
#include "copyright.h"
#include "utility.h"
#include "callback.h"
// The following class defines a physical disk I/O device. The disk
// has a single surface, split up into "tracks", and each track split
// up into "sectors" (the same number of sectors on each track, and each
// sector has the same number of bytes of storage).
//
// Addressing is by sector number -- each sector on the disk is given
// a unique number: track * SectorsPerTrack + offset within a track.
//
// As with other I/O devices, the raw physical disk is an asynchronous device --
// requests to read or write portions of the disk return immediately,
// and an interrupt is invoked later to signal that the operation completed.
//
// The physical disk is in fact simulated via operations on a UNIX file.
//
// To make life a little more realistic, the simulated time for
// each operation reflects a "track buffer" -- RAM to store the contents
// of the current track as the disk head passes by. The idea is that the
// disk always transfers to the track buffer, in case that data is requested
// later on. This has the benefit of eliminating the need for
// "skip-sector" scheduling -- a read request which comes in shortly after
// the head has passed the beginning of the sector can be satisfied more
// quickly, because its contents are in the track buffer. Most
// disks these days now come with a track buffer.
//
// The track buffer simulation can be disabled by compiling with -DNOTRACKBUF
const int SectorSize = 128; // number of bytes per disk sector
const int SectorsPerTrack = 32; // number of sectors per disk track
const int NumTracks = 32; // number of tracks per disk
const int NumSectors = (SectorsPerTrack * NumTracks);
// total # of sectors per disk
class Disk : public CallBackObj {
public:
Disk(CallBackObj *toCall); // Create a simulated disk.
// Invoke toCall->CallBack()
// when each request completes.
~Disk(); // Deallocate the disk.
void ReadRequest(int sectorNumber, char* data);
// Read/write an single disk sector.
// These routines send a request to
// the disk and return immediately.
// Only one request allowed at a time!
void WriteRequest(int sectorNumber, char* data);
void CallBack(); // Invoked when disk request
// finishes. In turn calls, callWhenDone.
int ComputeLatency(int newSector, bool writing);
// Return how long a request to
// newSector will take:
// (seek + rotational delay + transfer)
private:
int fileno; // UNIX file number for simulated disk
char diskname[32]; // name of simulated disk's file
CallBackObj *callWhenDone; // Invoke when any disk request finishes
bool active; // Is a disk operation in progress?
int lastSector; // The previous disk request
int bufferInit; // When the track buffer started
// being loaded
int TimeToSeek(int newSector, int *rotate); // time to get to the new track
int ModuloDiff(int to, int from); // # sectors between to and from
void UpdateLast(int newSector);
};
#endif // DISK_H

361
code/machine/interrupt.cc Normal file
View File

@@ -0,0 +1,361 @@
// interrupt.cc
// Routines to simulate hardware interrupts.
//
// The hardware provides a routine (SetLevel) to enable or disable
// interrupts.
//
// In order to emulate the hardware, we need to keep track of all
// interrupts the hardware devices would cause, and when they
// are supposed to occur.
//
// This module also keeps track of simulated time. Time advances
// only when the following occur:
// interrupts are re-enabled
// a user instruction is executed
// there is nothing in the ready queue
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "interrupt.h"
#include "main.h"
// String definitions for debugging messages
static char *intLevelNames[] = { "off", "on"};
static char *intTypeNames[] = { "timer", "disk", "console write",
"console read", "network send",
"network recv"};
//----------------------------------------------------------------------
// PendingInterrupt::PendingInterrupt
// Initialize a hardware device interrupt that is to be scheduled
// to occur in the near future.
//
// "callOnInt" is the object to call when the interrupt occurs
// "time" is when (in simulated time) the interrupt is to occur
// "kind" is the hardware device that generated the interrupt
//----------------------------------------------------------------------
PendingInterrupt::PendingInterrupt(CallBackObj *callOnInt,
int time, IntType kind)
{
callOnInterrupt = callOnInt;
when = time;
type = kind;
}
//----------------------------------------------------------------------
// PendingCompare
// Compare to interrupts based on which should occur first.
//----------------------------------------------------------------------
static int
PendingCompare (PendingInterrupt *x, PendingInterrupt *y)
{
if (x->when < y->when) { return -1; }
else if (x->when > y->when) { return 1; }
else { return 0; }
}
//----------------------------------------------------------------------
// Interrupt::Interrupt
// Initialize the simulation of hardware device interrupts.
//
// Interrupts start disabled, with no interrupts pending, etc.
//----------------------------------------------------------------------
Interrupt::Interrupt()
{
level = IntOff;
pending = new SortedList<PendingInterrupt *>(PendingCompare);
inHandler = FALSE;
yieldOnReturn = FALSE;
status = SystemMode;
}
//----------------------------------------------------------------------
// Interrupt::~Interrupt
// De-allocate the data structures needed by the interrupt simulation.
//----------------------------------------------------------------------
Interrupt::~Interrupt()
{
while (!pending->IsEmpty()) {
delete pending->RemoveFront();
}
delete pending;
}
//----------------------------------------------------------------------
// Interrupt::ChangeLevel
// Change interrupts to be enabled or disabled, without advancing
// the simulated time (normally, enabling interrupts advances the time).
//
// Used internally.
//
// "old" -- the old interrupt status
// "now" -- the new interrupt status
//----------------------------------------------------------------------
void
Interrupt::ChangeLevel(IntStatus old, IntStatus now)
{
level = now;
DEBUG(dbgInt, "\tinterrupts: " << intLevelNames[old] << " -> " << intLevelNames[now]);
}
//----------------------------------------------------------------------
// Interrupt::SetLevel
// Change interrupts to be enabled or disabled, and if interrupts
// are being enabled, advance simulated time by calling OneTick().
//
// Returns:
// The old interrupt status.
// Parameters:
// "now" -- the new interrupt status
//----------------------------------------------------------------------
IntStatus
Interrupt::SetLevel(IntStatus now)
{
IntStatus old = level;
// interrupt handlers are prohibited from enabling interrupts
ASSERT((now == IntOff) || (inHandler == FALSE));
ChangeLevel(old, now); // change to new state
if ((now == IntOn) && (old == IntOff)) {
OneTick(); // advance simulated time
}
return old;
}
//----------------------------------------------------------------------
// Interrupt::OneTick
// Advance simulated time and check if there are any pending
// interrupts to be called.
//
// Two things can cause OneTick to be called:
// interrupts are re-enabled
// a user instruction is executed
//----------------------------------------------------------------------
void
Interrupt::OneTick()
{
MachineStatus oldStatus = status;
Statistics *stats = kernel->stats;
// advance simulated time
if (status == SystemMode) {
stats->totalTicks += SystemTick;
stats->systemTicks += SystemTick;
} else {
stats->totalTicks += UserTick;
stats->userTicks += UserTick;
}
DEBUG(dbgInt, "== Tick " << stats->totalTicks << " ==");
// check any pending interrupts are now ready to fire
ChangeLevel(IntOn, IntOff); // first, turn off interrupts
// (interrupt handlers run with
// interrupts disabled)
CheckIfDue(FALSE); // check for pending interrupts
ChangeLevel(IntOff, IntOn); // re-enable interrupts
if (yieldOnReturn) { // if the timer device handler asked
// for a context switch, ok to do it now
yieldOnReturn = FALSE;
status = SystemMode; // yield is a kernel routine
kernel->currentThread->Yield();
status = oldStatus;
}
}
//----------------------------------------------------------------------
// Interrupt::YieldOnReturn
// Called from within an interrupt handler, to cause a context switch
// (for example, on a time slice) in the interrupted thread,
// when the handler returns.
//
// We can't do the context switch here, because that would switch
// out the interrupt handler, and we want to switch out the
// interrupted thread.
//----------------------------------------------------------------------
void
Interrupt::YieldOnReturn()
{
ASSERT(inHandler == TRUE);
yieldOnReturn = TRUE;
}
//----------------------------------------------------------------------
// Interrupt::Idle
// Routine called when there is nothing in the ready queue.
//
// Since something has to be running in order to put a thread
// on the ready queue, the only thing to do is to advance
// simulated time until the next scheduled hardware interrupt.
//
// If there are no pending interrupts, stop. There's nothing
// more for us to do.
//----------------------------------------------------------------------
void
Interrupt::Idle()
{
DEBUG(dbgInt, "Machine idling; checking for interrupts.");
status = IdleMode;
if (CheckIfDue(TRUE)) { // check for any pending interrupts
status = SystemMode;
return; // return in case there's now
// a runnable thread
}
// if there are no pending interrupts, and nothing is on the ready
// queue, it is time to stop. If the console or the network is
// operating, there are *always* pending interrupts, so this code
// is not reached. Instead, the halt must be invoked by the user program.
DEBUG(dbgInt, "Machine idle. No interrupts to do.");
cout << "No threads ready or runnable, and no pending interrupts.\n";
cout << "Assuming the program completed.\n";
Halt();
}
//----------------------------------------------------------------------
// Interrupt::Halt
// Shut down Nachos cleanly, printing out performance statistics.
//----------------------------------------------------------------------
void
Interrupt::Halt()
{
cout << "Machine halting!\n\n";
cout << "This is halt\n";
kernel->stats->Print();
delete kernel; // Never returns.
}
int
Interrupt::CreateFile(char *filename)
{
return kernel->CreateFile(filename);
}
//----------------------------------------------------------------------
// Interrupt::Schedule
// Arrange for the CPU to be interrupted when simulated time
// reaches "now + when".
//
// Implementation: just put it on a sorted list.
//
// NOTE: the Nachos kernel should not call this routine directly.
// Instead, it is only called by the hardware device simulators.
//
// "toCall" is the object to call when the interrupt occurs
// "fromNow" is how far in the future (in simulated time) the
// interrupt is to occur
// "type" is the hardware device that generated the interrupt
//----------------------------------------------------------------------
void
Interrupt::Schedule(CallBackObj *toCall, int fromNow, IntType type)
{
int when = kernel->stats->totalTicks + fromNow;
PendingInterrupt *toOccur = new PendingInterrupt(toCall, when, type);
DEBUG(dbgInt, "Scheduling interrupt handler the " << intTypeNames[type] << " at time = " << when);
ASSERT(fromNow > 0);
pending->Insert(toOccur);
}
//----------------------------------------------------------------------
// Interrupt::CheckIfDue
// Check if any interrupts are scheduled to occur, and if so,
// fire them off.
//
// Returns:
// TRUE, if we fired off any interrupt handlers
// Params:
// "advanceClock" -- if TRUE, there is nothing in the ready queue,
// so we should simply advance the clock to when the next
// pending interrupt would occur (if any).
//----------------------------------------------------------------------
bool
Interrupt::CheckIfDue(bool advanceClock)
{
PendingInterrupt *next;
Statistics *stats = kernel->stats;
ASSERT(level == IntOff); // interrupts need to be disabled,
// to invoke an interrupt handler
if (debug->IsEnabled(dbgInt)) {
DumpState();
}
if (pending->IsEmpty()) { // no pending interrupts
return FALSE;
}
next = pending->Front();
if (next->when > stats->totalTicks) {
if (!advanceClock) { // not time yet
return FALSE;
}
else { // advance the clock to next interrupt
stats->idleTicks += (next->when - stats->totalTicks);
stats->totalTicks = next->when;
// UDelay(1000L); // rcgood - to stop nachos from spinning.
}
}
DEBUG(dbgInt, "Invoking interrupt handler for the ");
DEBUG(dbgInt, intTypeNames[next->type] << " at time " << next->when);
if (kernel->machine != NULL) {
kernel->machine->DelayedLoad(0, 0);
}
inHandler = TRUE;
do {
next = pending->RemoveFront(); // pull interrupt off list
next->callOnInterrupt->CallBack();// call the interrupt handler
delete next;
} while (!pending->IsEmpty()
&& (pending->Front()->when <= stats->totalTicks));
inHandler = FALSE;
return TRUE;
}
//----------------------------------------------------------------------
// PrintPending
// Print information about an interrupt that is scheduled to occur.
// When, where, why, etc.
//----------------------------------------------------------------------
static void
PrintPending (PendingInterrupt *pending)
{
cout << "Interrupt handler "<< intTypeNames[pending->type];
cout << ", scheduled at " << pending->when;
}
//----------------------------------------------------------------------
// DumpState
// Print the complete interrupt state - the status, and all interrupts
// that are scheduled to occur in the future.
//----------------------------------------------------------------------
void
Interrupt::DumpState()
{
cout << "Time: " << kernel->stats->totalTicks;
cout << ", interrupts " << intLevelNames[level] << "\n";
cout << "Pending interrupts:\n";
pending->Apply(PrintPending);
cout << "\nEnd of pending interrupts\n";
}

145
code/machine/interrupt.h Normal file
View File

@@ -0,0 +1,145 @@
// interrupt.h
// Data structures to emulate low-level interrupt hardware.
//
// The hardware provides a routine (SetLevel) to enable or disable
// interrupts.
//
// In order to emulate the hardware, we need to keep track of all
// interrupts the hardware devices would cause, and when they
// are supposed to occur.
//
// This module also keeps track of simulated time. Time advances
// only when the following occur:
// interrupts are re-enabled
// a user instruction is executed
// there is nothing in the ready queue
//
// As a result, unlike real hardware, interrupts (and thus time-slice
// context switches) cannot occur anywhere in the code where interrupts
// are enabled, but rather only at those places in the code where
// simulated time advances (so that it becomes time to invoke an
// interrupt in the hardware simulation).
//
// NOTE: this means that incorrectly synchronized code may work
// fine on this hardware simulation (even with randomized time slices),
// but it wouldn't work on real hardware.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef INTERRUPT_H
#define INTERRUPT_H
#include "copyright.h"
#include "list.h"
#include "callback.h"
// Interrupts can be disabled (IntOff) or enabled (IntOn)
enum IntStatus { IntOff, IntOn };
// Nachos can be running kernel code (SystemMode), user code (UserMode),
// or there can be no runnable thread, because the ready list
// is empty (IdleMode).
enum MachineStatus {IdleMode, SystemMode, UserMode};
// IntType records which hardware device generated an interrupt.
// In Nachos, we support a hardware timer device, a disk, a console
// display and keyboard, and a network.
enum IntType { TimerInt, DiskInt, ConsoleWriteInt, ConsoleReadInt,
NetworkSendInt, NetworkRecvInt};
// The following class defines an interrupt that is scheduled
// to occur in the future. The internal data structures are
// left public to make it simpler to manipulate.
class PendingInterrupt {
public:
PendingInterrupt(CallBackObj *callOnInt, int time, IntType kind);
// initialize an interrupt that will
// occur in the future
CallBackObj *callOnInterrupt;// The object (in the hardware device
// emulator) to call when the interrupt occurs
int when; // When the interrupt is supposed to fire
IntType type; // for debugging
};
// The following class defines the data structures for the simulation
// of hardware interrupts. We record whether interrupts are enabled
// or disabled, and any hardware interrupts that are scheduled to occur
// in the future.
class Interrupt {
public:
Interrupt(); // initialize the interrupt simulation
~Interrupt(); // de-allocate data structures
IntStatus SetLevel(IntStatus level);
// Disable or enable interrupts
// and return previous setting.
void Enable() { (void) SetLevel(IntOn); }
// Enable interrupts.
IntStatus getLevel() {return level;}
// Return whether interrupts
// are enabled or disabled
void Idle(); // The ready queue is empty, roll
// simulated time forward until the
// next interrupt
void Halt(); // quit and print out stats
void PrintInt(int number);
int CreateFile(char *filename);
void YieldOnReturn(); // cause a context switch on return
// from an interrupt handler
MachineStatus getStatus() { return status; }
void setStatus(MachineStatus st) { status = st; }
// idle, kernel, user
void DumpState(); // Print interrupt state
// NOTE: the following are internal to the hardware simulation code.
// DO NOT call these directly. I should make them "private",
// but they need to be public since they are called by the
// hardware device simulators.
void Schedule(CallBackObj *callTo, int when, IntType type);
// Schedule an interrupt to occur
// at time "when". This is called
// by the hardware device simulators.
void OneTick(); // Advance simulated time
private:
IntStatus level; // are interrupts enabled or disabled?
SortedList<PendingInterrupt *> *pending;
// the list of interrupts scheduled
// to occur in the future
//int writeFileNo; //UNIX file emulating the display
bool inHandler; // TRUE if we are running an interrupt handler
//bool putBusy; // Is a PrintInt operation in progress
//If so, you cannoot do another one
bool yieldOnReturn; // TRUE if we are to context switch
// on return from the interrupt handler
MachineStatus status; // idle, kernel mode, user mode
// these functions are internal to the interrupt simulation code
bool CheckIfDue(bool advanceClock);
// Check if any interrupts are supposed
// to occur now, and if so, do them
void ChangeLevel(IntStatus old, // SetLevel, without advancing the
IntStatus now); // simulated time
};
#endif // INTERRRUPT_H

224
code/machine/machine.cc Normal file
View File

@@ -0,0 +1,224 @@
// machine.cc
// Routines for simulating the execution of user programs.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "machine.h"
#include "main.h"
// Textual names of the exceptions that can be generated by user program
// execution, for debugging.
static char* exceptionNames[] = { "no exception", "syscall",
"page fault/no TLB entry", "page read only",
"bus error", "address error", "overflow",
"illegal instruction" };
//----------------------------------------------------------------------
// CheckEndian
// Check to be sure that the host really uses the format it says it
// does, for storing the bytes of an integer. Stop on error.
//----------------------------------------------------------------------
static
void CheckEndian()
{
union checkit {
char charword[4];
unsigned int intword;
} check;
check.charword[0] = 1;
check.charword[1] = 2;
check.charword[2] = 3;
check.charword[3] = 4;
#ifdef HOST_IS_BIG_ENDIAN
ASSERT (check.intword == 0x01020304);
#else
ASSERT (check.intword == 0x04030201);
#endif
}
//----------------------------------------------------------------------
// Machine::Machine
// Initialize the simulation of user program execution.
//
// "debug" -- if TRUE, drop into the debugger after each user instruction
// is executed.
//----------------------------------------------------------------------
Machine::Machine(bool debug)
{
int i;
for (i = 0; i < NumTotalRegs; i++)
registers[i] = 0;
mainMemory = new char[MemorySize];
for (i = 0; i < MemorySize; i++)
mainMemory[i] = 0;
#ifdef USE_TLB
tlb = new TranslationEntry[TLBSize];
for (i = 0; i < TLBSize; i++)
tlb[i].valid = FALSE;
pageTable = NULL;
#else // use linear page table
tlb = NULL;
pageTable = NULL;
#endif
singleStep = debug;
CheckEndian();
}
//----------------------------------------------------------------------
// Machine::~Machine
// De-allocate the data structures used to simulate user program execution.
//----------------------------------------------------------------------
Machine::~Machine()
{
delete [] mainMemory;
if (tlb != NULL)
delete [] tlb;
}
//----------------------------------------------------------------------
// Machine::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(dbgMach, "Exception: " << exceptionNames[which]);
registers[BadVAddrReg] = badVAddr;
DelayedLoad(0, 0); // finish anything in progress
kernel->interrupt->setStatus(SystemMode);
ExceptionHandler(which); // interrupts are enabled at this point
kernel->interrupt->setStatus(UserMode);
}
//----------------------------------------------------------------------
// Machine::Debugger
// Primitive debugger for user programs. Note that we can't use
// gdb to debug user programs, since gdb doesn't run on top of Nachos.
// It could, but you'd have to implement *a lot* more system calls
// to get it to work!
//
// So just allow single-stepping, and printing the contents of memory.
//----------------------------------------------------------------------
void Machine::Debugger()
{
char *buf = new char[80];
int num;
bool done = FALSE;
kernel->interrupt->DumpState();
DumpState();
while (!done) {
// read commands until we should proceed with more execution
// prompt for input, giving current simulation time in the prompt
cout << kernel->stats->totalTicks << ">";
// read one line of input (80 chars max)
cin.get(buf, 80);
if (sscanf(buf, "%d", &num) == 1) {
runUntilTime = num;
done = TRUE;
}
else {
runUntilTime = 0;
switch (*buf) {
case '\0':
done = TRUE;
break;
case 'c':
singleStep = FALSE;
done = TRUE;
break;
case '?':
cout << "Machine commands:\n";
cout << " <return> execute one instruction\n";
cout << " <number> run until the given timer tick\n";
cout << " c run until completion\n";
cout << " ? print help message\n";
break;
default:
cout << "Unknown command: " << buf << "\n";
cout << "Type ? for help.\n";
}
}
// consume the newline delimiter, which does not get
// eaten by cin.get(buf,80) above.
buf[0] = cin.get();
}
delete [] buf;
}
//----------------------------------------------------------------------
// Machine::DumpState
// Print the user program's CPU state. We might print the contents
// of memory, but that seemed like overkill.
//----------------------------------------------------------------------
void
Machine::DumpState()
{
int i;
cout << "Machine registers:\n";
for (i = 0; i < NumGPRegs; i++) {
switch (i) {
case StackReg:
cout << "\tSP(" << i << "):\t" << registers[i];
break;
case RetAddrReg:
cout << "\tRA(" << i << "):\t" << registers[i];
break;
default:
cout << "\t" << i << ":\t" << registers[i];
break;
}
if ((i % 4) == 3) { cout << "\n"; }
}
cout << "\tHi:\t" << registers[HiReg];
cout << "\tLo:\t" << registers[LoReg];
cout << "\tPC:\t" << registers[PCReg];
cout << "\tNextPC:\t" << registers[NextPCReg];
cout << "\tPrevPC:\t" << registers[PrevPCReg];
cout << "\tLoad:\t" << registers[LoadReg];
cout << "\tLoadV:\t" << registers[LoadValueReg] << "\n";
}
//----------------------------------------------------------------------
// Machine::ReadRegister/WriteRegister
// Fetch or write the contents of a user program register.
//----------------------------------------------------------------------
int
Machine::ReadRegister(int num)
{
ASSERT((num >= 0) && (num < NumTotalRegs));
return registers[num];
}
void
Machine::WriteRegister(int num, int value)
{
ASSERT((num >= 0) && (num < NumTotalRegs));
registers[num] = value;
}

206
code/machine/machine.h Normal file
View File

@@ -0,0 +1,206 @@
// machine.h
// Data structures for simulating the execution of user programs
// running on top of Nachos.
//
// User programs are loaded into "mainMemory"; to Nachos,
// this looks just like an array of bytes. Of course, the Nachos
// kernel is in memory too -- but as in most machines these days,
// the kernel is loaded into a separate memory region from user
// programs, and accesses to kernel memory are not translated or paged.
//
// In Nachos, user programs are executed one instruction at a time,
// by the simulator. Each memory reference is translated, checked
// for errors, etc.
//
// DO NOT CHANGE EXCEPT AS NOTED BELOW -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef MACHINE_H
#define MACHINE_H
#include "copyright.h"
#include "utility.h"
#include "translate.h"
// Definitions related to the size, and format of user memory
const int PageSize = 128; // set the page size equal to
// the disk sector size, for simplicity
//
// You are allowed to change this value.
// Doing so will change the number of pages of physical memory
// available on the simulated machine.
//
const int NumPhysPages = 128;
const int MemorySize = (NumPhysPages * PageSize);
const int TLBSize = 4; // if there is a TLB, make it small
enum ExceptionType { NoException, // Everything ok!
SyscallException, // A program executed a system call.
PageFaultException, // No valid translation found
ReadOnlyException, // Write attempted to page marked
// "read-only"
BusErrorException, // Translation resulted in an
// invalid physical address
AddressErrorException, // Unaligned reference or one that
// was beyond the end of the
// address space
OverflowException, // Integer overflow in add or sub.
IllegalInstrException, // Unimplemented or reserved instr.
NumExceptionTypes
};
// User program CPU state. The full set of MIPS registers, plus a few
// more because we need to be able to start/stop a user program between
// any two instructions (thus we need to keep track of things like load
// delay slots, etc.)
#define StackReg 29 // User's stack pointer
#define RetAddrReg 31 // Holds return address for procedure calls
#define NumGPRegs 32 // 32 general purpose registers on MIPS
#define HiReg 32 // Double register to hold multiply result
#define LoReg 33
#define PCReg 34 // Current program counter
#define NextPCReg 35 // Next program counter (for branch delay)
#define PrevPCReg 36 // Previous program counter (for debugging)
#define LoadReg 37 // The register target of a delayed load.
#define LoadValueReg 38 // The value to be loaded by a delayed load.
#define BadVAddrReg 39 // The failing virtual address on an exception
#define NumTotalRegs 40
// The following class defines the simulated host workstation hardware, as
// seen by user programs -- the CPU registers, main memory, etc.
// User programs shouldn't be able to tell that they are running on our
// simulator or on the real hardware, except
// we don't support floating point instructions
// the system call interface to Nachos is not the same as UNIX
// (10 system calls in Nachos vs. 200 in UNIX!)
// If we were to implement more of the UNIX system calls, we ought to be
// able to run Nachos on top of Nachos!
//
// The procedures in this class are defined in machine.cc, mipssim.cc, and
// translate.cc.
class Instruction;
class Interrupt;
class Machine {
public:
Machine(bool debug); // Initialize the simulation of the hardware
// for running user programs
~Machine(); // De-allocate the data structures
// Routines callable by the Nachos kernel
void Run(); // Run a user program
int ReadRegister(int num); // read the contents of a CPU register
void WriteRegister(int num, int value);
// store a value into a CPU register
// Data structures accessible to the Nachos kernel -- main memory and the
// page table/TLB.
//
// Note that *all* communication between the user program and the kernel
// are in terms of these data structures (plus the CPU registers).
char *mainMemory; // physical memory to store user program,
// code and data, while executing
// NOTE: the hardware translation of virtual addresses in the user program
// to physical addresses (relative to the beginning of "mainMemory")
// can be controlled by one of:
// a traditional linear page table
// a software-loaded translation lookaside buffer (tlb) -- a cache of
// mappings of virtual page #'s to physical page #'s
//
// If "tlb" is NULL, the linear page table is used
// If "tlb" is non-NULL, the Nachos kernel is responsible for managing
// the contents of the TLB. But the kernel can use any data structure
// it wants (eg, segmented paging) for handling TLB cache misses.
//
// For simplicity, both the page table pointer and the TLB pointer are
// public. However, while there can be multiple page tables (one per address
// space, stored in memory), there is only one TLB (implemented in hardware).
// Thus the TLB pointer should be considered as *read-only*, although
// the contents of the TLB are free to be modified by the kernel software.
TranslationEntry *tlb; // this pointer should be considered
// "read-only" to Nachos kernel code
TranslationEntry *pageTable;
unsigned int pageTableSize;
bool ReadMem(int addr, int size, int* value);
bool WriteMem(int addr, int size, int value);
// Read or write 1, 2, or 4 bytes of virtual
// memory (at addr). Return FALSE if a
// correct translation couldn't be found.
private:
// Routines internal to the machine simulation -- DO NOT call these directly
void DelayedLoad(int nextReg, int nextVal);
// Do a pending delayed load (modifying a reg)
void OneInstruction(Instruction *instr);
// Run one instruction of a user program.
ExceptionType Translate(int virtAddr, int* physAddr, int size,bool writing);
// Translate an address, and check for
// alignment. Set the use and dirty bits in
// the translation entry appropriately,
// and return an exception code if the
// translation couldn't be completed.
void RaiseException(ExceptionType which, int badVAddr);
// Trap to the Nachos kernel, because of a
// system call or other exception.
void Debugger(); // invoke the user program debugger
void DumpState(); // print the user CPU and memory state
// Internal data structures
int registers[NumTotalRegs]; // CPU registers, for executing user programs
bool singleStep; // drop back into the debugger after each
// simulated instruction
int runUntilTime; // drop back into the debugger when simulated
// time reaches this value
friend class Interrupt; // calls DelayedLoad()
};
extern void ExceptionHandler(ExceptionType which);
// Entry point into Nachos for handling
// user system calls and exceptions
// Defined in exception.cc
// Routines for converting Words and Short Words to and from the
// simulated machine's format of little endian. If the host machine
// is little endian (DEC and Intel), these end up being NOPs.
//
// What is stored in each format:
// host byte ordering:
// kernel data structures
// user registers
// simulated machine byte ordering:
// contents of main memory
unsigned int WordToHost(unsigned int word);
unsigned short ShortToHost(unsigned short shortword);
unsigned int WordToMachine(unsigned int word);
unsigned short ShortToMachine(unsigned short shortword);
#endif // MACHINE_H

828
code/machine/mipssim.cc Normal file
View File

@@ -0,0 +1,828 @@
// mipssim.cc -- simulate a MIPS R2/3000 processor
//
// This code has been adapted from Ousterhout's MIPSSIM package.
// Byte ordering is little-endian, so we can be compatible with
// DEC RISC systems.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
// Simulation fixes done by Peter E Reissner, class of Winter 1994/95 (York)
// I've not been able to test this extensively.
// Ported to newer version of Nachos at Waterloo by Scott Graham (Mar 99).
#include "copyright.h"
#include "debug.h"
#include "machine.h"
#include "mipssim.h"
#include "main.h"
static void Mult(int a, int b, bool signedArith, int* hiPtr, int* loPtr);
// The following class defines an instruction, represented in both
// undecoded binary form
// decoded to identify
// operation to do
// registers to act on
// any immediate operand value
class Instruction {
public:
void Decode(); // decode the binary representation of the instruction
unsigned int value; // binary representation of the instruction
char opCode; // Type of instruction. This is NOT the same as the
// opcode field from the instruction: see defs in mips.h
char rs, rt, rd; // Three registers from instruction.
int extra; // Immediate or target or shamt field or offset.
// Immediates are sign-extended.
};
//----------------------------------------------------------------------
// Machine::Run
// Simulate the execution of a user-level program on Nachos.
// Called by the kernel when the program starts up; never returns.
//
// This routine is re-entrant, in that it can be called multiple
// times concurrently -- one for each thread executing user code.
//----------------------------------------------------------------------
void
Machine::Run()
{
Instruction *instr = new Instruction; // storage for decoded instruction
if (debug->IsEnabled('m')) {
cout << "Starting program in thread: " << kernel->currentThread->getName();
cout << ", at time: " << kernel->stats->totalTicks << "\n";
}
kernel->interrupt->setStatus(UserMode);
for (;;) {
OneInstruction(instr);
kernel->interrupt->OneTick();
if (singleStep && (runUntilTime <= kernel->stats->totalTicks))
Debugger();
}
}
//----------------------------------------------------------------------
// TypeToReg
// Retrieve the register # referred to in an instruction.
//----------------------------------------------------------------------
static int
TypeToReg(RegType reg, Instruction *instr)
{
switch (reg) {
case RS:
return instr->rs;
case RT:
return instr->rt;
case RD:
return instr->rd;
case EXTRA:
return instr->extra;
default:
return -1;
}
}
//----------------------------------------------------------------------
// Machine::OneInstruction
// Execute one instruction from a user-level program
//
// If there is any kind of exception or interrupt, we invoke the
// exception handler, and when it returns, we return to Run(), which
// will re-invoke us in a loop. This allows us to
// re-start the instruction execution from the beginning, in
// case any of our state has changed. On a syscall,
// the OS software must increment the PC so execution begins
// at the instruction immediately after the syscall.
//
// This routine is re-entrant, in that it can be called multiple
// times concurrently -- one for each thread executing user code.
// We get re-entrancy by never caching any data -- we always re-start the
// simulation from scratch each time we are called (or after trapping
// back to the Nachos kernel on an exception or interrupt), and we always
// store all data back to the machine registers and memory before
// leaving. This allows the Nachos kernel to control our behavior
// by controlling the contents of memory, the translation table,
// and the register set.
//----------------------------------------------------------------------
void
Machine::OneInstruction(Instruction *instr)
{
#ifdef SIM_FIX
int byte; // described in Kane for LWL,LWR,...
#endif
int raw;
int nextLoadReg = 0;
int nextLoadValue = 0; // record delayed load operation, to apply
// in the future
// Fetch instruction
if (!ReadMem(registers[PCReg], 4, &raw))
return; // exception occurred
instr->value = raw;
instr->Decode();
if (debug->IsEnabled('m')) {
struct OpString *str = &opStrings[instr->opCode];
char buf[80];
ASSERT(instr->opCode <= MaxOpcode);
cout << "At PC = " << registers[PCReg];
sprintf(buf, str->format, TypeToReg(str->args[0], instr),
TypeToReg(str->args[1], instr), TypeToReg(str->args[2], instr));
cout << "\t" << buf << "\n";
}
// Compute next pc, but don't install in case there's an error or branch.
int pcAfter = registers[NextPCReg] + 4;
int sum, diff, tmp, value;
unsigned int rs, rt, imm;
// Execute the instruction (cf. Kane's book)
switch (instr->opCode) {
case OP_ADD:
sum = registers[instr->rs] + registers[instr->rt];
if (!((registers[instr->rs] ^ registers[instr->rt]) & SIGN_BIT) &&
((registers[instr->rs] ^ sum) & SIGN_BIT)) {
RaiseException(OverflowException, 0);
return;
}
registers[instr->rd] = sum;
break;
case OP_ADDI:
sum = registers[instr->rs] + instr->extra;
if (!((registers[instr->rs] ^ instr->extra) & SIGN_BIT) &&
((instr->extra ^ sum) & SIGN_BIT)) {
RaiseException(OverflowException, 0);
return;
}
registers[instr->rt] = sum;
break;
case OP_ADDIU:
registers[instr->rt] = registers[instr->rs] + instr->extra;
break;
case OP_ADDU:
registers[instr->rd] = registers[instr->rs] + registers[instr->rt];
break;
case OP_AND:
registers[instr->rd] = registers[instr->rs] & registers[instr->rt];
break;
case OP_ANDI:
registers[instr->rt] = registers[instr->rs] & (instr->extra & 0xffff);
break;
case OP_BEQ:
if (registers[instr->rs] == registers[instr->rt])
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_BGEZAL:
registers[R31] = registers[NextPCReg] + 4;
case OP_BGEZ:
if (!(registers[instr->rs] & SIGN_BIT))
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_BGTZ:
if (registers[instr->rs] > 0)
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_BLEZ:
if (registers[instr->rs] <= 0)
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_BLTZAL:
registers[R31] = registers[NextPCReg] + 4;
case OP_BLTZ:
if (registers[instr->rs] & SIGN_BIT)
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_BNE:
if (registers[instr->rs] != registers[instr->rt])
pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra);
break;
case OP_DIV:
if (registers[instr->rt] == 0) {
registers[LoReg] = 0;
registers[HiReg] = 0;
} else {
registers[LoReg] = registers[instr->rs] / registers[instr->rt];
registers[HiReg] = registers[instr->rs] % registers[instr->rt];
}
break;
case OP_DIVU:
rs = (unsigned int) registers[instr->rs];
rt = (unsigned int) registers[instr->rt];
if (rt == 0) {
registers[LoReg] = 0;
registers[HiReg] = 0;
} else {
tmp = rs / rt;
registers[LoReg] = (int) tmp;
tmp = rs % rt;
registers[HiReg] = (int) tmp;
}
break;
case OP_JAL:
registers[R31] = registers[NextPCReg] + 4;
case OP_J:
pcAfter = (pcAfter & 0xf0000000) | IndexToAddr(instr->extra);
break;
case OP_JALR:
registers[instr->rd] = registers[NextPCReg] + 4;
case OP_JR:
pcAfter = registers[instr->rs];
break;
case OP_LB:
case OP_LBU:
tmp = registers[instr->rs] + instr->extra;
if (!ReadMem(tmp, 1, &value))
return;
if ((value & 0x80) && (instr->opCode == OP_LB))
value |= 0xffffff00;
else
value &= 0xff;
nextLoadReg = instr->rt;
nextLoadValue = value;
break;
case OP_LH:
case OP_LHU:
tmp = registers[instr->rs] + instr->extra;
if (tmp & 0x1) {
RaiseException(AddressErrorException, tmp);
return;
}
if (!ReadMem(tmp, 2, &value))
return;
if ((value & 0x8000) && (instr->opCode == OP_LH))
value |= 0xffff0000;
else
value &= 0xffff;
nextLoadReg = instr->rt;
nextLoadValue = value;
break;
case OP_LUI:
DEBUG(dbgMach, "Executing: LUI r" << instr->rt << ", " << instr->extra);
registers[instr->rt] = instr->extra << 16;
break;
case OP_LW:
tmp = registers[instr->rs] + instr->extra;
if (tmp & 0x3) {
RaiseException(AddressErrorException, tmp);
return;
}
if (!ReadMem(tmp, 4, &value))
return;
nextLoadReg = instr->rt;
nextLoadValue = value;
break;
case OP_LWL:
tmp = registers[instr->rs] + instr->extra;
#ifdef SIM_FIX
// The only difference between this code and the BIG ENDIAN code
// is that the ReadMem call is guaranteed an aligned access as it
// should be (Kane's book hides the fact that all memory access
// are done using aligned loads - what the instruction asks for
// is a arbitrary) This is the whole purpose of LWL and LWR etc.
// Then the switch uses 3 - (tmp & 0x3) instead of (tmp & 0x3)
byte = tmp & 0x3;
// DEBUG('P', "Addr 0x%X\n",tmp-byte);
if (!ReadMem(tmp-byte, 4, &value))
return;
#else
// ReadMem assumes all 4 byte requests are aligned on an even
// word boundary. Also, the little endian/big endian swap code would
// fail (I think) if the other cases are ever exercised.
ASSERT((tmp & 0x3) == 0);
if (!ReadMem(tmp, 4, &value))
return;
#endif
if (registers[LoadReg] == instr->rt)
nextLoadValue = registers[LoadValueReg];
else
nextLoadValue = registers[instr->rt];
#ifdef SIM_FIX
switch (3 - byte)
#else
switch (tmp & 0x3)
#endif
{
case 0:
nextLoadValue = value;
break;
case 1:
nextLoadValue = (nextLoadValue & 0xff) | (value << 8);
break;
case 2:
nextLoadValue = (nextLoadValue & 0xffff) | (value << 16);
break;
case 3:
nextLoadValue = (nextLoadValue & 0xffffff) | (value << 24);
break;
}
nextLoadReg = instr->rt;
break;
case OP_LWR:
tmp = registers[instr->rs] + instr->extra;
#ifdef SIM_FIX
// The only difference between this code and the BIG ENDIAN code
// is that the ReadMem call is guaranteed an aligned access as it
// should be (Kane's book hides the fact that all memory access
// are done using aligned loads - what the instruction asks
// for is a arbitrary) This is the whole purpose of LWL and LWR etc.
// Then the switch uses 3 - (tmp & 0x3) instead of (tmp & 0x3)
byte = tmp & 0x3;
// DEBUG('P', "Addr 0x%X\n",tmp-byte);
if (!ReadMem(tmp-byte, 4, &value))
return;
#else
// ReadMem assumes all 4 byte requests are aligned on an even
// word boundary. Also, the little endian/big endian swap code would
// fail (I think) if the other cases are ever exercised.
ASSERT((tmp & 0x3) == 0);
if (!ReadMem(tmp, 4, &value))
return;
#endif
if (registers[LoadReg] == instr->rt)
nextLoadValue = registers[LoadValueReg];
else
nextLoadValue = registers[instr->rt];
#ifdef SIM_FIX
switch (3 - byte)
#else
switch (tmp & 0x3)
#endif
{
case 0:
nextLoadValue = (nextLoadValue & 0xffffff00) |
((value >> 24) & 0xff);
break;
case 1:
nextLoadValue = (nextLoadValue & 0xffff0000) |
((value >> 16) & 0xffff);
break;
case 2:
nextLoadValue = (nextLoadValue & 0xff000000)
| ((value >> 8) & 0xffffff);
break;
case 3:
nextLoadValue = value;
break;
}
nextLoadReg = instr->rt;
break;
case OP_MFHI:
registers[instr->rd] = registers[HiReg];
break;
case OP_MFLO:
registers[instr->rd] = registers[LoReg];
break;
case OP_MTHI:
registers[HiReg] = registers[instr->rs];
break;
case OP_MTLO:
registers[LoReg] = registers[instr->rs];
break;
case OP_MULT:
Mult(registers[instr->rs], registers[instr->rt], TRUE,
&registers[HiReg], &registers[LoReg]);
break;
case OP_MULTU:
Mult(registers[instr->rs], registers[instr->rt], FALSE,
&registers[HiReg], &registers[LoReg]);
break;
case OP_NOR:
registers[instr->rd] = ~(registers[instr->rs] | registers[instr->rt]);
break;
case OP_OR:
registers[instr->rd] = registers[instr->rs] | registers[instr->rt];
break;
case OP_ORI:
registers[instr->rt] = registers[instr->rs] | (instr->extra & 0xffff);
break;
case OP_SB:
if (!WriteMem((unsigned)
(registers[instr->rs] + instr->extra), 1, registers[instr->rt]))
return;
break;
case OP_SH:
if (!WriteMem((unsigned)
(registers[instr->rs] + instr->extra), 2, registers[instr->rt]))
return;
break;
case OP_SLL:
registers[instr->rd] = registers[instr->rt] << instr->extra;
break;
case OP_SLLV:
registers[instr->rd] = registers[instr->rt] <<
(registers[instr->rs] & 0x1f);
break;
case OP_SLT:
if (registers[instr->rs] < registers[instr->rt])
registers[instr->rd] = 1;
else
registers[instr->rd] = 0;
break;
case OP_SLTI:
if (registers[instr->rs] < instr->extra)
registers[instr->rt] = 1;
else
registers[instr->rt] = 0;
break;
case OP_SLTIU:
rs = registers[instr->rs];
imm = instr->extra;
if (rs < imm)
registers[instr->rt] = 1;
else
registers[instr->rt] = 0;
break;
case OP_SLTU:
rs = registers[instr->rs];
rt = registers[instr->rt];
if (rs < rt)
registers[instr->rd] = 1;
else
registers[instr->rd] = 0;
break;
case OP_SRA:
registers[instr->rd] = registers[instr->rt] >> instr->extra;
break;
case OP_SRAV:
registers[instr->rd] = registers[instr->rt] >>
(registers[instr->rs] & 0x1f);
break;
case OP_SRL:
tmp = registers[instr->rt];
tmp >>= instr->extra;
registers[instr->rd] = tmp;
break;
case OP_SRLV:
tmp = registers[instr->rt];
tmp >>= (registers[instr->rs] & 0x1f);
registers[instr->rd] = tmp;
break;
case OP_SUB:
diff = registers[instr->rs] - registers[instr->rt];
if (((registers[instr->rs] ^ registers[instr->rt]) & SIGN_BIT) &&
((registers[instr->rs] ^ diff) & SIGN_BIT)) {
RaiseException(OverflowException, 0);
return;
}
registers[instr->rd] = diff;
break;
case OP_SUBU:
registers[instr->rd] = registers[instr->rs] - registers[instr->rt];
break;
case OP_SW:
if (!WriteMem((unsigned)
(registers[instr->rs] + instr->extra), 4, registers[instr->rt]))
return;
break;
case OP_SWL:
tmp = registers[instr->rs] + instr->extra;
#ifdef SIM_FIX
// The only difference between this code and the BIG ENDIAN code
// is that the ReadMem call is guaranteed an aligned access as it
// should be (Kane's book hides the fact that all memory access
// are done using aligned loads - what the instruction asks for
// is a arbitrary) This is the whole purpose of LWL and LWR etc.
byte = tmp & 0x3;
// DEBUG('P', "Addr 0x%X\n",tmp-byte);
if (!ReadMem(tmp-byte, 4, &value))
return;
// DEBUG('P', "Value 0x%X\n",value);
#else
// The little endian/big endian swap code would
// fail (I think) if the other cases are ever exercised.
ASSERT((tmp & 0x3) == 0);
if (!ReadMem((tmp & ~0x3), 4, &value))
return;
#endif
#ifdef SIM_FIX
switch( 3 - byte )
#else
switch (tmp & 0x3)
#endif // SIM_FIX
{
case 0:
value = registers[instr->rt];
break;
case 1:
value = (value & 0xff000000) | ((registers[instr->rt] >> 8) &
0xffffff);
break;
case 2:
value = (value & 0xffff0000) | ((registers[instr->rt] >> 16) &
0xffff);
break;
case 3:
value = (value & 0xffffff00) | ((registers[instr->rt] >> 24) &
0xff);
break;
}
#ifndef SIM_FIX
if (!WriteMem((tmp & ~0x3), 4, value))
return;
#else
// DEBUG('P', "Value 0x%X\n",value);
if (!WriteMem((tmp - byte), 4, value))
return;
#endif // SIM_FIX
break;
case OP_SWR:
tmp = registers[instr->rs] + instr->extra;
#ifndef SIM_FIX
// The little endian/big endian swap code would
// fail (I think) if the other cases are ever exercised.
ASSERT((tmp & 0x3) == 0);
if (!ReadMem((tmp & ~0x3), 4, &value))
return;
#else
// The only difference between this code and the BIG ENDIAN code
// is that the ReadMem call is guaranteed an aligned access as
// it should be (Kane's book hides the fact that all memory
// access are done using aligned loads - what the instruction
// asks for is a arbitrary) This is the whole purpose of LWL
// and LWR etc.
byte = tmp & 0x3;
// DEBUG('P', "Addr 0x%X\n",tmp-byte);
if (!ReadMem(tmp-byte, 4, &value))
return;
// DEBUG('P', "Value 0x%X\n",value);
#endif // SIM_FIX
#ifndef SIM_FIX
switch (tmp & 0x3)
#else
switch( 3 - byte )
#endif // SIM_FIX
{
case 0:
value = (value & 0xffffff) | (registers[instr->rt] << 24);
break;
case 1:
value = (value & 0xffff) | (registers[instr->rt] << 16);
break;
case 2:
value = (value & 0xff) | (registers[instr->rt] << 8);
break;
case 3:
value = registers[instr->rt];
break;
}
#ifndef SIM_FIX
if (!WriteMem((tmp & ~0x3), 4, value))
return;
#else
// DEBUG('P', "Value 0x%X\n",value);
if (!WriteMem((tmp - byte), 4, value))
return;
#endif // SIM_FIX
break;
case OP_SYSCALL:
RaiseException(SyscallException, 0);
return;
case OP_XOR:
registers[instr->rd] = registers[instr->rs] ^ registers[instr->rt];
break;
case OP_XORI:
registers[instr->rt] = registers[instr->rs] ^ (instr->extra & 0xffff);
break;
case OP_RES:
case OP_UNIMP:
RaiseException(IllegalInstrException, 0);
return;
default:
ASSERT(FALSE);
}
// Now we have successfully executed the instruction.
// Do any delayed load operation
DelayedLoad(nextLoadReg, nextLoadValue);
// Advance program counters.
registers[PrevPCReg] = registers[PCReg]; // for debugging, in case we
// are jumping into lala-land
registers[PCReg] = registers[NextPCReg];
registers[NextPCReg] = pcAfter;
}
//----------------------------------------------------------------------
// Machine::DelayedLoad
// Simulate effects of a delayed load.
//
// NOTE -- RaiseException/CheckInterrupts must also call DelayedLoad,
// since any delayed load must get applied before we trap to the kernel.
//----------------------------------------------------------------------
void
Machine::DelayedLoad(int nextReg, int nextValue)
{
registers[registers[LoadReg]] = registers[LoadValueReg];
registers[LoadReg] = nextReg;
registers[LoadValueReg] = nextValue;
registers[0] = 0; // and always make sure R0 stays zero.
}
//----------------------------------------------------------------------
// Instruction::Decode
// Decode a MIPS instruction
//----------------------------------------------------------------------
void
Instruction::Decode()
{
OpInfo *opPtr;
rs = (value >> 21) & 0x1f;
rt = (value >> 16) & 0x1f;
rd = (value >> 11) & 0x1f;
opPtr = &opTable[(value >> 26) & 0x3f];
opCode = opPtr->opCode;
if (opPtr->format == IFMT) {
extra = value & 0xffff;
if (extra & 0x8000) {
extra |= 0xffff0000;
}
} else if (opPtr->format == RFMT) {
extra = (value >> 6) & 0x1f;
} else {
extra = value & 0x3ffffff;
}
if (opCode == SPECIAL) {
opCode = specialTable[value & 0x3f];
} else if (opCode == BCOND) {
int i = value & 0x1f0000;
if (i == 0) {
opCode = OP_BLTZ;
} else if (i == 0x10000) {
opCode = OP_BGEZ;
} else if (i == 0x100000) {
opCode = OP_BLTZAL;
} else if (i == 0x110000) {
opCode = OP_BGEZAL;
} else {
opCode = OP_UNIMP;
}
}
}
//----------------------------------------------------------------------
// Mult
// Simulate R2000 multiplication.
// The words at *hiPtr and *loPtr are overwritten with the
// double-length result of the multiplication.
//----------------------------------------------------------------------
static void
Mult(int a, int b, bool signedArith, int* hiPtr, int* loPtr)
{
if ((a == 0) || (b == 0)) {
*hiPtr = *loPtr = 0;
return;
}
// Compute the sign of the result, then make everything positive
// so unsigned computation can be done in the main loop.
bool negative = FALSE;
if (signedArith) {
if (a < 0) {
negative = !negative;
a = -a;
}
if (b < 0) {
negative = !negative;
b = -b;
}
}
// Compute the result in unsigned arithmetic (check a's bits one at
// a time, and add in a shifted value of b).
unsigned int bLo = b;
unsigned int bHi = 0;
unsigned int lo = 0;
unsigned int hi = 0;
for (int i = 0; i < 32; i++) {
if (a & 1) {
lo += bLo;
if (lo < bLo) // Carry out of the low bits?
hi += 1;
hi += bHi;
if ((a & 0xfffffffe) == 0)
break;
}
bHi <<= 1;
if (bLo & 0x80000000)
bHi |= 1;
bLo <<= 1;
a >>= 1;
}
// If the result is supposed to be negative, compute the two's
// complement of the double-word result.
if (negative) {
hi = ~hi;
lo = ~lo;
lo++;
if (lo == 0)
hi++;
}
*hiPtr = (int) hi;
*loPtr = (int) lo;
}

229
code/machine/mipssim.h Normal file
View File

@@ -0,0 +1,229 @@
// mipssim.h
// Internal data structures for simulating the MIPS instruction set.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef MIPSSIM_H
#define MIPSSIM_H
#include "copyright.h"
/*
* OpCode values. The names are straight from the MIPS
* manual except for the following special ones:
*
* OP_UNIMP - means that this instruction is legal, but hasn't
* been implemented in the simulator yet.
* OP_RES - means that this is a reserved opcode (it isn't
* supported by the architecture).
*/
#define OP_ADD 1
#define OP_ADDI 2
#define OP_ADDIU 3
#define OP_ADDU 4
#define OP_AND 5
#define OP_ANDI 6
#define OP_BEQ 7
#define OP_BGEZ 8
#define OP_BGEZAL 9
#define OP_BGTZ 10
#define OP_BLEZ 11
#define OP_BLTZ 12
#define OP_BLTZAL 13
#define OP_BNE 14
#define OP_DIV 16
#define OP_DIVU 17
#define OP_J 18
#define OP_JAL 19
#define OP_JALR 20
#define OP_JR 21
#define OP_LB 22
#define OP_LBU 23
#define OP_LH 24
#define OP_LHU 25
#define OP_LUI 26
#define OP_LW 27
#define OP_LWL 28
#define OP_LWR 29
#define OP_MFHI 31
#define OP_MFLO 32
#define OP_MTHI 34
#define OP_MTLO 35
#define OP_MULT 36
#define OP_MULTU 37
#define OP_NOR 38
#define OP_OR 39
#define OP_ORI 40
#define OP_RFE 41
#define OP_SB 42
#define OP_SH 43
#define OP_SLL 44
#define OP_SLLV 45
#define OP_SLT 46
#define OP_SLTI 47
#define OP_SLTIU 48
#define OP_SLTU 49
#define OP_SRA 50
#define OP_SRAV 51
#define OP_SRL 52
#define OP_SRLV 53
#define OP_SUB 54
#define OP_SUBU 55
#define OP_SW 56
#define OP_SWL 57
#define OP_SWR 58
#define OP_XOR 59
#define OP_XORI 60
#define OP_SYSCALL 61
#define OP_UNIMP 62
#define OP_RES 63
#define MaxOpcode 63
/*
* Miscellaneous definitions:
*/
#define IndexToAddr(x) ((x) << 2)
#define SIGN_BIT 0x80000000
#define R31 31
/*
* The table below is used to translate bits 31:26 of the instruction
* into a value suitable for the "opCode" field of a MemWord structure,
* or into a special value for further decoding.
*/
#define SPECIAL 100
#define BCOND 101
#define IFMT 1
#define JFMT 2
#define RFMT 3
struct OpInfo {
int opCode; /* Translated op code. */
int format; /* Format type (IFMT or JFMT or RFMT) */
};
static OpInfo opTable[] = {
{SPECIAL, RFMT}, {BCOND, IFMT}, {OP_J, JFMT}, {OP_JAL, JFMT},
{OP_BEQ, IFMT}, {OP_BNE, IFMT}, {OP_BLEZ, IFMT}, {OP_BGTZ, IFMT},
{OP_ADDI, IFMT}, {OP_ADDIU, IFMT}, {OP_SLTI, IFMT}, {OP_SLTIU, IFMT},
{OP_ANDI, IFMT}, {OP_ORI, IFMT}, {OP_XORI, IFMT}, {OP_LUI, IFMT},
{OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT},
{OP_LB, IFMT}, {OP_LH, IFMT}, {OP_LWL, IFMT}, {OP_LW, IFMT},
{OP_LBU, IFMT}, {OP_LHU, IFMT}, {OP_LWR, IFMT}, {OP_RES, IFMT},
{OP_SB, IFMT}, {OP_SH, IFMT}, {OP_SWL, IFMT}, {OP_SW, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_SWR, IFMT}, {OP_RES, IFMT},
{OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT},
{OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT},
{OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}
};
/*
* The table below is used to convert the "funct" field of SPECIAL
* instructions into the "opCode" field of a MemWord.
*/
static int specialTable[] = {
OP_SLL, OP_RES, OP_SRL, OP_SRA, OP_SLLV, OP_RES, OP_SRLV, OP_SRAV,
OP_JR, OP_JALR, OP_RES, OP_RES, OP_SYSCALL, OP_UNIMP, OP_RES, OP_RES,
OP_MFHI, OP_MTHI, OP_MFLO, OP_MTLO, OP_RES, OP_RES, OP_RES, OP_RES,
OP_MULT, OP_MULTU, OP_DIV, OP_DIVU, OP_RES, OP_RES, OP_RES, OP_RES,
OP_ADD, OP_ADDU, OP_SUB, OP_SUBU, OP_AND, OP_OR, OP_XOR, OP_NOR,
OP_RES, OP_RES, OP_SLT, OP_SLTU, OP_RES, OP_RES, OP_RES, OP_RES,
OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES,
OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES
};
// Stuff to help print out each instruction, for debugging
enum RegType { NONE, RS, RT, RD, EXTRA };
struct OpString {
char *format; // Printed version of instruction
RegType args[3];
};
static struct OpString opStrings[] = {
{"Shouldn't happen", {NONE, NONE, NONE}},
{"ADD r%d,r%d,r%d", {RD, RS, RT}},
{"ADDI r%d,r%d,%d", {RT, RS, EXTRA}},
{"ADDIU r%d,r%d,%d", {RT, RS, EXTRA}},
{"ADDU r%d,r%d,r%d", {RD, RS, RT}},
{"AND r%d,r%d,r%d", {RD, RS, RT}},
{"ANDI r%d,r%d,%d", {RT, RS, EXTRA}},
{"BEQ r%d,r%d,%d", {RS, RT, EXTRA}},
{"BGEZ r%d,%d", {RS, EXTRA, NONE}},
{"BGEZAL r%d,%d", {RS, EXTRA, NONE}},
{"BGTZ r%d,%d", {RS, EXTRA, NONE}},
{"BLEZ r%d,%d", {RS, EXTRA, NONE}},
{"BLTZ r%d,%d", {RS, EXTRA, NONE}},
{"BLTZAL r%d,%d", {RS, EXTRA, NONE}},
{"BNE r%d,r%d,%d", {RS, RT, EXTRA}},
{"Shouldn't happen", {NONE, NONE, NONE}},
{"DIV r%d,r%d", {RS, RT, NONE}},
{"DIVU r%d,r%d", {RS, RT, NONE}},
{"J %d", {EXTRA, NONE, NONE}},
{"JAL %d", {EXTRA, NONE, NONE}},
{"JALR r%d,r%d", {RD, RS, NONE}},
{"JR r%d,r%d", {RD, RS, NONE}},
{"LB r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LBU r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LH r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LHU r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LUI r%d,%d", {RT, EXTRA, NONE}},
{"LW r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LWL r%d,%d(r%d)", {RT, EXTRA, RS}},
{"LWR r%d,%d(r%d)", {RT, EXTRA, RS}},
{"Shouldn't happen", {NONE, NONE, NONE}},
{"MFHI r%d", {RD, NONE, NONE}},
{"MFLO r%d", {RD, NONE, NONE}},
{"Shouldn't happen", {NONE, NONE, NONE}},
{"MTHI r%d", {RS, NONE, NONE}},
{"MTLO r%d", {RS, NONE, NONE}},
{"MULT r%d,r%d", {RS, RT, NONE}},
{"MULTU r%d,r%d", {RS, RT, NONE}},
{"NOR r%d,r%d,r%d", {RD, RS, RT}},
{"OR r%d,r%d,r%d", {RD, RS, RT}},
{"ORI r%d,r%d,%d", {RT, RS, EXTRA}},
{"RFE", {NONE, NONE, NONE}},
{"SB r%d,%d(r%d)", {RT, EXTRA, RS}},
{"SH r%d,%d(r%d)", {RT, EXTRA, RS}},
{"SLL r%d,r%d,%d", {RD, RT, EXTRA}},
{"SLLV r%d,r%d,r%d", {RD, RT, RS}},
{"SLT r%d,r%d,r%d", {RD, RS, RT}},
{"SLTI r%d,r%d,%d", {RT, RS, EXTRA}},
{"SLTIU r%d,r%d,%d", {RT, RS, EXTRA}},
{"SLTU r%d,r%d,r%d", {RD, RS, RT}},
{"SRA r%d,r%d,%d", {RD, RT, EXTRA}},
{"SRAV r%d,r%d,r%d", {RD, RT, RS}},
{"SRL r%d,r%d,%d", {RD, RT, EXTRA}},
{"SRLV r%d,r%d,r%d", {RD, RT, RS}},
{"SUB r%d,r%d,r%d", {RD, RS, RT}},
{"SUBU r%d,r%d,r%d", {RD, RS, RT}},
{"SW r%d,%d(r%d)", {RT, EXTRA, RS}},
{"SWL r%d,%d(r%d)", {RT, EXTRA, RS}},
{"SWR r%d,%d(r%d)", {RT, EXTRA, RS}},
{"XOR r%d,r%d,r%d", {RD, RS, RT}},
{"XORI r%d,r%d,%d", {RT, RS, EXTRA}},
{"SYSCALL", {NONE, NONE, NONE}},
{"Unimplemented", {NONE, NONE, NONE}},
{"Reserved", {NONE, NONE, NONE}}
};
#endif // MIPSSIM_H

182
code/machine/network.cc Normal file
View File

@@ -0,0 +1,182 @@
// network.cc
// Routines to simulate a network interface, using UNIX sockets
// to deliver packets between multiple invocations of nachos.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "network.h"
#include "main.h"
//-----------------------------------------------------------------------
// NetworkInput::NetworkInput
// Initialize the simulation for the network input
//
// "toCall" is the interrupt handler to call when packet arrives
//-----------------------------------------------------------------------
NetworkInput::NetworkInput(CallBackObj *toCall)
{
// set up the stuff to emulate asynchronous interrupts
callWhenAvail = toCall;
packetAvail = FALSE;
inHdr.length = 0;
sock = OpenSocket();
sprintf(sockName, "SOCKET_%d", kernel->hostName);
AssignNameToSocket(sockName, sock); // Bind socket to a filename
// in the current directory.
// start polling for incoming packets
kernel->interrupt->Schedule(this, NetworkTime, NetworkRecvInt);
}
//-----------------------------------------------------------------------
// NetworkInput::NetworkInput
// Deallocate the simulation for the network input
// (basically, deallocate the input mailbox)
//-----------------------------------------------------------------------
NetworkInput::~NetworkInput()
{
CloseSocket(sock);
DeAssignNameToSocket(sockName);
}
//-----------------------------------------------------------------------
// NetworkInput::CallBack
// Simulator calls this when a packet may be available to
// be read in from the simulated network.
//
// First check to make sure packet is available & there's space to
// pull it in. Then invoke the "callBack" registered by whoever
// wants the packet.
//-----------------------------------------------------------------------
void
NetworkInput::CallBack()
{
// schedule the next time to poll for a packet
kernel->interrupt->Schedule(this, NetworkTime, NetworkRecvInt);
if (inHdr.length != 0) // do nothing if packet is already buffered
return;
if (!PollSocket(sock)) // do nothing if no packet to be read
return;
// otherwise, read packet in
char *buffer = new char[MaxWireSize];
ReadFromSocket(sock, buffer, MaxWireSize);
// divide packet into header and data
inHdr = *(PacketHeader *)buffer;
ASSERT((inHdr.to == kernel->hostName) && (inHdr.length <= MaxPacketSize));
bcopy(buffer + sizeof(PacketHeader), inbox, inHdr.length);
delete [] buffer ;
DEBUG(dbgNet, "Network received packet from " << inHdr.from << ", length " << inHdr.length);
kernel->stats->numPacketsRecvd++;
// tell post office that the packet has arrived
callWhenAvail->CallBack();
}
//-----------------------------------------------------------------------
// NetworkInput::Receive
// Read a packet, if one is buffered
//-----------------------------------------------------------------------
PacketHeader
NetworkInput::Receive(char* data)
{
PacketHeader hdr = inHdr;
inHdr.length = 0;
if (hdr.length != 0) {
bcopy(inbox, data, hdr.length);
}
return hdr;
}
//-----------------------------------------------------------------------
// NetworkOutput::NetworkOutput
// Initialize the simulation for sending network packets
//
// "reliability" says whether we drop packets to emulate unreliable links
// "toCall" is the interrupt handler to call when next packet can be sent
//-----------------------------------------------------------------------
NetworkOutput::NetworkOutput(double reliability, CallBackObj *toCall)
{
if (reliability < 0) chanceToWork = 0;
else if (reliability > 1) chanceToWork = 1;
else chanceToWork = reliability;
// set up the stuff to emulate asynchronous interrupts
callWhenDone = toCall;
sendBusy = FALSE;
sock = OpenSocket();
}
//-----------------------------------------------------------------------
// NetworkOutput::~NetworkOutput
// Deallocate the simulation for sending network packets
//-----------------------------------------------------------------------
NetworkOutput::~NetworkOutput()
{
CloseSocket(sock);
}
//-----------------------------------------------------------------------
// NetworkOutput::CallBack
// Called by simulator when another packet can be sent.
//-----------------------------------------------------------------------
void
NetworkOutput::CallBack()
{
sendBusy = FALSE;
kernel->stats->numPacketsSent++;
callWhenDone->CallBack();
}
//-----------------------------------------------------------------------
// NetworkOutput::Send
// Send a packet into the simulated network, to the destination in hdr.
// Concatenate hdr and data, and schedule an interrupt to tell the user
// when the next packet can be sent
//
// Note we always pad out a packet to MaxWireSize before putting it into
// the socket, because it's simpler at the receive end.
//-----------------------------------------------------------------------
void
NetworkOutput::Send(PacketHeader hdr, char* data)
{
char toName[32];
sprintf(toName, "SOCKET_%d", (int)hdr.to);
ASSERT((sendBusy == FALSE) && (hdr.length > 0) &&
(hdr.length <= MaxPacketSize) && (hdr.from == kernel->hostName));
DEBUG(dbgNet, "Sending to addr " << hdr.to << ", length " << hdr.length);
kernel->interrupt->Schedule(this, NetworkTime, NetworkSendInt);
if (RandomNumber() % 100 >= chanceToWork * 100) { // emulate a lost packet
DEBUG(dbgNet, "oops, lost it!");
return;
}
// concatenate hdr and data into a single buffer, and send it out
char *buffer = new char[MaxWireSize];
*(PacketHeader *)buffer = hdr;
bcopy(data, buffer + sizeof(PacketHeader), hdr.length);
SendToSocket(sock, buffer, MaxWireSize, toName);
delete [] buffer;
}

110
code/machine/network.h Normal file
View File

@@ -0,0 +1,110 @@
// network.h
// Data structures to emulate a physical network connection.
// The network provides the abstraction of ordered, unreliable,
// fixed-size packet delivery to other machines on the network.
//
// You may note that the interface to the network is similar to
// the console device -- both are full duplex channels.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef NETWORK_H
#define NETWORK_H
#include "copyright.h"
#include "utility.h"
#include "callback.h"
// Network address -- uniquely identifies a machine. This machine's ID
// is given on the command line.
typedef int NetworkAddress;
// The following class defines the network packet header.
// The packet header is prepended to the data payload by the Network driver,
// before the packet is sent over the wire. The format on the wire is:
// packet header (PacketHeader)
// data (containing MailHeader from the PostOffice!)
class PacketHeader {
public:
NetworkAddress to; // Destination machine ID
NetworkAddress from; // source machine ID
unsigned length; // bytes of packet data, excluding the
// packet header (but including the
// MailHeader prepended by the post office)
};
#define MaxWireSize 64 // largest packet that can go out on the wire
#define MaxPacketSize (MaxWireSize - sizeof(struct PacketHeader))
// data "payload" of the largest packet
// The following two classes defines a physical network device. The network
// is capable of delivering fixed sized packets, in order but unreliably,
// to other machines connected to the network.
//
// The "reliability" of the network can be specified to the constructor.
// This number, between 0 and 1, is the chance that the network will lose
// a packet. Note that you can change the seed for the random number
// generator, by changing the arguments to RandomInit() in Initialize().
// The random number generator is used to choose which packets to drop.
class NetworkInput : public CallBackObj{
public:
NetworkInput(CallBackObj *toCall);
// Allocate and initialize network input driver
~NetworkInput(); // De-allocate the network input driver data
PacketHeader Receive(char* data);
// Poll the network for incoming messages.
// If there is a packet waiting, copy the
// packet into "data" and return the header.
// If no packet is waiting, return a header
// with length 0.
void CallBack(); // A packet may have arrived.
private:
int sock; // UNIX socket number for incoming packets
char sockName[32]; // File name corresponding to UNIX socket
CallBackObj *callWhenAvail; // Interrupt handler, signalling packet has
// arrived.
bool packetAvail; // Packet has arrived, can be pulled off of
// network
PacketHeader inHdr; // Information about arrived packet
char inbox[MaxPacketSize]; // Data for arrived packet
};
class NetworkOutput : public CallBackObj {
public:
NetworkOutput(double reliability, CallBackObj *toCall);
// Allocate and initialize network output driver
~NetworkOutput(); // De-allocate the network input driver data
void Send(PacketHeader hdr, char* data);
// Send the packet data to a remote machine,
// specified by "hdr". Returns immediately.
// "callWhenDone" is invoked once the next
// packet can be sent. Note that callWhenDone
// is called whether or not the packet is
// dropped, and note that the "from" field of
// the PacketHeader is filled in automatically
// by Send().
void CallBack(); // Interrupt handler, called when message is
// sent
private:
int sock; // UNIX socket number for outgoing packets
double chanceToWork; // Likelihood packet will be dropped
CallBackObj *callWhenDone; // Interrupt handler, signalling next packet
// can be sent.
bool sendBusy; // Packet is being sent.
};
#endif // NETWORK_H

45
code/machine/stats.cc Normal file
View File

@@ -0,0 +1,45 @@
// stats.h
// Routines for managing statistics about Nachos performance.
//
// DO NOT CHANGE -- these stats are maintained by the machine emulation.
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "debug.h"
#include "stats.h"
//----------------------------------------------------------------------
// Statistics::Statistics
// Initialize performance metrics to zero, at system startup.
//----------------------------------------------------------------------
Statistics::Statistics()
{
totalTicks = idleTicks = systemTicks = userTicks = 0;
numDiskReads = numDiskWrites = 0;
numConsoleCharsRead = numConsoleCharsWritten = 0;
numPageFaults = numPacketsSent = numPacketsRecvd = 0;
}
//----------------------------------------------------------------------
// Statistics::Print
// Print performance metrics, when we've finished everything
// at system shutdown.
//----------------------------------------------------------------------
void
Statistics::Print()
{
cout << "Ticks: total " << totalTicks << ", idle " << idleTicks;
cout << ", system " << systemTicks << ", user " << userTicks <<"\n";
cout << "Disk I/O: reads " << numDiskReads;
cout << ", writes " << numDiskWrites << "\n";
cout << "Console I/O: reads " << numConsoleCharsRead;
cout << ", writes " << numConsoleCharsWritten << "\n";
cout << "Paging: faults " << numPageFaults << "\n";
cout << "Network I/O: packets received " << numPacketsRecvd;
cout << ", sent " << numPacketsSent << "\n";
}

60
code/machine/stats.h Normal file
View File

@@ -0,0 +1,60 @@
// stats.h
// Data structures for gathering statistics about Nachos performance.
//
// DO NOT CHANGE -- these stats are maintained by the machine emulation
//
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef STATS_H
#define STATS_H
#include "copyright.h"
// The following class defines the statistics that are to be kept
// about Nachos behavior -- how much time (ticks) elapsed, how
// many user instructions executed, etc.
//
// The fields in this class are public to make it easier to update.
class Statistics {
public:
int totalTicks; // Total time running Nachos
int idleTicks; // Time spent idle (no threads to run)
int systemTicks; // Time spent executing system code
int userTicks; // Time spent executing user code
// (this is also equal to # of
// user instructions executed)
int numDiskReads; // number of disk read requests
int numDiskWrites; // number of disk write requests
int numConsoleCharsRead; // number of characters read from the keyboard
int numConsoleCharsWritten; // number of characters written to the display
int numPageFaults; // number of virtual memory page faults
int numPacketsSent; // number of packets sent over the network
int numPacketsRecvd; // number of packets received over the network
Statistics(); // initialize everything to zero
void Print(); // print collected statistics
};
// Constants used to reflect the relative time an operation would
// take in a real system. A "tick" is a just a unit of time -- if you
// like, a microsecond.
//
// Since Nachos kernel code is directly executed, and the time spent
// in the kernel measured by the number of calls to enable interrupts,
// these time constants are none too exact.
const int UserTick = 1; // advance for each user-level instruction
const int SystemTick = 10; // advance each time interrupts are enabled
const int RotationTime = 500; // time disk takes to rotate one sector
const int SeekTime = 500; // time disk takes to seek past one track
const int ConsoleTime = 100; // time to read or write one character
const int NetworkTime = 100; // time to send or receive one packet
const int TimerTicks = 100; // (average) time between timer interrupts
#endif // STATS_H

81
code/machine/timer.cc Normal file
View File

@@ -0,0 +1,81 @@
// timer.cc
// Routines to emulate a hardware timer device.
//
// A hardware timer generates a CPU interrupt every X milliseconds.
// This means it can be used for implementing time-slicing.
//
// We emulate a hardware timer by scheduling an interrupt to occur
// every time stats->totalTicks has increased by TimerTicks.
//
// In order to introduce some randomness into time-slicing, if "doRandom"
// is set, then the interrupt is comes after a random number of ticks.
//
// Remember -- nothing in here is part of Nachos. It is just
// an emulation for the hardware that Nachos is running on top of.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "timer.h"
#include "main.h"
#include "sysdep.h"
//----------------------------------------------------------------------
// Timer::Timer
// Initialize a hardware timer device. Save the place to call
// on each interrupt, and then arrange for the timer to start
// generating interrupts.
//
// "doRandom" -- if true, arrange for the interrupts to occur
// at random, instead of fixed, intervals.
// "toCall" is the interrupt handler to call when the timer expires.
//----------------------------------------------------------------------
Timer::Timer(bool doRandom, CallBackObj *toCall)
{
randomize = doRandom;
callPeriodically = toCall;
disable = FALSE;
SetInterrupt();
}
//----------------------------------------------------------------------
// Timer::CallBack
// Routine called when interrupt is generated by the hardware
// timer device. Schedule the next interrupt, and invoke the
// interrupt handler.
//----------------------------------------------------------------------
void
Timer::CallBack()
{
// invoke the Nachos interrupt handler for this device
callPeriodically->CallBack();
SetInterrupt(); // do last, to let software interrupt handler
// decide if it wants to disable future interrupts
}
//----------------------------------------------------------------------
// Timer::SetInterrupt
// Cause a timer interrupt to occur in the future, unless
// future interrupts have been disabled. The delay is either
// fixed or random.
//----------------------------------------------------------------------
void
Timer::SetInterrupt()
{
if (!disable) {
int delay = TimerTicks;
if (randomize) {
delay = 1 + (RandomNumber() % (TimerTicks * 2));
}
// schedule the next timer device interrupt
kernel->interrupt->Schedule(this, delay, TimerInt);
}
}

53
code/machine/timer.h Normal file
View File

@@ -0,0 +1,53 @@
// timer.h
// Data structures to emulate a hardware timer.
//
// A hardware timer generates a CPU interrupt every X milliseconds.
// This means it can be used for implementing time-slicing, or for
// having a thread go to sleep for a specific period of time.
//
// We emulate a hardware timer by scheduling an interrupt to occur
// every time stats->totalTicks has increased by TimerTicks.
//
// In order to introduce some randomness into time-slicing, if "doRandom"
// is set, then the interrupt comes after a random number of ticks.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef TIMER_H
#define TIMER_H
#include "copyright.h"
#include "utility.h"
#include "callback.h"
// The following class defines a hardware timer.
class Timer : public CallBackObj {
public:
Timer(bool doRandom, CallBackObj *toCall);
// Initialize the timer, and callback to "toCall"
// every time slice.
virtual ~Timer() {}
void Disable() { disable = TRUE; }
// Turn timer device off, so it doesn't
// generate any more interrupts.
private:
bool randomize; // set if we need to use a random timeout delay
CallBackObj *callPeriodically; // call this every TimerTicks time units
bool disable; // turn off the timer device after next
// interrupt.
void CallBack(); // called internally when the hardware
// timer generates an interrupt
void SetInterrupt(); // cause an interrupt to occur in the
// the future after a fixed or random
// delay
};
#endif // TIMER_H

250
code/machine/translate.cc Normal file
View File

@@ -0,0 +1,250 @@
// translate.cc
// Routines to translate virtual addresses to physical addresses.
// Software sets up a table of legal translations. We look up
// in the table on every memory reference to find the true physical
// memory location.
//
// Two types of translation are supported here.
//
// Linear page table -- the virtual page # is used as an index
// into the table, to find the physical page #.
//
// Translation lookaside buffer -- associative lookup in the table
// to find an entry with the same virtual page #. If found,
// this entry is used for the translation.
// If not, it traps to software with an exception.
//
// In practice, the TLB is much smaller than the amount of physical
// memory (16 entries is common on a machine that has 1000's of
// pages). Thus, there must also be a backup translation scheme
// (such as page tables), but the hardware doesn't need to know
// anything at all about that.
//
// Note that the contents of the TLB are specific to an address space.
// If the address space changes, so does the contents of the TLB!
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
#include "main.h"
// Routines for converting Words and Short Words to and from the
// simulated machine's format of little endian. These end up
// being NOPs when the host machine is also little endian (DEC and Intel).
unsigned int
WordToHost(unsigned int word) {
#ifdef HOST_IS_BIG_ENDIAN
register unsigned long result;
result = (word >> 24) & 0x000000ff;
result |= (word >> 8) & 0x0000ff00;
result |= (word << 8) & 0x00ff0000;
result |= (word << 24) & 0xff000000;
return result;
#else
return word;
#endif /* HOST_IS_BIG_ENDIAN */
}
unsigned short
ShortToHost(unsigned short shortword) {
#ifdef HOST_IS_BIG_ENDIAN
register unsigned short result;
result = (shortword << 8) & 0xff00;
result |= (shortword >> 8) & 0x00ff;
return result;
#else
return shortword;
#endif /* HOST_IS_BIG_ENDIAN */
}
unsigned int
WordToMachine(unsigned int word) { return WordToHost(word); }
unsigned short
ShortToMachine(unsigned short shortword) { return ShortToHost(shortword); }
//----------------------------------------------------------------------
// Machine::ReadMem
// Read "size" (1, 2, or 4) bytes of virtual memory at "addr" into
// the location pointed to by "value".
//
// Returns FALSE if the translation step from virtual to physical memory
// failed.
//
// "addr" -- the virtual address to read from
// "size" -- the number of bytes to read (1, 2, or 4)
// "value" -- the place to write the result
//----------------------------------------------------------------------
bool
Machine::ReadMem(int addr, int size, int *value)
{
int data;
ExceptionType exception;
int physicalAddress;
DEBUG(dbgAddr, "Reading VA " << addr << ", size " << size);
exception = Translate(addr, &physicalAddress, size, FALSE);
if (exception != NoException) {
RaiseException(exception, addr);
return FALSE;
}
switch (size) {
case 1:
data = mainMemory[physicalAddress];
*value = data;
break;
case 2:
data = *(unsigned short *) &mainMemory[physicalAddress];
*value = ShortToHost(data);
break;
case 4:
data = *(unsigned int *) &mainMemory[physicalAddress];
*value = WordToHost(data);
break;
default: ASSERT(FALSE);
}
DEBUG(dbgAddr, "\tvalue read = " << *value);
return (TRUE);
}
//----------------------------------------------------------------------
// Machine::WriteMem
// Write "size" (1, 2, or 4) bytes of the contents of "value" into
// virtual memory at location "addr".
//
// Returns FALSE if the translation step from virtual to physical memory
// failed.
//
// "addr" -- the virtual address to write to
// "size" -- the number of bytes to be written (1, 2, or 4)
// "value" -- the data to be written
//----------------------------------------------------------------------
bool
Machine::WriteMem(int addr, int size, int value)
{
ExceptionType exception;
int physicalAddress;
DEBUG(dbgAddr, "Writing VA " << addr << ", size " << size << ", value " << value);
exception = Translate(addr, &physicalAddress, size, TRUE);
if (exception != NoException) {
RaiseException(exception, addr);
return FALSE;
}
switch (size) {
case 1:
mainMemory[physicalAddress] = (unsigned char) (value & 0xff);
break;
case 2:
*(unsigned short *) &mainMemory[physicalAddress]
= ShortToMachine((unsigned short) (value & 0xffff));
break;
case 4:
*(unsigned int *) &mainMemory[physicalAddress]
= WordToMachine((unsigned int) value);
break;
default: ASSERT(FALSE);
}
return TRUE;
}
//----------------------------------------------------------------------
// Machine::Translate
// Translate a virtual address into a physical address, using
// either a page table or a TLB. Check for alignment and all sorts
// of other errors, and if everything is ok, set the use/dirty bits in
// the translation table entry, and store the translated physical
// address in "physAddr". If there was an error, returns the type
// of the exception.
//
// "virtAddr" -- the virtual address to translate
// "physAddr" -- the place to store the physical address
// "size" -- the amount of memory being read or written
// "writing" -- if TRUE, check the "read-only" bit in the TLB
//----------------------------------------------------------------------
ExceptionType
Machine::Translate(int virtAddr, int* physAddr, int size, bool writing)
{
int i;
unsigned int vpn, offset;
TranslationEntry *entry;
unsigned int pageFrame;
DEBUG(dbgAddr, "\tTranslate " << virtAddr << (writing ? " , write" : " , read"));
// check for alignment errors
if (((size == 4) && (virtAddr & 0x3)) || ((size == 2) && (virtAddr & 0x1))){
DEBUG(dbgAddr, "Alignment problem at " << virtAddr << ", size " << 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(dbgAddr, "Illegal virtual page # " << virtAddr);
return AddressErrorException;
} else if (!pageTable[vpn].valid) {
DEBUG(dbgAddr, "Invalid virtual page # " << virtAddr);
return PageFaultException;
}
entry = &pageTable[vpn];
} else {
for (entry = NULL, i = 0; i < TLBSize; i++)
if (tlb[i].valid && (tlb[i].virtualPage == ((int)vpn))) {
entry = &tlb[i]; // FOUND!
break;
}
if (entry == NULL) { // not found
DEBUG(dbgAddr, "Invalid TLB entry for this virtual page!");
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(dbgAddr, "Write to read-only page at " << virtAddr);
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(dbgAddr, "Illegal pageframe " << pageFrame);
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(dbgAddr, "phys addr = " << *physAddr);
return NoException;
}

45
code/machine/translate.h Normal file
View File

@@ -0,0 +1,45 @@
// translate.h
// Data structures for managing the translation from
// virtual page # -> physical page #, used for managing
// physical memory on behalf of user programs.
//
// The data structures in this file are "dual-use" - they
// serve both as a page table entry, and as an entry in
// a software-managed translation lookaside buffer (TLB).
// Either way, each entry is of the form:
// <virtual page #, physical page #>.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef TLB_H
#define TLB_H
#include "copyright.h"
#include "utility.h"
// The following class defines an entry in a translation table -- either
// in a page table or a TLB. Each entry defines a mapping from one
// virtual page to one physical page.
// In addition, there are some extra bits for access control (valid and
// read-only) and some bits for usage information (use and dirty).
class TranslationEntry {
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.
};
#endif