Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

MutexLock.h

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #ifndef __MUTEX_LOCK_ET__
00003 #define __MUTEX_LOCK_ET__
00004 
00005 #include <iostream>
00006 #ifndef PLATFORM_APERIOS
00007 #  include <unistd.h>
00008 #  include "SemaphoreManager.h"
00009 #else
00010 #  include <exception>
00011 #endif
00012 
00013 // If you want to use the same software-only lock on both
00014 // PLATFORM_LOCAL and Aperios, then uncomment this next line:
00015 //#define MUTEX_LOCK_ET_USE_SOFTWARE_ONLY
00016 
00017 // However, that's probably only of use if you want to debug a problem with the lock itself
00018 
00019 
00020 
00021 //! The main purpose of this base class is actually to allow setting of usleep_granularity across all locks
00022 /*! It would be nice if we just put functions in here so we could
00023  *  reference locks without regard to the number of doors, but
00024  *  then all processes which use the lock would have to have been
00025  *  created via fork to handle virtual calls properly, and I don't
00026  *  want to put that overhead on the otherwise lightweight SoundPlay
00027  *  process under Aperios. */
00028 class MutexLockBase {
00029 public:
00030   virtual ~MutexLockBase() {} //!< basic destructor
00031   
00032   static const unsigned int NO_OWNER=-1U; //!< marks as unlocked
00033   static unsigned int usleep_granularity; //!< the estimated cost in microseconds of usleep call itself -- value passed to usleep will be 10 times this (only used by software lock implementation on non-Aperios)
00034 
00035   //This section is only needed for the non-software-only locks, using the SemaphoreManager
00036 #ifndef PLATFORM_APERIOS
00037   class no_more_semaphores : public std::exception {
00038   public:
00039     no_more_semaphores() throw() : std::exception() {}
00040     virtual const char* what() const throw() { return "SemaphoreManager::getSemaphore() returned invalid()"; }
00041   };
00042   
00043   //! sets the SemaphoreManager which will hand out semaphores for any and all locks
00044   /*! see #preallocated for an explanation of why this function does what it does */
00045   static void setSemaphoreManager(SemaphoreManager* mgr) {
00046     if(mgr==NULL) {
00047       preallocated=*semgr;
00048       semgr=&preallocated;
00049     } else {
00050       *mgr=*semgr;
00051       semgr=mgr;
00052     }
00053   }
00054   static SemaphoreManager* getSemaphoreManager() {
00055     return semgr;
00056   }
00057   static void aboutToFork() {
00058     preallocated.aboutToFork();
00059   }
00060 protected:
00061   //! the global semaphore manager object for all locks, may point to preallocated during process initialization or destruction
00062   static SemaphoreManager* semgr;
00063   
00064   //! if a semaphore needs to be reserved, and #semgr is NULL, use #preallocated's current value and increment it
00065   /*! Here's the conundrum: each shared region needs a lock, each
00066    *  lock needs an ID from the semaphore manager, and the semaphore
00067    *  manager needs to be in a shared region to coordinate handing out
00068    *  IDs.  So this is resolved by having the locks check #semgr to see
00069    *  if it is initialized yet, and use this if it is not.
00070    *  Then, when the SemaphoreManager is assigned, we will copy over
00071    *  preallocated IDs from here
00072    *
00073    *  For reference, only MutexLock needs to worry about this because
00074    *  it's the only thing that's going to need an ID before the
00075    *  manager is created.*/
00076   static SemaphoreManager preallocated;
00077 #endif
00078 };
00079 
00080 
00081 
00082 #if !defined(PLATFORM_APERIOS) && !defined(MUTEX_LOCK_ET_USE_SOFTWARE_ONLY)
00083 #include "SemaphoreManager.h"
00084 
00085 //! Implements a mutual exclusion lock using semaphores (SYSV style through SemaphoreManager)
00086 /*! Use this to prevent more than one process from accessing a data structure
00087  *  at the same time (which often leads to unpredictable and unexpected results)
00088  *
00089  *  The template parameter specifies the maximum number of different processes
00090  *  which need to be protected.  This needs to be allocated ahead of time, as
00091  *  there doesn't seem to be a way to dynamically scale as needed without
00092  *  risking possible errors if two processes are both trying to set up at the
00093  *  same time.  Also, by using a template parameter, all data structures are
00094  *  contained within the class's memory allocation, so no pointers are involved.
00095  *
00096  *  Locks in this class can be recursive or non-recursive, depending
00097  *  whether you call releaseAll() or unlock().  If you lock 5 times, then
00098  *  you need to call unlock() 5 times as well before it will be
00099  *  unlocked.  However, if you lock 5 times, just one call to releaseAll()
00100  *  will undo all 5 levels of locking.
00101  *
00102  *  Just remember, unlock() releases one level.  But releaseAll() completely unlocks.
00103  *
00104  *  Note that there is no check that the process doing the unlocking is the one
00105  *  that actually has the lock.  Be careful about this.
00106  *
00107  *  @warning Doing mutual exclusion in software is tricky business, be careful about any
00108  *  modifications you make!
00109  */
00110 template<unsigned int num_doors>
00111 class MutexLock : public MutexLockBase {
00112 public:
00113   //! constructor, gets a new semaphore from the semaphore manager
00114   MutexLock()
00115     : sem(semgr->getSemaphore()), owner_index(NO_OWNER)
00116   {
00117     if(sem==semgr->invalid())
00118       throw no_more_semaphores();
00119     semgr->setValue(sem,0);
00120   }
00121   
00122   //! constructor, use this if you already have a semaphore id you want to use from semaphore manager
00123   MutexLock(SemaphoreManager::semid_t semid)
00124     : sem(semid), owner_index(NO_OWNER)
00125   {
00126     if(sem==semgr->invalid())
00127       throw no_more_semaphores();
00128     semgr->setValue(sem,0);
00129   }
00130   
00131   //! destructor, releases semaphore back to semaphore manager
00132   ~MutexLock() {
00133     owner_index=NO_OWNER;
00134     if(semgr!=NULL && !semgr->hadFault())
00135       semgr->releaseSemaphore(sem);
00136     else
00137       std::cerr << "Warning: MutexLock leaked semaphore " << sem << " because SemaphoreManager is NULL" << std::endl;
00138   }
00139   
00140   //! blocks until lock is achieved.  This is done efficiently using a SysV style semaphore
00141   /*! You should pass some process-specific ID number as the input - just
00142    *  make sure no other process will be using the same value. */
00143   void lock(int id) {
00144     if(owner_index!=static_cast<unsigned>(id)) {
00145       //have to wait and then claim lock
00146       if(semgr!=NULL && !semgr->hadFault())
00147         semgr->testZero_add(sem,1);
00148       else
00149         std::cerr << "Warning: MutexLock assuming lock of " << sem << " because SemaphoreManager is NULL" << std::endl;
00150       owner_index=id;
00151     } else {
00152       //we already have lock, add one to its lock level
00153       if(semgr!=NULL && !semgr->hadFault())
00154         semgr->raise(sem,1);
00155       else
00156         std::cerr << "Warning: MutexLock assuming lock of " << sem << " because SemaphoreManager is NULL" << std::endl;
00157     }
00158   }
00159   
00160   //! attempts to get a lock, returns true if it succeeds
00161   /*! You should pass some process-specific ID number as the input - just
00162    *  make sure no other process will be using the same value.*/
00163   bool try_lock(int id) {
00164     if(semgr==NULL || semgr->hadFault()) {
00165       std::cerr << "Warning: MutexLock assuming try_lock success of " << sem << " because SemaphoreManager is NULL" << std::endl;
00166       owner_index=id;
00167       return true;
00168     }
00169     if(owner()==id) {
00170       //we already have lock, add one to its lock level
00171       semgr->raise(sem,1);
00172       return true;
00173     } else {
00174       if(semgr->testZero_add(sem,1,false)) {
00175         owner_index=id;
00176         return true;
00177       } else
00178         return false;
00179     }
00180   }
00181   
00182   //! releases one recursive lock-level from whoever has the current lock
00183   inline void unlock() {
00184     if(semgr==NULL || semgr->hadFault()) {
00185       std::cerr << "Warning: MutexLock assuming unlock of " << sem << " from " << owner_index << " because SemaphoreManager is NULL" << std::endl;
00186       owner_index=NO_OWNER;
00187       return;
00188     }
00189     if(semgr->getValue(sem)<=0) {
00190       std::cerr << "Warning: MutexLock::unlock caused underflow" << std::endl;
00191       owner_index=NO_OWNER;
00192       return;
00193     }
00194     if(semgr->getValue(sem)==1)
00195       owner_index=NO_OWNER;
00196     if(!semgr->lower(sem,1,false))
00197       std::cerr << "Warning: MutexLock::unlock caused strange underflow" << std::endl;
00198   }
00199   
00200   //! completely unlocks, regardless of how many times a recursive lock has been obtained
00201   void releaseAll() {
00202     owner_index=NO_OWNER;
00203     if(semgr==NULL || semgr->hadFault()) {
00204       std::cerr << "Warning: MutexLock assuming releaseAll of " << sem << " because SemaphoreManager is NULL" << std::endl;
00205       return;
00206     }
00207     semgr->setValue(sem,0);
00208   }
00209   
00210   //! returns the lockcount
00211   unsigned int get_lock_level() const {
00212     if(semgr==NULL || semgr->hadFault())
00213       return (owner_index==NO_OWNER) ? 0 : 1;
00214     else
00215       return semgr->getValue(sem);
00216   }
00217   
00218   //! returns the current owner's id
00219   inline int owner() { return owner_index; }
00220   
00221 protected:
00222   SemaphoreManager::semid_t sem; //!< the SysV semaphore number
00223   unsigned int owner_index; //!< holds the tekkotsu process id of the current lock owner
00224 };
00225 
00226 
00227 
00228 
00229 #else //SOFTWARE ONLY mutual exclusion, used on Aperios, or if MUTEX_LOCK_ET_USE_SOFTWARE_ONLY is defined
00230 
00231 
00232 
00233 
00234 //#define MUTEX_LOCK_ET_USE_SPINCOUNT
00235 
00236 //! A software only mutual exclusion lock. (does not depend on processor or OS support)
00237 /*! Use this to prevent more than one process from accessing a data structure
00238  *  at the same time (which often leads to unpredictable and unexpected results)
00239  *
00240  *  The template parameter specifies the maximum number of different processes
00241  *  which need to be protected.  This needs to be allocated ahead of time, as
00242  *  there doesn't seem to be a way to dynamically scale as needed without
00243  *  risking possible errors if two processes are both trying to set up at the
00244  *  same time.  Also, by using a template parameter, all data structures are
00245  *  contained within the class's memory allocation, so no pointers are involved.
00246  *
00247  *  Locks in this class can be recursive or non-recursive, depending
00248  *  whether you call releaseAll() or unlock().  If you lock 5 times, then
00249  *  you need to call unlock() 5 times as well before it will be
00250  *  unlocked.  However, if you lock 5 times, just one call to releaseAll()
00251  *  will undo all 5 levels of locking.
00252  *
00253  *  Just remember, unlock() releases one level.  But releaseAll() completely unlocks.
00254  *
00255  *  Note that there is no check that the process doing the unlocking is the one
00256  *  that actually has the lock.  Be careful about this.
00257  *
00258  *  @warning Doing mutual exclusion in software is tricky business, be careful about any
00259  *  modifications you make!
00260  *
00261  * Implements a first-come-first-served Mutex as laid out on page 11 of: \n
00262  * "A First Come First Served Mutal Exclusion Algorithm with Small Communication Variables" \n
00263  * Edward A. Lycklama, Vassos Hadzilacos - Aug. 1991
00264 */
00265 template<unsigned int num_doors>
00266 class MutexLock : public MutexLockBase {
00267  public:
00268   //! constructor, just calls the init() function.
00269   MutexLock() : doors_used(0), owner_index(NO_OWNER), lockcount(0) { init();  }
00270 
00271   //! blocks (by busy looping on do_try_lock()) until a lock is achieved
00272   /*! You should pass some process-specific ID number as the input - just
00273    *  make sure no other process will be using the same value.
00274    *  @todo - I'd like to not use a loop here */
00275   void lock(int id);
00276 
00277   //! attempts to get a lock, returns true if it succeeds
00278   /*! You should pass some process-specific ID number as the input - just
00279    *  make sure no other process will be using the same value.*/
00280   bool try_lock(int id);
00281 
00282   //! releases one recursive lock-level from whoever has the current lock
00283   inline void unlock();
00284 
00285   //! completely unlocks, regardless of how many times a recursive lock has been obtained
00286   void releaseAll() { lockcount=1; unlock(); }
00287   
00288   //! returns the lockcount
00289   unsigned int get_lock_level() const { return lockcount; }
00290 
00291   //! returns the current owner's id
00292   inline int owner() { return owner_index==NO_OWNER ? NO_OWNER : doors[owner_index].id; }
00293 
00294   //! allows you to reset one of the possible owners, so another process can take its place.  This is not tested
00295   void forget(int id);
00296 
00297 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00298   inline unsigned int getSpincount() { return spincount; } //!< returns the number of times the spin() function has been called
00299   inline unsigned int resetSpincount() { spincount=0; } //!< resets the counter of the number of times the spin() function has been called
00300 #endif
00301   
00302  protected:
00303   //! Does the work of trying to get a lock
00304   /*! Pass @c true for @a block if you want it to use FCFS blocking
00305    *  instead of just returning right away if another process has the lock */
00306   bool do_try_lock(unsigned int index, bool block);
00307 
00308   //! returns the internal index mapping to the id number supplied by the process
00309   unsigned int lookup(int id); //may create a new entry
00310 
00311 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00312   volatile unsigned int spincount; //!< handy to track how much time we're wasting
00313   void init() { spincount=0; }//memset((void*)doors,0,sizeof(doors)); } //!< just resets spincount
00314   inline void spin() {
00315     spincount++;
00316 #ifndef PLATFORM_APERIOS
00317     usleep(usleep_granularity*10); //this is a carefully chosen value intended to solve all the world's problems (not)
00318 #endif
00319   } //!< if you find a way to sleep for a few microseconds instead of busy waiting, put it here
00320 #else
00321   void init() { } //!< Doesn't do anything if you have the MUTEX_LOCK_ET_USE_SPINCOUNT undef'ed.  Used to do a memset, but that was causing problems....
00322   //memset((void*)doors,0,sizeof(doors)); } 
00323   inline void spin() {
00324 #ifndef PLATFORM_APERIOS
00325     usleep(usleep_granularity*10); //this is a carefully chosen value intended to solve all the world's problems (not)
00326 #endif
00327   } //!< If you find a way to sleep for a few microseconds instead of busy waiting, put it here
00328 #endif
00329     
00330   //! Holds per process shared info, one of these per process
00331   struct door_t {
00332     door_t() : id(NO_OWNER), FCFS_in_use(false), BL_ready(false), BL_in_use(false), turn('\0'), next_turn_bit('\0') {} //!< constructor
00333     //door_t(int i) : id(i), FCFS_in_use(false), BL_ready(false), BL_in_use(false), next_turn_bit('\0') {}
00334     int id; //!< process ID this doorway is assigned to
00335     volatile bool FCFS_in_use; //!< In FCFS doorway, corresponds to 'c_i'
00336     volatile bool BL_ready; //!< Signals past FCFS doorway, ready for BL doorway, corresponds to 'v_i'
00337     volatile bool BL_in_use; //!< Burns-Lamport doorway, corresponds to 'x_i'
00338     volatile unsigned char turn; //!< clock pulse, initial value doesn't matter
00339     unsigned char next_turn_bit; //!< selects which bit of turn will be flipped next
00340   };
00341 
00342   door_t doors[num_doors]; //!< holds all the doors
00343   unsigned int doors_used; //!< counts the number of doors used
00344   unsigned int owner_index; //!< holds the door index of the current lock owner
00345   unsigned int lockcount; //!< the depth of the lock, 0 when unlocked
00346 };
00347 
00348 
00349 template<unsigned int num_doors>
00350 void
00351 MutexLock<num_doors>::lock(int id) {
00352   if(owner()!=id)
00353     if(!do_try_lock(lookup(id),true)) {
00354       //spin(); //note the block argument above -- should never spin if that is actually working
00355       std::cout << "Warning: lock() failed to achieve lock" << std::endl;
00356     }
00357   lockcount++;
00358 }
00359 
00360 
00361 template<unsigned int num_doors>
00362 bool
00363 MutexLock<num_doors>::try_lock(int id) {
00364   if(owner()==id) {
00365     lockcount++;
00366     return true;
00367   } else {
00368     if(do_try_lock(lookup(id),false)) {
00369       lockcount++;
00370       return true;
00371     } else
00372       return false;
00373   }
00374 }
00375 
00376 
00377 template<unsigned int num_doors>
00378 void
00379 MutexLock<num_doors>::unlock() {
00380   if(lockcount==0)
00381     std::cerr << "Warning: MutexLock::unlock caused underflow" << std::endl;
00382   else if(--lockcount==0)
00383     if(owner_index!=NO_OWNER) {
00384       unsigned int tmp = owner_index;
00385       owner_index=NO_OWNER;
00386       doors[tmp].BL_in_use=false;
00387       doors[tmp].BL_ready=false;
00388       // *** Lock has been released *** //
00389     }
00390 }
00391 
00392 
00393 //! If you define this to do something more interesting, can use it to see what's going on in the locking process
00394 #define mutexdebugout(i,c) {}
00395 //#define mutexdebugout(i,c) { std::cout << ((char)(i==0?c:((i==1?'M':'a')+(c-'A')))) << std::flush; }
00396 
00397 
00398 template<unsigned int num_doors>
00399 bool
00400 MutexLock<num_doors>::do_try_lock(unsigned int i, bool block) {
00401   if(i==NO_OWNER) {
00402     std::cerr << "WARNING: new process attempted to lock beyond num_doors ("<<num_doors<<")" << std::endl;
00403     return false;
00404   }
00405   unsigned char S[num_doors]; // a local copy of everyone's doors
00406   // *** Entering FCFS doorway *** //
00407 //  pprintf(TextOutputStream,"**%d**\n",i);
00408 mutexdebugout(i,'A');
00409   doors[i].FCFS_in_use=true;
00410   for(unsigned int j=0; j<num_doors; j++)
00411     S[j]=doors[j].turn;
00412   doors[i].next_turn_bit=1-doors[i].next_turn_bit;
00413   doors[i].turn^=(1<<doors[i].next_turn_bit);
00414   doors[i].BL_ready=true;
00415   doors[i].FCFS_in_use=false;
00416   // *** Leaving FCFS doorway *** //
00417 mutexdebugout(i,'B');
00418   for(unsigned int j=0; j<num_doors; j++) {
00419 mutexdebugout(i,'C');
00420     while(doors[j].FCFS_in_use || (doors[j].BL_ready && S[j]==doors[j].turn))
00421       if(block)
00422         spin();
00423       else {
00424         doors[i].BL_ready=false;
00425         return false;
00426       }
00427 mutexdebugout(i,'D');
00428   }
00429   // *** Entering Burns-Lamport *** //
00430 mutexdebugout(i,'E');
00431   do {
00432     doors[i].BL_in_use=true;
00433     for(unsigned int t=0; t<i; t++)
00434       if(doors[t].BL_in_use) {
00435         doors[i].BL_in_use=false;
00436         if(!block) {
00437           doors[i].BL_ready=false;
00438           return false;
00439         }
00440 mutexdebugout(i,'F');
00441         while(doors[t].BL_in_use)
00442           spin();
00443 mutexdebugout(i,'G');
00444         break;
00445       }
00446   } while(!doors[i].BL_in_use);
00447   for(unsigned int t=i+1; t<num_doors; t++)
00448     while(doors[t].BL_in_use)
00449       spin();
00450   // *** Leaving Burns-Lamport ***//
00451   // *** Lock has been given *** //
00452 mutexdebugout(i,'H');
00453   owner_index=i;
00454   return true;
00455 }
00456 
00457 
00458 template<unsigned int num_doors>
00459 unsigned int
00460 MutexLock<num_doors>::lookup(int id) {
00461   // TODO - this could break if two new processes are adding themselves at the same time
00462   //        or an id is being forgotten at the same time
00463   //I'm expecting a very small number of processes to be involved
00464   //probably not worth overhead of doing something fancy like a sorted array
00465   unsigned int i;
00466   for(i=0; i<doors_used; i++)
00467     if(doors[i].id==id)
00468       return i;
00469   if(i==num_doors)
00470     return NO_OWNER;
00471   doors[i].id=id;
00472   doors_used++;
00473   return i;
00474 }
00475 
00476 
00477 template<unsigned int num_doors>
00478 void
00479 MutexLock<num_doors>::forget(int id) { //not tested thoroughly (or at all?)
00480   unsigned int i = lookup(id);
00481   do_try_lock(i,true);
00482   doors[i].id=doors[--doors_used].id;
00483   doors[doors_used].id=NO_OWNER;
00484   release();
00485 }
00486 
00487 #endif //MUTEX_LOCK_ET_USE_SOFTWARE_ONLY
00488 
00489 /*! @file 
00490  * @brief Defines MutexLock, a software only mutual exclusion lock.
00491  * @author ejt (Creator), Edward A. Lycklama, Vassos Hadzilacos (paper from which this was based)
00492  *
00493  * $Author: ejt $
00494  * $Name: tekkotsu-2_4_1 $
00495  * $Revision: 1.6 $
00496  * $State: Exp $
00497  * $Date: 2005/07/26 03:22:02 $
00498  */
00499 
00500 #endif

Tekkotsu v2.4.1
Generated Tue Aug 16 16:32:48 2005 by Doxygen 1.4.4