// synch.cc // Routines for synchronizing threads. Three kinds of // synchronization routines are defined here: semaphores, locks // and condition variables (the implementation of the last two // are left to the reader). // // Any implementation of a synchronization routine needs some // primitive atomic operation. We assume Nachos is running on // a uniprocessor, and thus atomicity can be provided by // turning off interrupts. While interrupts are disabled, no // context switch can occur, and thus the current thread is guaranteed // to hold the CPU throughout, until interrupts are reenabled. // // Because some of these routines might be called with interrupts // already disabled (Semaphore::V for one), instead of turning // on interrupts at the end of the atomic operation, we always simply // re-set the interrupt state back to its original value (whether // that be disabled or enabled). // // 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 "synch.h" #include "system.h" //---------------------------------------------------------------------- // Semaphore::Semaphore // Initialize a semaphore, so that it can be used for synchronization. // // "debugName" is an arbitrary name, useful for debugging. // "initialValue" is the initial value of the semaphore. //---------------------------------------------------------------------- Semaphore::Semaphore(char* debugName, int initialValue) { name = debugName; value = initialValue; queue = new List; } //---------------------------------------------------------------------- // Semaphore::Semaphore // De-allocate semaphore, when no longer needed. Assume no one // is still waiting on the semaphore! //---------------------------------------------------------------------- Semaphore::~Semaphore() { delete queue; } //---------------------------------------------------------------------- // Semaphore::P // Wait until semaphore value > 0, then decrement. Checking the // value and decrementing must be done atomically, so we // need to disable interrupts before checking the value. // // Note that Thread::Sleep assumes that interrupts are disabled // when it is called. //---------------------------------------------------------------------- void Semaphore::P() { IntStatus oldLevel = interrupt->SetLevel(IntOff); // disable interrupts while (value == 0) { // semaphore not available queue->Append((void *)currentThread); // so go to sleep currentThread->Sleep(); } value--; // semaphore available, // consume its value (void) interrupt->SetLevel(oldLevel); // re-enable interrupts } //---------------------------------------------------------------------- // Semaphore::V // Increment semaphore value, waking up a waiter if necessary. // As with P(), this operation must be atomic, so we need to disable // interrupts. Scheduler::ReadyToRun() assumes that threads // are disabled when it is called. //---------------------------------------------------------------------- void Semaphore::V() { Thread *thread; IntStatus oldLevel = interrupt->SetLevel(IntOff); thread = (Thread *)queue->Remove(); if (thread != NULL) // make thread ready, consuming the V immediately scheduler->ReadyToRun(thread); value++; (void) interrupt->SetLevel(oldLevel); } // Dummy functions -- so we can compile our later assignments // Note -- without a correct implementation of Condition::Wait(), // the test case in the network assignment won't work! Lock::Lock(char* debugName="NONE") { name = debugName; status = FREE; m_lockHolder = NULL; m_lock = new Semaphore(name,1); } Lock::~Lock() { ASSERT(m_lockHolder == NULL && status == FREE); } void Lock::Acquire() { m_lock->P(); status = BUSY; m_lockHolder = currentThread; DEBUG('l', "%s Acquiring lock\n", m_lockHolder->getName()); } void Lock::Release() { ASSERT(m_lockHolder == currentThread); DEBUG('l', "%s releasing lock\n", m_lockHolder->getName()); status = FREE; m_lockHolder = NULL; m_lock->V(); } bool Lock::isHeldByCurrentThread() { if(currentThread==m_lockHolder) return true; else return false; } /////////////////////////////////////////////////////////// Condition::Condition(char* debugName) { //m_WaitingOnCondition = false; strcpy(name, debugName); } Condition::~Condition() { delete m_WaitQueue; m_WaitQueue = NULL; delete this; } //Method Name : Wait //Input Parameters : conditionLock, pointer to lock needing to acquire //Output Parameters : none //Algorithm : Turn interrupts off // : Release the lock // : Put ourself on m_WaitQueue // : Sleep // : AquireLock on wake up. void Condition::Wait(Lock* conditionLock) { interrupt->SetLevel(IntOff); conditionLock->Release(); m_WaitQueue->Append(currentThread); currentThread->Sleep(); conditionLock->Acquire(); ASSERT(FALSE); } //Method Name : Signal //Input Parameters : conditionLock, pointer to Lock to release //Output Parameters : none //Algorithm : assert that current thread has lock // : if anyone waiting, wake them up(put on ReadyList) // : otherwise, no operation performed void Condition::Signal(Lock* conditionLock) { ASSERT(conditionLock->isHeldByCurrentThread()); if(!m_WaitQueue->IsEmpty()) { scheduler->ReadyToRun((Thread*)m_WaitQueue->Remove()); } } void Condition::Broadcast(Lock* conditionLock) { ASSERT(conditionLock->isHeldByCurrentThread()); while(!m_WaitQueue->IsEmpty()) { Signal(conditionLock); } }