Homepage Demos Overview Downloads Tutorials 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 "debuget.h"
00006 #include <iostream>
00007 
00008 //#define MUTEX_LOCK_ET_USE_SPINCOUNT
00009 
00010 //! A software only mutual exclusion lock.
00011 /*! Use this to prevent more than one process from accessing a data structure
00012  *  at the same time (which often leads to unpredictable and unexpected results)
00013  *
00014  *  The template parameter specifies the maximum number of different processes
00015  *  which need to be protected.  This needs to be allocated ahead of time, as
00016  *  there doesn't seem to be a way to dynamically scale as needed without
00017  *  risking possible errors if two processes are both trying to set up at the
00018  *  same time.  Also, by using a template parameter, all data structures are
00019  *  contained within the class's memory allocation, so no pointers are involved.
00020  *
00021  *  Locks in this class can be recursive or non-recursive, depending
00022  *  whether you call release() or unlock().  If you lock 5 times, then
00023  *  you should call release() 5 times as well before it will be
00024  *  unlocked.  However, if you lock 5 times, just one call to unlock()
00025  *  will undo all 5 levels of locking.
00026  *
00027  *  Just remember, release() releases one level.  But unlock() completely unlocks.
00028  *
00029  *  Note that there is no check that the process doing the unlocking is the one
00030  *  that actually has the lock.  Be careful about this.
00031  *
00032  *  @warning Doing mutual exclusion in software is tricky business, be careful about any
00033  *  modifications you make!
00034  *
00035  * Implements a first-come-first-served Mutex as laid out on page 11 of: \n
00036  * "A First Come First Served Mutal Exclusion Algorithm with Small Communication Variables" \n
00037  * Edward A. Lycklama, Vassos Hadzilacos - Aug. 1991
00038 */
00039 template<unsigned int num_doors>
00040 class MutexLock {
00041  public:
00042   static const unsigned int NO_OWNER=-1U; //!< marks as unlocked
00043 
00044   //! constructor, just calls the init() function.
00045   MutexLock() : doors_used(0), owner_index(NO_OWNER), lockcount(0) { init();  }
00046 
00047   //! blocks (by busy looping on do_try_lock()) until a lock is achieved
00048   /*! You should pass some process-specific ID number as the input - just
00049    *  make sure no other process will be using the same value.
00050    *
00051    *  This is not a recursive lock - repeated locks still only require one
00052    *  release to undo them.
00053    *  @todo - I'd like to not use a loop here */
00054   void lock(int id);
00055 
00056   //! attempts to get a lock, returns true if it succeeds
00057   /*! You should pass some process-specific ID number as the input - just
00058    *  make sure no other process will be using the same value.
00059    *
00060    *  This is not a recursive lock - repeated locks still only require one
00061    *  release to undo them. */
00062   bool try_lock(int id);
00063 
00064   //! releases one recursive lock-level from whoever has the current lock
00065   void release();
00066 
00067   //! completely unlocks, regardless of how many times a recursive lock has been obtained
00068   inline void unlock() { lockcount=1; release(); }
00069 
00070   //! returns the lockcount
00071   unsigned int get_lock_level() const { return lockcount; }
00072 
00073   //! returns the current owner's id
00074   inline int owner() { return owner_index==NO_OWNER ? NO_OWNER : doors[owner_index].id; }
00075 
00076   //! allows you to reset one of the possible owners, so another process can take its place.  This is not tested
00077   void forget(int id);
00078 
00079 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00080   inline unsigned int getSpincount() { return spincount; } //!< returns the number of times the spin() function has been called
00081   inline unsigned int resetSpincount() { spincount=0; } //!< resets the counter of the number of times the spin() function has been called
00082 #endif
00083   
00084  protected:
00085   //! Does the work of trying to get a lock
00086   /*! Pass @c true for @a block if you want it to use FCFS blocking
00087    *  instead of just returning right away if another process has the lock */
00088   bool do_try_lock(unsigned int index, bool block);
00089 
00090   //! returns the internal index mapping to the id number supplied by the process
00091   unsigned int lookup(int id); //may create a new entry
00092 
00093 #ifdef MUTEX_LOCK_ET_USE_SPINCOUNT
00094   volatile unsigned int spincount; //!< handy to track how much time we're wasting
00095   void init() { spincount=0; }//memset((void*)doors,0,sizeof(doors)); } //!< just resets spincount
00096   inline void spin() { spincount++; } //!< if you find a way to sleep for a few microseconds instead of busy waiting, put it here
00097 #else
00098   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....
00099   //memset((void*)doors,0,sizeof(doors)); } 
00100   inline void spin() {} //!< If you find a way to sleep for a few microseconds instead of busy waiting, put it here
00101 #endif
00102     
00103   //! Holds per process shared info, one of these per process
00104   struct door_t {
00105     door_t() : id(NO_OWNER), FCFS_in_use(false), BL_ready(false), BL_in_use(false), turn('\0'), next_turn_bit('\0') {} //!< constructor
00106     //door_t(int i) : id(i), FCFS_in_use(false), BL_ready(false), BL_in_use(false), next_turn_bit('\0') {}
00107     int id; //!< process ID this doorway is assigned to
00108     volatile bool FCFS_in_use; //!< In FCFS doorway, corresponds to 'c_i'
00109     volatile bool BL_ready; //!< Signals past FCFS doorway, ready for BL doorway, corresponds to 'v_i'
00110     volatile bool BL_in_use; //!< Burns-Lamport doorway, corresponds to 'x_i'
00111     volatile unsigned char turn; //!< clock pulse, initial value doesn't matter
00112     unsigned char next_turn_bit; //!< selects which bit of turn will be flipped next
00113   };
00114 
00115   door_t doors[num_doors]; //!< holds all the doors
00116   unsigned int doors_used; //!< counts the number of doors used
00117   unsigned int owner_index; //!< holds the door index of the current lock owner
00118   unsigned int lockcount; //!< the depth of the lock, 0 when unlocked
00119 };
00120 
00121 
00122 template<unsigned int num_doors>
00123 void
00124 MutexLock<num_doors>::lock(int id) {
00125   if(owner()!=id)
00126     while(!do_try_lock(lookup(id),true))
00127       spin();
00128   lockcount++;
00129 }
00130 
00131 
00132 template<unsigned int num_doors>
00133 bool
00134 MutexLock<num_doors>::try_lock(int id) {
00135   if(owner()==id) {
00136     lockcount++;
00137     return true;
00138   } else {
00139     if(do_try_lock(lookup(id),false)) {
00140       lockcount++;
00141       return true;
00142     } else
00143       return false;
00144   }
00145 }
00146 
00147 
00148 template<unsigned int num_doors>
00149 void
00150 MutexLock<num_doors>::release() {
00151   if(lockcount>0)
00152     if(--lockcount==0)
00153       if(owner_index!=NO_OWNER) {
00154         unsigned int tmp = owner_index;
00155         owner_index=NO_OWNER;
00156         doors[tmp].BL_in_use=false;
00157         doors[tmp].BL_ready=false;
00158         // *** Lock has been released *** //
00159       }
00160 }
00161 
00162 
00163 //! If you define this to do something more interesting, can use it to see what's going on in the locking process
00164 #define mutexdebugout(i,c) {}
00165 //#define mutexdebugout(i,c) { std::cout << ((char)(i==0?c:((i==1?'M':'a')+(c-'A')))) << std::flush; }
00166 
00167 
00168 template<unsigned int num_doors>
00169 bool
00170 MutexLock<num_doors>::do_try_lock(unsigned int i, bool block) {
00171   if(i==NO_OWNER) {
00172     std::cerr << "WARNING: new process attempted to lock beyond num_doors ("<<num_doors<<")" << std::endl;
00173     return false;
00174   }
00175   unsigned char S[num_doors]; // a local copy of everyone's doors
00176   // *** Entering FCFS doorway *** //
00177 //  pprintf(TextOutputStream,"**%d**\n",i);
00178 mutexdebugout(i,'A');
00179   doors[i].FCFS_in_use=true;
00180   for(unsigned int j=0; j<num_doors; j++)
00181     S[j]=doors[j].turn;
00182   doors[i].next_turn_bit=1-doors[i].next_turn_bit;
00183   doors[i].turn^=(1<<doors[i].next_turn_bit);
00184   doors[i].BL_ready=true;
00185   doors[i].FCFS_in_use=false;
00186   // *** Leaving FCFS doorway *** //
00187 mutexdebugout(i,'B');
00188   for(unsigned int j=0; j<num_doors; j++) {
00189 mutexdebugout(i,'C');
00190     while(doors[j].FCFS_in_use || (doors[j].BL_ready && S[j]==doors[j].turn))
00191       if(block)
00192         spin();
00193       else {
00194         doors[i].BL_ready=false;
00195         return false;
00196       }
00197 mutexdebugout(i,'D');
00198   }
00199   // *** Entering Burns-Lamport *** //
00200 mutexdebugout(i,'E');
00201   do {
00202     doors[i].BL_in_use=true;
00203     for(unsigned int t=0; t<i; t++)
00204       if(doors[t].BL_in_use) {
00205         doors[i].BL_in_use=false;
00206         if(!block) {
00207           doors[i].BL_ready=false;
00208           return false;
00209         }
00210 mutexdebugout(i,'F');
00211         while(doors[t].BL_in_use)
00212           spin();
00213 mutexdebugout(i,'G');
00214         break;
00215       }
00216   } while(!doors[i].BL_in_use);
00217   for(unsigned int t=i+1; t<num_doors; t++)
00218     while(doors[t].BL_in_use)
00219       spin();
00220   // *** Leaving Burns-Lamport ***//
00221   // *** Lock has been given *** //
00222 mutexdebugout(i,'H');
00223   owner_index=i;
00224   return true;
00225 }
00226 
00227 
00228 template<unsigned int num_doors>
00229 unsigned int
00230 MutexLock<num_doors>::lookup(int id) {
00231   // TODO - this could break if two new processes are adding themselves at the same time
00232   //        or an id is being forgotten at the same time
00233   //I'm expecting a very small number of processes to be involved
00234   //probably not worth overhead of doing something fancy like a sorted array
00235   unsigned int i;
00236   for(i=0; i<doors_used; i++)
00237     if(doors[i].id==id)
00238       return i;
00239   if(i==num_doors)
00240     return NO_OWNER;
00241   doors[i].id=id;
00242   doors_used++;
00243   return i;
00244 }
00245 
00246 
00247 template<unsigned int num_doors>
00248 void
00249 MutexLock<num_doors>::forget(int id) { //not tested thoroughly
00250   unsigned int i = lookup(id);
00251   do_try_lock(i,true);
00252   doors[i].id=doors[--doors_used].id;
00253   doors[doors_used].id=NO_OWNER;
00254   release();
00255 }
00256 
00257 
00258 /*! @file 
00259  * @brief Defines MutexLock, a software only mutual exclusion lock.
00260  * @author ejt (Creator), Edward A. Lycklama, Vassos Hadzilacos (paper from which this was based)
00261  *
00262  * $Author: ejt $
00263  * $Name: tekkotsu-2_2 $
00264  * $Revision: 1.9 $
00265  * $State: Exp $
00266  * $Date: 2004/02/12 17:48:59 $
00267  */
00268 
00269 #endif

Tekkotsu v2.2
Generated Tue Oct 19 14:19:15 2004 by Doxygen 1.3.9.1