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

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

55
code/threads/alarm.cc Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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