Initial version
This commit is contained in:
commit
6f405265a5
102 changed files with 14486 additions and 0 deletions
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
|
||||
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue