NachOS/code/machine/disk.cc

272 lines
9.4 KiB
C++

// 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 "system.h"
// We put this 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).
#define MagicNumber 0x456789ab
#define MagicSize sizeof(int)
#define DiskSize (MagicSize + (NumSectors * SectorSize))
// dummy procedure because we can't take a pointer of a member function
static void DiskDone(void *arg) { ((Disk *)arg)->HandleInterrupt(); }
//----------------------------------------------------------------------
// 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.
//
// "name" -- text name of the file simulating the Nachos disk
// "callWhenDone" -- interrupt handler to be called when disk read/write
// request completes
// "callArg" -- argument to pass the interrupt handler
//----------------------------------------------------------------------
Disk::Disk(const char* name, VoidFunctionPtr callWhenDone, void *callArg)
{
int magicNum;
int tmp = 0;
DEBUG('d', "Initializing the disk, 0x%x 0x%x\n", callWhenDone, callArg);
handler = callWhenDone;
handlerArg = callArg;
lastSector = 0;
bufferInit = 0;
fileno = OpenForReadWrite(name, FALSE);
if (fileno >= 0) { // file exists, check magic number
Read(fileno, &magicNum, MagicSize);
ASSERT(magicNum == MagicNumber);
} else { // file doesn't exist, create it
fileno = OpenForWrite(name);
magicNum = MagicNumber;
WriteFile(fileno, &magicNum, MagicSize); // write magic number
// need to write at end of file, so that reads will not return EOF
Lseek(fileno, DiskSize - sizeof(int), SEEK_SET);
WriteFile(fileno, &tmp, sizeof(int));
}
active = FALSE;
}
//----------------------------------------------------------------------
// Disk::~Disk()
// Clean up disk simulation, by closing the UNIX file representing the
// disk.
//----------------------------------------------------------------------
Disk::~Disk()
{
Close(fileno);
fileno = -1;
}
//----------------------------------------------------------------------
// Disk::PrintSector()
// Dump the data in a disk read/write request, for debugging.
//----------------------------------------------------------------------
static void
PrintSector (bool writing, int sector, const void *data)
{
const int *p = (const int *) data;
if (writing)
printf("Writing sector: %d\n", sector);
else
printf("Reading sector: %d\n", sector);
for (unsigned int i = 0; i < (SectorSize/sizeof(int)); i++)
printf("%x ", p[i]);
printf("\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, void* data)
{
int ticks = ComputeLatency(sectorNumber, FALSE);
ASSERT(!active); // only one request at a time
ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors));
DEBUG('d', "Reading from sector %d\n", sectorNumber);
Lseek(fileno, SectorSize * sectorNumber + MagicSize, SEEK_SET);
Read(fileno, data, SectorSize);
if (DebugIsEnabled('d'))
PrintSector(FALSE, sectorNumber, data);
active = TRUE;
UpdateLast(sectorNumber);
stats->numDiskReads++;
interrupt->Schedule(DiskDone, this, ticks, DiskInt);
}
void
Disk::WriteRequest(int sectorNumber, const void* data)
{
int ticks = ComputeLatency(sectorNumber, TRUE);
ASSERT(!active);
ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors));
DEBUG('d', "Writing to sector %d\n", sectorNumber);
Lseek(fileno, SectorSize * sectorNumber + MagicSize, SEEK_SET);
WriteFile(fileno, data, SectorSize);
if (DebugIsEnabled('d'))
PrintSector(TRUE, sectorNumber, data);
active = TRUE;
UpdateLast(sectorNumber);
stats->numDiskWrites++;
interrupt->Schedule(DiskDone, this, ticks, DiskInt);
}
//----------------------------------------------------------------------
// Disk::HandleInterrupt()
// Called when it is time to invoke the disk interrupt handler,
// to tell the Nachos kernel that the disk request is done.
//----------------------------------------------------------------------
void
Disk::HandleInterrupt ()
{
active = FALSE;
(*handler)(handlerArg);
}
//----------------------------------------------------------------------
// 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 = (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 = 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('d', "Request latency = %d\n", RotationTime);
return RotationTime; // time to transfer sector from the track buffer
}
#endif
rotation += ModuloDiff(newSector, timeAfter / RotationTime) * RotationTime;
DEBUG('d', "Request latency = %d\n", 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 = stats->totalTicks + seek + rotate;
lastSector = newSector;
DEBUG('d', "Updating last sector = %d, %d\n", lastSector, bufferInit);
}