This commit is contained in:
2024-12-30 05:59:42 +08:00
parent bf78b95c9d
commit 72320ede22
42 changed files with 1758 additions and 1556 deletions

View File

@@ -1,4 +1,4 @@
// thread.cc
// thread.cc
// Routines to manage threads. These are the main operations:
//
// Fork -- create a thread to run a procedure concurrently
@@ -9,11 +9,11 @@
// 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
// In other words, it will not run again, until explicitly
// put back on the ready queue.
//
// Copyright (c) 1992-1996 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#include "copyright.h"
@@ -33,19 +33,20 @@ const int STACK_FENCEPOST = 0xdedbeef;
// "threadName" is an arbitrary string, useful for debugging.
//----------------------------------------------------------------------
Thread::Thread(char* threadName, int threadID)
Thread::Thread(char *threadName, int threadID)
{
ID = threadID;
name = threadName;
stackTop = NULL;
stack = NULL;
status = JUST_CREATED;
for (int i = 0; i < MachineStateSize; i++) {
machineState[i] = NULL; // not strictly necessary, since
// new thread ignores contents
// of machine registers
}
space = NULL;
ID = threadID;
name = threadName;
stackTop = NULL;
stack = NULL;
status = JUST_CREATED;
for (int i = 0; i < MachineStateSize; i++)
{
machineState[i] = NULL; // not strictly necessary, since
// new thread ignores contents
// of machine registers
}
space = NULL;
}
//----------------------------------------------------------------------
@@ -62,15 +63,15 @@ Thread::Thread(char* threadName, int threadID)
Thread::~Thread()
{
DEBUG(dbgThread, "Deleting thread: " << name);
ASSERT(this != kernel->currentThread);
if (stack != NULL)
DeallocBoundedArray((char *) stack, StackSize * sizeof(int));
DEBUG(dbgThread, "Deleting thread: " << name);
ASSERT(this != kernel->currentThread);
if (stack != NULL)
DeallocBoundedArray((char *)stack, StackSize * sizeof(int));
}
//----------------------------------------------------------------------
// Thread::Fork
// Invoke (*func)(arg), allowing caller and callee to execute
// Invoke (*func)(arg), allowing caller and callee to execute
// concurrently.
//
// NOTE: although our definition allows only a single argument
@@ -83,26 +84,25 @@ Thread::~Thread()
// 2. Initialize the stack so that a call to SWITCH will
// cause it to run the procedure
// 3. Put the thread on the ready queue
//
//
// "func" is the procedure to run concurrently.
// "arg" is a single argument to be passed to the procedure.
//----------------------------------------------------------------------
void
Thread::Fork(VoidFunctionPtr func, void *arg)
void Thread::Fork(VoidFunctionPtr func, void *arg)
{
Interrupt *interrupt = kernel->interrupt;
Scheduler *scheduler = kernel->scheduler;
IntStatus oldLevel;
Interrupt *interrupt = kernel->interrupt;
Scheduler *scheduler = kernel->scheduler;
IntStatus oldLevel;
DEBUG(dbgThread, "Forking thread: " << name << " f(a): " << (int) func << " " << arg);
StackAllocate(func, arg);
DEBUG(dbgThread, "Forking thread: " << name << " f(a): " << (int)func << " " << arg);
StackAllocate(func, arg);
oldLevel = interrupt->SetLevel(IntOff);
scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts
// are disabled!
(void) interrupt->SetLevel(oldLevel);
}
oldLevel = interrupt->SetLevel(IntOff);
scheduler->ReadyToRun(this); // ReadyToRun assumes that interrupts
// are disabled!
(void)interrupt->SetLevel(oldLevel);
}
//----------------------------------------------------------------------
// Thread::CheckOverflow
@@ -119,16 +119,16 @@ Thread::Fork(VoidFunctionPtr func, void *arg)
// Don't do this: void foo() { int bigArray[10000]; ... }
//----------------------------------------------------------------------
void
Thread::CheckOverflow()
void Thread::CheckOverflow()
{
if (stack != NULL) {
#ifdef HPUX // Stacks grow upward on the Snakes
ASSERT(stack[StackSize - 1] == STACK_FENCEPOST);
if (stack != NULL)
{
#ifdef HPUX // Stacks grow upward on the Snakes
ASSERT(stack[StackSize - 1] == STACK_FENCEPOST);
#else
ASSERT(*stack == STACK_FENCEPOST);
ASSERT(*stack == STACK_FENCEPOST);
#endif
}
}
}
//----------------------------------------------------------------------
@@ -137,28 +137,27 @@ Thread::CheckOverflow()
// executing the forked procedure.
//
// It's main responsibilities are:
// 1. deallocate the previously running thread if it finished
// 1. deallocate the previously running thread if it finished
// (see Thread::Finish())
// 2. enable interrupts (so we can get time-sliced)
//----------------------------------------------------------------------
void
Thread::Begin ()
void Thread::Begin()
{
ASSERT(this == kernel->currentThread);
DEBUG(dbgThread, "Beginning thread: " << name);
kernel->scheduler->CheckToBeDestroyed();
kernel->interrupt->Enable();
}
//----------------------------------------------------------------------
// Thread::Finish
// Called by ThreadRoot when a thread is done executing the
// Called by ThreadRoot when a thread is done executing the
// forked procedure.
//
// NOTE: we can't immediately de-allocate the thread data structure
// or the execution stack, because we're still running in the thread
// NOTE: we can't immediately de-allocate the thread data structure
// or the execution stack, because we're still running in the thread
// and we're still on the stack! Instead, we tell the scheduler
// to call the destructor, once it is running in the context of a different thread.
//
@@ -167,18 +166,16 @@ Thread::Begin ()
//----------------------------------------------------------------------
//
void
Thread::Finish ()
void Thread::Finish()
{
(void) kernel->interrupt->SetLevel(IntOff);
ASSERT(this == kernel->currentThread);
(void)kernel->interrupt->SetLevel(IntOff);
ASSERT(this == kernel->currentThread);
DEBUG(dbgThread, "Finishing thread: " << name);
Sleep(TRUE); // invokes SWITCH
// not reached
DEBUG(dbgThread, "Finishing thread: " << name);
Sleep(TRUE); // invokes SWITCH
// not reached
}
//----------------------------------------------------------------------
// Thread::Yield
// Relinquish the CPU if any other thread is ready to run.
@@ -192,33 +189,33 @@ Thread::Finish ()
// 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.
// original state, in case we are called with interrupts disabled.
//
// Similar to Thread::Sleep(), but a little different.
//----------------------------------------------------------------------
void
Thread::Yield ()
void Thread::Yield()
{
Thread *nextThread;
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
Thread *nextThread;
IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff);
ASSERT(this == kernel->currentThread);
ASSERT(this == kernel->currentThread);
DEBUG(dbgThread, "Yielding thread: " << name);
DEBUG(dbgThread, "Yielding thread: " << name);
nextThread = kernel->scheduler->FindNextToRun();
if (nextThread != NULL) {
kernel->scheduler->ReadyToRun(this);
kernel->scheduler->Run(nextThread, FALSE);
}
(void) kernel->interrupt->SetLevel(oldLevel);
nextThread = kernel->scheduler->FindNextToRun();
if (nextThread != NULL)
{
kernel->scheduler->ReadyToRun(this);
kernel->scheduler->Run(nextThread, FALSE);
}
(void)kernel->interrupt->SetLevel(oldLevel);
}
//----------------------------------------------------------------------
// Thread::Sleep
// Relinquish the CPU, because the current thread has either
// finished or is blocked waiting on a synchronization
// finished or is blocked waiting on a synchronization
// variable (Semaphore, Lock, or Condition). In the latter case,
// eventually some thread will wake this thread up, and put it
// back on the ready queue, so that it can be re-scheduled.
@@ -231,34 +228,34 @@ Thread::Yield ()
//
// 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
// disable interrupts for atomicity. We need interrupts off
// so that there can't be a time slice between pulling the first thread
// off the ready list, and switching to it.
//----------------------------------------------------------------------
void
Thread::Sleep (bool finishing)
void Thread::Sleep(bool finishing)
{
Thread *nextThread;
Thread *nextThread;
ASSERT(this == kernel->currentThread);
ASSERT(kernel->interrupt->getLevel() == IntOff);
ASSERT(this == kernel->currentThread);
ASSERT(kernel->interrupt->getLevel() == IntOff);
DEBUG(dbgThread, "Sleeping thread: " << name);
DEBUG(dbgThread, "Sleeping thread: " << name);
status = BLOCKED;
//cout << "debug Thread::Sleep " << name << "wait for Idle\n";
while ((nextThread = kernel->scheduler->FindNextToRun()) == NULL) {
kernel->interrupt->Idle(); // no one to run, wait for an interrupt
}
// returns when it's time for us to run
kernel->scheduler->Run(nextThread, finishing);
status = BLOCKED;
// cout << "debug Thread::Sleep " << name << "wait for Idle\n";
while ((nextThread = kernel->scheduler->FindNextToRun()) == NULL)
{
kernel->interrupt->Idle(); // no one to run, wait for an interrupt
}
// returns when it's time for us to run
kernel->scheduler->Run(nextThread, finishing);
}
//----------------------------------------------------------------------
// ThreadBegin, ThreadFinish, ThreadPrint
// Dummy functions because C++ does not (easily) allow pointers to member
// functions. So we create a dummy C function
// (which we can pass a pointer to), that then simply calls the
// (which we can pass a pointer to), that then simply calls the
// member function.
//----------------------------------------------------------------------
@@ -277,16 +274,19 @@ void ThreadPrint(Thread *t) { t->Print(); }
static void *
PLabelToAddr(void *plabel)
{
int funcPtr = (int) plabel;
int funcPtr = (int)plabel;
if (funcPtr & 0x02) {
// L-Field is set. This is a PLT pointer
funcPtr -= 2; // Get rid of the L bit
return (*(void **)funcPtr);
} else {
// L-field not set.
return plabel;
}
if (funcPtr & 0x02)
{
// L-Field is set. This is a PLT pointer
funcPtr -= 2; // Get rid of the L bit
return (*(void **)funcPtr);
}
else
{
// L-field not set.
return plabel;
}
}
#endif
@@ -302,62 +302,60 @@ PLabelToAddr(void *plabel)
// "arg" is the parameter to be passed to the procedure
//----------------------------------------------------------------------
void
Thread::StackAllocate (VoidFunctionPtr func, void *arg)
void Thread::StackAllocate(VoidFunctionPtr func, void *arg)
{
stack = (int *) AllocBoundedArray(StackSize * sizeof(int));
stack = (int *)AllocBoundedArray(StackSize * sizeof(int));
#ifdef PARISC
// HP stack works from low addresses to high addresses
// everyone else works the other way: from high addresses to low addresses
stackTop = stack + 16; // HP requires 64-byte frame marker
stack[StackSize - 1] = STACK_FENCEPOST;
// HP stack works from low addresses to high addresses
// everyone else works the other way: from high addresses to low addresses
stackTop = stack + 16; // HP requires 64-byte frame marker
stack[StackSize - 1] = STACK_FENCEPOST;
#endif
#ifdef SPARC
stackTop = stack + StackSize - 96; // SPARC stack must contains at
// least 1 activation record
// to start with.
*stack = STACK_FENCEPOST;
#endif
stackTop = stack + StackSize - 96; // SPARC stack must contains at
// least 1 activation record
// to start with.
*stack = STACK_FENCEPOST;
#endif
#ifdef PowerPC // RS6000
stackTop = stack + StackSize - 16; // RS6000 requires 64-byte frame marker
*stack = STACK_FENCEPOST;
#endif
#ifdef PowerPC // RS6000
stackTop = stack + StackSize - 16; // RS6000 requires 64-byte frame marker
*stack = STACK_FENCEPOST;
#endif
#ifdef DECMIPS
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
*stack = STACK_FENCEPOST;
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
*stack = STACK_FENCEPOST;
#endif
#ifdef ALPHA
stackTop = stack + StackSize - 8; // -8 to be on the safe side!
*stack = STACK_FENCEPOST;
stackTop = stack + StackSize - 8; // -8 to be on the safe side!
*stack = STACK_FENCEPOST;
#endif
#ifdef x86
// the x86 passes the return address on the stack. In order for SWITCH()
// to go to ThreadRoot when we switch to this thread, the return addres
// used in SWITCH() must be the starting address of ThreadRoot.
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
*(--stackTop) = (int) ThreadRoot;
*stack = STACK_FENCEPOST;
// the x86 passes the return address on the stack. In order for SWITCH()
// to go to ThreadRoot when we switch to this thread, the return addres
// used in SWITCH() must be the starting address of ThreadRoot.
stackTop = stack + StackSize - 4; // -4 to be on the safe side!
*(--stackTop) = (int)ThreadRoot;
*stack = STACK_FENCEPOST;
#endif
#ifdef PARISC
machineState[PCState] = PLabelToAddr(ThreadRoot);
machineState[StartupPCState] = PLabelToAddr(ThreadBegin);
machineState[InitialPCState] = PLabelToAddr(func);
machineState[InitialArgState] = arg;
machineState[WhenDonePCState] = PLabelToAddr(ThreadFinish);
machineState[PCState] = PLabelToAddr(ThreadRoot);
machineState[StartupPCState] = PLabelToAddr(ThreadBegin);
machineState[InitialPCState] = PLabelToAddr(func);
machineState[InitialArgState] = arg;
machineState[WhenDonePCState] = PLabelToAddr(ThreadFinish);
#else
machineState[PCState] = (void*)ThreadRoot;
machineState[StartupPCState] = (void*)ThreadBegin;
machineState[InitialPCState] = (void*)func;
machineState[InitialArgState] = (void*)arg;
machineState[WhenDonePCState] = (void*)ThreadFinish;
machineState[PCState] = (void *)ThreadRoot;
machineState[StartupPCState] = (void *)ThreadBegin;
machineState[InitialPCState] = (void *)func;
machineState[InitialArgState] = (void *)arg;
machineState[WhenDonePCState] = (void *)ThreadFinish;
#endif
}
@@ -367,38 +365,35 @@ Thread::StackAllocate (VoidFunctionPtr func, void *arg)
// 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
// 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()
void Thread::SaveUserState()
{
for (int i = 0; i < NumTotalRegs; i++)
userRegisters[i] = kernel->machine->ReadRegister(i);
userRegisters[i] = kernel->machine->ReadRegister(i);
}
//----------------------------------------------------------------------
// Thread::RestoreUserState
// Restore the CPU state of a user program on a context switch.
//
// Note that a user program thread has *two* sets of CPU registers --
// one for its state while executing user code, one for its state
// 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()
void Thread::RestoreUserState()
{
for (int i = 0; i < NumTotalRegs; i++)
kernel->machine->WriteRegister(i, userRegisters[i]);
for (int i = 0; i < NumTotalRegs; i++)
kernel->machine->WriteRegister(i, userRegisters[i]);
}
//----------------------------------------------------------------------
// SimpleThread
// Loop 5 times, yielding the CPU to another ready thread
// Loop 5 times, yielding the CPU to another ready thread
// each iteration.
//
// "which" is simply a number identifying the thread, for debugging
@@ -409,27 +404,40 @@ static void
SimpleThread(int which)
{
int num;
for (num = 0; num < 5; num++) {
cout << "*** thread " << which << " looped " << num << " times\n";
for (num = 0; num < 5; num++)
{
cout << "*** thread " << which << " looped " << num << " times\n";
kernel->currentThread->Yield();
}
}
//----------------------------------------------------------------------
// Thread::SelfTest
// Set up a ping-pong between two threads, by forking a thread
// Set up a ping-pong between two threads, by forking a thread
// to call SimpleThread, and then calling SimpleThread ourselves.
//----------------------------------------------------------------------
void
Thread::SelfTest()
void Thread::SelfTest()
{
DEBUG(dbgThread, "Entering Thread::SelfTest");
Thread *t = new Thread("forked thread", 1);
t->Fork((VoidFunctionPtr) SimpleThread, (void *) 1);
t->Fork((VoidFunctionPtr)SimpleThread, (void *)1);
kernel->currentThread->Yield();
SimpleThread(0);
}
// Todo ----
int Thread::getPriority() const
{
return priority;
}
void Thread::setPriority(int p)
{
ASSERT(p >= 0 && p <= 149);
priority = p;
}
// ---------