Initial version

This commit is contained in:
Yorick Barbanneau 2021-10-11 22:27:00 +02:00
commit 6f405265a5
102 changed files with 14486 additions and 0 deletions

17
code/threads/Makefile Normal file
View file

@ -0,0 +1,17 @@
# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make".
#
# Makefile for the threads assignment. The threads assignment must
# be done first!
#
# Copyright (c) 1992 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.
DEFINES = -DTHREADS
INCPATH = -I../threads -I../machine
C_OFILES = $(THREAD_O)
include ../Makefile.common
include ../Makefile.dep

14
code/threads/bool.h Normal file
View file

@ -0,0 +1,14 @@
// Defining TRUE and FALSE is usually a Bad Idea,
// because you will probably be inconsistent with anyone
// else who had the same clever idea.
// Therefore: DON'T USE THIS FILE.
#ifndef _bool_h
#define _bool_h 1
#include <stdbool.h>
#define TRUE true
#define FALSE false
#endif

25
code/threads/copyright.h Normal file
View file

@ -0,0 +1,25 @@
/*
Copyright (c) 1992-1993 The Regents of the University of California.
All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without written agreement is
hereby granted, provided that the above copyright notice and the following
two paragraphs appear in all copies of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*/
#ifdef MAIN /* include the copyright message in every executable */
static const char *copyright =
"Copyright (c) 1992-1993 The Regents of the University of California. All rights reserved.";
#endif // MAIN

332
code/threads/list.cc Normal file
View file

@ -0,0 +1,332 @@
// list.cc
//
// Routines to manage a singly-linked list of "things".
//
// A "ListElement" is allocated for each item to be put on the
// list; it is de-allocated when the item is removed. This means
// we don't need to keep a "next" pointer in every object we
// want to put on a list.
//
// NOTE: Mutual exclusion must be provided by the caller.
// If you want a synchronized list, you must use the routines
// in synchlist.cc.
//
// 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 "list.h"
//----------------------------------------------------------------------
// ListElement::ListElement
// Initialize a list element, so it can be added somewhere on a list.
//
// "itemPtr" is the item to be put on the list. It can be a pointer
// to anything.
// "sortKey" is the priority of the item, if any.
//----------------------------------------------------------------------
ListElement::ListElement (void *itemPtr, long long sortKey)
{
item = itemPtr;
key = sortKey;
next = NULL; // assume we'll put it at the end of the list
}
//----------------------------------------------------------------------
// List::List
// Initialize a list, empty to start with.
// Elements can now be added to the list.
//----------------------------------------------------------------------
List::List ()
{
first = last = NULL;
}
//----------------------------------------------------------------------
// List::~List
// Prepare a list for deallocation. If the list still contains any
// ListElements, de-allocate them. However, note that we do *not*
// de-allocate the "items" on the list -- this module allocates
// and de-allocates the ListElements to keep track of each item,
// but a given item may be on multiple lists, so we can't
// de-allocate them here.
//----------------------------------------------------------------------
List::~List ()
{
while (Remove () != NULL)
; // delete all the list elements
}
//----------------------------------------------------------------------
// List::Append
// Append an "item" to the end of the list.
//
// Allocate a ListElement to keep track of the item.
// If the list is empty, then this will be the only element.
// Otherwise, put it at the end.
//
// "item" is the thing to put on the list, it can be a pointer to
// anything.
//----------------------------------------------------------------------
void
List::Append (void *item)
{
ListElement *element = new ListElement (item, 0);
if (IsEmpty ())
{ // list is empty
first = element;
last = element;
}
else
{ // else put it after last
last->next = element;
last = element;
}
}
//----------------------------------------------------------------------
// List::Prepend
// Put an "item" on the front of the list.
//
// Allocate a ListElement to keep track of the item.
// If the list is empty, then this will be the only element.
// Otherwise, put it at the beginning.
//
// "item" is the thing to put on the list, it can be a pointer to
// anything.
//----------------------------------------------------------------------
void
List::Prepend (void *item)
{
ListElement *element = new ListElement (item, 0);
if (IsEmpty ())
{ // list is empty
first = element;
last = element;
}
else
{ // else put it before first
element->next = first;
first = element;
}
}
//----------------------------------------------------------------------
// List::FirstItem
// Read item off the front of the list
//
// Returns:
// Pointer to first item, NULL if nothing on the list.
//----------------------------------------------------------------------
ListElement *
List::FirstElement ()
{
return first;
}
//----------------------------------------------------------------------
// List::Remove
// Remove the first "item" from the front of the list.
//
// Returns:
// Pointer to removed item, NULL if nothing on the list.
//----------------------------------------------------------------------
void *
List::Remove ()
{
return SortedRemove (NULL); // Same as SortedRemove, but ignore the key
}
//----------------------------------------------------------------------
// List::Remove
// Remove "item" off the list.
//----------------------------------------------------------------------
void
List::Remove (void *item)
{
ListElement **cur;
ListElement *prec = NULL, *next;
for (cur = &first; *cur; prec=*cur, cur = &(*cur)->next)
{
if ((*cur)->item == item)
{
if(*cur==last)
last = prec;
next = (*cur)->next;
delete *cur;
*cur = next;
return;
}
}
ASSERT(FALSE);
}
//----------------------------------------------------------------------
// List::Length
// Return the length of the list.
//----------------------------------------------------------------------
int
List::Length (void)
{
ListElement *cur;
int n = 0;
for (cur = first; cur; cur = cur->next)
n++;
return n;
}
//----------------------------------------------------------------------
// List::Mapcar
// Apply a function to each item on the list, by walking through
// the list, one element at a time.
//
// Unlike LISP, this mapcar does not return anything!
//
// "func" is the procedure to apply to each element of the list.
//----------------------------------------------------------------------
void
List::Mapcar (VoidFunctionPtr func)
{
ListElement *next;
for (ListElement * ptr = first; ptr != NULL; ptr = next)
{
next = ptr->next;
DEBUG ('l', "In mapcar, about to invoke %p(%p)\n", func, ptr->item);
(*func) (ptr->item);
}
}
//----------------------------------------------------------------------
// List::Mapcar
// Similar to the former, but also passes an addition argument to the
// function.
//----------------------------------------------------------------------
void
List::Mapcar (VoidFunctionPtr2 func, void *arg)
{
ListElement *next;
for (ListElement * ptr = first; ptr != NULL; ptr = next)
{
next = ptr->next;
DEBUG ('l', "In mapcar, about to invoke %p(%p)\n", func, ptr->item);
(*func) (ptr->item, arg);
}
}
//----------------------------------------------------------------------
// List::IsEmpty
// Returns TRUE if the list is empty (has no items).
//----------------------------------------------------------------------
bool
List::IsEmpty ()
{
if (first == NULL)
return TRUE;
else
return FALSE;
}
//----------------------------------------------------------------------
// List::SortedInsert
// Insert an "item" into a list, so that the list elements are
// sorted in increasing order by "sortKey".
//
// Allocate a ListElement to keep track of the item.
// If the list is empty, then this will be the only element.
// Otherwise, walk through the list, one element at a time,
// to find where the new item should be placed.
//
// "item" is the thing to put on the list, it can be a pointer to
// anything.
// "sortKey" is the priority of the item.
//----------------------------------------------------------------------
void
List::SortedInsert (void *item, long long sortKey)
{
ListElement *element = new ListElement (item, sortKey);
ListElement *ptr; // keep track
if (IsEmpty ())
{ // if list is empty, put
first = element;
last = element;
}
else if (sortKey < first->key)
{
// item goes on front of list
element->next = first;
first = element;
}
else
{ // look for first elt in list bigger than item
for (ptr = first; ptr->next != NULL; ptr = ptr->next)
{
if (sortKey < ptr->next->key)
{
element->next = ptr->next;
ptr->next = element;
return;
}
}
last->next = element; // item goes at end of list
last = element;
}
}
//----------------------------------------------------------------------
// List::SortedRemove
// Remove the first "item" from the front of a sorted list.
//
// Returns:
// Pointer to removed item, NULL if nothing on the list.
// Sets *keyPtr to the priority value of the removed item
// (this is needed by interrupt.cc, for instance).
//
// "keyPtr" is a pointer to the location in which to store the
// priority of the removed item.
//----------------------------------------------------------------------
void *
List::SortedRemove (long long *keyPtr)
{
ListElement *element = first;
void *thing;
if (IsEmpty ())
return NULL;
thing = first->item;
if (first == last)
{ // list had one item, now has none
first = NULL;
last = NULL;
}
else
{
first = element->next;
}
if (keyPtr != NULL)
*keyPtr = element->key;
delete element;
return thing;
}

74
code/threads/list.h Normal file
View file

@ -0,0 +1,74 @@
// list.h
// Data structures to manage LISP-like lists.
//
// As in LISP, a list can contain any type of data structure
// as an item on the list: thread control blocks,
// pending interrupts, etc. That is why each item is a "void *",
// or in other words, a "pointers to anything".
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef LIST_H
#define LIST_H
#include "copyright.h"
#include "utility.h"
// The following class defines a "list element" -- which is
// used to keep track of one item on a list. It is equivalent to a
// LISP cell, with a "car" ("next") pointing to the next element on the list,
// and a "cdr" ("item") pointing to the item on the list.
//
// Internal data structures kept public so that List operations can
// access them directly.
class ListElement:public dontcopythis
{
public:
ListElement (void *itemPtr, long long sortKey); // initialize a list element
ListElement *next; // next element on list,
// NULL if this is the last
long long key; // priority, for a sorted list
void *item; // pointer to item on the list
};
// The following class defines a "list" -- a singly linked list of
// list elements, each of which points to a single item on the list.
//
// By using the "Sorted" functions, the list can be kept in sorted
// in increasing order by "key" in ListElement.
class List:public dontcopythis
{
public:
List (); // initialize the list
~List (); // de-allocate the list
void Prepend (void *item); // Put item at the beginning of the list
void Append (void *item); // Put item at the end of the list
ListElement *FirstElement (); // Read item off the front of the list
void *Remove (); // Take item off the front of the list
void Remove (void *item); // Remove item off the list
int Length (); // Return length
void Mapcar (VoidFunctionPtr func); // Apply "func" to every element
// on the list
void Mapcar (VoidFunctionPtr2 func, void *arg); // Apply "func" to every
// element on the list with the
// additional arg
bool IsEmpty (); // is the list empty?
// Routines to put/get items on/off list in order (sorted by key)
void SortedInsert (void *item, long long sortKey); // Put item into list
void *SortedRemove (long long *keyPtr); // Remove first item from list
private:
ListElement *first; // Head of the list, NULL if list is empty
ListElement *last; // Last element of list
};
#endif // LIST_H

190
code/threads/main.cc Normal file
View file

@ -0,0 +1,190 @@
// main.cc
// Bootstrap code to initialize the operating system kernel.
//
// Allows direct calls into internal operating system functions,
// to simplify debugging and testing. In practice, the
// bootstrap code would just initialize data structures,
// and start a user program to print the login prompt.
//
// Most of this file is not needed until later assignments.
//
// 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.
#define MAIN
#include "copyright.h"
#undef MAIN
#include "utility.h"
#include "system.h"
#ifdef USER_PROGRAM
#include "progtest.h"
#endif
#include <malloc.h>
// External functions used by this file
extern void ThreadTest (void), Copy (char *unixFile, char *nachosFile);
extern void Print (char *file), PerformanceTest (void);
extern void MailTest (int networkID);
//----------------------------------------------------------------------
// main
// Bootstrap the operating system kernel.
//
// Check command line arguments
// Initialize data structures
// (optionally) Call test procedure
//
// "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 argCount; // the number of arguments
// for a particular command
if (argc > 1 && !strcmp (argv[1], "-h")) // print help
{
// NOTE -- flags are ignored until the relevant assignment.
// Some of the flags are interpreted here; some in system.cc.
//
printf (
"Usage: nachos -d <debugflags> -rs <random seed #> -z -h\n"
#ifdef USER_PROGRAM
" -s -x <nachos file> -c <consoleIn> <consoleOut>\n"
#endif
#ifdef FILESYS
" -f -cp <unix file> <nachos file>\n"
" -p <nachos file> -r <nachos file> -l -D -t\n"
#endif
#ifdef NETWORK
" -n <network reliability> -m <machine id>\n"
" -o <other machine id>\n"
#endif
"\n"
"-d causes certain debugging messages to be printed (cf. utility.h)\n"
"-rs causes Yield to occur at random (but repeatable) spots\n"
"-z prints the copyright message\n"
"-h prints some help about options\n"
"\n"
#ifdef USER_PROGRAM
"USER_PROGRAM\n"
"-s causes user programs to be executed in single-step mode\n"
"-x runs a user program\n"
"-c tests the console\n"
#endif
#ifdef FILESYS
"FILESYS\n"
"-f causes the physical disk to be formatted\n"
"-cp copies a file from UNIX to Nachos\n"
"-p prints a Nachos file to stdout\n"
"-r removes a Nachos file from the file system\n"
"-l lists the contents of the Nachos directory\n"
"-D prints the contents of the entire file system\n"
"-t tests the performance of the Nachos file system\n"
#endif
#ifdef NETWORK
"NETWORK\n"
"-n sets the network reliability\n"
"-m sets this machine's host id (needed for the network)\n"
"-o runs a simple test of the Nachos network software"
#endif
);
return (0);
}
DEBUG ('t', "Entering main");
(void) Initialize (argc, argv);
#ifdef THREADS
ThreadTest ();
#endif
for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount)
{
argCount = 1;
if (!strcmp (*argv, "-z")) // print copyright
printf ("%s", copyright);
#ifdef USER_PROGRAM
if (!strcmp (*argv, "-x"))
{ // run a user program
ASSERT (argc > 1);
StartProcess (*(argv + 1));
argCount = 2;
}
else if (!strcmp (*argv, "-c"))
{ // test the console
if (argc == 1)
ConsoleTest (NULL, NULL);
else
{
ASSERT (argc > 2);
ConsoleTest (*(argv + 1), *(argv + 2));
argCount = 3;
}
}
#endif // USER_PROGRAM
#ifdef FILESYS
if (!strcmp (*argv, "-cp"))
{ // copy from UNIX to Nachos
ASSERT (argc > 2);
Copy (*(argv + 1), *(argv + 2));
argCount = 3;
}
else if (!strcmp (*argv, "-p"))
{ // print a Nachos file
ASSERT (argc > 1);
Print (*(argv + 1));
argCount = 2;
}
else if (!strcmp (*argv, "-r"))
{ // remove Nachos file
ASSERT (argc > 1);
fileSystem->Remove (*(argv + 1));
argCount = 2;
}
else if (!strcmp (*argv, "-l"))
{ // list Nachos directory
fileSystem->List ();
}
else if (!strcmp (*argv, "-D"))
{ // print entire filesystem
fileSystem->Print ();
}
else if (!strcmp (*argv, "-t"))
{ // performance test
PerformanceTest ();
}
#endif // FILESYS
#ifdef NETWORK
if (!strcmp (*argv, "-o"))
{
ASSERT (argc > 1);
Delay (2); // delay for 2 seconds
// to give the user time to
// start up another nachos
MailTest (atoi (*(argv + 1)));
argCount = 2;
}
#endif // NETWORK
}
currentThread->Finish (); // NOTE: if the procedure "main"
// returns, then the program "nachos"
// will exit (as any other normal program
// would). But there may be other
// threads on the ready list. We switch
// to those threads by saying that the
// "main" thread is finished, preventing
// it from returning.
return (0); // Not reached...
}

182
code/threads/scheduler.cc Normal file
View file

@ -0,0 +1,182 @@
// 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-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 "scheduler.h"
#include "system.h"
#ifdef __SANITIZE_ADDRESS__
#include <sanitizer/asan_interface.h>
#endif
//----------------------------------------------------------------------
// Scheduler::Scheduler
// Initialize the list of ready but not running threads to empty.
//----------------------------------------------------------------------
Scheduler::Scheduler ()
{
readyList = new List;
halted = FALSE;
}
//----------------------------------------------------------------------
// Scheduler::Stop
// Prevent further context switches, used when halting the system
//----------------------------------------------------------------------
void
Scheduler::Stop ()
{
halted = TRUE;
}
//----------------------------------------------------------------------
// Scheduler::~Scheduler
// De-allocate the list of ready threads.
//----------------------------------------------------------------------
Scheduler::~Scheduler ()
{
delete readyList;
readyList = NULL;
}
//----------------------------------------------------------------------
// 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)
{
DEBUG ('t', "Putting thread %p %s on ready list.\n", thread, thread->getName ());
thread->setStatus (READY);
readyList->Append ((void *) 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 ()
{
if (halted)
return NULL;
return (Thread *) readyList->Remove ();
}
//----------------------------------------------------------------------
// 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 currentThread becomes nextThread.
//
// "nextThread" is the thread to be put into the CPU.
//----------------------------------------------------------------------
void
Scheduler::Run (Thread * nextThread)
{
Thread *oldThread = currentThread;
// LB: For safety...
ASSERT (interrupt->getLevel () == IntOff);
// End of addition
#ifdef USER_PROGRAM // ignore until running user programs
if (currentThread->space != NULL)
{ // if this thread is a user program,
currentThread->SaveUserState (); // save the user's CPU registers
currentThread->space->SaveState ();
}
#endif
oldThread->CheckOverflow (); // check if the old thread
// had an undetected stack overflow
currentThread = nextThread; // switch to the next thread
currentThread->setStatus (RUNNING); // nextThread is now running
DEBUG ('t', "Switching from thread %p \"%s\" to thread %p \"%s\"\n",
oldThread, oldThread->getName (), nextThread, 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".
#ifdef __SANITIZE_ADDRESS__
if (threadToBeDestroyed == oldThread)
__sanitizer_start_switch_fiber (NULL, nextThread->stack, nextThread->stack_size);
else
__sanitizer_start_switch_fiber (&oldThread->fake_stack, nextThread->stack, nextThread->stack_size);
#endif
SWITCH (oldThread, nextThread);
#ifdef __SANITIZE_ADDRESS__
__sanitizer_finish_switch_fiber (currentThread->fake_stack, NULL, NULL);
#endif
DEBUG ('t', "Now in thread %p \"%s\"\n", currentThread, currentThread->getName ());
// 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!
if (threadToBeDestroyed != NULL)
{
Thread *destroying = threadToBeDestroyed;
threadToBeDestroyed = NULL;
delete destroying;
}
#ifdef USER_PROGRAM
if (currentThread->space != NULL)
{ // if there is an address space
currentThread->RestoreUserState (); // to restore, do it.
currentThread->space->RestoreState ();
}
#endif
}
//----------------------------------------------------------------------
// Scheduler::Print
// Print the scheduler state -- in other words, the contents of
// the ready list. For debugging.
//----------------------------------------------------------------------
void
Scheduler::Print ()
{
printf ("Ready list contents:\n");
readyList->Mapcar (ThreadPrint);
}

39
code/threads/scheduler.h Normal file
View file

@ -0,0 +1,39 @@
// scheduler.h
// Data structures for the thread dispatcher and scheduler.
// Primarily, the list of threads that are ready to run.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef 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 dontcopythis
{
public:
Scheduler (); // Initialize list of ready threads
void Stop (); // Prevent further context switches
~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); // Cause nextThread to start running
void Print (); // Print contents of ready list
private:
List * readyList; // queue of threads that are ready to run,
// but not running
bool halted; // Whether we should prevent context switches
};
#endif // SCHEDULER_H

555
code/threads/switch.S Normal file
View file

@ -0,0 +1,555 @@
/* 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
* SUN SPARC
* HP PA-RISC
* Intel x86 (Linux + Solaris)
* Mac OS X 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-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 "switch.h"
#if defined(SOLARIS) || defined(MAC_OS)
/* Those systems prepend a _ to symbol names */
#define ThreadRoot _ThreadRoot
#define SWITCH _SWITCH
#endif
#ifdef HOST_MIPS
/* 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 we re 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 /* HOST_MIPS */
#ifdef HOST_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.)
*/
#ifndef SOLARIS
#include <sun4/trap.h>
#include <sun4/asm_linkage.h>
#else
#include <sys/trap.h>
#define MINFRAME 256
#define STACK_ALIGN 32L
#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.
*/
.globl ThreadRoot
ThreadRoot:
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
.globl SWITCH
SWITCH:
save %sp, -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 /* HOST_SPARC */
#ifdef HOST_PPC
.globl ThreadRoot
ThreadRoot:
mr r30, r1
stw r5, 128(r1)
stw r6, 132(r1)
stw r7, 136(r1)
#Call startupPC
mtctr r8
bctrl
mr r2, r31
# Call InitialPC with InitialArg
lwz r3, 132(r1)
lwz r5, 128(r1)
mtctr r5
bctrl
# Call WhenDonePC
lwz r7, 136(r1)
mtctr r7
bctrl
mr r31, r2
.globl SWITCH
SWITCH:
mflr r0
stw r0,4(r3)
stw r1,0(r3)
stw r2,8(r3)
stw r3,12(r3)
stw r5,20(r3)
stw r6,24(r3)
stw r7,28(r3)
stw r8,32(r3)
stw r9,36(r3)
stw r10,40(r3)
stw r11,44(r3)
stw r12,48(r3)
stw r13,52(r3)
stw r14,56(r3)
stw r15,60(r3)
stw r16,64(r3)
stw r17,68(r3)
stw r18,72(r3)
stw r19,76(r3)
stw r20,80(r3)
stw r21,84(r3)
stw r22,88(r3)
stw r23,92(r3)
stw r24,96(r3)
stw r25,100(r3)
stw r26,104(r3)
stw r27,108(r3)
stw r28,112(r3)
stw r29,116(r3)
stw r30,120(r3)
stw r31,124(r3)
lwz r0,4(r4)
lwz r1,0(r4)
lwz r2,8(r4)
lwz r3,12(r4)
lwz r5,20(r4)
lwz r6,24(r4)
lwz r7,28(r4)
lwz r8,32(r4)
lwz r9,36(r4)
lwz r10,40(r4)
lwz r11,44(r4)
lwz r12,48(r4)
lwz r13,52(r4)
lwz r14,56(r4)
lwz r15,60(r4)
lwz r16,64(r4)
lwz r17,68(r4)
lwz r18,72(r4)
lwz r19,76(r4)
lwz r20,80(r4)
lwz r21,84(r4)
lwz r22,88(r4)
lwz r23,92(r4)
lwz r24,96(r4)
lwz r25,100(r4)
lwz r26,104(r4)
lwz r27,108(r4)
lwz r28,112(r4)
lwz r29,116(r4)
lwz r30,120(r4)
lwz r31,124(r4)
mtlr r0
blr
#endif /* HOST_PPC */
#ifdef HOST_SNAKE
;rp = r2, sp = r30
;arg0 = r26, arg1 = r25, arg2 = r24, arg3 = r23
.SPACE $TEXT$
.SUBSPA $CODE$
ThreadRoot
.PROC
.CALLINFO CALLER,FRAME=0
.ENTRY
.CALL
ble 0(%r6) ;call StartupPC
or %r31, 0, %rp ;put return address in proper register
or %r4, 0, %arg0 ;load InitialArg
.CALL ;in=26
ble 0(%r3) ;call InitialPC
or %r31, 0, %rp ;put return address in proper register
.CALL
ble 0(%r5) ;call WhenDonePC
.EXIT
or %r31, 0, %rp ;shouldn't really matter - doesn't return
.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
#ifdef HOST_i386
.text
.align 2
/* 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()
*/
.globl ThreadRoot
ThreadRoot:
.cfi_startproc
.cfi_undefined rip
xorl %ebp,%ebp
pushl InitialArg
call *StartupPC
call *InitialPC
call *WhenDonePC
/* NOT REACHED*/
movl %ebp,%esp
popl %ebp
ret
.cfi_endproc
/* 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
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,0(%esp) /* copy over the ret address on the stack */
movl _eax_save,%eax
ret
#endif
#ifdef HOST_x86_64
.text
.align 2
/* void ThreadRoot( void )
**
** expects the following registers to be initialized:
** rcx points to startup function (interrupt enable)
** rdx contains inital argument to thread function
** rsi points to thread function
** rdi point to Thread::Finish()
*/
.globl ThreadRoot
ThreadRoot:
.cfi_startproc
.cfi_undefined rip
xorq %rbp,%rbp
subq $8,%rsp /* Alignment for SSE */
pushq WhenDonePC
pushq InitialPC
pushq InitialArg
call *StartupPC
movq (%rsp),%rdi
movq 8(%rsp),InitialPC
call *InitialPC
movq 16(%rsp),WhenDonePC
call *WhenDonePC
/* NOT REACHED*/
popq InitialArg
popq InitialPC
popq WhenDonePC
addq $8, %rsp
movq %rbp,%rsp
popq %rbp
ret
.cfi_endproc
/* void SWITCH( thread *t1, thread *t2 )
**
** on entry, stack looks like this:
** (rsp) -> return address
**
** rdi contains thread *t1
** rsi contains thread *t2
**
** we push the current rax on the stack so that we can use it as
** a pointer to t1, this decrements rsp by 8, so when we use it
** to reference stuff on the stack, we add 8 to the offset.
*/
.comm _rax_save,8
.globl SWITCH
SWITCH:
movq %rax,_rax_save(%rip) /* save the value of rax */
movq %rdi,%rax /* move pointer to t1 into rax */
movq %rbx,_RBX(%rax) /* save registers */
movq %rcx,_RCX(%rax)
movq %rdx,_RDX(%rax)
movq %rsi,_RSI(%rax)
movq %rdi,_RDI(%rax)
movq %r12,_R12(%rax)
movq %r13,_R13(%rax)
movq %r14,_R14(%rax)
movq %r15,_R15(%rax)
movq %rbp,_RBP(%rax)
movq %rsp,_RSP(%rax) /* save stack pointer */
movq _rax_save(%rip),%rbx /* get the saved value of rax */
movq %rbx,_RAX(%rax) /* store it */
movq 0(%rsp),%rbx /* get return address from stack into rbx */
movq %rbx,_PC(%rax) /* save it into the pc storage */
movq %rsi,%rax /* move pointer to t2 into rax */
movq _RAX(%rax),%rbx /* get new value for rax into rbx */
movq %rbx,_rax_save(%rip) /* save it */
movq _RBX(%rax),%rbx /* retore old registers */
movq _RCX(%rax),%rcx
movq _RDX(%rax),%rdx
movq _RSI(%rax),%rsi
movq _RDI(%rax),%rdi
movq _R12(%rax),%r12
movq _R13(%rax),%r13
movq _R14(%rax),%r14
movq _R15(%rax),%r15
movq _RBP(%rax),%rbp
movq _RSP(%rax),%rsp /* restore stack pointer */
movq _PC(%rax),%rax /* restore return address into rax */
movq %rax,0(%rsp) /* copy over the ret address on the stack */
movq _rax_save(%rip),%rax
ret
#endif

223
code/threads/switch.h Normal file
View file

@ -0,0 +1,223 @@
/* 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 and SUN SPARC architectures.
*/
/*
Copyright (c) 1992-1993 The Regents of the University of California.
All rights reserved. See copyright.h for copyright notice and limitation
of liability and disclaimer of warranty provisions.
*/
#ifndef SWITCH_H
#define SWITCH_H
#include "copyright.h"
#ifdef HOST_MIPS
/* 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 // HOST_MIPS
#ifdef HOST_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 // HOST_SPARC
#ifdef HOST_SNAKE
/* 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 // HOST_SNAKE
#ifdef HOST_PPC
#define R0 4
#define R1 0 // SP must be the first field of the Thread struct!
#define R2 8
#define R3 12
#define R4 16
#define R5 20
#define R6 24
#define R7 28
#define R8 32
#define R9 36
#define R10 40
#define R11 44
#define R12 48
#define R13 52
#define R14 56
#define R15 60
#define R16 64
#define R17 68
#define R18 72
/* Registers for ThreadRoot. See comment above. */
#define InitialPC %r5
#define InitialArg %r6
#define WhenDonePC %r7
#define StartupPC %r8
/* WARNING: SP is not part of machineState! */
#define PCState 0
#define InitialPCState 4
#define InitialArgState 5
#define WhenDonePCState 6
#define StartupPCState 7
#endif // HOST_PPC
#ifdef HOST_i386
/* 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
#ifdef HOST_x86_64
/* the offsets of the registers from the beginning of the thread object */
#define _RSP 0
#define _RAX 8
#define _RBX 16
#define _RCX 24
#define _RDX 32
#define _RBP 40
#define _RSI 48
#define _RDI 56
#define _R12 64
#define _R13 72
#define _R14 80
#define _R15 88
#define _PC 96
/* These definitions are used in Thread::AllocateStack(). */
#define PCState (_PC/8-1)
#define FPState (_RBP/8-1)
#define InitialPCState (_RSI/8-1)
#define InitialArgState (_RDX/8-1)
#define WhenDonePCState (_RDI/8-1)
#define StartupPCState (_RCX/8-1)
#define InitialPC %rsi
#define InitialArg %rdx
#define WhenDonePC %rdi
#define StartupPC %rcx
#endif
#endif // SWITCH_H

164
code/threads/synch.cc Normal file
View file

@ -0,0 +1,164 @@
// synch.cc
// Routines for synchronizing threads. Three kinds of
// synchronization routines are defined here: semaphores, locks
// and condition variables (the implementation of the last two
// are left to the reader).
//
// 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).
//
// 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 "synch.h"
#include "system.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 (const char *debugName, int initialValue)
{
name = debugName;
value = initialValue;
queue = new List;
}
//----------------------------------------------------------------------
// Semaphore::Semaphore
// De-allocate semaphore, when no longer needed. Assume no one
// is still waiting on the semaphore!
//----------------------------------------------------------------------
Semaphore::~Semaphore ()
{
delete queue;
queue = NULL;
value = -1;
}
//----------------------------------------------------------------------
// 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 ()
{
IntStatus oldLevel = interrupt->SetLevel (IntOff); // disable interrupts
ASSERT(value >= 0);
while (value == 0)
{ // semaphore not available
queue->Append ((void *) currentThread); // so go to sleep
currentThread->Sleep ();
}
value--; // semaphore available,
// consume its value
(void) interrupt->SetLevel (oldLevel); // re-enable interrupts
}
//----------------------------------------------------------------------
// 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 threads
// are disabled when it is called.
//----------------------------------------------------------------------
void
Semaphore::V ()
{
Thread *thread;
IntStatus oldLevel = interrupt->SetLevel (IntOff);
ASSERT(value >= 0);
thread = (Thread *) queue->Remove ();
if (thread != NULL) // make thread ready, consuming the V immediately
scheduler->ReadyToRun (thread);
value++;
(void) interrupt->SetLevel (oldLevel);
}
// Dummy functions -- so we can compile our later assignments
// Note -- without a correct implementation of Condition::Wait(),
// the test case in the network assignment won't work!
Lock::Lock (const char *debugName)
{
(void) debugName;
/* TODO */
ASSERT(FALSE);
}
Lock::~Lock ()
{
}
void
Lock::Acquire ()
{
/* TODO */
ASSERT(FALSE);
}
void
Lock::Release ()
{
/* TODO */
ASSERT(FALSE);
}
Condition::Condition (const char *debugName)
{
(void) debugName;
/* TODO */
ASSERT(FALSE);
}
Condition::~Condition ()
{
}
void
Condition::Wait (Lock * conditionLock)
{
(void) conditionLock;
/* TODO */
ASSERT (FALSE);
}
void
Condition::Signal (Lock * conditionLock)
{
(void) conditionLock;
/* TODO */
ASSERT(FALSE);
}
void
Condition::Broadcast (Lock * conditionLock)
{
(void) conditionLock;
/* TODO */
ASSERT(FALSE);
}

148
code/threads/synch.h Normal file
View file

@ -0,0 +1,148 @@
// 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-1993 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"
// 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 dontcopythis
{
public:
Semaphore (const char *debugName, int initialValue); // set initial value
~Semaphore (); // de-allocate semaphore
const char *getName ()
{
return name;
} // debugging assist
void P (); // these are the only operations on a semaphore
void V (); // they are both *atomic*
private:
const char *name; // useful for debugging
int value; // semaphore value, always >= 0
List *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 dontcopythis
{
public:
Lock (const char *debugName); // initialize lock to be FREE
~Lock (); // deallocate lock
const char *getName ()
{
return name;
} // debugging assist
void Acquire (); // these are the only operations on a lock
void Release (); // they are both *atomic*
bool isHeldByCurrentThread (); // true if the current thread
// holds this lock. Useful for
// checking in Release, and in
// Condition variable ops below.
private:
const char *name; // for debugging
// plus some other stuff you'll need to define
};
// 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.
class Condition:public dontcopythis
{
public:
Condition (const char *debugName); // initialize condition to
// "no one waiting"
~Condition (); // deallocate the condition
const 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
private:
const char *name;
// plus some other stuff you'll need to define
};
#endif // SYNCH_H

101
code/threads/synchlist.cc Normal file
View file

@ -0,0 +1,101 @@
// synchlist.cc
// Routines for synchronized access to a list.
//
// Implemented by surrounding the List abstraction
// with synchronization routines.
//
// 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::SynchList
// Allocate and initialize the data structures needed for a
// synchronized list, empty to start with.
// Elements can now be added to the list.
//----------------------------------------------------------------------
SynchList::SynchList ()
{
list = new List ();
lock = new Lock ("list lock");
listEmpty = new Condition ("list empty cond");
}
//----------------------------------------------------------------------
// SynchList::~SynchList
// De-allocate the data structures created for synchronizing a list.
//----------------------------------------------------------------------
SynchList::~SynchList ()
{
delete list;
list = NULL;
delete lock;
lock = NULL;
delete listEmpty;
listEmpty = NULL;
}
//----------------------------------------------------------------------
// SynchList::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, it can be a pointer to
// anything.
//----------------------------------------------------------------------
void
SynchList::Append (void *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::Remove
// Remove an "item" from the beginning of the list. Wait if
// the list is empty.
// Returns:
// The removed item.
//----------------------------------------------------------------------
void *
SynchList::Remove ()
{
void *item;
lock->Acquire (); // enforce mutual exclusion
while (list->IsEmpty ())
listEmpty->Wait (lock); // wait until list isn't empty
item = list->Remove ();
ASSERT (item != NULL);
lock->Release ();
return item;
}
//----------------------------------------------------------------------
// SynchList::Mapcar
// Apply function to every item on the list. Obey mutual exclusion
// constraints.
//
// "func" is the procedure to be applied.
//----------------------------------------------------------------------
void
SynchList::Mapcar (VoidFunctionPtr func)
{
lock->Acquire ();
list->Mapcar (func);
lock->Release ();
}

43
code/threads/synchlist.h Normal file
View file

@ -0,0 +1,43 @@
// synchlist.h
// Data structures for synchronized access to a list.
//
// Implemented by surrounding the List abstraction
// with synchronization routines.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef 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
class SynchList:public dontcopythis
{
public:
SynchList (); // initialize a synchronized list
~SynchList (); // de-allocate a synchronized list
void Append (void *item); // append item to the end of the list,
// and wake up any thread waiting in remove
void *Remove (); // remove the first item from the front of
// the list, waiting if the list is empty
// apply function to every item in the list
void Mapcar (VoidFunctionPtr func);
private:
List * list; // the unsynchronized list
Lock *lock; // enforce mutual exclusive access to the list
Condition *listEmpty; // wait in Remove if the list is empty
};
#endif // SYNCHLIST_H

269
code/threads/system.cc Normal file
View file

@ -0,0 +1,269 @@
// system.cc
// Nachos initialization and cleanup routines.
//
// 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 "system.h"
#include <locale.h>
#ifdef __GLIBC__
#include <malloc.h>
#endif
// This defines *all* of the global data structures used by Nachos.
// These are all initialized and de-allocated by this file.
Thread *currentThread; // the thread we are running now
Thread *threadToBeDestroyed; // the thread that just finished
Scheduler *scheduler; // the ready list
Interrupt *interrupt; // interrupt status
Statistics *stats; // performance metrics
Timer *timer; // the hardware timer device,
// for invoking context switches
#ifdef FILESYS_NEEDED
FileSystem *fileSystem;
#endif
#ifdef FILESYS
SynchDisk *synchDisk;
#endif
#ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB
Machine *machine; // user program memory and registers
#endif
#ifdef NETWORK
PostOffice *postOffice;
#endif
// External definition, to allow us to take a pointer to this function
extern void Cleanup ();
//----------------------------------------------------------------------
// TimerInterruptHandler
// 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.
//
// "dummy" is because every interrupt handler takes one argument,
// whether it needs it or not.
//----------------------------------------------------------------------
static void
TimerInterruptHandler (void *dummy)
{
(void) dummy;
if (interrupt->getStatus () != IdleMode)
interrupt->YieldOnReturn ();
}
//----------------------------------------------------------------------
// Initialize
// Initialize Nachos global data structures. Interpret command
// line arguments in order to determine flags for the initialization.
//
// "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", "+"}
//----------------------------------------------------------------------
void
Initialize (int argc, char **argv)
{
int argCount;
const char *debugArgs = "";
bool randomYield = FALSE;
#ifdef USER_PROGRAM
bool debugUserProg = FALSE; // single step user program
#endif
#ifdef FILESYS_NEEDED
bool format = FALSE; // format disk
#endif
#ifdef NETWORK
double rely = 1; // network reliability
int netname = 0; // UNIX socket name
#endif
setlocale(LC_CTYPE,"");
#ifdef __GLIBC__
#if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4)
/* Make free() fill freed memory, to trap bad code */
mallopt(M_PERTURB, 0xfefefefe);
#endif
#endif
ThrashStack();
for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount)
{
argCount = 1;
if (!strcmp (*argv, "-d"))
{
if (argc == 1)
debugArgs = "+"; // turn on all debug flags
else
{
debugArgs = *(argv + 1);
argCount = 2;
}
}
else if (!strcmp (*argv, "-rs"))
{
int seed;
ASSERT (argc > 1);
seed = atoi (*(argv + 1));
if (seed == 0)
{
fprintf(stderr,"-rs option needs a seed value\n");
exit(EXIT_FAILURE);
}
RandomInit (seed); // initialize pseudo-random
// number generator
randomYield = TRUE;
argCount = 2;
}
#ifdef USER_PROGRAM
if (!strcmp (*argv, "-s"))
debugUserProg = TRUE;
#endif
#ifdef FILESYS_NEEDED
if (!strcmp (*argv, "-f"))
format = TRUE;
#endif
#ifdef NETWORK
if (!strcmp (*argv, "-l"))
{
ASSERT (argc > 1);
rely = atof (*(argv + 1));
argCount = 2;
}
else if (!strcmp (*argv, "-m"))
{
ASSERT (argc > 1);
netname = atoi (*(argv + 1));
argCount = 2;
}
#endif
}
DebugInit (debugArgs); // initialize DEBUG messages
stats = new Statistics (); // collect statistics
interrupt = new Interrupt; // start up interrupt handling
scheduler = new Scheduler (); // initialize the ready queue
if (randomYield) // start the timer (if needed)
timer = new Timer (TimerInterruptHandler, 0, randomYield);
threadToBeDestroyed = NULL;
// 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");
currentThread->SetMain ();
interrupt->Enable ();
CallOnUserAbort (Cleanup); // if user hits ctl-C
#ifdef USER_PROGRAM
machine = new Machine (debugUserProg); // this must come first
#endif
#ifdef FILESYS
synchDisk = new SynchDisk ("DISK");
#endif
#ifdef FILESYS_NEEDED
fileSystem = new FileSystem (format);
#endif
#ifdef NETWORK
postOffice = new PostOffice (netname, rely, 10);
#endif
}
//----------------------------------------------------------------------
// Cleanup
// Nachos is halting. De-allocate global data structures.
//----------------------------------------------------------------------
void
Cleanup ()
{
static int cleaning;
if (cleaning) {
printf ("\nCtrl-C while cleaning, stopping here hard.\n");
Exit (0);
}
cleaning = 1;
printf ("\nCleaning up...\n");
/* Allow more interrupts but prevent other threads from continuing to use
* the system while we are waiting for the last interrupts */
if (scheduler)
scheduler->Stop();
if (interrupt)
interrupt->Enable();
#ifdef NETWORK
if (postOffice) {
delete postOffice;
postOffice = NULL;
}
#endif
#ifdef USER_PROGRAM
if (machine) {
delete machine;
machine = NULL;
}
#endif
#ifdef FILESYS_NEEDED
if (fileSystem) {
delete fileSystem;
fileSystem = NULL;
}
#endif
#ifdef FILESYS
if (synchDisk) {
delete synchDisk;
synchDisk = NULL;
}
#endif
if (timer) {
delete timer;
timer = NULL;
}
if (scheduler) {
delete scheduler;
scheduler = NULL;
}
if (interrupt) {
delete interrupt;
interrupt = NULL;
}
if (stats) {
delete stats;
stats = NULL;
}
ThreadList.Remove(currentThread);
Exit (0);
}

52
code/threads/system.h Normal file
View file

@ -0,0 +1,52 @@
// system.h
// All global variables used in Nachos are defined here.
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef SYSTEM_H
#define SYSTEM_H
#include "copyright.h"
#include "utility.h"
#include "thread.h"
#include "scheduler.h"
#include "interrupt.h"
#include "stats.h"
#include "timer.h"
// Initialization and cleanup routines
extern void Initialize (int argc, char **argv); // Initialization,
// called before anything else
extern void Cleanup (); // Cleanup, called when
// Nachos is done.
extern Thread *currentThread; // the thread holding the CPU
extern Thread *threadToBeDestroyed; // the thread that just finished
extern Scheduler *scheduler; // the ready list
extern Interrupt *interrupt; // interrupt status
extern Statistics *stats; // performance metrics
extern Timer *timer; // the hardware alarm clock
#ifdef USER_PROGRAM
#include "machine.h"
extern Machine *machine; // user program memory and registers
#endif
#ifdef FILESYS_NEEDED // FILESYS or FILESYS_STUB
#include "filesys.h"
extern FileSystem *fileSystem;
#endif
#ifdef FILESYS
#include "synchdisk.h"
extern SynchDisk *synchDisk;
#endif
#ifdef NETWORK
#include "post.h"
extern PostOffice *postOffice;
#endif
#endif // SYSTEM_H

533
code/threads/thread.cc Normal file
View file

@ -0,0 +1,533 @@
// thread.cc
// Routines to manage threads. There are four main operations:
//
// Start -- create a thread to run a procedure concurrently
// with the caller (this is done in two steps -- first
// allocate the Thread object, then call Start on it)
// 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-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 "thread.h"
#include "switch.h"
#include "synch.h"
#include "system.h"
#include "valgrind.h"
#ifdef USER_PROGRAM
#include "machine.h"
#endif
#ifdef __SANITIZE_ADDRESS__
#include <pthread.h>
#include <sanitizer/asan_interface.h>
#endif
#define STACK_FENCEPOST 0xdeadbeef // this is put at the top of the
// execution stack, for detecting
// stack overflows
//----------------------------------------------------------------------
// ThreadList
// List of all threads, for debugging
//----------------------------------------------------------------------
List ThreadList;
//----------------------------------------------------------------------
// Thread::Thread
// Initialize a thread control block, so that we can then call
// Thread::Start.
//
// "threadName" is an arbitrary string, useful for debugging.
//----------------------------------------------------------------------
Thread::Thread (const char *threadName)
{
name = threadName;
stackTop = NULL;
stack = NULL;
valgrind_id = 0;
#ifdef __SANITIZE_ADDRESS__
fake_stack = NULL;
#endif
stack_size = 0;
main_stack = 0;
status = JUST_CREATED;
#ifdef USER_PROGRAM
space = NULL;
// must be explicitly set to 0 since when Enabling interrupts,
// DelayedLoad is called !!!
userRegisters[LoadReg] = 0;
userRegisters[LoadValueReg] = 0;
#endif
ThreadList.Append(this);
}
//----------------------------------------------------------------------
// Thread::SetMain
// Configures the thread as representing the main thread, i.e. the thread
// initially created by the OS, with an OS-provided stack.
//----------------------------------------------------------------------
void
Thread::SetMain (void)
{
#ifdef __SANITIZE_ADDRESS__
pthread_attr_t attr;
void *addr;
pthread_getattr_np (pthread_self (), &attr);
pthread_attr_getstack (&attr, &addr, &stack_size);
pthread_attr_destroy (&attr);
stack = (unsigned long *) addr;
#endif
main_stack = 1;
setStatus (RUNNING);
}
//----------------------------------------------------------------------
// 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 ('t', "Deleting thread %p \"%s\"\n", this, name);
ASSERT (this != currentThread);
if (!main_stack && stack != NULL) {
DeallocBoundedArray ((char *) stack, StackSize * sizeof (unsigned long));
VALGRIND_STACK_DEREGISTER (valgrind_id);
stack = NULL;
}
ThreadList.Remove(this);
}
//----------------------------------------------------------------------
// ThrashStack
// Fill the stack with bogus values, to avoid getting 0 values only by luck
//----------------------------------------------------------------------
void
ThrashStack(void)
{
char c[StackSize];
memset(c, 0x02, sizeof(c));
}
//----------------------------------------------------------------------
// Thread::Start
// Invoke (*func)(arg), allowing caller and callee to execute
// concurrently.
//
// NOTE: although our definition allows only a single integer 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::Start (VoidFunctionPtr func, void *arg)
{
DEBUG ('t', "Starting thread %p \"%s\" with func = %p, arg = %p\n",
this, name, func, arg);
ASSERT(status == JUST_CREATED);
StackAllocate (func, arg);
IntStatus 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 (!main_stack && stack != NULL)
#ifdef HOST_SNAKE // Stacks grow upward on the Snakes
ASSERT (stack[StackSize - 1] == STACK_FENCEPOST);
#else
ASSERT (*stack == (unsigned long) STACK_FENCEPOST);
#endif
}
//----------------------------------------------------------------------
// Thread::Finish
// Called by ThreadRoot when a thread is done executing the
// forked procedure.
//
// NOTE: we don'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 set "threadToBeDestroyed",
// so that Scheduler::Run() will call the destructor, once we're
// running in the context of a different thread.
//
// NOTE: we disable interrupts, so that we don't get a time slice
// between setting threadToBeDestroyed, and going to sleep.
//----------------------------------------------------------------------
//
void
Thread::Finish ()
{
(void) interrupt->SetLevel (IntOff);
ASSERT (this == currentThread);
DEBUG ('t', "Finishing thread %p \"%s\"\n", this, getName ());
// LB: Be careful to guarantee that no thread to be destroyed
// is ever lost
ASSERT (threadToBeDestroyed == NULL);
// End of addition
threadToBeDestroyed = currentThread;
Sleep (); // 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 = interrupt->SetLevel (IntOff);
ASSERT (this == currentThread);
DEBUG ('t', "Yielding thread %p \"%s\"\n", this, getName ());
nextThread = scheduler->FindNextToRun ();
if (nextThread != NULL)
{
scheduler->ReadyToRun (this);
scheduler->Run (nextThread);
}
(void) interrupt->SetLevel (oldLevel);
}
//----------------------------------------------------------------------
// Thread::Sleep
// Relinquish the CPU, because the current thread is blocked
// waiting on a synchronization variable (Semaphore, Lock, or Condition).
// 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 ()
{
Thread *nextThread;
ASSERT (this == currentThread);
ASSERT (interrupt->getLevel () == IntOff);
DEBUG ('t', "Sleeping thread %p \"%s\"\n", this, getName ());
status = BLOCKED;
while ((nextThread = scheduler->FindNextToRun ()) == NULL)
interrupt->Idle (); // no one to run, wait for an interrupt
scheduler->Run (nextThread); // returns when we've been signalled
}
//----------------------------------------------------------------------
// ThreadFinish, InterruptEnable, ThreadPrint
// Dummy functions because C++ does not allow a pointer to a member
// function. So in order to do this, we create a dummy C function
// (which we can pass a pointer to), that then simply calls the
// member function.
//----------------------------------------------------------------------
static void
ThreadFinish ()
{
currentThread->Finish ();
}
static void
InterruptEnable ()
{
interrupt->Enable ();
}
// LB: This function has to be called on starting a new thread to set
// up the pagetable correctly. This was missing from the original
// version. Credits to Clement Menier for finding this bug!
void
SetupThreadState ()
{
#ifdef __SANITIZE_ADDRESS__
__sanitizer_finish_switch_fiber (currentThread->fake_stack, NULL, NULL);
#endif
// LB: Similar to the second part of Scheduler::Run. This has to be
// done each time a thread is scheduled, either by SWITCH, or by
// getting created.
if (threadToBeDestroyed != NULL)
{
delete threadToBeDestroyed;
threadToBeDestroyed = NULL;
}
#ifdef USER_PROGRAM
// LB: Now, we have to simulate the RestoreUserState/RestoreState
// part of Scheduler::Run
// Be very careful! We have no information about the thread which is
// currently running at the time this function is called. Actually,
// there is no reason why the running thread should have the same
// pageTable as the thread just being created.
// This is definitely the case as soon as several *processes* are
// running together.
if (currentThread->space != NULL)
{ // if there is an address space
// LB: Actually, the user state is void at that time. Keep this
// action for consistency with the Scheduler::Run function
currentThread->RestoreUserState (); // to restore, do it.
currentThread->space->RestoreState ();
}
#endif // USER_PROGRAM
// LB: The default level for interrupts is IntOn.
InterruptEnable ();
}
// End of addition
void
ThreadPrint (void *arg)
{
Thread *t = (Thread *) arg;
t->Print ();
}
//----------------------------------------------------------------------
// Thread::StackAllocate
// Allocate and initialize a kernel 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 = (unsigned long *) AllocBoundedArray (StackSize * sizeof (unsigned long));
stack_size = StackSize * sizeof (unsigned long);
valgrind_id = VALGRIND_STACK_REGISTER(stack, stack + StackSize);
#ifdef HOST_SNAKE
// HP stack works from low addresses to high addresses
stackTop = stack + 16; // HP requires 64-byte frame marker
stack[StackSize - 1] = STACK_FENCEPOST;
#else
// other archs stack works from high addresses to low addresses
#ifdef HOST_SPARC
// SPARC stack must contains at least 1 activation record to start with.
stackTop = stack + StackSize - 96;
#elif defined(HOST_PPC)
stackTop = stack + StackSize - 192;
#elif defined(HOST_i386)
stackTop = stack + StackSize - 4; // -4 for the return address
#elif defined(HOST_x86_64)
stackTop = stack + StackSize - 16; // room for the return address, and align for SSE*
#elif defined(HOST_MIPS)
stackTop = stack + StackSize; // no special need
#else
#error uh??
#endif
*stack = STACK_FENCEPOST;
#endif // stack direction
memset(&machineState, 0, sizeof(machineState));
machineState[PCState] = (unsigned long) ThreadRoot;
// LB: It is not sufficient to enable interrupts!
// A more complex function has to be called here...
// machineState[StartupPCState] = (int) InterruptEnable;
machineState[StartupPCState] = (unsigned long) SetupThreadState;
// End of modification
machineState[InitialPCState] = (unsigned long) func;
machineState[InitialArgState] = (unsigned long) arg;
machineState[WhenDonePCState] = (unsigned long) ThreadFinish;
}
#ifdef USER_PROGRAM
#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] = 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++)
machine->WriteRegister (i, userRegisters[i]);
}
//----------------------------------------------------------------------
// Thread::DumpThreadState
// Draw the states for this thread
//----------------------------------------------------------------------
void
Thread::DumpThreadState(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, unsigned virtual_y, unsigned blocksize)
{
if (this == currentThread)
machine->DumpRegs(output, ptr_x, ptr_y, virtual_x, virtual_y, blocksize);
else
{
machine->DumpReg(output, userRegisters[PCReg], "PC", "#000000", ptr_x, ptr_y, virtual_x, virtual_y, blocksize);
machine->DumpReg(output, userRegisters[StackReg], "SP", "#000000", ptr_x, ptr_y, virtual_x, virtual_y, blocksize);
}
}
//----------------------------------------------------------------------
// DumpThreadsState
// Draw the states for threads belonging to a given address space
//----------------------------------------------------------------------
void
DumpThreadsState(FILE *output, AddrSpace *space, unsigned ptr_x, unsigned virtual_x, unsigned virtual_y, unsigned blocksize)
{
ListElement *element;
ptr_x += 4*blocksize;
unsigned nthreads = ThreadList.Length();
unsigned y_offset;
unsigned ptr_y;
if (nthreads == 1)
{
y_offset = 0;
ptr_y = virtual_y;
}
else
{
y_offset = (blocksize/2) / (nthreads-1);
ptr_y = virtual_y - (blocksize/4);
}
for (element = ThreadList.FirstElement();
element;
element = element->next)
{
Thread *thread = (Thread *) element->item;
if (thread->space != space)
continue;
thread->DumpThreadState(output, ptr_x, ptr_y, virtual_x, virtual_y, blocksize);
// Offset names a bit on the left
ptr_x -= blocksize;
// And offset lines a bit down
ptr_y += y_offset;
}
}
#endif

180
code/threads/thread.h Normal file
View file

@ -0,0 +1,180 @@
// 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 -- StackSize.
//
// 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-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef THREAD_H
#define THREAD_H
#include "copyright.h"
#include "utility.h"
#include "list.h"
#ifdef USER_PROGRAM
#include "machine.h"
#include "addrspace.h"
#endif
// CPU register state to be saved on context switch.
// The SPARC and MIPS only need 10 registers, but the PPC needs 32.
// For simplicity, this is just the max over all architectures.
#define MachineStateSize 32
// Size of the thread's private execution stack.
// WATCH OUT IF THIS ISN'T BIG ENOUGH!!!!!
#define StackSize (8 * 1024) // in words
// Thread state
enum ThreadStatus
{ JUST_CREATED, RUNNING, READY, BLOCKED };
// external function, dummy routine whose sole job is to call Thread::Print
extern void ThreadPrint (void *arg);
// 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:public dontcopythis
{
private:
// NOTE: DO NOT CHANGE the order of these first two members.
// THEY MUST be in this position for SWITCH to work.
unsigned long *stackTop; // the current kernel stack pointer
unsigned long machineState[MachineStateSize]; // all kernel registers except for stackTop
public:
Thread (const char *debugName); // initialize a Thread
void SetMain (void); // initialize Thread as main thread
~Thread (); // deallocate a Thread
// NOTE -- thread being deleted
// must not be running when delete
// is called
// basic thread operations
void Start (VoidFunctionPtr func, void *arg); // Make thread run (*func)(arg)
void Yield (); // Relinquish the CPU if any
// other thread is runnable
void Sleep (); // Put the thread to sleep and
// relinquish the processor
void Finish (); // The thread is done executing
void CheckOverflow (); // Check if thread has
// overflowed its stack
void setStatus (ThreadStatus st)
{
status = st;
}
const char *getName ()
{
return (name);
}
void Print ()
{
printf ("%s, ", name);
}
#ifdef USER_PROGRAM
void DumpThreadState(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, unsigned virtual_y, unsigned blocksize);
// Draw the state for thread
#endif
// some of the private data for this class is listed above
unsigned long *stack; // Bottom of the stack
size_t stack_size; // Stack size
// NULL if this is the main thread
// (If NULL, don't deallocate stack)
unsigned int valgrind_id; // valgrind ID for the stack
#ifdef __SANITIZE_ADDRESS__
void *fake_stack; // Fake stack of libasan
#endif
private:
int main_stack; // Whether this is the main stack provided by OS
ThreadStatus status; // ready, running or blocked
const char *name;
void StackAllocate (VoidFunctionPtr func, void *arg);
// Allocate a stack for thread.
// Used internally by Start()
#ifdef USER_PROGRAM
// 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; // Address space this thread is running in.
#endif
};
void ThrashStack(void);
extern List ThreadList;
#ifdef USER_PROGRAM
void DumpThreadsState(FILE *output, AddrSpace *space, unsigned ptr_x, unsigned virtual_x, unsigned virtual_y, unsigned blocksize);
// Draw the states for threads
#endif
// Magical machine-dependent routines, defined in switch.s
extern "C"
{
// First frame on thread execution stack;
// enable interrupts
// 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

View file

@ -0,0 +1,52 @@
// threadtest.cc
// Simple test case for the threads assignment.
//
// Create two threads, and have them context switch
// back and forth between themselves by calling Thread::Yield,
// to illustratethe inner workings of the thread system.
//
// 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 "system.h"
//----------------------------------------------------------------------
// SimpleThread
// Loop 10 times, yielding the CPU to another ready thread
// each iteration.
//
// "which" is simply a number identifying the thread, for debugging
// purposes.
//----------------------------------------------------------------------
void
SimpleThread (void *arg)
{
int which = (long) arg;
int num;
for (num = 0; num < 10; num++)
{
printf ("*** thread %d looped %d times\n", which, num);
currentThread->Yield ();
}
}
//----------------------------------------------------------------------
// ThreadTest
// Set up a ping-pong between two threads, by forking a thread
// to call SimpleThread, and then calling SimpleThread ourselves.
//----------------------------------------------------------------------
void
ThreadTest ()
{
DEBUG ('t', "Entering SimpleTest\n");
Thread *t = new Thread ("forked thread");
t->Start (SimpleThread, (void*) 1);
SimpleThread (0);
}

115
code/threads/utility.cc Normal file
View file

@ -0,0 +1,115 @@
// utility.cc
// Debugging routines. Allows users to control whether to
// print DEBUG statements, based on a command line argument.
//
// 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 "utility.h"
#include <unistd.h>
// this seems to be dependent on how the compiler is configured.
// if you have problems with va_start, try both of these alternatives
#if defined(HOST_SNAKE) || defined(HOST_SPARC) || defined(HOST_i386) || defined(HOST_PPC) || defined(HOST_x86_64)
#include <stdarg.h>
#else
#include "/usr/include/stdarg.h"
#endif
static const char *enableFlags = NULL; // controls which DEBUG messages are printed
//----------------------------------------------------------------------
// SetColor
// Set the color for subsequent printouts
//
// This assumes that the TTY recognizes ANSI colors
//----------------------------------------------------------------------
void
SetColor (FILE *output, enum AnsiColor color)
{
if (isatty(fileno(output)))
fprintf(output, "\e[%dm", 30 + color);
}
//----------------------------------------------------------------------
// SetBold
// Set bold attribute for subsequent printouts
//
// This assumes that the TTY recognizes ANSI colors
//----------------------------------------------------------------------
void
SetBold (FILE *output)
{
if (isatty(fileno(output)))
fprintf(output, "\e[1m");
}
//----------------------------------------------------------------------
// ClearColor
// Clear the color to default for subsequent printouts
//
// This assumes that the TTY recognizes ANSI colors
//----------------------------------------------------------------------
void
ClearColor (FILE *output)
{
if (isatty(fileno(output)))
fprintf(output, "\e[0m");
}
//----------------------------------------------------------------------
// DebugInit
// Initialize so that only DEBUG messages with a flag in flagList
// will be printed.
//
// If the flag is "+", we enable all DEBUG messages.
//
// "flagList" is a string of characters for whose DEBUG messages are
// to be enabled.
//----------------------------------------------------------------------
void
DebugInit (const char *flagList)
{
enableFlags = flagList;
}
//----------------------------------------------------------------------
// DebugIsEnabled
// Return TRUE if DEBUG messages with "flag" are to be printed.
//----------------------------------------------------------------------
bool
DebugIsEnabled (char flag)
{
if (enableFlags != NULL)
return (strchr (enableFlags, flag) != 0)
|| (strchr (enableFlags, '+') != 0);
else
return FALSE;
}
//----------------------------------------------------------------------
// DEBUG
// Print a debug message, if flag is enabled. Like printf,
// only with an extra argument on the front.
//----------------------------------------------------------------------
void
DEBUG (char flag, const char *format, ...)
{
if (DebugIsEnabled (flag))
{
va_list ap;
// You will get an unused variable message here -- ignore it.
va_start (ap, format);
SetColor(stdout, ColorMagenta);
vfprintf (stdout, format, ap);
ClearColor(stdout);
va_end (ap);
fflush (stdout);
}
}

111
code/threads/utility.h Normal file
View file

@ -0,0 +1,111 @@
// utility.h
// Miscellaneous useful definitions, including debugging routines.
//
// The debugging routines allow the user to turn on selected
// debugging messages, controllable from the command line arguments
// passed to Nachos (-d). You are encouraged to add your own
// debugging flags. The pre-defined debugging flags are:
//
// '+' -- turn on all debug messages
// 't' -- thread system
// 's' -- semaphores, locks, and conditions
// 'i' -- interrupt emulation
// 'm' -- machine emulation (USER_PROGRAM)
// 'd' -- disk emulation (FILESYS)
// 'f' -- file system (FILESYS)
// 'a' -- address spaces (USER_PROGRAM)
// 'n' -- network emulation (NETWORK)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef UTILITY_H
#define UTILITY_H
#include "copyright.h"
// Miscellaneous useful routines
#include <bool.h> // Boolean values
#include <algorithm> // min
// Divide and either round up or down
#define divRoundDown(n,s) ((n) / (s))
#define divRoundUp(n,s) (((n) / (s)) + ((((n) % (s)) > 0) ? 1 : 0))
enum AnsiColor {
ColorBlack,
ColorRed,
ColorGreen,
ColorYellow,
ColorBlue,
ColorMagenta,
ColorCyan,
ColorWhite,
};
// This declares the type "VoidFunctionPtr" to be a "pointer to a
// function taking an integer argument and returning nothing". With
// such a function pointer (say it is "func"), we can call it like this:
//
// (*func) (17);
//
// This is used by Thread::Start and for interrupt handlers, as well
// as a couple of other places.
typedef void (*VoidFunctionPtr) (void *arg);
typedef void (*VoidFunctionPtr2) (void *arg, void *arg2);
typedef void (*VoidNoArgFunctionPtr) ();
// Include interface that isolates us from the host machine system library.
// Requires definition of bool, and VoidFunctionPtr
#include "sysdep.h"
// Interface to debugging routines.
extern void DebugInit (const char *flags); // enable printing debug messages
extern bool DebugIsEnabled (char flag); // Is this debug flag enabled?
extern void DEBUG (char flag, const char *format, ...); // Print debug message
// if flag is enabled
extern void SetColor(FILE *output, enum AnsiColor color);
extern void SetBold(FILE *output);
extern void ClearColor(FILE *output);
//----------------------------------------------------------------------
// ASSERT
// If condition is false, print a message and dump core.
// Useful for documenting assumptions in the code.
//
// NOTE: needs to be a #define, to be able to print the location
// where the error occurred.
//----------------------------------------------------------------------
#define ASSERT(condition) do { \
if (!(condition)) { \
SetColor(stderr, ColorRed); \
SetBold(stderr); \
fprintf(stderr, "Assertion %s failed: line %d, file \"%s:%d\"\n", \
#condition, \
__LINE__, __FILE__, __LINE__); \
ClearColor(stderr); \
fflush(stderr); \
Abort(); \
} \
} while(0)
/* A lot of classes should not be allowed to be copied, e.g. it doesn't make
* sense to copy a Semaphore. To enforce this, inherit from this class. */
class dontcopythis {
private:
dontcopythis (const dontcopythis &);
dontcopythis& operator= (const dontcopythis&);
public:
dontcopythis() {};
};
#endif /* UTILITY_H */