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:
55
code/threads/alarm.cc
Normal file
55
code/threads/alarm.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
// alarm.cc
|
||||
// Routines to use a hardware timer device to provide a
|
||||
// software alarm clock. For now, we just provide time-slicing.
|
||||
//
|
||||
// Not completely implemented.
|
||||
//
|
||||
// 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 "alarm.h"
|
||||
#include "main.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Alarm::Alarm
|
||||
// Initialize a software alarm clock. Start up a timer device
|
||||
//
|
||||
// "doRandom" -- if true, arrange for the hardware interrupts to
|
||||
// occur at random, instead of fixed, intervals.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Alarm::Alarm(bool doRandom)
|
||||
{
|
||||
timer = new Timer(doRandom, this);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Alarm::CallBack
|
||||
// Software interrupt handler for the timer device. The timer device is
|
||||
// set up to interrupt the CPU periodically (once every TimerTicks).
|
||||
// This routine is called each time there is a timer interrupt,
|
||||
// with interrupts disabled.
|
||||
//
|
||||
// Note that instead of calling Yield() directly (which would
|
||||
// suspend the interrupt handler, not the interrupted thread
|
||||
// which is what we wanted to context switch), we set a flag
|
||||
// so that once the interrupt handler is done, it will appear as
|
||||
// if the interrupted thread called Yield at the point it is
|
||||
// was interrupted.
|
||||
//
|
||||
// For now, just provide time-slicing. Only need to time slice
|
||||
// if we're currently running something (in other words, not idle).
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Alarm::CallBack()
|
||||
{
|
||||
Interrupt *interrupt = kernel->interrupt;
|
||||
MachineStatus status = interrupt->getStatus();
|
||||
|
||||
if (status != IdleMode) {
|
||||
interrupt->YieldOnReturn();
|
||||
}
|
||||
}
|
||||
42
code/threads/alarm.h
Normal file
42
code/threads/alarm.h
Normal file
@@ -0,0 +1,42 @@
|
||||
// alarm.h
|
||||
// Data structures for a software alarm clock.
|
||||
//
|
||||
// We make use of a hardware timer device, that generates
|
||||
// an interrupt every X time ticks (on real systems, X is
|
||||
// usually between 0.25 - 10 milliseconds).
|
||||
//
|
||||
// From this, we provide the ability for a thread to be
|
||||
// woken up after a delay; we also provide time-slicing.
|
||||
//
|
||||
// NOTE: this abstraction is not completely implemented.
|
||||
//
|
||||
// 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 ALARM_H
|
||||
#define ALARM_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "utility.h"
|
||||
#include "callback.h"
|
||||
#include "timer.h"
|
||||
|
||||
// The following class defines a software alarm clock.
|
||||
class Alarm : public CallBackObj {
|
||||
public:
|
||||
Alarm(bool doRandomYield); // Initialize the timer, and callback
|
||||
// to "toCall" every time slice.
|
||||
~Alarm() { delete timer; }
|
||||
|
||||
void WaitUntil(int x); // suspend execution until time > now + x
|
||||
// this method is not yet implemented
|
||||
|
||||
private:
|
||||
Timer *timer; // the hardware timer device
|
||||
|
||||
void CallBack(); // called when the hardware
|
||||
// timer generates an interrupt
|
||||
};
|
||||
|
||||
#endif // ALARM_H
|
||||
311
code/threads/kernel.cc
Normal file
311
code/threads/kernel.cc
Normal file
@@ -0,0 +1,311 @@
|
||||
// kernel.cc
|
||||
// Initialization and cleanup routines for the Nachos kernel.
|
||||
//
|
||||
// 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 "main.h"
|
||||
#include "kernel.h"
|
||||
#include "sysdep.h"
|
||||
#include "synch.h"
|
||||
#include "synchlist.h"
|
||||
#include "libtest.h"
|
||||
#include "string.h"
|
||||
#include "synchdisk.h"
|
||||
#include "post.h"
|
||||
#include "synchconsole.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::Kernel
|
||||
// Interpret command line arguments in order to determine flags
|
||||
// for the initialization (see also comments in main.cc)
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Kernel::Kernel(int argc, char **argv)
|
||||
{
|
||||
randomSlice = FALSE;
|
||||
debugUserProg = FALSE;
|
||||
consoleIn = NULL; // default is stdin
|
||||
consoleOut = NULL; // default is stdout
|
||||
#ifndef FILESYS_STUB
|
||||
formatFlag = FALSE;
|
||||
#endif
|
||||
reliability = 1; // network reliability, default is 1.0
|
||||
hostName = 0; // machine id, also UNIX socket name
|
||||
// 0 is the default machine id
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-rs") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
RandomInit(atoi(argv[i + 1]));// initialize pseudo-random
|
||||
// number generator
|
||||
randomSlice = TRUE;
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-s") == 0) {
|
||||
debugUserProg = TRUE;
|
||||
} else if (strcmp(argv[i], "-e") == 0) {
|
||||
execfile[++execfileNum]= argv[++i];
|
||||
cout << execfile[execfileNum] << "\n";
|
||||
} else if (strcmp(argv[i], "-ci") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
consoleIn = argv[i + 1];
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-co") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
consoleOut = argv[i + 1];
|
||||
i++;
|
||||
#ifndef FILESYS_STUB
|
||||
} else if (strcmp(argv[i], "-f") == 0) {
|
||||
formatFlag = TRUE;
|
||||
#endif
|
||||
} else if (strcmp(argv[i], "-n") == 0) {
|
||||
ASSERT(i + 1 < argc); // next argument is float
|
||||
reliability = atof(argv[i + 1]);
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-m") == 0) {
|
||||
ASSERT(i + 1 < argc); // next argument is int
|
||||
hostName = atoi(argv[i + 1]);
|
||||
i++;
|
||||
} else if (strcmp(argv[i], "-u") == 0) {
|
||||
cout << "Partial usage: nachos [-rs randomSeed]\n";
|
||||
cout << "Partial usage: nachos [-s]\n";
|
||||
cout << "Partial usage: nachos [-ci consoleIn] [-co consoleOut]\n";
|
||||
#ifndef FILESYS_STUB
|
||||
cout << "Partial usage: nachos [-nf]\n";
|
||||
#endif
|
||||
cout << "Partial usage: nachos [-n #] [-m #]\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::Initialize
|
||||
// Initialize Nachos global data structures. Separate from the
|
||||
// constructor because some of these refer to earlier initialized
|
||||
// data via the "kernel" global variable.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Kernel::Initialize()
|
||||
{
|
||||
// We didn't explicitly allocate the current thread we are running in.
|
||||
// But if it ever tries to give up the CPU, we better have a Thread
|
||||
// object to save its state.
|
||||
|
||||
|
||||
currentThread = new Thread("main", threadNum++);
|
||||
currentThread->setStatus(RUNNING);
|
||||
|
||||
stats = new Statistics(); // collect statistics
|
||||
interrupt = new Interrupt; // start up interrupt handling
|
||||
scheduler = new Scheduler(); // initialize the ready queue
|
||||
alarm = new Alarm(randomSlice); // start up time slicing
|
||||
machine = new Machine(debugUserProg);
|
||||
synchConsoleIn = new SynchConsoleInput(consoleIn); // input from stdin
|
||||
synchConsoleOut = new SynchConsoleOutput(consoleOut); // output to stdout
|
||||
synchDisk = new SynchDisk(); //
|
||||
#ifdef FILESYS_STUB
|
||||
fileSystem = new FileSystem();
|
||||
#else
|
||||
fileSystem = new FileSystem(formatFlag);
|
||||
#endif // FILESYS_STUB
|
||||
postOfficeIn = new PostOfficeInput(10);
|
||||
postOfficeOut = new PostOfficeOutput(reliability);
|
||||
|
||||
interrupt->Enable();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::~Kernel
|
||||
// Nachos is halting. De-allocate global data structures.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Kernel::~Kernel()
|
||||
{
|
||||
delete stats;
|
||||
delete interrupt;
|
||||
delete scheduler;
|
||||
delete alarm;
|
||||
delete machine;
|
||||
delete synchConsoleIn;
|
||||
delete synchConsoleOut;
|
||||
delete synchDisk;
|
||||
delete fileSystem;
|
||||
delete postOfficeIn;
|
||||
delete postOfficeOut;
|
||||
|
||||
Exit(0);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::ThreadSelfTest
|
||||
// Test threads, semaphores, synchlists
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Kernel::ThreadSelfTest() {
|
||||
Semaphore *semaphore;
|
||||
SynchList<int> *synchList;
|
||||
|
||||
LibSelfTest(); // test library routines
|
||||
|
||||
currentThread->SelfTest(); // test thread switching
|
||||
|
||||
// test semaphore operation
|
||||
semaphore = new Semaphore("test", 0);
|
||||
semaphore->SelfTest();
|
||||
delete semaphore;
|
||||
|
||||
// test locks, condition variables
|
||||
// using synchronized lists
|
||||
synchList = new SynchList<int>;
|
||||
synchList->SelfTest(9);
|
||||
delete synchList;
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::ConsoleTest
|
||||
// Test the synchconsole
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Kernel::ConsoleTest() {
|
||||
char ch;
|
||||
|
||||
cout << "Testing the console device.\n"
|
||||
<< "Typed characters will be echoed, until ^D is typed.\n"
|
||||
<< "Note newlines are needed to flush input through UNIX.\n";
|
||||
cout.flush();
|
||||
|
||||
do {
|
||||
ch = synchConsoleIn->GetChar();
|
||||
if(ch != EOF) synchConsoleOut->PutChar(ch); // echo it!
|
||||
} while (ch != EOF);
|
||||
|
||||
cout << "\n";
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Kernel::NetworkTest
|
||||
// Test whether the post office is working. On machines #0 and #1, do:
|
||||
//
|
||||
// 1. send a message to the other machine at mail box #0
|
||||
// 2. wait for the other machine's message to arrive (in our mailbox #0)
|
||||
// 3. send an acknowledgment for the other machine's message
|
||||
// 4. wait for an acknowledgement from the other machine to our
|
||||
// original message
|
||||
//
|
||||
// This test works best if each Nachos machine has its own window
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Kernel::NetworkTest() {
|
||||
|
||||
if (hostName == 0 || hostName == 1) {
|
||||
// if we're machine 1, send to 0 and vice versa
|
||||
int farHost = (hostName == 0 ? 1 : 0);
|
||||
PacketHeader outPktHdr, inPktHdr;
|
||||
MailHeader outMailHdr, inMailHdr;
|
||||
char *data = "Hello there!";
|
||||
char *ack = "Got it!";
|
||||
char buffer[MaxMailSize];
|
||||
|
||||
// construct packet, mail header for original message
|
||||
// To: destination machine, mailbox 0
|
||||
// From: our machine, reply to: mailbox 1
|
||||
outPktHdr.to = farHost;
|
||||
outMailHdr.to = 0;
|
||||
outMailHdr.from = 1;
|
||||
outMailHdr.length = strlen(data) + 1;
|
||||
|
||||
// Send the first message
|
||||
postOfficeOut->Send(outPktHdr, outMailHdr, data);
|
||||
|
||||
// Wait for the first message from the other machine
|
||||
postOfficeIn->Receive(0, &inPktHdr, &inMailHdr, buffer);
|
||||
cout << "Got: " << buffer << " : from " << inPktHdr.from << ", box "
|
||||
<< inMailHdr.from << "\n";
|
||||
cout.flush();
|
||||
|
||||
// Send acknowledgement to the other machine (using "reply to" mailbox
|
||||
// in the message that just arrived
|
||||
outPktHdr.to = inPktHdr.from;
|
||||
outMailHdr.to = inMailHdr.from;
|
||||
outMailHdr.length = strlen(ack) + 1;
|
||||
postOfficeOut->Send(outPktHdr, outMailHdr, ack);
|
||||
|
||||
// Wait for the ack from the other machine to the first message we sent
|
||||
postOfficeIn->Receive(1, &inPktHdr, &inMailHdr, buffer);
|
||||
cout << "Got: " << buffer << " : from " << inPktHdr.from << ", box "
|
||||
<< inMailHdr.from << "\n";
|
||||
cout.flush();
|
||||
}
|
||||
|
||||
// Then we're done!
|
||||
}
|
||||
|
||||
void ForkExecute(Thread *t)
|
||||
{
|
||||
if ( !t->space->Load(t->getName()) ) {
|
||||
return; // executable not found
|
||||
}
|
||||
|
||||
t->space->Execute(t->getName());
|
||||
|
||||
}
|
||||
|
||||
void Kernel::ExecAll()
|
||||
{
|
||||
for (int i=1;i<=execfileNum;i++) {
|
||||
int a = Exec(execfile[i]);
|
||||
}
|
||||
currentThread->Finish();
|
||||
//Kernel::Exec();
|
||||
}
|
||||
|
||||
|
||||
int Kernel::Exec(char* name)
|
||||
{
|
||||
t[threadNum] = new Thread(name, threadNum);
|
||||
t[threadNum]->space = new AddrSpace();
|
||||
t[threadNum]->Fork((VoidFunctionPtr) &ForkExecute, (void *)t[threadNum]);
|
||||
threadNum++;
|
||||
|
||||
return threadNum-1;
|
||||
/*
|
||||
cout << "Total threads number is " << execfileNum << endl;
|
||||
for (int n=1;n<=execfileNum;n++) {
|
||||
t[n] = new Thread(execfile[n]);
|
||||
t[n]->space = new AddrSpace();
|
||||
t[n]->Fork((VoidFunctionPtr) &ForkExecute, (void *)t[n]);
|
||||
cout << "Thread " << execfile[n] << " is executing." << endl;
|
||||
}
|
||||
cout << "debug Kernel::Run finished.\n";
|
||||
*/
|
||||
// Thread *t1 = new Thread(execfile[1]);
|
||||
// Thread *t1 = new Thread("../test/test1");
|
||||
// Thread *t2 = new Thread("../test/test2");
|
||||
|
||||
// AddrSpace *halt = new AddrSpace();
|
||||
// t1->space = new AddrSpace();
|
||||
// t2->space = new AddrSpace();
|
||||
|
||||
// halt->Execute("../test/halt");
|
||||
// t1->Fork((VoidFunctionPtr) &ForkExecute, (void *)t1);
|
||||
// t2->Fork((VoidFunctionPtr) &ForkExecute, (void *)t2);
|
||||
|
||||
// currentThread->Finish();
|
||||
// Kernel::Run();
|
||||
// cout << "after ThreadedKernel:Run();" << endl; // unreachable
|
||||
}
|
||||
|
||||
int Kernel::CreateFile(char *filename)
|
||||
{
|
||||
return fileSystem->Create(filename);
|
||||
}
|
||||
|
||||
|
||||
86
code/threads/kernel.h
Normal file
86
code/threads/kernel.h
Normal file
@@ -0,0 +1,86 @@
|
||||
// kernel.h
|
||||
// Global variables for the Nachos kernel.
|
||||
//
|
||||
// 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 KERNEL_H
|
||||
#define KERNEL_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "debug.h"
|
||||
#include "utility.h"
|
||||
#include "thread.h"
|
||||
#include "scheduler.h"
|
||||
#include "interrupt.h"
|
||||
#include "stats.h"
|
||||
#include "alarm.h"
|
||||
#include "filesys.h"
|
||||
#include "machine.h"
|
||||
|
||||
class PostOfficeInput;
|
||||
class PostOfficeOutput;
|
||||
class SynchConsoleInput;
|
||||
class SynchConsoleOutput;
|
||||
class SynchDisk;
|
||||
|
||||
|
||||
|
||||
class Kernel {
|
||||
public:
|
||||
Kernel(int argc, char **argv);
|
||||
// Interpret command line arguments
|
||||
~Kernel(); // deallocate the kernel
|
||||
|
||||
void Initialize(); // initialize the kernel -- separated
|
||||
// from constructor because
|
||||
// refers to "kernel" as a global
|
||||
void ExecAll();
|
||||
int Exec(char* name);
|
||||
void ThreadSelfTest(); // self test of threads and synchronization
|
||||
|
||||
void ConsoleTest(); // interactive console self test
|
||||
void NetworkTest(); // interactive 2-machine network test
|
||||
Thread* getThread(int threadID){return t[threadID];}
|
||||
|
||||
int CreateFile(char* filename); // fileSystem call
|
||||
|
||||
// These are public for notational convenience; really,
|
||||
// they're global variables used everywhere.
|
||||
|
||||
Thread *currentThread; // the thread holding the CPU
|
||||
Scheduler *scheduler; // the ready list
|
||||
Interrupt *interrupt; // interrupt status
|
||||
Statistics *stats; // performance metrics
|
||||
Alarm *alarm; // the software alarm clock
|
||||
Machine *machine; // the simulated CPU
|
||||
SynchConsoleInput *synchConsoleIn;
|
||||
SynchConsoleOutput *synchConsoleOut;
|
||||
SynchDisk *synchDisk;
|
||||
FileSystem *fileSystem;
|
||||
PostOfficeInput *postOfficeIn;
|
||||
PostOfficeOutput *postOfficeOut;
|
||||
|
||||
int hostName; // machine identifier
|
||||
|
||||
private:
|
||||
|
||||
Thread* t[10];
|
||||
char* execfile[10];
|
||||
int execfileNum;
|
||||
int threadNum;
|
||||
bool randomSlice; // enable pseudo-random time slicing
|
||||
bool debugUserProg; // single step user program
|
||||
double reliability; // likelihood messages are dropped
|
||||
char *consoleIn; // file to read console input from
|
||||
char *consoleOut; // file to send console output to
|
||||
#ifndef FILESYS_STUB
|
||||
bool formatFlag; // format the disk if this is true
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
#endif // KERNEL_H
|
||||
|
||||
|
||||
297
code/threads/main.cc
Normal file
297
code/threads/main.cc
Normal file
@@ -0,0 +1,297 @@
|
||||
// main.cc
|
||||
// Driver code to initialize, selftest, and run the
|
||||
// operating system kernel.
|
||||
//
|
||||
// Usage: nachos -d <debugflags> -rs <random seed #>
|
||||
// -s -x <nachos file> -ci <consoleIn> -co <consoleOut>
|
||||
// -f -cp <unix file> <nachos file>
|
||||
// -p <nachos file> -r <nachos file> -l -D
|
||||
// -n <network reliability> -m <machine id>
|
||||
// -z -K -C -N
|
||||
//
|
||||
// -d causes certain debugging messages to be printed (see debug.h)
|
||||
// -rs causes Yield to occur at random (but repeatable) spots
|
||||
// -z prints the copyright message
|
||||
// -s causes user programs to be executed in single-step mode
|
||||
// -x runs a user program
|
||||
// -ci specify file for console input (stdin is the default)
|
||||
// -co specify file for console output (stdout is the default)
|
||||
// -n sets the network reliability
|
||||
// -m sets this machine's host id (needed for the network)
|
||||
// -K run a simple self test of kernel threads and synchronization
|
||||
// -C run an interactive console test
|
||||
// -N run a two-machine network test (see Kernel::NetworkTest)
|
||||
//
|
||||
// Filesystem-related flags:
|
||||
// -f forces the Nachos disk to be formatted
|
||||
// -cp copies a file from UNIX to Nachos
|
||||
// -p prints a Nachos file to stdout
|
||||
// -r removes a Nachos file from the file system
|
||||
// -l lists the contents of the Nachos directory
|
||||
// -D prints the contents of the entire file system
|
||||
//
|
||||
// Note: the file system flags are not used if the stub filesystem
|
||||
// is being used
|
||||
//
|
||||
// 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.
|
||||
|
||||
#define MAIN
|
||||
#include "copyright.h"
|
||||
#undef MAIN
|
||||
|
||||
#include "main.h"
|
||||
#include "filesys.h"
|
||||
#include "openfile.h"
|
||||
#include "sysdep.h"
|
||||
|
||||
// global variables
|
||||
Kernel *kernel;
|
||||
Debug *debug;
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Cleanup
|
||||
// Delete kernel data structures; called when user hits "ctl-C".
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
Cleanup(int x)
|
||||
{
|
||||
cerr << "\nCleaning up after signal " << x << "\n";
|
||||
delete kernel;
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------
|
||||
// Constant used by "Copy" and "Print"
|
||||
// It is the number of bytes read from the Unix file (for Copy)
|
||||
// or the Nachos file (for Print) by each read operation
|
||||
//-------------------------------------------------------------------
|
||||
static const int TransferSize = 128;
|
||||
|
||||
|
||||
#ifndef FILESYS_STUB
|
||||
//----------------------------------------------------------------------
|
||||
// Copy
|
||||
// Copy the contents of the UNIX file "from" to the Nachos file "to"
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
Copy(char *from, char *to)
|
||||
{
|
||||
int fd;
|
||||
OpenFile* openFile;
|
||||
int amountRead, fileLength;
|
||||
char *buffer;
|
||||
|
||||
// Open UNIX file
|
||||
if ((fd = OpenForReadWrite(from,FALSE)) < 0) {
|
||||
printf("Copy: couldn't open input file %s\n", from);
|
||||
return;
|
||||
}
|
||||
|
||||
// Figure out length of UNIX file
|
||||
Lseek(fd, 0, 2);
|
||||
fileLength = Tell(fd);
|
||||
Lseek(fd, 0, 0);
|
||||
|
||||
// Create a Nachos file of the same length
|
||||
DEBUG('f', "Copying file " << from << " of size " << fileLength << " to file " << to);
|
||||
if (!kernel->fileSystem->Create(to, fileLength)) { // Create Nachos file
|
||||
printf("Copy: couldn't create output file %s\n", to);
|
||||
Close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
openFile = kernel->fileSystem->Open(to);
|
||||
ASSERT(openFile != NULL);
|
||||
|
||||
// Copy the data in TransferSize chunks
|
||||
buffer = new char[TransferSize];
|
||||
while ((amountRead=ReadPartial(fd, buffer, sizeof(char)*TransferSize)) > 0)
|
||||
openFile->Write(buffer, amountRead);
|
||||
delete [] buffer;
|
||||
|
||||
// Close the UNIX and the Nachos files
|
||||
delete openFile;
|
||||
Close(fd);
|
||||
}
|
||||
|
||||
#endif // FILESYS_STUB
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Print
|
||||
// Print the contents of the Nachos file "name".
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Print(char *name)
|
||||
{
|
||||
OpenFile *openFile;
|
||||
int i, amountRead;
|
||||
char *buffer;
|
||||
|
||||
if ((openFile = kernel->fileSystem->Open(name)) == NULL) {
|
||||
printf("Print: unable to open file %s\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
buffer = new char[TransferSize];
|
||||
while ((amountRead = openFile->Read(buffer, TransferSize)) > 0)
|
||||
for (i = 0; i < amountRead; i++)
|
||||
printf("%c", buffer[i]);
|
||||
delete [] buffer;
|
||||
|
||||
delete openFile; // close the Nachos file
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// main
|
||||
// Bootstrap the operating system kernel.
|
||||
//
|
||||
// Initialize kernel data structures
|
||||
// Call some test routines
|
||||
// Call "Run" to start an initial user program running
|
||||
//
|
||||
// "argc" is the number of command line arguments (including the name
|
||||
// of the command) -- ex: "nachos -d +" -> argc = 3
|
||||
// "argv" is an array of strings, one for each command line argument
|
||||
// ex: "nachos -d +" -> argv = {"nachos", "-d", "+"}
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
char *debugArg = "";
|
||||
char *userProgName = NULL; // default is not to execute a user prog
|
||||
bool threadTestFlag = false;
|
||||
bool consoleTestFlag = false;
|
||||
bool networkTestFlag = false;
|
||||
#ifndef FILESYS_STUB
|
||||
char *copyUnixFileName = NULL; // UNIX file to be copied into Nachos
|
||||
char *copyNachosFileName = NULL; // name of copied file in Nachos
|
||||
char *printFileName = NULL;
|
||||
char *removeFileName = NULL;
|
||||
bool dirListFlag = false;
|
||||
bool dumpFlag = false;
|
||||
#endif //FILESYS_STUB
|
||||
|
||||
// some command line arguments are handled here.
|
||||
// those that set kernel parameters are handled in
|
||||
// the Kernel constructor
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "-d") == 0) {
|
||||
ASSERT(i + 1 < argc); // next argument is debug string
|
||||
debugArg = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
else if (strcmp(argv[i], "-z") == 0) {
|
||||
cout << copyright << "\n";
|
||||
}
|
||||
else if (strcmp(argv[i], "-x") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
userProgName = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
else if (strcmp(argv[i], "-K") == 0) {
|
||||
threadTestFlag = TRUE;
|
||||
}
|
||||
else if (strcmp(argv[i], "-C") == 0) {
|
||||
consoleTestFlag = TRUE;
|
||||
}
|
||||
else if (strcmp(argv[i], "-N") == 0) {
|
||||
networkTestFlag = TRUE;
|
||||
}
|
||||
#ifndef FILESYS_STUB
|
||||
else if (strcmp(argv[i], "-cp") == 0) {
|
||||
ASSERT(i + 2 < argc);
|
||||
copyUnixFileName = argv[i + 1];
|
||||
copyNachosFileName = argv[i + 2];
|
||||
i += 2;
|
||||
}
|
||||
else if (strcmp(argv[i], "-p") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
printFileName = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
else if (strcmp(argv[i], "-r") == 0) {
|
||||
ASSERT(i + 1 < argc);
|
||||
removeFileName = argv[i + 1];
|
||||
i++;
|
||||
}
|
||||
else if (strcmp(argv[i], "-l") == 0) {
|
||||
dirListFlag = true;
|
||||
}
|
||||
else if (strcmp(argv[i], "-D") == 0) {
|
||||
dumpFlag = true;
|
||||
}
|
||||
#endif //FILESYS_STUB
|
||||
else if (strcmp(argv[i], "-u") == 0) {
|
||||
cout << "Partial usage: nachos [-z -d debugFlags]\n";
|
||||
cout << "Partial usage: nachos [-x programName]\n";
|
||||
cout << "Partial usage: nachos [-K] [-C] [-N]\n";
|
||||
#ifndef FILESYS_STUB
|
||||
cout << "Partial usage: nachos [-cp UnixFile NachosFile]\n";
|
||||
cout << "Partial usage: nachos [-p fileName] [-r fileName]\n";
|
||||
cout << "Partial usage: nachos [-l] [-D]\n";
|
||||
#endif //FILESYS_STUB
|
||||
}
|
||||
|
||||
}
|
||||
debug = new Debug(debugArg);
|
||||
|
||||
DEBUG(dbgThread, "Entering main");
|
||||
|
||||
kernel = new Kernel(argc, argv);
|
||||
|
||||
kernel->Initialize();
|
||||
|
||||
CallOnUserAbort(Cleanup); // if user hits ctl-C
|
||||
|
||||
// at this point, the kernel is ready to do something
|
||||
// run some tests, if requested
|
||||
if (threadTestFlag) {
|
||||
kernel->ThreadSelfTest(); // test threads and synchronization
|
||||
}
|
||||
if (consoleTestFlag) {
|
||||
kernel->ConsoleTest(); // interactive test of the synchronized console
|
||||
}
|
||||
if (networkTestFlag) {
|
||||
kernel->NetworkTest(); // two-machine test of the network
|
||||
}
|
||||
|
||||
#ifndef FILESYS_STUB
|
||||
if (removeFileName != NULL) {
|
||||
kernel->fileSystem->Remove(removeFileName);
|
||||
}
|
||||
if (copyUnixFileName != NULL && copyNachosFileName != NULL) {
|
||||
Copy(copyUnixFileName,copyNachosFileName);
|
||||
}
|
||||
if (dumpFlag) {
|
||||
kernel->fileSystem->Print();
|
||||
}
|
||||
if (dirListFlag) {
|
||||
kernel->fileSystem->List();
|
||||
}
|
||||
if (printFileName != NULL) {
|
||||
Print(printFileName);
|
||||
}
|
||||
#endif // FILESYS_STUB
|
||||
|
||||
// finally, run an initial user program if requested to do so
|
||||
|
||||
kernel->ExecAll();
|
||||
// If we don't run a user program, we may get here.
|
||||
// Calling "return" would terminate the program.
|
||||
// Instead, call Halt, which will first clean up, then
|
||||
// terminate.
|
||||
// kernel->interrupt->Halt();
|
||||
|
||||
ASSERTNOTREACHED();
|
||||
}
|
||||
|
||||
19
code/threads/main.h
Normal file
19
code/threads/main.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// main.h
|
||||
// This file defines the Nachos global variables
|
||||
//
|
||||
// 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 MAIN_H
|
||||
#define MAIN_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "debug.h"
|
||||
#include "kernel.h"
|
||||
|
||||
extern Kernel *kernel;
|
||||
extern Debug *debug;
|
||||
|
||||
#endif // MAIN_H
|
||||
|
||||
179
code/threads/scheduler.cc
Normal file
179
code/threads/scheduler.cc
Normal file
@@ -0,0 +1,179 @@
|
||||
// scheduler.cc
|
||||
// Routines to choose the next thread to run, and to dispatch to
|
||||
// that thread.
|
||||
//
|
||||
// These routines assume that interrupts are already disabled.
|
||||
// If interrupts are disabled, we can assume mutual exclusion
|
||||
// (since we are on a uniprocessor).
|
||||
//
|
||||
// NOTE: We can't use Locks to provide mutual exclusion here, since
|
||||
// if we needed to wait for a lock, and the lock was busy, we would
|
||||
// end up calling FindNextToRun(), and that would put us in an
|
||||
// infinite loop.
|
||||
//
|
||||
// Very simple implementation -- no priorities, straight FIFO.
|
||||
// Might need to be improved in later assignments.
|
||||
//
|
||||
// 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 "scheduler.h"
|
||||
#include "main.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::Scheduler
|
||||
// Initialize the list of ready but not running threads.
|
||||
// Initially, no ready threads.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Scheduler::Scheduler()
|
||||
{
|
||||
readyList = new List<Thread *>;
|
||||
toBeDestroyed = NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::~Scheduler
|
||||
// De-allocate the list of ready threads.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Scheduler::~Scheduler()
|
||||
{
|
||||
delete readyList;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::ReadyToRun
|
||||
// Mark a thread as ready, but not running.
|
||||
// Put it on the ready list, for later scheduling onto the CPU.
|
||||
//
|
||||
// "thread" is the thread to be put on the ready list.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Scheduler::ReadyToRun (Thread *thread)
|
||||
{
|
||||
ASSERT(kernel->interrupt->getLevel() == IntOff);
|
||||
DEBUG(dbgThread, "Putting thread on ready list: " << thread->getName());
|
||||
//cout << "Putting thread on ready list: " << thread->getName() << endl ;
|
||||
thread->setStatus(READY);
|
||||
readyList->Append(thread);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::FindNextToRun
|
||||
// Return the next thread to be scheduled onto the CPU.
|
||||
// If there are no ready threads, return NULL.
|
||||
// Side effect:
|
||||
// Thread is removed from the ready list.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Thread *
|
||||
Scheduler::FindNextToRun ()
|
||||
{
|
||||
ASSERT(kernel->interrupt->getLevel() == IntOff);
|
||||
|
||||
if (readyList->IsEmpty()) {
|
||||
return NULL;
|
||||
} else {
|
||||
return readyList->RemoveFront();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::Run
|
||||
// Dispatch the CPU to nextThread. Save the state of the old thread,
|
||||
// and load the state of the new thread, by calling the machine
|
||||
// dependent context switch routine, SWITCH.
|
||||
//
|
||||
// Note: we assume the state of the previously running thread has
|
||||
// already been changed from running to blocked or ready (depending).
|
||||
// Side effect:
|
||||
// The global variable kernel->currentThread becomes nextThread.
|
||||
//
|
||||
// "nextThread" is the thread to be put into the CPU.
|
||||
// "finishing" is set if the current thread is to be deleted
|
||||
// once we're no longer running on its stack
|
||||
// (when the next thread starts running)
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Scheduler::Run (Thread *nextThread, bool finishing)
|
||||
{
|
||||
Thread *oldThread = kernel->currentThread;
|
||||
|
||||
ASSERT(kernel->interrupt->getLevel() == IntOff);
|
||||
|
||||
if (finishing) { // mark that we need to delete current thread
|
||||
ASSERT(toBeDestroyed == NULL);
|
||||
toBeDestroyed = oldThread;
|
||||
}
|
||||
|
||||
if (oldThread->space != NULL) { // if this thread is a user program,
|
||||
oldThread->SaveUserState(); // save the user's CPU registers
|
||||
oldThread->space->SaveState();
|
||||
}
|
||||
|
||||
oldThread->CheckOverflow(); // check if the old thread
|
||||
// had an undetected stack overflow
|
||||
|
||||
kernel->currentThread = nextThread; // switch to the next thread
|
||||
nextThread->setStatus(RUNNING); // nextThread is now running
|
||||
|
||||
DEBUG(dbgThread, "Switching from: " << oldThread->getName() << " to: " << nextThread->getName());
|
||||
|
||||
// This is a machine-dependent assembly language routine defined
|
||||
// in switch.s. You may have to think
|
||||
// a bit to figure out what happens after this, both from the point
|
||||
// of view of the thread and from the perspective of the "outside world".
|
||||
|
||||
SWITCH(oldThread, nextThread);
|
||||
|
||||
// we're back, running oldThread
|
||||
|
||||
// interrupts are off when we return from switch!
|
||||
ASSERT(kernel->interrupt->getLevel() == IntOff);
|
||||
|
||||
DEBUG(dbgThread, "Now in thread: " << oldThread->getName());
|
||||
|
||||
CheckToBeDestroyed(); // check if thread we were running
|
||||
// before this one has finished
|
||||
// and needs to be cleaned up
|
||||
|
||||
if (oldThread->space != NULL) { // if there is an address space
|
||||
oldThread->RestoreUserState(); // to restore, do it.
|
||||
oldThread->space->RestoreState();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::CheckToBeDestroyed
|
||||
// If the old thread gave up the processor because it was finishing,
|
||||
// we need to delete its carcass. Note we cannot delete the thread
|
||||
// before now (for example, in Thread::Finish()), because up to this
|
||||
// point, we were still running on the old thread's stack!
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Scheduler::CheckToBeDestroyed()
|
||||
{
|
||||
if (toBeDestroyed != NULL) {
|
||||
delete toBeDestroyed;
|
||||
toBeDestroyed = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Scheduler::Print
|
||||
// Print the scheduler state -- in other words, the contents of
|
||||
// the ready list. For debugging.
|
||||
//----------------------------------------------------------------------
|
||||
void
|
||||
Scheduler::Print()
|
||||
{
|
||||
cout << "Ready list contents:\n";
|
||||
readyList->Apply(ThreadPrint);
|
||||
}
|
||||
44
code/threads/scheduler.h
Normal file
44
code/threads/scheduler.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// scheduler.h
|
||||
// Data structures for the thread dispatcher and scheduler.
|
||||
// Primarily, the list of threads that are ready to run.
|
||||
//
|
||||
// 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 SCHEDULER_H
|
||||
#define SCHEDULER_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "list.h"
|
||||
#include "thread.h"
|
||||
|
||||
// The following class defines the scheduler/dispatcher abstraction --
|
||||
// the data structures and operations needed to keep track of which
|
||||
// thread is running, and which threads are ready but not running.
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
Scheduler(); // Initialize list of ready threads
|
||||
~Scheduler(); // De-allocate ready list
|
||||
|
||||
void ReadyToRun(Thread* thread);
|
||||
// Thread can be dispatched.
|
||||
Thread* FindNextToRun(); // Dequeue first thread on the ready
|
||||
// list, if any, and return thread.
|
||||
void Run(Thread* nextThread, bool finishing);
|
||||
// Cause nextThread to start running
|
||||
void CheckToBeDestroyed();// Check if thread that had been
|
||||
// running needs to be deleted
|
||||
void Print(); // Print contents of ready list
|
||||
|
||||
// SelfTest for scheduler is implemented in class Thread
|
||||
|
||||
private:
|
||||
List<Thread *> *readyList; // queue of threads that are ready to run,
|
||||
// but not running
|
||||
Thread *toBeDestroyed; // finishing thread to be destroyed
|
||||
// by the next thread that runs
|
||||
};
|
||||
|
||||
#endif // SCHEDULER_H
|
||||
750
code/threads/switch.S
Normal file
750
code/threads/switch.S
Normal file
@@ -0,0 +1,750 @@
|
||||
/* switch.s
|
||||
* Machine dependent context switch routines. DO NOT MODIFY THESE!
|
||||
*
|
||||
* Context switching is inherently machine dependent, since
|
||||
* the registers to be saved, how to set up an initial
|
||||
* call frame, etc, are all specific to a processor architecture.
|
||||
*
|
||||
* This file currently supports the following architectures:
|
||||
* DEC MIPS (DECMIPS)
|
||||
* DEC Alpha (ALPHA)
|
||||
* SUN SPARC (SPARC)
|
||||
* HP PA-RISC (PARISC)
|
||||
* Intel 386 (x86)
|
||||
* IBM RS6000 (PowerPC) -- I hope it will also work for Mac PowerPC
|
||||
*
|
||||
* We define two routines for each architecture:
|
||||
*
|
||||
* ThreadRoot(InitialPC, InitialArg, WhenDonePC, StartupPC)
|
||||
* InitialPC - The program counter of the procedure to run
|
||||
* in this thread.
|
||||
* InitialArg - The single argument to the thread.
|
||||
* WhenDonePC - The routine to call when the thread returns.
|
||||
* StartupPC - Routine to call when the thread is started.
|
||||
*
|
||||
* ThreadRoot is called from the SWITCH() routine to start
|
||||
* a thread for the first time.
|
||||
*
|
||||
* SWITCH(oldThread, newThread)
|
||||
* oldThread - The current thread that was running, where the
|
||||
* CPU register state is to be saved.
|
||||
* newThread - The new thread to be run, where the CPU register
|
||||
* state is to be loaded from.
|
||||
*/
|
||||
|
||||
/*
|
||||
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 "switch.h"
|
||||
|
||||
|
||||
#ifdef DECMIPS
|
||||
|
||||
/* Symbolic register names */
|
||||
#define z $0 /* zero register */
|
||||
#define a0 $4 /* argument registers */
|
||||
#define a1 $5
|
||||
#define s0 $16 /* callee saved */
|
||||
#define s1 $17
|
||||
#define s2 $18
|
||||
#define s3 $19
|
||||
#define s4 $20
|
||||
#define s5 $21
|
||||
#define s6 $22
|
||||
#define s7 $23
|
||||
#define sp $29 /* stack pointer */
|
||||
#define fp $30 /* frame pointer */
|
||||
#define ra $31 /* return address */
|
||||
|
||||
.text
|
||||
.align 2
|
||||
|
||||
.globl ThreadRoot
|
||||
.ent ThreadRoot,0
|
||||
ThreadRoot:
|
||||
or fp,z,z # Clearing the frame pointer here
|
||||
# makes gdb backtraces of thread stacks
|
||||
# end here (I hope!)
|
||||
|
||||
jal StartupPC # call startup procedure
|
||||
move a0, InitialArg
|
||||
jal InitialPC # call main procedure
|
||||
jal WhenDonePC # when done, call clean up procedure
|
||||
|
||||
# NEVER REACHED
|
||||
.end ThreadRoot
|
||||
|
||||
# a0 -- pointer to old Thread
|
||||
# a1 -- pointer to new Thread
|
||||
.globl SWITCH
|
||||
.ent SWITCH,0
|
||||
SWITCH:
|
||||
sw sp, SP(a0) # save new stack pointer
|
||||
sw s0, S0(a0) # save all the callee-save registers
|
||||
sw s1, S1(a0)
|
||||
sw s2, S2(a0)
|
||||
sw s3, S3(a0)
|
||||
sw s4, S4(a0)
|
||||
sw s5, S5(a0)
|
||||
sw s6, S6(a0)
|
||||
sw s7, S7(a0)
|
||||
sw fp, FP(a0) # save frame pointer
|
||||
sw ra, PC(a0) # save return address
|
||||
|
||||
lw sp, SP(a1) # load the new stack pointer
|
||||
lw s0, S0(a1) # load the callee-save registers
|
||||
lw s1, S1(a1)
|
||||
lw s2, S2(a1)
|
||||
lw s3, S3(a1)
|
||||
lw s4, S4(a1)
|
||||
lw s5, S5(a1)
|
||||
lw s6, S6(a1)
|
||||
lw s7, S7(a1)
|
||||
lw fp, FP(a1)
|
||||
lw ra, PC(a1) # load the return address
|
||||
|
||||
j ra
|
||||
.end SWITCH
|
||||
|
||||
#endif // DECMIPS
|
||||
|
||||
|
||||
|
||||
#ifdef SPARC
|
||||
|
||||
/* NOTE! These files appear not to exist on Solaris --
|
||||
* you need to find where (the SPARC-specific) MINFRAME, ST_FLUSH_WINDOWS, ...
|
||||
* are defined. (I don't have a Solaris machine, so I have no way to tell.)
|
||||
*/
|
||||
#ifdef SOLARIS
|
||||
#include <sys/trap.h>
|
||||
#include <sys/asm_linkage.h>
|
||||
#else
|
||||
#include <sun4/trap.h>
|
||||
#include <sun4/asm_linkage.h>
|
||||
#endif
|
||||
.seg "text"
|
||||
|
||||
/* SPECIAL to the SPARC:
|
||||
* The first two instruction of ThreadRoot are skipped because
|
||||
* the address of ThreadRoot is made the return address of SWITCH()
|
||||
* by the routine Thread::StackAllocate. SWITCH() jumps here on the
|
||||
* "ret" instruction which is really at "jmp %o7+8". The 8 skips the
|
||||
* two nops at the beginning of the routine.
|
||||
*/
|
||||
|
||||
#ifdef SOLARIS
|
||||
.globl ThreadRoot
|
||||
ThreadRoot:
|
||||
#else
|
||||
.globl _ThreadRoot
|
||||
_ThreadRoot:
|
||||
#endif
|
||||
nop ; nop /* These 2 nops are skipped because we are called
|
||||
* with a jmp+8 instruction. */
|
||||
clr %fp /* Clearing the frame pointer makes gdb backtraces
|
||||
* of thread stacks end here. */
|
||||
/* Currently the arguments are in out registers we
|
||||
* save them into local registers so they won't be
|
||||
* trashed during the calls we make. */
|
||||
mov InitialPC, %l0
|
||||
mov InitialArg, %l1
|
||||
mov WhenDonePC, %l2
|
||||
/* Execute the code:
|
||||
* call StartupPC();
|
||||
* call InitialPC(InitialArg);
|
||||
* call WhenDonePC();
|
||||
*/
|
||||
call StartupPC,0
|
||||
nop
|
||||
call %l0, 1
|
||||
mov %l1, %o0 /* Using delay slot to setup argument to InitialPC */
|
||||
call %l2, 0
|
||||
nop
|
||||
/* WhenDonePC call should never return. If it does
|
||||
* we execute a trap into the debugger. */
|
||||
ta ST_BREAKPOINT
|
||||
|
||||
|
||||
#ifdef SOLARIS
|
||||
.globl SWITCH
|
||||
SWITCH:
|
||||
#else
|
||||
.globl _SWITCH
|
||||
_SWITCH:
|
||||
#endif
|
||||
save %sp, -SA(MINFRAME), %sp
|
||||
st %fp, [%i0]
|
||||
st %i0, [%i0+I0]
|
||||
st %i1, [%i0+I1]
|
||||
st %i2, [%i0+I2]
|
||||
st %i3, [%i0+I3]
|
||||
st %i4, [%i0+I4]
|
||||
st %i5, [%i0+I5]
|
||||
st %i7, [%i0+I7]
|
||||
ta ST_FLUSH_WINDOWS
|
||||
nop
|
||||
mov %i1, %l0
|
||||
ld [%l0+I0], %i0
|
||||
ld [%l0+I1], %i1
|
||||
ld [%l0+I2], %i2
|
||||
ld [%l0+I3], %i3
|
||||
ld [%l0+I4], %i4
|
||||
ld [%l0+I5], %i5
|
||||
ld [%l0+I7], %i7
|
||||
ld [%l0], %i6
|
||||
ret
|
||||
restore
|
||||
|
||||
#endif // SPARC
|
||||
|
||||
|
||||
|
||||
#ifdef PARISC
|
||||
|
||||
;rp = r2, sp = r30
|
||||
;arg0 = r26, arg1 = r25, arg2 = r24, arg3 = r23
|
||||
|
||||
.SPACE $TEXT$
|
||||
.SUBSPA $CODE$
|
||||
ThreadRoot
|
||||
.PROC
|
||||
.CALLINFO CALLER,FRAME=0
|
||||
.ENTER
|
||||
|
||||
.CALL
|
||||
ble 0(%r6) ;call StartupPC
|
||||
stw %r31, -24(%sp) ;put return address in proper stack
|
||||
;location for StartupPC export stub.
|
||||
|
||||
or %r4, 0, %arg0 ;load InitialArg
|
||||
.CALL ;in=26
|
||||
ble 0(%r3) ;call InitialPC
|
||||
stw %r31, -24(%sp) ;put return address in proper stack
|
||||
;location for InitialPC export stub.
|
||||
|
||||
.CALL
|
||||
ble 0(%r5) ;call WhenDonePC
|
||||
stw %r31, -24(%sp) ;put return address in proper stack
|
||||
;location for StartupPC export stub.
|
||||
.LEAVE
|
||||
|
||||
.PROCEND
|
||||
|
||||
|
||||
SWITCH
|
||||
.PROC
|
||||
.CALLINFO CALLER,FRAME=0
|
||||
.ENTRY
|
||||
|
||||
; save process state of oldThread
|
||||
stw %sp, SP(%arg0) ;save stack pointer
|
||||
stw %r3, S0(%arg0) ;save callee-save registers
|
||||
stw %r4, S1(%arg0)
|
||||
stw %r5, S2(%arg0)
|
||||
stw %r6, S3(%arg0)
|
||||
stw %r7, S4(%arg0)
|
||||
stw %r8, S5(%arg0)
|
||||
stw %r9, S6(%arg0)
|
||||
stw %r10, S7(%arg0)
|
||||
stw %r11, S8(%arg0)
|
||||
stw %r12, S9(%arg0)
|
||||
stw %r13, S10(%arg0)
|
||||
stw %r14, S11(%arg0)
|
||||
stw %r15, S12(%arg0)
|
||||
stw %r16, S13(%arg0)
|
||||
stw %r17, S14(%arg0)
|
||||
stw %r18, S15(%arg0)
|
||||
stw %rp, PC(%arg0) ;save program counter
|
||||
|
||||
; restore process state of nextThread
|
||||
ldw SP(%arg1), %sp ;restore stack pointer
|
||||
ldw S0(%arg1), %r3 ;restore callee-save registers
|
||||
ldw S1(%arg1), %r4
|
||||
ldw S2(%arg1), %r5
|
||||
ldw S3(%arg1), %r6
|
||||
ldw S4(%arg1), %r7
|
||||
ldw S5(%arg1), %r8
|
||||
ldw S6(%arg1), %r9
|
||||
ldw S7(%arg1), %r10
|
||||
ldw S8(%arg1), %r11
|
||||
ldw S9(%arg1), %r12
|
||||
ldw S10(%arg1), %r13
|
||||
ldw S11(%arg1), %r14
|
||||
ldw S12(%arg1), %r15
|
||||
ldw S13(%arg1), %r16
|
||||
ldw S14(%arg1), %r17
|
||||
ldw PC(%arg1), %rp ;save program counter
|
||||
bv 0(%rp)
|
||||
.EXIT
|
||||
ldw S15(%arg1), %r18
|
||||
|
||||
.PROCEND
|
||||
|
||||
.EXPORT SWITCH,ENTRY,PRIV_LEV=3,RTNVAL=GR
|
||||
.EXPORT ThreadRoot,ENTRY,PRIV_LEV=3,RTNVAL=GR
|
||||
|
||||
#endif // PARISC
|
||||
|
||||
|
||||
|
||||
#ifdef x86
|
||||
|
||||
.text
|
||||
.align 2
|
||||
|
||||
.globl ThreadRoot
|
||||
.globl _ThreadRoot
|
||||
|
||||
/* void ThreadRoot( void )
|
||||
**
|
||||
** expects the following registers to be initialized:
|
||||
** eax points to startup function (interrupt enable)
|
||||
** edx contains inital argument to thread function
|
||||
** esi points to thread function
|
||||
** edi point to Thread::Finish()
|
||||
*/
|
||||
_ThreadRoot:
|
||||
ThreadRoot:
|
||||
pushl %ebp
|
||||
movl %esp,%ebp
|
||||
pushl InitialArg
|
||||
call *StartupPC
|
||||
call *InitialPC
|
||||
call *WhenDonePC
|
||||
|
||||
# NOT REACHED
|
||||
movl %ebp,%esp
|
||||
popl %ebp
|
||||
ret
|
||||
|
||||
|
||||
|
||||
/* void SWITCH( thread *t1, thread *t2 )
|
||||
**
|
||||
** on entry, stack looks like this:
|
||||
** 8(esp) -> thread *t2
|
||||
** 4(esp) -> thread *t1
|
||||
** (esp) -> return address
|
||||
**
|
||||
** we push the current eax on the stack so that we can use it as
|
||||
** a pointer to t1, this decrements esp by 4, so when we use it
|
||||
** to reference stuff on the stack, we add 4 to the offset.
|
||||
*/
|
||||
.comm _eax_save,4
|
||||
|
||||
.globl SWITCH
|
||||
.globl _SWITCH
|
||||
_SWITCH:
|
||||
SWITCH:
|
||||
movl %eax,_eax_save # save the value of eax
|
||||
movl 4(%esp),%eax # move pointer to t1 into eax
|
||||
movl %ebx,_EBX(%eax) # save registers
|
||||
movl %ecx,_ECX(%eax)
|
||||
movl %edx,_EDX(%eax)
|
||||
movl %esi,_ESI(%eax)
|
||||
movl %edi,_EDI(%eax)
|
||||
movl %ebp,_EBP(%eax)
|
||||
movl %esp,_ESP(%eax) # save stack pointer
|
||||
movl _eax_save,%ebx # get the saved value of eax
|
||||
movl %ebx,_EAX(%eax) # store it
|
||||
movl 0(%esp),%ebx # get return address from stack into ebx
|
||||
movl %ebx,_PC(%eax) # save it into the pc storage
|
||||
|
||||
movl 8(%esp),%eax # move pointer to t2 into eax
|
||||
|
||||
movl _EAX(%eax),%ebx # get new value for eax into ebx
|
||||
movl %ebx,_eax_save # save it
|
||||
movl _EBX(%eax),%ebx # retore old registers
|
||||
movl _ECX(%eax),%ecx
|
||||
movl _EDX(%eax),%edx
|
||||
movl _ESI(%eax),%esi
|
||||
movl _EDI(%eax),%edi
|
||||
movl _EBP(%eax),%ebp
|
||||
movl _ESP(%eax),%esp # restore stack pointer
|
||||
movl _PC(%eax),%eax # restore return address into eax
|
||||
movl %eax,4(%esp) # copy over the ret address on the stack
|
||||
movl _eax_save,%eax
|
||||
|
||||
ret
|
||||
|
||||
#endif // x86
|
||||
|
||||
|
||||
#if defined(ApplePowerPC)
|
||||
|
||||
/* The AIX PowerPC code is incompatible with the assembler on MacOS X
|
||||
* and Linux. So the SWITCH code was adapted for IBM 750 compatible
|
||||
* processors, and ThreadRoot is modeled after the more reasonable
|
||||
* looking ThreadRoot's in this file.
|
||||
*
|
||||
* Joshua LeVasseur <jtl@ira.uka.de>
|
||||
*/
|
||||
|
||||
.align 2
|
||||
.globl _SWITCH
|
||||
_SWITCH:
|
||||
stw r1, 0(r3) /* Store stack pointer. */
|
||||
stmw r13, 20(r3) /* Store general purpose registers 13 - 31. */
|
||||
stfd f14, 96(r3) /* Store floating point registers 14 -31. */
|
||||
stfd f15, 104(r3)
|
||||
stfd f16, 112(r3)
|
||||
stfd f17, 120(r3)
|
||||
stfd f18, 128(r3)
|
||||
stfd f19, 136(r3)
|
||||
stfd f20, 144(r3)
|
||||
stfd f21, 152(r3)
|
||||
stfd f22, 160(r3)
|
||||
stfd f23, 168(r3)
|
||||
stfd f24, 176(r3)
|
||||
stfd f25, 184(r3)
|
||||
stfd f26, 192(r3)
|
||||
stfd f27, 200(r3)
|
||||
stfd f28, 208(r3)
|
||||
stfd f29, 216(r3)
|
||||
stfd f30, 224(r3)
|
||||
stfd f31, 232(r3)
|
||||
|
||||
mflr r0
|
||||
stw r0, 244(r3) /* Spill the link register. */
|
||||
|
||||
mfcr r12
|
||||
stw r12, 240(r3) /* Spill the condition register. */
|
||||
|
||||
lwz r1, 0(r4) /* Load the incoming stack pointer. */
|
||||
|
||||
lwz r0, 244(r4) /* Load the incoming link register. */
|
||||
mtlr r0 /* Restore the link register. */
|
||||
|
||||
lwz r12, 240(r4) /* Load the condition register value. */
|
||||
mtcrf 0xff, r12 /* Restore the condition register. */
|
||||
|
||||
lmw r13, 20(r4) /* Restore registers r13 - r31. */
|
||||
|
||||
lfd f14, 96(r4) /* Restore floating point register f14 - f31. */
|
||||
lfd f15, 104(r4)
|
||||
lfd f16, 112(r4)
|
||||
lfd f17, 120(r4)
|
||||
lfd f18, 128(r4)
|
||||
lfd f19, 136(r4)
|
||||
lfd f20, 144(r4)
|
||||
lfd f21, 152(r4)
|
||||
lfd f22, 160(r4)
|
||||
lfd f23, 168(r4)
|
||||
lfd f24, 176(r4)
|
||||
lfd f25, 184(r4)
|
||||
lfd f26, 192(r4)
|
||||
lfd f27, 200(r4)
|
||||
lfd f28, 208(r4)
|
||||
lfd f29, 216(r4)
|
||||
lfd f30, 224(r4)
|
||||
lfd f31, 232(r4)
|
||||
|
||||
/* When a thread first starts, the following blr instruction jumps
|
||||
* to ThreadRoot. ThreadRoot expects the incoming thread block
|
||||
* in r4.
|
||||
*/
|
||||
blr /* Branch to the address held in link register. */
|
||||
|
||||
|
||||
.align 2
|
||||
.globl _ThreadRoot
|
||||
_ThreadRoot:
|
||||
lwz r20, 16(r4) /* StartupPCState - ThreadBegin */
|
||||
lwz r21, 8(r4) /* InitialArgState - arg */
|
||||
lwz r22, 4(r4) /* InitialPCState - func */
|
||||
lwz r23, 12(r4) /* WhenDonePCState - ThreadFinish */
|
||||
|
||||
/* Call ThreadBegin function. */
|
||||
mtctr r20 /* The function pointer. */
|
||||
bctrl
|
||||
|
||||
/* Call the target function. */
|
||||
mr r3, r21 /* Function arg. */
|
||||
mtctr r22 /* Function pointer. */
|
||||
bctrl
|
||||
|
||||
/* Call the ThreadFinish function. */
|
||||
mtctr r23
|
||||
bctrl
|
||||
|
||||
/* We shouldn't execute here. */
|
||||
1: b 1b
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(PowerPC) && !defined(ApplePowerPC)
|
||||
.globl branch[ds]
|
||||
.csect branch[ds]
|
||||
.long .branch[PR]
|
||||
.long TOC[tc0]
|
||||
.long 0
|
||||
.toc
|
||||
T.branch: .tc .branch[tc], branch[ds]
|
||||
.globl .branch[PR]
|
||||
.csect .branch[PR]
|
||||
|
||||
l 0, 0x0(11) # load function address into r0
|
||||
mtctr 0 # move r0 into counter register
|
||||
l 2, 0x4(11) # move new TOC address into r2
|
||||
l 11, 0x8(11) # reset function address
|
||||
bctr # branch to the counter register
|
||||
|
||||
|
||||
.globl ThreadRoot[ds]
|
||||
.csect ThreadRoot[ds]
|
||||
.long .ThreadRoot[PR]
|
||||
.long TOC[tc0]
|
||||
.long 0
|
||||
.toc
|
||||
T.ThreadRoot: .tc .ThreadRoot[tc], ThreadRoot[ds]
|
||||
.globl .ThreadRoot[PR]
|
||||
.csect .ThreadRoot[PR]
|
||||
|
||||
.set argarea, 32
|
||||
.set linkarea, 24
|
||||
.set locstckarea, 0
|
||||
.set nfprs, 18
|
||||
.set ngprs, 19
|
||||
.set szdsa, 8*nfprs+4*ngprs+linkarea+argarea+locstckarea
|
||||
|
||||
|
||||
mflr 0
|
||||
mfcr 12
|
||||
bl ._savef14
|
||||
cror 0xf, 0xf, 0xf
|
||||
stm 13, -8*nfprs-4*ngprs(1)
|
||||
st 0, 8(1)
|
||||
st 12, 4(1)
|
||||
st 4, 24(1)
|
||||
st 5, 28(1)
|
||||
st 6, 32(1)
|
||||
stu 1, -szdsa(1)
|
||||
|
||||
muli 11,3,1 # copy contents of register r24 to r11
|
||||
bl .branch[PR] # call function branch
|
||||
cror 0xf, 0xf, 0xf # no operation
|
||||
|
||||
ai 1,1,szdsa
|
||||
lm 13, -8*nfprs-4*ngprs(1)
|
||||
bl ._restf14
|
||||
cror 0xf, 0xf, 0xf
|
||||
l 0, 8(1)
|
||||
l 12, 4(1)
|
||||
mtlr 0
|
||||
mtcrf 0x38, 12
|
||||
l 4, 24(1)
|
||||
l 5, 28(1)
|
||||
l 6, 32(1)
|
||||
|
||||
mflr 0
|
||||
mfcr 12
|
||||
bl ._savef14
|
||||
cror 0xf, 0xf, 0xf
|
||||
stm 13, -8*nfprs-4*ngprs(1)
|
||||
st 0, 8(1)
|
||||
st 12, 4(1)
|
||||
st 6, 24(1)
|
||||
stu 1, -szdsa(1)
|
||||
|
||||
muli 3, 4,1 # load user function parameter r22 to r3
|
||||
muli 11,5,1 # copy contents of register r21 to r11
|
||||
bl .branch[PR] # call function branch
|
||||
cror 0xf, 0xf, 0xf # no operation
|
||||
|
||||
ai 1,1,szdsa
|
||||
lm 13, -8*nfprs-4*ngprs(1)
|
||||
bl ._restf14
|
||||
cror 0xf, 0xf, 0xf
|
||||
l 0, 8(1)
|
||||
l 12, 4(1)
|
||||
mtlr 0
|
||||
mtcrf 0x38, 12
|
||||
l 6, 24(1)
|
||||
|
||||
muli 11,6,1 # copy contents of register r23 to r11
|
||||
bl .branch[PR] # call function branch
|
||||
cror 0xf, 0xf, 0xf # no operation
|
||||
brl # the programme should not return here.
|
||||
|
||||
.extern ._savef14
|
||||
.extern ._restf14
|
||||
|
||||
|
||||
|
||||
|
||||
.globl SWITCH[ds]
|
||||
.csect SWITCH[ds]
|
||||
.long .SWITCH[PR]
|
||||
.long TOC[tc0]
|
||||
.long 0
|
||||
.toc
|
||||
T.SWITCH: .tc .SWITCH[tc], SWITCH[ds]
|
||||
.globl .SWITCH[PR]
|
||||
.csect .SWITCH[PR]
|
||||
|
||||
st 1, 0(3) # store stack pointer
|
||||
stm 13, 20(3) # store general purpose registers (13 -31)
|
||||
stfd 14, 96(3) # store floating point registers (14 -31)
|
||||
stfd 15, 104(3) # there is no single instruction to do for
|
||||
stfd 16, 112(3) # floating point registers. so do one by one
|
||||
stfd 17, 120(3)
|
||||
stfd 18, 128(3)
|
||||
stfd 19, 136(3)
|
||||
stfd 20, 144(3)
|
||||
stfd 21, 152(3)
|
||||
stfd 22, 160(3)
|
||||
stfd 23, 168(3)
|
||||
stfd 24, 176(3)
|
||||
stfd 25, 184(3)
|
||||
stfd 26, 192(3)
|
||||
stfd 27, 200(3)
|
||||
stfd 28, 208(3)
|
||||
stfd 29, 216(3)
|
||||
stfd 30, 224(3)
|
||||
stfd 31, 232(3)
|
||||
mflr 0 # move link register value to register 0
|
||||
st 0, 244(3) # store link register value
|
||||
|
||||
mfcr 12 # move condition register to register 12
|
||||
st 12, 240(3) # store condition register value
|
||||
|
||||
|
||||
l 1, 0(4) # load stack pointer
|
||||
l 0, 244(4) # load link register value
|
||||
mtlr 0
|
||||
l 12, 240(4) # load condition register value
|
||||
mtcrf 0x38, 12
|
||||
|
||||
|
||||
|
||||
lm 13, 20(4) # load into general purpose registers (13 -31)
|
||||
lfd 14, 96(4) # load into floating point registers (14 -31)
|
||||
lfd 15, 104(4) # there is no single instruction for
|
||||
lfd 16, 112(4) # loading into more than one floating point
|
||||
lfd 17, 120(4) # registers. so do one by one.
|
||||
lfd 18, 128(4)
|
||||
lfd 19, 136(4)
|
||||
lfd 20, 144(4)
|
||||
lfd 21, 152(4)
|
||||
lfd 22, 160(4)
|
||||
lfd 23, 168(4)
|
||||
lfd 24, 176(4)
|
||||
lfd 25, 184(4)
|
||||
lfd 26, 192(4)
|
||||
lfd 27, 200(4)
|
||||
lfd 28, 208(4)
|
||||
lfd 29, 216(4)
|
||||
lfd 30, 224(4)
|
||||
lfd 31, 232(4)
|
||||
l 3, 16(4)
|
||||
l 5, 4(4)
|
||||
l 6, 12(4)
|
||||
l 4, 8(4)
|
||||
|
||||
brl # branch to the address held in link register.
|
||||
#endif // PowerPC
|
||||
|
||||
|
||||
#ifdef ALPHA
|
||||
|
||||
/*
|
||||
* Porting to Alpha was done by Shuichi Oikawa (shui@sfc.keio.ac.jp).
|
||||
*/
|
||||
|
||||
/*
|
||||
* Symbolic register names and register saving rules
|
||||
*
|
||||
* Legend:
|
||||
* T Saved by caller (Temporaries)
|
||||
* S Saved by callee (call-Safe registers)
|
||||
*/
|
||||
|
||||
#define v0 $0 /* (T) return value */
|
||||
#define t0 $1 /* (T) temporary registers */
|
||||
#define s0 $9 /* (S) call-safe registers */
|
||||
#define s1 $10
|
||||
#define s2 $11
|
||||
#define s3 $12
|
||||
#define s4 $13
|
||||
#define s5 $14
|
||||
#define s6 $15
|
||||
#define a0 $16 /* (T) argument registers */
|
||||
#define a1 $17
|
||||
#define ai $25 /* (T) argument information */
|
||||
#define ra $26 /* (T) return address */
|
||||
#define pv $27 /* (T) procedure value */
|
||||
#define gp $29 /* (T) (local) data pointer */
|
||||
#define sp $30 /* (S) stack pointer */
|
||||
#define zero $31 /* wired zero */
|
||||
|
||||
.set noreorder # unless overridden
|
||||
.align 3
|
||||
.text
|
||||
|
||||
.globl ThreadRoot
|
||||
.ent ThreadRoot,0
|
||||
ThreadRoot:
|
||||
.frame sp,0,ra
|
||||
ldgp gp,0(pv)
|
||||
|
||||
mov zero,s6 # Clearing the frame pointer here
|
||||
# makes gdb backtraces of thread stacks
|
||||
# end here (I hope!)
|
||||
mov StartupPC,pv
|
||||
jsr ra,(pv) # call startup procedure
|
||||
ldgp gp,0(ra)
|
||||
|
||||
mov InitialArg,a0
|
||||
mov InitialPC,pv
|
||||
jsr ra,(pv) # call main procedure
|
||||
ldgp gp,0(ra)
|
||||
|
||||
mov WhenDonePC,pv
|
||||
jsr ra,(pv) # when done, call clean up procedure
|
||||
ldgp gp,0(ra)
|
||||
|
||||
.end ThreadRoot # NEVER REACHED
|
||||
|
||||
/* a0 -- pointer to old Thread *
|
||||
* a1 -- pointer to new Thread */
|
||||
.globl SWITCH
|
||||
.ent SWITCH,0
|
||||
SWITCH:
|
||||
.frame sp,0,ra
|
||||
ldgp gp,0(pv)
|
||||
|
||||
stq ra, PC(a0) # save return address
|
||||
stq gp, GP(a0)
|
||||
stq sp, SP(a0) # save new stack pointer
|
||||
stq s0, S0(a0) # save all the callee-save registers
|
||||
stq s1, S1(a0)
|
||||
stq s2, S2(a0)
|
||||
stq s3, S3(a0)
|
||||
stq s4, S4(a0)
|
||||
stq s5, S5(a0)
|
||||
stq s6, S6(a0) # save frame pointer
|
||||
|
||||
ldq ra, PC(a1) # load the return address
|
||||
ldq gp, GP(a1)
|
||||
ldq sp, SP(a1) # load the new stack pointer
|
||||
ldq s0, S0(a1) # load the callee-save registers
|
||||
ldq s1, S1(a1)
|
||||
ldq s2, S2(a1)
|
||||
ldq s3, S3(a1)
|
||||
ldq s4, S4(a1)
|
||||
ldq s5, S5(a1)
|
||||
ldq s6, S6(a1)
|
||||
|
||||
mov ra,pv
|
||||
ret zero,(ra)
|
||||
|
||||
.end SWITCH
|
||||
|
||||
#endif // ALPHA
|
||||
268
code/threads/switch.h
Normal file
268
code/threads/switch.h
Normal file
@@ -0,0 +1,268 @@
|
||||
/* switch.h
|
||||
* Definitions needed for implementing context switching.
|
||||
*
|
||||
* Context switching is inherently machine dependent, since
|
||||
* the registers to be saved, how to set up an initial
|
||||
* call frame, etc, are all specific to a processor architecture.
|
||||
*
|
||||
* This file currently supports the DEC MIPS, DEC Alpha, SUN SPARC,
|
||||
* HP PARISC, IBM PowerPC, and Intel x86 architectures.
|
||||
*/
|
||||
|
||||
/*
|
||||
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 SWITCH_H
|
||||
#define SWITCH_H
|
||||
|
||||
#include "copyright.h"
|
||||
|
||||
#ifdef DECMIPS
|
||||
|
||||
/* Registers that must be saved during a context switch.
|
||||
* These are the offsets from the beginning of the Thread object,
|
||||
* in bytes, used in switch.s
|
||||
*/
|
||||
#define SP 0
|
||||
#define S0 4
|
||||
#define S1 8
|
||||
#define S2 12
|
||||
#define S3 16
|
||||
#define S4 20
|
||||
#define S5 24
|
||||
#define S6 28
|
||||
#define S7 32
|
||||
#define FP 36
|
||||
#define PC 40
|
||||
|
||||
/* To fork a thread, we set up its saved register state, so that
|
||||
* when we switch to the thread, it will start running in ThreadRoot.
|
||||
*
|
||||
* The following are the initial registers we need to set up to
|
||||
* pass values into ThreadRoot (for instance, containing the procedure
|
||||
* for the thread to run). The first set is the registers as used
|
||||
* by ThreadRoot; the second set is the locations for these initial
|
||||
* values in the Thread object -- used in Thread::AllocateStack().
|
||||
*/
|
||||
|
||||
#define InitialPC s0
|
||||
#define InitialArg s1
|
||||
#define WhenDonePC s2
|
||||
#define StartupPC s3
|
||||
|
||||
#define PCState (PC/4-1)
|
||||
#define FPState (FP/4-1)
|
||||
#define InitialPCState (S0/4-1)
|
||||
#define InitialArgState (S1/4-1)
|
||||
#define WhenDonePCState (S2/4-1)
|
||||
#define StartupPCState (S3/4-1)
|
||||
|
||||
#endif // DECMIPS
|
||||
|
||||
#ifdef SPARC
|
||||
|
||||
/* Registers that must be saved during a context switch. See comment above. */
|
||||
#define I0 4
|
||||
#define I1 8
|
||||
#define I2 12
|
||||
#define I3 16
|
||||
#define I4 20
|
||||
#define I5 24
|
||||
#define I6 28
|
||||
#define I7 32
|
||||
|
||||
/* Aliases used for clearing code. */
|
||||
#define FP I6
|
||||
#define PC I7
|
||||
|
||||
/* Registers for ThreadRoot. See comment above. */
|
||||
#define InitialPC %o0
|
||||
#define InitialArg %o1
|
||||
#define WhenDonePC %o2
|
||||
#define StartupPC %o3
|
||||
|
||||
#define PCState (PC/4-1)
|
||||
#define InitialPCState (I0/4-1)
|
||||
#define InitialArgState (I1/4-1)
|
||||
#define WhenDonePCState (I2/4-1)
|
||||
#define StartupPCState (I3/4-1)
|
||||
|
||||
#endif // SPARC
|
||||
|
||||
#ifdef PARISC
|
||||
|
||||
/* Registers that must be saved during a context switch. See comment above. */
|
||||
#define SP 0
|
||||
#define S0 4
|
||||
#define S1 8
|
||||
#define S2 12
|
||||
#define S3 16
|
||||
#define S4 20
|
||||
#define S5 24
|
||||
#define S6 28
|
||||
#define S7 32
|
||||
#define S8 36
|
||||
#define S9 40
|
||||
#define S10 44
|
||||
#define S11 48
|
||||
#define S12 52
|
||||
#define S13 56
|
||||
#define S14 60
|
||||
#define S15 64
|
||||
#define PC 68
|
||||
|
||||
/* Registers for ThreadRoot. See comment above. */
|
||||
#define InitialPC %r3 /* S0 */
|
||||
#define InitialArg %r4
|
||||
#define WhenDonePC %r5
|
||||
#define StartupPC %r6
|
||||
|
||||
#define PCState (PC/4-1)
|
||||
#define InitialPCState (S0/4-1)
|
||||
#define InitialArgState (S1/4-1)
|
||||
#define WhenDonePCState (S2/4-1)
|
||||
#define StartupPCState (S3/4-1)
|
||||
|
||||
#endif // PARISC
|
||||
|
||||
#ifdef x86
|
||||
|
||||
/* the offsets of the registers from the beginning of the thread object */
|
||||
#define _ESP 0
|
||||
#define _EAX 4
|
||||
#define _EBX 8
|
||||
#define _ECX 12
|
||||
#define _EDX 16
|
||||
#define _EBP 20
|
||||
#define _ESI 24
|
||||
#define _EDI 28
|
||||
#define _PC 32
|
||||
|
||||
/* These definitions are used in Thread::AllocateStack(). */
|
||||
#define PCState (_PC/4-1)
|
||||
#define FPState (_EBP/4-1)
|
||||
#define InitialPCState (_ESI/4-1)
|
||||
#define InitialArgState (_EDX/4-1)
|
||||
#define WhenDonePCState (_EDI/4-1)
|
||||
#define StartupPCState (_ECX/4-1)
|
||||
|
||||
#define InitialPC %esi
|
||||
#define InitialArg %edx
|
||||
#define WhenDonePC %edi
|
||||
#define StartupPC %ecx
|
||||
|
||||
#endif // x86
|
||||
|
||||
#ifdef PowerPC
|
||||
|
||||
#define SP 0 // stack pointer
|
||||
#define P1 4 // parameters
|
||||
#define P2 8
|
||||
#define P3 12
|
||||
#define P4 16
|
||||
#define GP13 20 // general purpose registers 13-31
|
||||
#define GP14 24
|
||||
#define GP15 28
|
||||
#define GP16 32
|
||||
#define GP17 36
|
||||
#define GP18 40
|
||||
#define GP19 44
|
||||
#define GP20 48
|
||||
#define GP21 52
|
||||
#define GP22 56
|
||||
#define GP23 60
|
||||
#define GP24 64
|
||||
#define GP25 68
|
||||
#define GP26 72
|
||||
#define GP27 76
|
||||
#define GP28 80
|
||||
#define GP29 84
|
||||
#define GP30 88
|
||||
#define GP31 92
|
||||
#define FP13 96 // floating point registers 14-31
|
||||
#define FP15 104
|
||||
#define FP16 112
|
||||
#define FP17 120
|
||||
#define FP18 128
|
||||
#define FP19 136
|
||||
#define FP20 144
|
||||
#define FP21 152
|
||||
#define FP22 160
|
||||
#define FP23 168
|
||||
#define FP24 176
|
||||
#define FP25 184
|
||||
#define FP26 192
|
||||
#define FP27 200
|
||||
#define FP28 208
|
||||
#define FP29 216
|
||||
#define FP30 224
|
||||
#define FP31 232
|
||||
#define CR 240 // control register
|
||||
#define LR 244 // link register
|
||||
#define TOC 248 // Table Of Contents
|
||||
|
||||
|
||||
// for ThreadRoot assembly function
|
||||
|
||||
#define InitialPCState 0 // (P1/4 - 1) // user function address
|
||||
#define InitialArgState 1 // (P2/4 - 1) // user function argument
|
||||
#define WhenDonePCState 2 // (P3/4 - 1) // clean up function addr
|
||||
#define StartupPCState 3 // (P4/4 - 1) // start up function addr
|
||||
#define PCState 60 // (LR/4 - 1) // ThreadRoot addr (first time).
|
||||
// Later PC addr when SWITCH
|
||||
// occured
|
||||
|
||||
#define InitialLR 21
|
||||
#define InitialArg 22
|
||||
#define WhenDoneLR 23
|
||||
#define StartupLR 24
|
||||
|
||||
#endif // PowerPC
|
||||
|
||||
#ifdef ALPHA
|
||||
|
||||
/*
|
||||
* Porting to Alpha was done by Shuichi Oikawa (shui@sfc.keio.ac.jp).
|
||||
*/
|
||||
/* Registers that must be saved during a context switch.
|
||||
* These are the offsets from the beginning of the Thread object,
|
||||
* in bytes, used in switch.s
|
||||
*/
|
||||
#define SP (0*8)
|
||||
#define S0 (1*8)
|
||||
#define S1 (2*8)
|
||||
#define S2 (3*8)
|
||||
#define S3 (4*8)
|
||||
#define S4 (5*8)
|
||||
#define S5 (6*8)
|
||||
#define S6 (7*8) /* used as FP (Frame Pointer) */
|
||||
#define GP (8*8)
|
||||
#define PC (9*8)
|
||||
|
||||
/* To fork a thread, we set up its saved register state, so that
|
||||
* when we switch to the thread, it will start running in ThreadRoot.
|
||||
*
|
||||
* The following are the initial registers we need to set up to
|
||||
* pass values into ThreadRoot (for instance, containing the procedure
|
||||
* for the thread to run). The first set is the registers as used
|
||||
* by ThreadRoot; the second set is the locations for these initial
|
||||
* values in the Thread object -- used in Thread::StackAllocate().
|
||||
*/
|
||||
#define InitialPC s0
|
||||
#define InitialArg s1
|
||||
#define WhenDonePC s2
|
||||
#define StartupPC s3
|
||||
|
||||
#define PCState (PC/8-1)
|
||||
#define FPState (S6/8-1)
|
||||
#define InitialPCState (S0/8-1)
|
||||
#define InitialArgState (S1/8-1)
|
||||
#define WhenDonePCState (S2/8-1)
|
||||
#define StartupPCState (S3/8-1)
|
||||
|
||||
#endif // HOST_ALPHA
|
||||
|
||||
#endif // SWITCH_H
|
||||
297
code/threads/synch.cc
Normal file
297
code/threads/synch.cc
Normal file
@@ -0,0 +1,297 @@
|
||||
// synch.cc
|
||||
// Routines for synchronizing threads. Three kinds of
|
||||
// synchronization routines are defined here: semaphores, locks
|
||||
// and condition variables.
|
||||
//
|
||||
// Any implementation of a synchronization routine needs some
|
||||
// primitive atomic operation. We assume Nachos is running on
|
||||
// a uniprocessor, and thus atomicity can be provided by
|
||||
// turning off interrupts. While interrupts are disabled, no
|
||||
// context switch can occur, and thus the current thread is guaranteed
|
||||
// to hold the CPU throughout, until interrupts are reenabled.
|
||||
//
|
||||
// Because some of these routines might be called with interrupts
|
||||
// already disabled (Semaphore::V for one), instead of turning
|
||||
// on interrupts at the end of the atomic operation, we always simply
|
||||
// re-set the interrupt state back to its original value (whether
|
||||
// that be disabled or enabled).
|
||||
//
|
||||
// Once we'e implemented one set of higher level atomic operations,
|
||||
// we can implement others using that implementation. We illustrate
|
||||
// this by implementing locks and condition variables on top of
|
||||
// semaphores, instead of directly enabling and disabling interrupts.
|
||||
//
|
||||
// Locks are implemented using a semaphore to keep track of
|
||||
// whether the lock is held or not -- a semaphore value of 0 means
|
||||
// the lock is busy; a semaphore value of 1 means the lock is free.
|
||||
//
|
||||
// The implementation of condition variables using semaphores is
|
||||
// a bit trickier, as explained below under Condition::Wait.
|
||||
//
|
||||
// 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 "synch.h"
|
||||
#include "main.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Semaphore::Semaphore
|
||||
// Initialize a semaphore, so that it can be used for synchronization.
|
||||
//
|
||||
// "debugName" is an arbitrary name, useful for debugging.
|
||||
// "initialValue" is the initial value of the semaphore.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Semaphore::Semaphore(char* debugName, int initialValue)
|
||||
{
|
||||
name = debugName;
|
||||
value = initialValue;
|
||||
queue = new List<Thread *>;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Semaphore::Semaphore
|
||||
// De-allocate semaphore, when no longer needed. Assume no one
|
||||
// is still waiting on the semaphore!
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Semaphore::~Semaphore()
|
||||
{
|
||||
delete queue;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Semaphore::P
|
||||
// Wait until semaphore value > 0, then decrement. Checking the
|
||||
// value and decrementing must be done atomically, so we
|
||||
// need to disable interrupts before checking the value.
|
||||
//
|
||||
// Note that Thread::Sleep assumes that interrupts are disabled
|
||||
// when it is called.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Semaphore::P()
|
||||
{
|
||||
Interrupt *interrupt = kernel->interrupt;
|
||||
Thread *currentThread = kernel->currentThread;
|
||||
|
||||
// disable interrupts
|
||||
IntStatus oldLevel = interrupt->SetLevel(IntOff);
|
||||
|
||||
while (value == 0) { // semaphore not available
|
||||
queue->Append(currentThread); // so go to sleep
|
||||
currentThread->Sleep(FALSE);
|
||||
}
|
||||
value--; // semaphore available, consume its value
|
||||
|
||||
// re-enable interrupts
|
||||
(void) interrupt->SetLevel(oldLevel);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Semaphore::V
|
||||
// Increment semaphore value, waking up a waiter if necessary.
|
||||
// As with P(), this operation must be atomic, so we need to disable
|
||||
// interrupts. Scheduler::ReadyToRun() assumes that interrupts
|
||||
// are disabled when it is called.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Semaphore::V()
|
||||
{
|
||||
Interrupt *interrupt = kernel->interrupt;
|
||||
|
||||
// disable interrupts
|
||||
IntStatus oldLevel = interrupt->SetLevel(IntOff);
|
||||
|
||||
if (!queue->IsEmpty()) { // make thread ready.
|
||||
kernel->scheduler->ReadyToRun(queue->RemoveFront());
|
||||
}
|
||||
value++;
|
||||
|
||||
// re-enable interrupts
|
||||
(void) interrupt->SetLevel(oldLevel);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Semaphore::SelfTest, SelfTestHelper
|
||||
// Test the semaphore implementation, by using a semaphore
|
||||
// to control two threads ping-ponging back and forth.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static Semaphore *ping;
|
||||
static void
|
||||
SelfTestHelper (Semaphore *pong)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ping->P();
|
||||
pong->V();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Semaphore::SelfTest()
|
||||
{
|
||||
Thread *helper = new Thread("ping", 1);
|
||||
|
||||
ASSERT(value == 0); // otherwise test won't work!
|
||||
ping = new Semaphore("ping", 0);
|
||||
helper->Fork((VoidFunctionPtr) SelfTestHelper, this);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ping->V();
|
||||
this->P();
|
||||
}
|
||||
delete ping;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Lock::Lock
|
||||
// Initialize a lock, so that it can be used for synchronization.
|
||||
// Initially, unlocked.
|
||||
//
|
||||
// "debugName" is an arbitrary name, useful for debugging.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Lock::Lock(char* debugName)
|
||||
{
|
||||
name = debugName;
|
||||
semaphore = new Semaphore("lock", 1); // initially, unlocked
|
||||
lockHolder = NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Lock::~Lock
|
||||
// Deallocate a lock
|
||||
//----------------------------------------------------------------------
|
||||
Lock::~Lock()
|
||||
{
|
||||
delete semaphore;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Lock::Acquire
|
||||
// Atomically wait until the lock is free, then set it to busy.
|
||||
// Equivalent to Semaphore::P(), with the semaphore value of 0
|
||||
// equal to busy, and semaphore value of 1 equal to free.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Lock::Acquire()
|
||||
{
|
||||
semaphore->P();
|
||||
lockHolder = kernel->currentThread;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Lock::Release
|
||||
// Atomically set lock to be free, waking up a thread waiting
|
||||
// for the lock, if any.
|
||||
// Equivalent to Semaphore::V(), with the semaphore value of 0
|
||||
// equal to busy, and semaphore value of 1 equal to free.
|
||||
//
|
||||
// By convention, only the thread that acquired the lock
|
||||
// may release it.
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
void Lock::Release()
|
||||
{
|
||||
ASSERT(IsHeldByCurrentThread());
|
||||
lockHolder = NULL;
|
||||
semaphore->V();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Condition::Condition
|
||||
// Initialize a condition variable, so that it can be
|
||||
// used for synchronization. Initially, no one is waiting
|
||||
// on the condition.
|
||||
//
|
||||
// "debugName" is an arbitrary name, useful for debugging.
|
||||
//----------------------------------------------------------------------
|
||||
Condition::Condition(char* debugName)
|
||||
{
|
||||
name = debugName;
|
||||
waitQueue = new List<Semaphore *>;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Condition::Condition
|
||||
// Deallocate the data structures implementing a condition variable.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Condition::~Condition()
|
||||
{
|
||||
delete waitQueue;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Condition::Wait
|
||||
// Atomically release monitor lock and go to sleep.
|
||||
// Our implementation uses semaphores to implement this, by
|
||||
// allocating a semaphore for each waiting thread. The signaller
|
||||
// will V() this semaphore, so there is no chance the waiter
|
||||
// will miss the signal, even though the lock is released before
|
||||
// calling P().
|
||||
//
|
||||
// Note: we assume Mesa-style semantics, which means that the
|
||||
// waiter must re-acquire the monitor lock when waking up.
|
||||
//
|
||||
// "conditionLock" -- lock protecting the use of this condition
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Condition::Wait(Lock* conditionLock)
|
||||
{
|
||||
Semaphore *waiter;
|
||||
|
||||
ASSERT(conditionLock->IsHeldByCurrentThread());
|
||||
|
||||
waiter = new Semaphore("condition", 0);
|
||||
waitQueue->Append(waiter);
|
||||
conditionLock->Release();
|
||||
waiter->P();
|
||||
conditionLock->Acquire();
|
||||
delete waiter;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Condition::Signal
|
||||
// Wake up a thread waiting on this condition, if any.
|
||||
//
|
||||
// Note: we assume Mesa-style semantics, which means that the
|
||||
// signaller doesn't give up control immediately to the thread
|
||||
// being woken up (unlike Hoare-style).
|
||||
//
|
||||
// Also note: we assume the caller holds the monitor lock
|
||||
// (unlike what is described in Birrell's paper). This allows
|
||||
// us to access waitQueue without disabling interrupts.
|
||||
//
|
||||
// "conditionLock" -- lock protecting the use of this condition
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Condition::Signal(Lock* conditionLock)
|
||||
{
|
||||
Semaphore *waiter;
|
||||
|
||||
ASSERT(conditionLock->IsHeldByCurrentThread());
|
||||
|
||||
if (!waitQueue->IsEmpty()) {
|
||||
waiter = waitQueue->RemoveFront();
|
||||
waiter->V();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Condition::Broadcast
|
||||
// Wake up all threads waiting on this condition, if any.
|
||||
//
|
||||
// "conditionLock" -- lock protecting the use of this condition
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void Condition::Broadcast(Lock* conditionLock)
|
||||
{
|
||||
while (!waitQueue->IsEmpty()) {
|
||||
Signal(conditionLock);
|
||||
}
|
||||
}
|
||||
144
code/threads/synch.h
Normal file
144
code/threads/synch.h
Normal file
@@ -0,0 +1,144 @@
|
||||
// synch.h
|
||||
// Data structures for synchronizing threads.
|
||||
//
|
||||
// Three kinds of synchronization are defined here: semaphores,
|
||||
// locks, and condition variables. The implementation for
|
||||
// semaphores is given; for the latter two, only the procedure
|
||||
// interface is given -- they are to be implemented as part of
|
||||
// the first assignment.
|
||||
//
|
||||
// Note that all the synchronization objects take a "name" as
|
||||
// part of the initialization. This is solely for debugging purposes.
|
||||
//
|
||||
// Copyright (c) 1992-1996 The Regents of the University of California.
|
||||
// All rights reserved. See copyright.h for copyright notice and limitation
|
||||
// synch.h -- synchronization primitives.
|
||||
|
||||
#ifndef SYNCH_H
|
||||
#define SYNCH_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "thread.h"
|
||||
#include "list.h"
|
||||
#include "main.h"
|
||||
|
||||
// The following class defines a "semaphore" whose value is a non-negative
|
||||
// integer. The semaphore has only two operations P() and V():
|
||||
//
|
||||
// P() -- waits until value > 0, then decrement
|
||||
//
|
||||
// V() -- increment, waking up a thread waiting in P() if necessary
|
||||
//
|
||||
// Note that the interface does *not* allow a thread to read the value of
|
||||
// the semaphore directly -- even if you did read the value, the
|
||||
// only thing you would know is what the value used to be. You don't
|
||||
// know what the value is now, because by the time you get the value
|
||||
// into a register, a context switch might have occurred,
|
||||
// and some other thread might have called P or V, so the true value might
|
||||
// now be different.
|
||||
|
||||
class Semaphore {
|
||||
public:
|
||||
Semaphore(char* debugName, int initialValue); // set initial value
|
||||
~Semaphore(); // de-allocate semaphore
|
||||
char* getName() { return name;} // debugging assist
|
||||
|
||||
void P(); // these are the only operations on a semaphore
|
||||
void V(); // they are both *atomic*
|
||||
void SelfTest(); // test routine for semaphore implementation
|
||||
|
||||
private:
|
||||
char* name; // useful for debugging
|
||||
int value; // semaphore value, always >= 0
|
||||
List<Thread *> *queue;
|
||||
// threads waiting in P() for the value to be > 0
|
||||
};
|
||||
|
||||
// The following class defines a "lock". A lock can be BUSY or FREE.
|
||||
// There are only two operations allowed on a lock:
|
||||
//
|
||||
// Acquire -- wait until the lock is FREE, then set it to BUSY
|
||||
//
|
||||
// Release -- set lock to be FREE, waking up a thread waiting
|
||||
// in Acquire if necessary
|
||||
//
|
||||
// In addition, by convention, only the thread that acquired the lock
|
||||
// may release it. As with semaphores, you can't read the lock value
|
||||
// (because the value might change immediately after you read it).
|
||||
|
||||
class Lock {
|
||||
public:
|
||||
Lock(char* debugName); // initialize lock to be FREE
|
||||
~Lock(); // deallocate lock
|
||||
char* getName() { return name; } // debugging assist
|
||||
|
||||
void Acquire(); // these are the only operations on a lock
|
||||
void Release(); // they are both *atomic*
|
||||
|
||||
bool IsHeldByCurrentThread() {
|
||||
return lockHolder == kernel->currentThread; }
|
||||
// return true if the current thread
|
||||
// holds this lock.
|
||||
|
||||
// Note: SelfTest routine provided by SynchList
|
||||
|
||||
private:
|
||||
char *name; // debugging assist
|
||||
Thread *lockHolder; // thread currently holding lock
|
||||
Semaphore *semaphore; // we use a semaphore to implement lock
|
||||
};
|
||||
|
||||
// The following class defines a "condition variable". A condition
|
||||
// variable does not have a value, but threads may be queued, waiting
|
||||
// on the variable. These are only operations on a condition variable:
|
||||
//
|
||||
// Wait() -- release the lock, relinquish the CPU until signaled,
|
||||
// then re-acquire the lock
|
||||
//
|
||||
// Signal() -- wake up a thread, if there are any waiting on
|
||||
// the condition
|
||||
//
|
||||
// Broadcast() -- wake up all threads waiting on the condition
|
||||
//
|
||||
// All operations on a condition variable must be made while
|
||||
// the current thread has acquired a lock. Indeed, all accesses
|
||||
// to a given condition variable must be protected by the same lock.
|
||||
// In other words, mutual exclusion must be enforced among threads calling
|
||||
// the condition variable operations.
|
||||
//
|
||||
// In Nachos, condition variables are assumed to obey *Mesa*-style
|
||||
// semantics. When a Signal or Broadcast wakes up another thread,
|
||||
// it simply puts the thread on the ready list, and it is the responsibility
|
||||
// of the woken thread to re-acquire the lock (this re-acquire is
|
||||
// taken care of within Wait()). By contrast, some define condition
|
||||
// variables according to *Hoare*-style semantics -- where the signalling
|
||||
// thread gives up control over the lock and the CPU to the woken thread,
|
||||
// which runs immediately and gives back control over the lock to the
|
||||
// signaller when the woken thread leaves the critical section.
|
||||
//
|
||||
// The consequence of using Mesa-style semantics is that some other thread
|
||||
// can acquire the lock, and change data structures, before the woken
|
||||
// thread gets a chance to run. The advantage to Mesa-style semantics
|
||||
// is that it is a lot easier to implement than Hoare-style.
|
||||
|
||||
class Condition {
|
||||
public:
|
||||
Condition(char* debugName); // initialize condition to
|
||||
// "no one waiting"
|
||||
~Condition(); // deallocate the condition
|
||||
char* getName() { return (name); }
|
||||
|
||||
void Wait(Lock *conditionLock); // these are the 3 operations on
|
||||
// condition variables; releasing the
|
||||
// lock and going to sleep are
|
||||
// *atomic* in Wait()
|
||||
void Signal(Lock *conditionLock); // conditionLock must be held by
|
||||
void Broadcast(Lock *conditionLock);// the currentThread for all of
|
||||
// these operations
|
||||
// SelfTest routine provided by SyncLists
|
||||
|
||||
private:
|
||||
char* name;
|
||||
List<Semaphore *> *waitQueue; // list of waiting threads
|
||||
};
|
||||
#endif // SYNCH_H
|
||||
130
code/threads/synchlist.cc
Normal file
130
code/threads/synchlist.cc
Normal file
@@ -0,0 +1,130 @@
|
||||
// synchlist.cc
|
||||
// Routines for synchronized access to a list.
|
||||
//
|
||||
// Implemented in "monitor"-style -- surround each procedure with a
|
||||
// lock acquire and release pair, using condition signal and wait for
|
||||
// synchronization.
|
||||
//
|
||||
// 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 "synchlist.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::SynchList
|
||||
// Allocate and initialize the data structures needed for a
|
||||
// synchronized list, empty to start with.
|
||||
// Elements can now be added to the list.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
SynchList<T>::SynchList()
|
||||
{
|
||||
list = new List<T>;
|
||||
lock = new Lock("list lock");
|
||||
listEmpty = new Condition("list empty cond");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::~SynchList
|
||||
// De-allocate the data structures created for synchronizing a list.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
SynchList<T>::~SynchList()
|
||||
{
|
||||
delete listEmpty;
|
||||
delete lock;
|
||||
delete list;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::Append
|
||||
// Append an "item" to the end of the list. Wake up anyone
|
||||
// waiting for an element to be appended.
|
||||
//
|
||||
// "item" is the thing to put on the list.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
void
|
||||
SynchList<T>::Append(T item)
|
||||
{
|
||||
lock->Acquire(); // enforce mutual exclusive access to the list
|
||||
list->Append(item);
|
||||
listEmpty->Signal(lock); // wake up a waiter, if any
|
||||
lock->Release();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::RemoveFront
|
||||
// Remove an "item" from the beginning of the list. Wait if
|
||||
// the list is empty.
|
||||
// Returns:
|
||||
// The removed item.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
T
|
||||
SynchList<T>::RemoveFront()
|
||||
{
|
||||
T item;
|
||||
|
||||
lock->Acquire(); // enforce mutual exclusion
|
||||
while (list->IsEmpty())
|
||||
listEmpty->Wait(lock); // wait until list isn't empty
|
||||
item = list->RemoveFront();
|
||||
lock->Release();
|
||||
return item;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::Apply
|
||||
// Apply function to every item on a list.
|
||||
//
|
||||
// "func" -- the function to apply
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
void
|
||||
SynchList<T>::Apply(void (*func)(T))
|
||||
{
|
||||
lock->Acquire(); // enforce mutual exclusion
|
||||
list->Apply(func);
|
||||
lock->Release();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SynchList<T>::SelfTest, SelfTestHelper
|
||||
// Test whether the SynchList implementation is working,
|
||||
// by having two threads ping-pong a value between them
|
||||
// using two synchronized lists.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
template <class T>
|
||||
void
|
||||
SynchList<T>::SelfTestHelper (void* data)
|
||||
{
|
||||
SynchList<T>* _this = (SynchList<T>*)data;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
_this->Append(_this->selfTestPing->RemoveFront());
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void
|
||||
SynchList<T>::SelfTest(T val)
|
||||
{
|
||||
Thread *helper = new Thread("ping", 1);
|
||||
|
||||
ASSERT(list->IsEmpty());
|
||||
selfTestPing = new SynchList<T>;
|
||||
helper->Fork(SynchList<T>::SelfTestHelper, this);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
selfTestPing->Append(val);
|
||||
ASSERT(val == this->RemoveFront());
|
||||
}
|
||||
delete selfTestPing;
|
||||
}
|
||||
51
code/threads/synchlist.h
Normal file
51
code/threads/synchlist.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// synchlist.h
|
||||
// Data structures for synchronized access to a list.
|
||||
//
|
||||
// Identical interface to List, except accesses are synchronized.
|
||||
//
|
||||
// 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 SYNCHLIST_H
|
||||
#define SYNCHLIST_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "list.h"
|
||||
#include "synch.h"
|
||||
|
||||
// The following class defines a "synchronized list" -- a list for which
|
||||
// these constraints hold:
|
||||
// 1. Threads trying to remove an item from a list will
|
||||
// wait until the list has an element on it.
|
||||
// 2. One thread at a time can access list data structures
|
||||
|
||||
template <class T>
|
||||
class SynchList {
|
||||
public:
|
||||
SynchList(); // initialize a synchronized list
|
||||
~SynchList(); // de-allocate a synchronized list
|
||||
|
||||
void Append(T item); // append item to the end of the list,
|
||||
// and wake up any thread waiting in remove
|
||||
|
||||
T RemoveFront(); // remove the first item from the front of
|
||||
// the list, waiting if the list is empty
|
||||
|
||||
void Apply(void (*f)(T)); // apply function to all elements in list
|
||||
|
||||
void SelfTest(T value); // test the SynchList implementation
|
||||
|
||||
private:
|
||||
List<T> *list; // the list of things
|
||||
Lock *lock; // enforce mutual exclusive access to the list
|
||||
Condition *listEmpty; // wait in Remove if the list is empty
|
||||
|
||||
// these are only to assist SelfTest()
|
||||
SynchList<T> *selfTestPing;
|
||||
static void SelfTestHelper(void* data);
|
||||
};
|
||||
|
||||
#include "synchlist.cc"
|
||||
|
||||
#endif // SYNCHLIST_H
|
||||
435
code/threads/thread.cc
Normal file
435
code/threads/thread.cc
Normal file
@@ -0,0 +1,435 @@
|
||||
// thread.cc
|
||||
// Routines to manage threads. These are the main operations:
|
||||
//
|
||||
// Fork -- create a thread to run a procedure concurrently
|
||||
// with the caller (this is done in two steps -- first
|
||||
// allocate the Thread object, then call Fork on it)
|
||||
// Begin -- called when the forked procedure starts up, to turn
|
||||
// interrupts on and clean up after last thread
|
||||
// Finish -- called when the forked procedure finishes, to clean up
|
||||
// Yield -- relinquish control over the CPU to another ready thread
|
||||
// Sleep -- relinquish control over the CPU, but thread is now blocked.
|
||||
// In other words, it will not run again, until explicitly
|
||||
// put back on the ready queue.
|
||||
//
|
||||
// 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 "thread.h"
|
||||
#include "switch.h"
|
||||
#include "synch.h"
|
||||
#include "sysdep.h"
|
||||
|
||||
// this is put at the top of the execution stack, for detecting stack overflows
|
||||
const int STACK_FENCEPOST = 0xdedbeef;
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Thread
|
||||
// Initialize a thread control block, so that we can then call
|
||||
// Thread::Fork.
|
||||
//
|
||||
// "threadName" is an arbitrary string, useful for debugging.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Thread::Thread(char* threadName, int threadID)
|
||||
{
|
||||
ID = threadID;
|
||||
name = threadName;
|
||||
stackTop = NULL;
|
||||
stack = NULL;
|
||||
status = JUST_CREATED;
|
||||
for (int i = 0; i < MachineStateSize; i++) {
|
||||
machineState[i] = NULL; // not strictly necessary, since
|
||||
// new thread ignores contents
|
||||
// of machine registers
|
||||
}
|
||||
space = NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::~Thread
|
||||
// De-allocate a thread.
|
||||
//
|
||||
// NOTE: the current thread *cannot* delete itself directly,
|
||||
// since it is still running on the stack that we need to delete.
|
||||
//
|
||||
// NOTE: if this is the main thread, we can't delete the stack
|
||||
// because we didn't allocate it -- we got it automatically
|
||||
// as part of starting up Nachos.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
Thread::~Thread()
|
||||
{
|
||||
DEBUG(dbgThread, "Deleting thread: " << name);
|
||||
ASSERT(this != kernel->currentThread);
|
||||
if (stack != NULL)
|
||||
DeallocBoundedArray((char *) stack, StackSize * sizeof(int));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Fork
|
||||
// Invoke (*func)(arg), allowing caller and callee to execute
|
||||
// concurrently.
|
||||
//
|
||||
// NOTE: although our definition allows only a single argument
|
||||
// to be passed to the procedure, it is possible to pass multiple
|
||||
// arguments by making them fields of a structure, and passing a pointer
|
||||
// to the structure as "arg".
|
||||
//
|
||||
// Implemented as the following steps:
|
||||
// 1. Allocate a stack
|
||||
// 2. Initialize the stack so that a call to SWITCH will
|
||||
// cause it to run the procedure
|
||||
// 3. Put the thread on the ready queue
|
||||
//
|
||||
// "func" is the procedure to run concurrently.
|
||||
// "arg" is a single argument to be passed to the procedure.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::Fork(VoidFunctionPtr func, void *arg)
|
||||
{
|
||||
Interrupt *interrupt = kernel->interrupt;
|
||||
Scheduler *scheduler = kernel->scheduler;
|
||||
IntStatus oldLevel;
|
||||
|
||||
DEBUG(dbgThread, "Forking thread: " << name << " f(a): " << (int) func << " " << arg);
|
||||
StackAllocate(func, arg);
|
||||
|
||||
oldLevel = interrupt->SetLevel(IntOff);
|
||||
scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts
|
||||
// are disabled!
|
||||
(void) interrupt->SetLevel(oldLevel);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::CheckOverflow
|
||||
// Check a thread's stack to see if it has overrun the space
|
||||
// that has been allocated for it. If we had a smarter compiler,
|
||||
// we wouldn't need to worry about this, but we don't.
|
||||
//
|
||||
// NOTE: Nachos will not catch all stack overflow conditions.
|
||||
// In other words, your program may still crash because of an overflow.
|
||||
//
|
||||
// If you get bizarre results (such as seg faults where there is no code)
|
||||
// then you *may* need to increase the stack size. You can avoid stack
|
||||
// overflows by not putting large data structures on the stack.
|
||||
// Don't do this: void foo() { int bigArray[10000]; ... }
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::CheckOverflow()
|
||||
{
|
||||
if (stack != NULL) {
|
||||
#ifdef HPUX // Stacks grow upward on the Snakes
|
||||
ASSERT(stack[StackSize - 1] == STACK_FENCEPOST);
|
||||
#else
|
||||
ASSERT(*stack == STACK_FENCEPOST);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Begin
|
||||
// Called by ThreadRoot when a thread is about to begin
|
||||
// executing the forked procedure.
|
||||
//
|
||||
// It's main responsibilities are:
|
||||
// 1. deallocate the previously running thread if it finished
|
||||
// (see Thread::Finish())
|
||||
// 2. enable interrupts (so we can get time-sliced)
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::Begin ()
|
||||
{
|
||||
ASSERT(this == kernel->currentThread);
|
||||
DEBUG(dbgThread, "Beginning thread: " << name);
|
||||
|
||||
kernel->scheduler->CheckToBeDestroyed();
|
||||
kernel->interrupt->Enable();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Finish
|
||||
// Called by ThreadRoot when a thread is done executing the
|
||||
// forked procedure.
|
||||
//
|
||||
// NOTE: we can't immediately de-allocate the thread data structure
|
||||
// or the execution stack, because we're still running in the thread
|
||||
// and we're still on the stack! Instead, we tell the scheduler
|
||||
// to call the destructor, once it is running in the context of a different thread.
|
||||
//
|
||||
// NOTE: we disable interrupts, because Sleep() assumes interrupts
|
||||
// are disabled.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
//
|
||||
void
|
||||
Thread::Finish ()
|
||||
{
|
||||
(void) kernel->interrupt->SetLevel(IntOff);
|
||||
ASSERT(this == kernel->currentThread);
|
||||
|
||||
DEBUG(dbgThread, "Finishing thread: " << name);
|
||||
Sleep(TRUE); // invokes SWITCH
|
||||
// not reached
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Yield
|
||||
// Relinquish the CPU if any other thread is ready to run.
|
||||
// If so, put the thread on the end of the ready list, so that
|
||||
// it will eventually be re-scheduled.
|
||||
//
|
||||
// NOTE: returns immediately if no other thread on the ready queue.
|
||||
// Otherwise returns when the thread eventually works its way
|
||||
// to the front of the ready list and gets re-scheduled.
|
||||
//
|
||||
// NOTE: we disable interrupts, so that looking at the thread
|
||||
// on the front of the ready list, and switching to it, can be done
|
||||
// atomically. On return, we re-set the interrupt level to its
|
||||
// original state, in case we are called with interrupts disabled.
|
||||
//
|
||||
// Similar to Thread::Sleep(), but a little different.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::Yield ()
|
||||
{
|
||||
Thread *nextThread;
|
||||
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
|
||||
|
||||
ASSERT(this == kernel->currentThread);
|
||||
|
||||
DEBUG(dbgThread, "Yielding thread: " << name);
|
||||
|
||||
nextThread = kernel->scheduler->FindNextToRun();
|
||||
if (nextThread != NULL) {
|
||||
kernel->scheduler->ReadyToRun(this);
|
||||
kernel->scheduler->Run(nextThread, FALSE);
|
||||
}
|
||||
(void) kernel->interrupt->SetLevel(oldLevel);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::Sleep
|
||||
// Relinquish the CPU, because the current thread has either
|
||||
// finished or is blocked waiting on a synchronization
|
||||
// variable (Semaphore, Lock, or Condition). In the latter case,
|
||||
// eventually some thread will wake this thread up, and put it
|
||||
// back on the ready queue, so that it can be re-scheduled.
|
||||
//
|
||||
// NOTE: if there are no threads on the ready queue, that means
|
||||
// we have no thread to run. "Interrupt::Idle" is called
|
||||
// to signify that we should idle the CPU until the next I/O interrupt
|
||||
// occurs (the only thing that could cause a thread to become
|
||||
// ready to run).
|
||||
//
|
||||
// NOTE: we assume interrupts are already disabled, because it
|
||||
// is called from the synchronization routines which must
|
||||
// disable interrupts for atomicity. We need interrupts off
|
||||
// so that there can't be a time slice between pulling the first thread
|
||||
// off the ready list, and switching to it.
|
||||
//----------------------------------------------------------------------
|
||||
void
|
||||
Thread::Sleep (bool finishing)
|
||||
{
|
||||
Thread *nextThread;
|
||||
|
||||
ASSERT(this == kernel->currentThread);
|
||||
ASSERT(kernel->interrupt->getLevel() == IntOff);
|
||||
|
||||
DEBUG(dbgThread, "Sleeping thread: " << name);
|
||||
|
||||
status = BLOCKED;
|
||||
//cout << "debug Thread::Sleep " << name << "wait for Idle\n";
|
||||
while ((nextThread = kernel->scheduler->FindNextToRun()) == NULL) {
|
||||
kernel->interrupt->Idle(); // no one to run, wait for an interrupt
|
||||
}
|
||||
// returns when it's time for us to run
|
||||
kernel->scheduler->Run(nextThread, finishing);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// ThreadBegin, ThreadFinish, ThreadPrint
|
||||
// Dummy functions because C++ does not (easily) allow pointers to member
|
||||
// functions. So we create a dummy C function
|
||||
// (which we can pass a pointer to), that then simply calls the
|
||||
// member function.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static void ThreadFinish() { kernel->currentThread->Finish(); }
|
||||
static void ThreadBegin() { kernel->currentThread->Begin(); }
|
||||
void ThreadPrint(Thread *t) { t->Print(); }
|
||||
|
||||
#ifdef PARISC
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// PLabelToAddr
|
||||
// On HPUX, function pointers don't always directly point to code,
|
||||
// so we need to do the conversion.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static void *
|
||||
PLabelToAddr(void *plabel)
|
||||
{
|
||||
int funcPtr = (int) plabel;
|
||||
|
||||
if (funcPtr & 0x02) {
|
||||
// L-Field is set. This is a PLT pointer
|
||||
funcPtr -= 2; // Get rid of the L bit
|
||||
return (*(void **)funcPtr);
|
||||
} else {
|
||||
// L-field not set.
|
||||
return plabel;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::StackAllocate
|
||||
// Allocate and initialize an execution stack. The stack is
|
||||
// initialized with an initial stack frame for ThreadRoot, which:
|
||||
// enables interrupts
|
||||
// calls (*func)(arg)
|
||||
// calls Thread::Finish
|
||||
//
|
||||
// "func" is the procedure to be forked
|
||||
// "arg" is the parameter to be passed to the procedure
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::StackAllocate (VoidFunctionPtr func, void *arg)
|
||||
{
|
||||
stack = (int *) AllocBoundedArray(StackSize * sizeof(int));
|
||||
|
||||
#ifdef PARISC
|
||||
// HP stack works from low addresses to high addresses
|
||||
// everyone else works the other way: from high addresses to low addresses
|
||||
stackTop = stack + 16; // HP requires 64-byte frame marker
|
||||
stack[StackSize - 1] = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
#ifdef SPARC
|
||||
stackTop = stack + StackSize - 96; // SPARC stack must contains at
|
||||
// least 1 activation record
|
||||
// to start with.
|
||||
*stack = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
#ifdef PowerPC // RS6000
|
||||
stackTop = stack + StackSize - 16; // RS6000 requires 64-byte frame marker
|
||||
*stack = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
#ifdef DECMIPS
|
||||
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
|
||||
*stack = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
#ifdef ALPHA
|
||||
stackTop = stack + StackSize - 8; // -8 to be on the safe side!
|
||||
*stack = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef x86
|
||||
// the x86 passes the return address on the stack. In order for SWITCH()
|
||||
// to go to ThreadRoot when we switch to this thread, the return addres
|
||||
// used in SWITCH() must be the starting address of ThreadRoot.
|
||||
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
|
||||
*(--stackTop) = (int) ThreadRoot;
|
||||
*stack = STACK_FENCEPOST;
|
||||
#endif
|
||||
|
||||
#ifdef PARISC
|
||||
machineState[PCState] = PLabelToAddr(ThreadRoot);
|
||||
machineState[StartupPCState] = PLabelToAddr(ThreadBegin);
|
||||
machineState[InitialPCState] = PLabelToAddr(func);
|
||||
machineState[InitialArgState] = arg;
|
||||
machineState[WhenDonePCState] = PLabelToAddr(ThreadFinish);
|
||||
#else
|
||||
machineState[PCState] = (void*)ThreadRoot;
|
||||
machineState[StartupPCState] = (void*)ThreadBegin;
|
||||
machineState[InitialPCState] = (void*)func;
|
||||
machineState[InitialArgState] = (void*)arg;
|
||||
machineState[WhenDonePCState] = (void*)ThreadFinish;
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "machine.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::SaveUserState
|
||||
// Save the CPU state of a user program on a context switch.
|
||||
//
|
||||
// Note that a user program thread has *two* sets of CPU registers --
|
||||
// one for its state while executing user code, one for its state
|
||||
// while executing kernel code. This routine saves the former.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::SaveUserState()
|
||||
{
|
||||
for (int i = 0; i < NumTotalRegs; i++)
|
||||
userRegisters[i] = kernel->machine->ReadRegister(i);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::RestoreUserState
|
||||
// Restore the CPU state of a user program on a context switch.
|
||||
//
|
||||
// Note that a user program thread has *two* sets of CPU registers --
|
||||
// one for its state while executing user code, one for its state
|
||||
// while executing kernel code. This routine restores the former.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::RestoreUserState()
|
||||
{
|
||||
for (int i = 0; i < NumTotalRegs; i++)
|
||||
kernel->machine->WriteRegister(i, userRegisters[i]);
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// SimpleThread
|
||||
// Loop 5 times, yielding the CPU to another ready thread
|
||||
// each iteration.
|
||||
//
|
||||
// "which" is simply a number identifying the thread, for debugging
|
||||
// purposes.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
SimpleThread(int which)
|
||||
{
|
||||
int num;
|
||||
|
||||
for (num = 0; num < 5; num++) {
|
||||
cout << "*** thread " << which << " looped " << num << " times\n";
|
||||
kernel->currentThread->Yield();
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Thread::SelfTest
|
||||
// Set up a ping-pong between two threads, by forking a thread
|
||||
// to call SimpleThread, and then calling SimpleThread ourselves.
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void
|
||||
Thread::SelfTest()
|
||||
{
|
||||
DEBUG(dbgThread, "Entering Thread::SelfTest");
|
||||
|
||||
Thread *t = new Thread("forked thread", 1);
|
||||
|
||||
t->Fork((VoidFunctionPtr) SimpleThread, (void *) 1);
|
||||
kernel->currentThread->Yield();
|
||||
SimpleThread(0);
|
||||
}
|
||||
152
code/threads/thread.h
Normal file
152
code/threads/thread.h
Normal file
@@ -0,0 +1,152 @@
|
||||
// thread.h
|
||||
// Data structures for managing threads. A thread represents
|
||||
// sequential execution of code within a program.
|
||||
// So the state of a thread includes the program counter,
|
||||
// the processor registers, and the execution stack.
|
||||
//
|
||||
// Note that because we allocate a fixed size stack for each
|
||||
// thread, it is possible to overflow the stack -- for instance,
|
||||
// by recursing to too deep a level. The most common reason
|
||||
// for this occuring is allocating large data structures
|
||||
// on the stack. For instance, this will cause problems:
|
||||
//
|
||||
// void foo() { int buf[1000]; ...}
|
||||
//
|
||||
// Instead, you should allocate all data structures dynamically:
|
||||
//
|
||||
// void foo() { int *buf = new int[1000]; ...}
|
||||
//
|
||||
//
|
||||
// Bad things happen if you overflow the stack, and in the worst
|
||||
// case, the problem may not be caught explicitly. Instead,
|
||||
// the only symptom may be bizarre segmentation faults. (Of course,
|
||||
// other problems can cause seg faults, so that isn't a sure sign
|
||||
// that your thread stacks are too small.)
|
||||
//
|
||||
// One thing to try if you find yourself with seg faults is to
|
||||
// increase the size of thread stack -- ThreadStackSize.
|
||||
//
|
||||
// In this interface, forking a thread takes two steps.
|
||||
// We must first allocate a data structure for it: "t = new Thread".
|
||||
// Only then can we do the fork: "t->fork(f, arg)".
|
||||
//
|
||||
// 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 THREAD_H
|
||||
#define THREAD_H
|
||||
|
||||
#include "copyright.h"
|
||||
#include "utility.h"
|
||||
#include "sysdep.h"
|
||||
#include "machine.h"
|
||||
#include "addrspace.h"
|
||||
|
||||
// CPU register state to be saved on context switch.
|
||||
// The x86 needs to save only a few registers,
|
||||
// SPARC and MIPS needs to save 10 registers,
|
||||
// the Snake needs 18,
|
||||
// and the RS6000 needs to save 75 (!)
|
||||
// For simplicity, I just take the maximum over all architectures.
|
||||
|
||||
#define MachineStateSize 75
|
||||
|
||||
|
||||
// Size of the thread's private execution stack.
|
||||
// WATCH OUT IF THIS ISN'T BIG ENOUGH!!!!!
|
||||
const int StackSize = (8 * 1024); // in words
|
||||
|
||||
|
||||
// Thread state
|
||||
enum ThreadStatus { JUST_CREATED, RUNNING, READY, BLOCKED, ZOMBIE };
|
||||
|
||||
|
||||
// The following class defines a "thread control block" -- which
|
||||
// represents a single thread of execution.
|
||||
//
|
||||
// Every thread has:
|
||||
// an execution stack for activation records ("stackTop" and "stack")
|
||||
// space to save CPU registers while not running ("machineState")
|
||||
// a "status" (running/ready/blocked)
|
||||
//
|
||||
// Some threads also belong to a user address space; threads
|
||||
// that only run in the kernel have a NULL address space.
|
||||
|
||||
class Thread {
|
||||
private:
|
||||
// NOTE: DO NOT CHANGE the order of these first two members.
|
||||
// THEY MUST be in this position for SWITCH to work.
|
||||
int *stackTop; // the current stack pointer
|
||||
void *machineState[MachineStateSize]; // all registers except for stackTop
|
||||
|
||||
public:
|
||||
Thread(char* debugName, int threadID); // initialize a Thread
|
||||
~Thread(); // deallocate a Thread
|
||||
// NOTE -- thread being deleted
|
||||
// must not be running when delete
|
||||
// is called
|
||||
|
||||
// basic thread operations
|
||||
|
||||
void Fork(VoidFunctionPtr func, void *arg);
|
||||
// Make thread run (*func)(arg)
|
||||
void Yield(); // Relinquish the CPU if any
|
||||
// other thread is runnable
|
||||
void Sleep(bool finishing); // Put the thread to sleep and
|
||||
// relinquish the processor
|
||||
void Begin(); // Startup code for the thread
|
||||
void Finish(); // The thread is done executing
|
||||
|
||||
void CheckOverflow(); // Check if thread stack has overflowed
|
||||
void setStatus(ThreadStatus st) { status = st; }
|
||||
ThreadStatus getStatus() { return (status); }
|
||||
char* getName() { return (name); }
|
||||
|
||||
int getID() { return (ID); }
|
||||
void Print() { cout << name; }
|
||||
void SelfTest(); // test whether thread impl is working
|
||||
|
||||
private:
|
||||
// some of the private data for this class is listed above
|
||||
|
||||
int *stack; // Bottom of the stack
|
||||
// NULL if this is the main thread
|
||||
// (If NULL, don't deallocate stack)
|
||||
ThreadStatus status; // ready, running or blocked
|
||||
char* name;
|
||||
int ID;
|
||||
void StackAllocate(VoidFunctionPtr func, void *arg);
|
||||
// Allocate a stack for thread.
|
||||
// Used internally by Fork()
|
||||
|
||||
// A thread running a user program actually has *two* sets of CPU registers --
|
||||
// one for its state while executing user code, one for its state
|
||||
// while executing kernel code.
|
||||
|
||||
int userRegisters[NumTotalRegs]; // user-level CPU register state
|
||||
|
||||
public:
|
||||
void SaveUserState(); // save user-level register state
|
||||
void RestoreUserState(); // restore user-level register state
|
||||
|
||||
AddrSpace *space; // User code this thread is running.
|
||||
};
|
||||
|
||||
// external function, dummy routine whose sole job is to call Thread::Print
|
||||
extern void ThreadPrint(Thread *thread);
|
||||
|
||||
// Magical machine-dependent routines, defined in switch.s
|
||||
|
||||
extern "C" {
|
||||
// First frame on thread execution stack;
|
||||
// call ThreadBegin
|
||||
// call "func"
|
||||
// (when func returns, if ever) call ThreadFinish()
|
||||
void ThreadRoot();
|
||||
|
||||
// Stop running oldThread and start running newThread
|
||||
void SWITCH(Thread *oldThread, Thread *newThread);
|
||||
}
|
||||
|
||||
#endif // THREAD_H
|
||||
Reference in New Issue
Block a user