From 190bd54f674f1e234651a8f8c387e63b6d5039c2 Mon Sep 17 00:00:00 2001 From: "David S. Miller" <davem@nuts.davemloft.net> Date: Sun, 6 Feb 2005 05:20:23 -0800 Subject: [PATCH] [DOC]: Some atomic_ops.txt updates. Based upon feedback from Linus: - Touch on xchg(), cmpxchg() and spinlocks lightly. - Discuss atomic_dec_and_test() - Add some historical platform notes. Signed-off-by: David S. Miller <davem@davemloft.net> --- Documentation/atomic_ops.txt | 113 ++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/Documentation/atomic_ops.txt b/Documentation/atomic_ops.txt index 479abc0d209e..6f8793752022 100644 --- a/Documentation/atomic_ops.txt +++ b/Documentation/atomic_ops.txt @@ -4,8 +4,8 @@ David S. Miller This document is intended to serve as a guide to Linux port -maintainers on how to implement atomic counter and bitops interfaces -properly. +maintainers on how to implement atomic counter, bitops, and spinlock +interfaces properly. The atomic_t type should be defined as a signed integer. Also, it should be made opaque such that any kind of cast to a normal @@ -242,6 +242,19 @@ happen. Specifically, in the above case the atomic_dec_and_test() counter decrement would not become globally visible until the obj->active update does. +As a historical note, 32-bit Sparc used to only allow usage of +24-bits of it's atomic_t type. This was because it used 8 bits +as a spinlock for SMP safety. Sparc32 lacked a "compare and swap" +type instruction. However, 32-bit Sparc has since been moved over +to a "hash table of spinlocks" scheme, that allows the full 32-bit +counter to be realized. Essentially, an array of spinlocks are +indexed into based upon the address of the atomic_t being operated +on, and that lock protects the atomic operation. Parisc uses the +same scheme. + +Another note is that the atomic_t operations returning values are +extremely slow on an old 386. + We will now cover the atomic bitmask operations. You will find that their SMP and memory barrier semantics are similar in shape and scope to the atomic_t ops above. @@ -345,3 +358,99 @@ except that two underscores are prefixed to the interface name. These non-atomic variants also do not require any special memory barrier semantics. + +The routines xchg() and cmpxchg() need the same exact memory barriers +as the atomic and bit operations returning values. + +Spinlocks and rwlocks have memory barrier expectations as well. +The rule to follow is simple: + +1) When acquiring a lock, the implementation must make it globally + visible before any subsequent memory operation. + +2) When releasing a lock, the implementation must make it such that + all previous memory operations are globally visible before the + lock release. + +Which finally brings us to _atomic_dec_and_lock(). There is an +architecture-neutral version implemented in lib/dec_and_lock.c, +but most platforms will wish to optimize this in assembler. + + int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock); + +Atomically decrement the given counter, and if will drop to zero +atomically acquire the given spinlock and perform the decrement +of the counter to zero. If it does not drop to zero, do nothing +with the spinlock. + +It is actually pretty simple to get the memory barrier correct. +Simply satisfy the spinlock grab requirements, which is make +sure the spinlock operation is globally visible before any +subsequent memory operation. + +We can demonstrate this operation more clearly if we define +an abstract atomic operation: + + long cas(long *mem, long old, long new); + +"cas" stands for "compare and swap". It atomically: + +1) Compares "old" with the value currently at "mem". +2) If they are equal, "new" is written to "mem". +3) Regardless, the current value at "mem" is returned. + +As an example usage, here is what an atomic counter update +might look like: + +void example_atomic_inc(long *counter) +{ + long old, new, ret; + + while (1) { + old = *counter; + new = old + 1; + + ret = cas(counter, old, new); + if (ret == old) + break; + } +} + +Let's use cas() in order to build a pseudo-C atomic_dec_and_lock(): + +int _atomic_dec_and_lock(atomic_t *atomic, spinlock_t *lock) +{ + long old, new, ret; + int went_to_zero; + + went_to_zero = 0; + while (1) { + old = atomic_read(atomic); + new = old - 1; + if (new == 0) { + went_to_zero = 1; + spin_lock(lock); + } + ret = cas(atomic, old, new); + if (ret == old) + break; + if (went_to_zero) { + spin_unlock(lock); + went_to_zero = 0; + } + } + + return went_to_zero; +} + +Now, as far as memory barriers go, as long as spin_lock() +strictly orders all subsequent memory operations (including +the cas()) with respect to itself, things will be fine. + +Said another way, _atomic_dec_and_lock() must guarentee that +a counter dropping to zero is never made visible before the +spinlock being acquired. + +Note that this also means that for the case where the counter +is not dropping to zero, there are no memory ordering +requirements. -- 2.30.9