Files
NE_YuR/openflow/include/click/sync.hh

569 lines
15 KiB
C++

// -*- c-basic-offset: 4 -*-
#ifndef CLICK_SYNC_HH
#define CLICK_SYNC_HH
#include <click/machine.hh>
#include <click/glue.hh>
#include <click/atomic.hh>
#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 <click/sync.hh>
* @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