533 lines
17 KiB
C++
533 lines
17 KiB
C++
// 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
|
|
|
|
|