// -*- c-basic-offset: 4 -*- #ifndef CLICK_SYNC_HH #define CLICK_SYNC_HH #include #include #include #if CLICK_LINUXMODULE || (CLICK_USERLEVEL && HAVE_MULTITHREAD) # define CLICK_MULTITHREAD_SPINLOCK 1 #endif #if CLICK_USERLEVEL && !NDEBUG # define SPINLOCK_ASSERTLEVEL "<-999>" #else # define SPINLOCK_ASSERTLEVEL "<1>" #endif CLICK_DECLS /** @file * @brief Classes for synchronizing among multiple CPUs, particularly in the * Linux kernel. */ /** @class Spinlock * @brief A recursive spinlock for SMP Click threads. * * The Spinlock class abstracts a recursive spinlock, or polling mutex, in SMP * Click. This is a type of mutual-exclusion lock in which acquiring the lock * is a polling operation (basically a "while (lock.acquired()) do nothing;" * loop). Spinlocks can be used to synchronize access to shared data among * multiple Click SMP threads. Spinlocks should not be held for long periods * of time: use them for quick updates and such. * * Spinlock operations do nothing unless Click was compiled with SMP support * (with --enable-multithread). Therefore, Spinlock should not be used to, * for example, synchronize handlers with main element threads. See also * SpinlockIRQ. * * The main Spinlock operations are acquire(), which acquires the lock, and * release(), which releases the lock. attempt() acquires the lock only if it * can be acquired instantaneously. * * It is OK for a thread to acquire a lock it has already acquired, but you * must release it as many times as you have acquired it. * * @sa SimpleSpinlock, SpinlockIRQ */ class Spinlock { public: inline Spinlock(); inline ~Spinlock(); inline void acquire(); inline void release(); inline bool attempt(); inline bool nested() const; #if CLICK_MULTITHREAD_SPINLOCK private: atomic_uint32_t _lock; int32_t _depth; click_processor_t _owner; #endif }; /** @brief Create a Spinlock. */ inline Spinlock::Spinlock() #if CLICK_MULTITHREAD_SPINLOCK : _depth(0), _owner(click_invalid_processor()) #endif { #if CLICK_MULTITHREAD_SPINLOCK _lock = 0; #endif } inline Spinlock::~Spinlock() { #if CLICK_MULTITHREAD_SPINLOCK if (_depth != 0) click_chatter(SPINLOCK_ASSERTLEVEL "Spinlock::~Spinlock(): assertion \"_depth == 0\" failed"); #endif } /** @brief Acquires the Spinlock. * * On return, this thread has acquired the lock. The function will spin * indefinitely until the lock is acquired. It is OK to acquire a lock you * have already acquired, but you must release it as many times as you have * acquired it. */ inline void Spinlock::acquire() { #if CLICK_MULTITHREAD_SPINLOCK click_processor_t my_cpu = click_get_processor(); if (_owner != my_cpu) { while (_lock.swap(1) != 0) do { click_relax_fence(); } while (_lock != 0); _owner = my_cpu; } _depth++; #endif } /** @brief Attempts to acquire the Spinlock. * @return True iff the Spinlock was acquired. * * This function will acquire the lock and return true only if the Spinlock * can be acquired right away, without retries. */ inline bool Spinlock::attempt() { #if CLICK_MULTITHREAD_SPINLOCK click_processor_t my_cpu = click_get_processor(); if (_owner != my_cpu) { if (_lock.swap(1) != 0) { click_put_processor(); return false; } _owner = my_cpu; } _depth++; return true; #else return true; #endif } /** @brief Releases the Spinlock. * * The Spinlock must have been previously acquired by either Spinlock::acquire * or Spinlock::attempt. */ inline void Spinlock::release() { #if CLICK_MULTITHREAD_SPINLOCK if (unlikely(_owner != click_current_processor())) click_chatter(SPINLOCK_ASSERTLEVEL "Spinlock::release(): assertion \"owner == click_current_processor()\" failed"); if (likely(_depth > 0)) { if (--_depth == 0) { _owner = click_invalid_processor(); _lock = 0; } } else click_chatter(SPINLOCK_ASSERTLEVEL "Spinlock::release(): assertion \"_depth > 0\" failed"); click_put_processor(); #endif } /** @brief Returns true iff the Spinlock has been acquired more than once by * the current thread. */ inline bool Spinlock::nested() const { #if CLICK_MULTITHREAD_SPINLOCK return _depth > 1; #else return false; #endif } /** @class SimpleSpinlock * @brief A non-recursive spinlock for SMP Click threads. * * The Spinlock class abstracts a non-recursive spinlock, or polling mutex, in * SMP Click. This is a type of mutual-exclusion lock in which acquiring the * lock is a polling operation (basically a "while (lock.acquired()) do * nothing;" loop). Spinlocks can be used to synchronize access to shared * data among multiple Click SMP threads. Spinlocks should not be held for * long periods of time: use them for quick updates and such. * * Spinlock operations do nothing unless Click was compiled with SMP support * (with --enable-multithread). Therefore, Spinlock should not be used to, * for example, synchronize handlers with main element threads. See also * SpinlockIRQ. * * The main Spinlock operations are acquire(), which acquires the lock, and * release(), which releases the lock. attempt() acquires the lock only if it * can be acquired instantaneously. * * It is NOT OK for a thread to acquire a lock it has already acquired. * * @sa Spinlock, SpinlockIRQ */ class SimpleSpinlock { public: inline SimpleSpinlock(); inline ~SimpleSpinlock(); inline void acquire(); inline void release(); inline bool attempt(); #if CLICK_LINUXMODULE private: spinlock_t _lock; #elif CLICK_MULTITHREAD_SPINLOCK private: atomic_uint32_t _lock; #endif }; /** @brief Create a SimpleSpinlock. */ inline SimpleSpinlock::SimpleSpinlock() { #if CLICK_LINUXMODULE spin_lock_init(&_lock); #elif CLICK_MULTITHREAD_SPINLOCK _lock = 0; #endif } inline SimpleSpinlock::~SimpleSpinlock() { } /** @brief Acquires the SimpleSpinlock. * * On return, this thread has acquired the lock. The function will spin * indefinitely until the lock is acquired. */ inline void SimpleSpinlock::acquire() { #if CLICK_LINUXMODULE spin_lock(&_lock); #elif CLICK_MULTITHREAD_SPINLOCK while (_lock.swap(1) != 0) do { click_relax_fence(); } while (_lock != 0); #endif } /** @brief Attempts to acquire the SimpleSpinlock. * @return True iff the SimpleSpinlock was acquired. * * This function will acquire the lock and return true only if the * SimpleSpinlock can be acquired right away, without retries. */ inline bool SimpleSpinlock::attempt() { #if CLICK_LINUXMODULE return spin_trylock(&_lock); #elif CLICK_MULTITHREAD_SPINLOCK return _lock.swap(1) == 0; #else return true; #endif } /** @brief Releases the SimpleSpinlock. * * The SimpleSpinlock must have been previously acquired by either * SimpleSpinlock::acquire or SimpleSpinlock::attempt. */ inline void SimpleSpinlock::release() { #if CLICK_LINUXMODULE spin_unlock(&_lock); #elif CLICK_MULTITHREAD_SPINLOCK _lock = 0; #endif } /** @class SpinlockIRQ * @brief A spinlock that disables interrupts. * * The SpinlockIRQ class abstracts a spinlock, or polling mutex, that also * turns off interrupts. Spinlocks are a type of mutual-exclusion lock in * which acquiring the lock is a polling operation (basically a "while * (lock.acquired()) do nothing;" loop). The SpinlockIRQ variant can be used * to protect Click data structures from interrupts and from other threads. * Very few objects in Click need this protection; the Click Master object, * which protects the task list, uses it, but that's hidden from users. * Spinlocks should not be held for long periods of time: use them for quick * updates and such. * * In the Linux kernel, SpinlockIRQ is equivalent to a combination of * local_irq_save and the spinlock_t type. * * The SpinlockIRQ operations are acquire(), which acquires the lock, and * release(), which releases the lock. * * It is NOT OK for a SpinlockIRQ thread to acquire a lock it has already * acquired. */ class SpinlockIRQ { public: inline SpinlockIRQ(); #if CLICK_LINUXMODULE typedef unsigned long flags_t; #else typedef int flags_t; #endif inline flags_t acquire() CLICK_ALWAYS_INLINE; inline void release(flags_t) CLICK_ALWAYS_INLINE; #if CLICK_LINUXMODULE private: spinlock_t _lock; #elif CLICK_USERLEVEL && HAVE_MULTITHREAD private: Spinlock _lock; #endif }; /** @brief Creates a SpinlockIRQ. */ inline SpinlockIRQ::SpinlockIRQ() { #if CLICK_LINUXMODULE spin_lock_init(&_lock); #endif } /** @brief Acquires the SpinlockIRQ. * @return The current state of the interrupt flags. */ inline SpinlockIRQ::flags_t SpinlockIRQ::acquire() { #if CLICK_LINUXMODULE flags_t flags; spin_lock_irqsave(&_lock, flags); return flags; #elif CLICK_USERLEVEL && HAVE_MULTITHREAD _lock.acquire(); return 0; #else return 0; #endif } /** @brief Releases the SpinlockIRQ. * @param flags The value returned by SpinlockIRQ::acquire(). */ inline void SpinlockIRQ::release(flags_t flags) { #if CLICK_LINUXMODULE spin_unlock_irqrestore(&_lock, flags); #elif CLICK_USERLEVEL && HAVE_MULTITHREAD (void) flags; _lock.release(); #else (void) flags; #endif } // read-write lock // // on read: acquire local read lock // on write: acquire every read lock // // alternatively, we could use a read counter and a write lock. we don't do // that because we'd like to avoid a cache miss for read acquires. this makes // reads very fast, and writes more expensive /** @class ReadWriteLock * @brief A read/write lock. * * The ReadWriteLock class abstracts a read/write lock in SMP Click. Multiple * SMP Click threads can hold read locks simultaneously, but if any thread * holds a write lock, then no other thread holds any kind of lock. The * read/write lock is implemented with Spinlock objects, so acquiring a lock * is a polling operation. ReadWriteLocks can be used to synchronize access * to shared data among multiple Click SMP threads. ReadWriteLocks should not * be held for long periods of time. * * ReadWriteLock operations do nothing unless Click was compiled with * --enable-multithread. Therefore, ReadWriteLock should not be used to, for * example, synchronize handlers with main element threads. * * The main ReadWriteLock operations are acquire_read() and acquire_write(), * which acquire the lock for reading or writing, respectively, and * release_read() and release_write(), which similarly release the lock. * attempt_read() and attempt_write() acquire the lock only if it can be * acquired instantaneously. * * It is OK for a thread to acquire a lock it has already acquired, but you * must release it as many times as you have acquired it. * * ReadWriteLock objects are relatively large in terms of memory usage; don't * create too many of them. */ class ReadWriteLock { public: inline ReadWriteLock(); #if CLICK_LINUXMODULE && defined(CONFIG_SMP) inline ~ReadWriteLock(); #endif inline void acquire_read(); inline bool attempt_read(); inline void release_read(); inline void acquire_write(); inline bool attempt_write(); inline void release_write(); #if CLICK_LINUXMODULE && defined(CONFIG_SMP) private: // allocate a cache line for every member struct lock_t { Spinlock _lock; unsigned char reserved[L1_CACHE_BYTES - sizeof(Spinlock)]; } *_l; #endif }; /** @brief Creates a ReadWriteLock. */ inline ReadWriteLock::ReadWriteLock() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) _l = new lock_t[num_possible_cpus()]; #endif } #if CLICK_LINUXMODULE && defined(CONFIG_SMP) inline ReadWriteLock::~ReadWriteLock() { delete[] _l; } #endif /** @brief Acquires the ReadWriteLock for reading. * * On return, this thread has acquired the lock for reading. The function * will spin indefinitely until the lock is acquired. It is OK to acquire a * lock you have already acquired, but you must release it as many times as * you have acquired it. * * @sa Spinlock::acquire */ inline void ReadWriteLock::acquire_read() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) click_processor_t my_cpu = click_get_processor(); _l[my_cpu]._lock.acquire(); #endif } /** @brief Attempts to acquire the ReadWriteLock for reading. * @return True iff the ReadWriteLock was acquired. * * This function will acquire the lock for reading and return true only if the * ReadWriteLock can be acquired right away, without retries. */ inline bool ReadWriteLock::attempt_read() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) click_processor_t my_cpu = click_get_processor(); bool result = _l[my_cpu]._lock.attempt(); if (!result) click_put_processor(); return result; #else return true; #endif } /** @brief Releases the ReadWriteLock for reading. * * The ReadWriteLock must have been previously acquired by either * ReadWriteLock::acquire_read or ReadWriteLock::attempt_read. Do not call * release_read() on a lock that was acquired for writing. */ inline void ReadWriteLock::release_read() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) _l[click_current_processor()]._lock.release(); click_put_processor(); #endif } /** @brief Acquires the ReadWriteLock for writing. * * On return, this thread has acquired the lock for writing. The function * will spin indefinitely until the lock is acquired. It is OK to acquire a * lock you have already acquired, but you must release it as many times as * you have acquired it. * * @sa ReadWriteLock::acquire_read */ inline void ReadWriteLock::acquire_write() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) for (unsigned i = 0; i < (unsigned) num_possible_cpus(); i++) _l[i]._lock.acquire(); #endif } /** @brief Attempts to acquire the ReadWriteLock for writing. * @return True iff the ReadWriteLock was acquired. * * This function will acquire the lock for writing and return true only if the * ReadWriteLock can be acquired right away, without retries. Note, however, * that acquiring a ReadWriteLock requires as many operations as there are * CPUs. * * @sa ReadWriteLock::attempt_read */ inline bool ReadWriteLock::attempt_write() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) bool all = true; unsigned i; for (i = 0; i < (unsigned) num_possible_cpus(); i++) if (!(_l[i]._lock.attempt())) { all = false; break; } if (!all) for (unsigned j = 0; j < i; j++) _l[j]._lock.release(); return all; #else return true; #endif } /** @brief Releases the ReadWriteLock for writing. * * The ReadWriteLock must have been previously acquired by either * ReadWriteLock::acquire_write or ReadWriteLock::attempt_write. Do not call * release_write() on a lock that was acquired for reading. * * @sa ReadWriteLock::release_read */ inline void ReadWriteLock::release_write() { #if CLICK_LINUXMODULE && defined(CONFIG_SMP) for (unsigned i = 0; i < (unsigned) num_possible_cpus(); i++) _l[i]._lock.release(); #endif } CLICK_ENDDECLS #undef SPINLOCK_ASSERTLEVEL #endif