📄 thread.cc
字号:
// thread.cc // Routines to manage threads. There are four 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)// 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"#define STACK_FENCEPOST 0xdeadbeef // this is put at the top of the // execution stack, for detecting // stack overflows//----------------------------------------------------------------------// 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;#ifdef USER_PROGRAM space = NULL;#endif}//----------------------------------------------------------------------// 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 \"%s\"\n", name); ASSERT(this != 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 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::Fork(VoidFunctionPtr func, int arg){ DEBUG('t', "Forking thread \"%s\" with func = 0x%x, arg = %d\n", name, (int) func, arg); 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]; ... }//----------------------------------------------------------------------voidThread::CheckOverflow(){ if (stack != NULL)#ifdef HOST_SNAKE // Stacks grow upward on the Snakes ASSERT(stack[StackSize - 1] == STACK_FENCEPOST);#else ASSERT((int) *stack == (int) 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.//----------------------------------------------------------------------//voidThread::Finish (){ (void) interrupt->SetLevel(IntOff); ASSERT(this == currentThread); DEBUG('t', "Finishing thread \"%s\"\n", getName()); 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.//----------------------------------------------------------------------voidThread::Yield (){ Thread *nextThread; IntStatus oldLevel = interrupt->SetLevel(IntOff); ASSERT(this == currentThread); DEBUG('t', "Yielding thread \"%s\"\n", 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.//----------------------------------------------------------------------voidThread::Sleep (){ Thread *nextThread; ASSERT(this == currentThread); ASSERT(interrupt->getLevel() == IntOff); DEBUG('t', "Sleeping thread \"%s\"\n", 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(); }void ThreadPrint(int arg){ Thread *t = (Thread *)arg; t->Print(); }//----------------------------------------------------------------------// 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, int arg){ stack = (int *) AllocBoundedArray(StackSize * sizeof(int));#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 // i386 & MIPS & SPARC 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;#else // HOST_MIPS || HOST_i386 stackTop = stack + StackSize - 4; // -4 to be on the safe side!#ifdef HOST_i386 // the 80386 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) = (int)ThreadRoot;#endif#endif // HOST_SPARC *stack = STACK_FENCEPOST;#endif // HOST_SNAKE machineState[PCState] = (int) ThreadRoot; machineState[StartupPCState] = (int) InterruptEnable; machineState[InitialPCState] = (int) func; machineState[InitialArgState] = arg; machineState[WhenDonePCState] = (int) 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.//----------------------------------------------------------------------voidThread::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.//----------------------------------------------------------------------voidThread::RestoreUserState(){ for (int i = 0; i < NumTotalRegs; i++) machine->WriteRegister(i, userRegisters[i]);}#endif
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -