Initial version
This commit is contained in:
commit
6f405265a5
102 changed files with 14486 additions and 0 deletions
17
code/threads/Makefile
Normal file
17
code/threads/Makefile
Normal 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
14
code/threads/bool.h
Normal 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
25
code/threads/copyright.h
Normal 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
332
code/threads/list.cc
Normal 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
74
code/threads/list.h
Normal 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
190
code/threads/main.cc
Normal 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
182
code/threads/scheduler.cc
Normal 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
39
code/threads/scheduler.h
Normal 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
555
code/threads/switch.S
Normal 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
223
code/threads/switch.h
Normal 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
164
code/threads/synch.cc
Normal 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
148
code/threads/synch.h
Normal 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
101
code/threads/synchlist.cc
Normal 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
43
code/threads/synchlist.h
Normal 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
269
code/threads/system.cc
Normal 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
52
code/threads/system.h
Normal 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
533
code/threads/thread.cc
Normal 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
180
code/threads/thread.h
Normal 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
|
52
code/threads/threadtest.cc
Normal file
52
code/threads/threadtest.cc
Normal 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
115
code/threads/utility.cc
Normal 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
111
code/threads/utility.h
Normal 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 */
|
Loading…
Add table
Add a link
Reference in a new issue