// 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 overflows const 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, 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; } //---------------------------------------------------------------------- // 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]; ... } //---------------------------------------------------------------------- void Thread::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) //---------------------------------------------------------------------- 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 // 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. //---------------------------------------------------------------------- // void Thread::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. //---------------------------------------------------------------------- void Thread::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. //---------------------------------------------------------------------- void Thread::Sleep(bool finishing) { Thread *nextThread; ASSERT(this == kernel->currentThread); ASSERT(kernel->interrupt->getLevel() == IntOff); 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); } //---------------------------------------------------------------------- // 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 //---------------------------------------------------------------------- void Thread::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. //---------------------------------------------------------------------- void Thread::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. //---------------------------------------------------------------------- void Thread::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 void SimpleThread(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. //---------------------------------------------------------------------- void Thread::SelfTest() { DEBUG(dbgThread, "Entering Thread::SelfTest"); Thread *t = new Thread("forked thread", 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; } // ---------