📄 thread.cc
字号:
// thread.cc // Routines to manage threads. These are the main operations://// Fork -- create a thread to run a procedure concurrently// with the caller (this is done in two steps -- first// allocate the Thread object, then call Fork on it)// Begin -- called when the forked procedure starts up, to turn// interrupts on and clean up after last thread// 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-1996 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 "sysdep.h"// this is put at the top of the execution stack, for detecting stack overflowsconst int STACK_FENCEPOST = 0xdedbeef;//----------------------------------------------------------------------// Thread::Thread// Initialize a thread control block, so that we can then call// Thread::Fork.//// "threadName" is an arbitrary string, useful for debugging.//----------------------------------------------------------------------Thread::Thread(char* threadName){ 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;}//----------------------------------------------------------------------// 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(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 // concurrently.//// NOTE: although our definition allows only a single 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::Fork(VoidFunctionPtr func, void *arg){ Interrupt *interrupt = kernel->interrupt; Scheduler *scheduler = kernel->scheduler; IntStatus oldLevel; 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);} //----------------------------------------------------------------------// 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]; ... }//----------------------------------------------------------------------voidThread::CheckOverflow(){ if (stack != NULL) {#ifdef HPUX // Stacks grow upward on the Snakes ASSERT(stack[StackSize - 1] == STACK_FENCEPOST);#else ASSERT(*stack == STACK_FENCEPOST);#endif }}//----------------------------------------------------------------------// Thread::Begin// Called by ThreadRoot when a thread is about to begin// executing the forked procedure.//// It's main responsibilities are:// 1. deallocate the previously running thread if it finished // (see Thread::Finish())// 2. enable interrupts (so we can get time-sliced)//----------------------------------------------------------------------voidThread::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 // 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 // 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.//// NOTE: we disable interrupts, because Sleep() assumes interrupts// are disabled.//----------------------------------------------------------------------//voidThread::Finish (){ (void) kernel->interrupt->SetLevel(IntOff); ASSERT(this == kernel->currentThread); DEBUG(dbgThread, "Finishing thread: " << name); Sleep(TRUE); // 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.//----------------------------------------------------------------------voidThread::Yield (){ Thread *nextThread; IntStatus oldLevel = kernel->interrupt->SetLevel(IntOff); ASSERT(this == kernel->currentThread); 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);}//----------------------------------------------------------------------// Thread::Sleep// Relinquish the CPU, because the current thread has either// 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.//// 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.//----------------------------------------------------------------------voidThread::Sleep (bool finishing){ Thread *nextThread; ASSERT(this == kernel->currentThread); ASSERT(kernel->interrupt->getLevel() == IntOff); DEBUG(dbgThread, "Sleeping thread: " << name); status = BLOCKED; 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 // member function.//----------------------------------------------------------------------static void ThreadFinish() { kernel->currentThread->Finish(); }static void ThreadBegin() { kernel->currentThread->Begin(); }void ThreadPrint(Thread *t) { t->Print(); }#ifdef PARISC//----------------------------------------------------------------------// PLabelToAddr// On HPUX, function pointers don't always directly point to code,// so we need to do the conversion.//----------------------------------------------------------------------static void *PLabelToAddr(void *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; }}#endif//----------------------------------------------------------------------// Thread::StackAllocate// Allocate and initialize an 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//----------------------------------------------------------------------voidThread::StackAllocate (VoidFunctionPtr func, void *arg){ 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;#endif#ifdef SPARC 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 DECMIPS 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;#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;#endif #ifdef PARISC 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;#endif}#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.//----------------------------------------------------------------------voidThread::SaveUserState(){ for (int i = 0; i < NumTotalRegs; 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 // while executing kernel code. This routine restores the former.//----------------------------------------------------------------------voidThread::RestoreUserState(){ for (int i = 0; i < NumTotalRegs; i++) kernel->machine->WriteRegister(i, userRegisters[i]);}//----------------------------------------------------------------------// SimpleThread// Loop 5 times, yielding the CPU to another ready thread // each iteration.//// "which" is simply a number identifying the thread, for debugging// purposes.//----------------------------------------------------------------------static voidSimpleThread(int which){ int num; 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 // to call SimpleThread, and then calling SimpleThread ourselves.//----------------------------------------------------------------------voidThread::SelfTest(){ DEBUG(dbgThread, "Entering Thread::SelfTest"); Thread *t = new Thread("forked thread"); t->Fork((VoidFunctionPtr) SimpleThread, (void *) 1); kernel->currentThread->Yield(); SimpleThread(0);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -