Merge branch 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull locking changes from Ingo Molnar:
"Various updates:
- Futex scalability improvements: remove page lock use for shared
futex get_futex_key(), which speeds up 'perf bench futex hash'
benchmarks by over 40% on a 60-core Westmere. This makes anon-mem
shared futexes perform close to private futexes. (Mel Gorman)
- lockdep hash collision detection and fix (Alfredo Alvarez
Fernandez)
- lockdep testing enhancements (Alfredo Alvarez Fernandez)
- robustify lockdep init by using hlists (Andrew Morton, Andrey
Ryabinin)
- mutex and csd_lock micro-optimizations (Davidlohr Bueso)
- small x86 barriers tweaks (Michael S Tsirkin)
- qspinlock updates (Waiman Long)"
* 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (25 commits)
locking/csd_lock: Use smp_cond_acquire() in csd_lock_wait()
locking/csd_lock: Explicitly inline csd_lock*() helpers
futex: Replace barrier() in unqueue_me() with READ_ONCE()
locking/lockdep: Detect chain_key collisions
locking/lockdep: Prevent chain_key collisions
tools/lib/lockdep: Fix link creation warning
tools/lib/lockdep: Add tests for AA and ABBA locking
tools/lib/lockdep: Add userspace version of READ_ONCE()
tools/lib/lockdep: Fix the build on recent kernels
locking/qspinlock: Move __ARCH_SPIN_LOCK_UNLOCKED to qspinlock_types.h
locking/mutex: Allow next waiter lockless wakeup
locking/pvqspinlock: Enable slowpath locking count tracking
locking/qspinlock: Use smp_cond_acquire() in pending code
locking/pvqspinlock: Move lock stealing count tracking code into pv_queued_spin_steal_lock()
locking/mcs: Fix mcs_spin_lock() ordering
futex: Remove requirement for lock_page() in get_futex_key()
futex: Rename barrier references in ordering guarantees
locking/atomics: Update comment about READ_ONCE() and structures
locking/lockdep: Eliminate lockdep_init()
locking/lockdep: Convert hash tables to hlists
...
This commit is contained in:
+113
-26
@@ -124,16 +124,16 @@
|
||||
* futex_wait(futex, val);
|
||||
*
|
||||
* waiters++; (a)
|
||||
* mb(); (A) <-- paired with -.
|
||||
* |
|
||||
* lock(hash_bucket(futex)); |
|
||||
* |
|
||||
* uval = *futex; |
|
||||
* | *futex = newval;
|
||||
* | sys_futex(WAKE, futex);
|
||||
* | futex_wake(futex);
|
||||
* |
|
||||
* `-------> mb(); (B)
|
||||
* smp_mb(); (A) <-- paired with -.
|
||||
* |
|
||||
* lock(hash_bucket(futex)); |
|
||||
* |
|
||||
* uval = *futex; |
|
||||
* | *futex = newval;
|
||||
* | sys_futex(WAKE, futex);
|
||||
* | futex_wake(futex);
|
||||
* |
|
||||
* `--------> smp_mb(); (B)
|
||||
* if (uval == val)
|
||||
* queue();
|
||||
* unlock(hash_bucket(futex));
|
||||
@@ -334,7 +334,7 @@ static inline void futex_get_mm(union futex_key *key)
|
||||
/*
|
||||
* Ensure futex_get_mm() implies a full barrier such that
|
||||
* get_futex_key() implies a full barrier. This is relied upon
|
||||
* as full barrier (B), see the ordering comment above.
|
||||
* as smp_mb(); (B), see the ordering comment above.
|
||||
*/
|
||||
smp_mb__after_atomic();
|
||||
}
|
||||
@@ -407,10 +407,10 @@ static void get_futex_key_refs(union futex_key *key)
|
||||
|
||||
switch (key->both.offset & (FUT_OFF_INODE|FUT_OFF_MMSHARED)) {
|
||||
case FUT_OFF_INODE:
|
||||
ihold(key->shared.inode); /* implies MB (B) */
|
||||
ihold(key->shared.inode); /* implies smp_mb(); (B) */
|
||||
break;
|
||||
case FUT_OFF_MMSHARED:
|
||||
futex_get_mm(key); /* implies MB (B) */
|
||||
futex_get_mm(key); /* implies smp_mb(); (B) */
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
@@ -418,7 +418,7 @@ static void get_futex_key_refs(union futex_key *key)
|
||||
* mm, therefore the only purpose of calling get_futex_key_refs
|
||||
* is because we need the barrier for the lockless waiter check.
|
||||
*/
|
||||
smp_mb(); /* explicit MB (B) */
|
||||
smp_mb(); /* explicit smp_mb(); (B) */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,7 +497,7 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, int rw)
|
||||
if (!fshared) {
|
||||
key->private.mm = mm;
|
||||
key->private.address = address;
|
||||
get_futex_key_refs(key); /* implies MB (B) */
|
||||
get_futex_key_refs(key); /* implies smp_mb(); (B) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -520,7 +520,20 @@ again:
|
||||
else
|
||||
err = 0;
|
||||
|
||||
lock_page(page);
|
||||
/*
|
||||
* The treatment of mapping from this point on is critical. The page
|
||||
* lock protects many things but in this context the page lock
|
||||
* stabilizes mapping, prevents inode freeing in the shared
|
||||
* file-backed region case and guards against movement to swap cache.
|
||||
*
|
||||
* Strictly speaking the page lock is not needed in all cases being
|
||||
* considered here and page lock forces unnecessarily serialization
|
||||
* From this point on, mapping will be re-verified if necessary and
|
||||
* page lock will be acquired only if it is unavoidable
|
||||
*/
|
||||
page = compound_head(page);
|
||||
mapping = READ_ONCE(page->mapping);
|
||||
|
||||
/*
|
||||
* If page->mapping is NULL, then it cannot be a PageAnon
|
||||
* page; but it might be the ZERO_PAGE or in the gate area or
|
||||
@@ -536,19 +549,31 @@ again:
|
||||
* shmem_writepage move it from filecache to swapcache beneath us:
|
||||
* an unlikely race, but we do need to retry for page->mapping.
|
||||
*/
|
||||
mapping = compound_head(page)->mapping;
|
||||
if (!mapping) {
|
||||
int shmem_swizzled = PageSwapCache(page);
|
||||
if (unlikely(!mapping)) {
|
||||
int shmem_swizzled;
|
||||
|
||||
/*
|
||||
* Page lock is required to identify which special case above
|
||||
* applies. If this is really a shmem page then the page lock
|
||||
* will prevent unexpected transitions.
|
||||
*/
|
||||
lock_page(page);
|
||||
shmem_swizzled = PageSwapCache(page) || page->mapping;
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
|
||||
if (shmem_swizzled)
|
||||
goto again;
|
||||
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
/*
|
||||
* Private mappings are handled in a simple way.
|
||||
*
|
||||
* If the futex key is stored on an anonymous page, then the associated
|
||||
* object is the mm which is implicitly pinned by the calling process.
|
||||
*
|
||||
* NOTE: When userspace waits on a MAP_SHARED mapping, even if
|
||||
* it's a read-only handle, it's expected that futexes attach to
|
||||
* the object not the particular process.
|
||||
@@ -566,16 +591,74 @@ again:
|
||||
key->both.offset |= FUT_OFF_MMSHARED; /* ref taken on mm */
|
||||
key->private.mm = mm;
|
||||
key->private.address = address;
|
||||
|
||||
get_futex_key_refs(key); /* implies smp_mb(); (B) */
|
||||
|
||||
} else {
|
||||
struct inode *inode;
|
||||
|
||||
/*
|
||||
* The associated futex object in this case is the inode and
|
||||
* the page->mapping must be traversed. Ordinarily this should
|
||||
* be stabilised under page lock but it's not strictly
|
||||
* necessary in this case as we just want to pin the inode, not
|
||||
* update the radix tree or anything like that.
|
||||
*
|
||||
* The RCU read lock is taken as the inode is finally freed
|
||||
* under RCU. If the mapping still matches expectations then the
|
||||
* mapping->host can be safely accessed as being a valid inode.
|
||||
*/
|
||||
rcu_read_lock();
|
||||
|
||||
if (READ_ONCE(page->mapping) != mapping) {
|
||||
rcu_read_unlock();
|
||||
put_page(page);
|
||||
|
||||
goto again;
|
||||
}
|
||||
|
||||
inode = READ_ONCE(mapping->host);
|
||||
if (!inode) {
|
||||
rcu_read_unlock();
|
||||
put_page(page);
|
||||
|
||||
goto again;
|
||||
}
|
||||
|
||||
/*
|
||||
* Take a reference unless it is about to be freed. Previously
|
||||
* this reference was taken by ihold under the page lock
|
||||
* pinning the inode in place so i_lock was unnecessary. The
|
||||
* only way for this check to fail is if the inode was
|
||||
* truncated in parallel so warn for now if this happens.
|
||||
*
|
||||
* We are not calling into get_futex_key_refs() in file-backed
|
||||
* cases, therefore a successful atomic_inc return below will
|
||||
* guarantee that get_futex_key() will still imply smp_mb(); (B).
|
||||
*/
|
||||
if (WARN_ON_ONCE(!atomic_inc_not_zero(&inode->i_count))) {
|
||||
rcu_read_unlock();
|
||||
put_page(page);
|
||||
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Should be impossible but lets be paranoid for now */
|
||||
if (WARN_ON_ONCE(inode->i_mapping != mapping)) {
|
||||
err = -EFAULT;
|
||||
rcu_read_unlock();
|
||||
iput(inode);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
key->both.offset |= FUT_OFF_INODE; /* inode-based key */
|
||||
key->shared.inode = mapping->host;
|
||||
key->shared.inode = inode;
|
||||
key->shared.pgoff = basepage_index(page);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
get_futex_key_refs(key); /* implies MB (B) */
|
||||
|
||||
out:
|
||||
unlock_page(page);
|
||||
put_page(page);
|
||||
return err;
|
||||
}
|
||||
@@ -1864,7 +1947,7 @@ static inline struct futex_hash_bucket *queue_lock(struct futex_q *q)
|
||||
|
||||
q->lock_ptr = &hb->lock;
|
||||
|
||||
spin_lock(&hb->lock); /* implies MB (A) */
|
||||
spin_lock(&hb->lock); /* implies smp_mb(); (A) */
|
||||
return hb;
|
||||
}
|
||||
|
||||
@@ -1927,8 +2010,12 @@ static int unqueue_me(struct futex_q *q)
|
||||
|
||||
/* In the common case we don't take the spinlock, which is nice. */
|
||||
retry:
|
||||
lock_ptr = q->lock_ptr;
|
||||
barrier();
|
||||
/*
|
||||
* q->lock_ptr can change between this read and the following spin_lock.
|
||||
* Use READ_ONCE to forbid the compiler from reloading q->lock_ptr and
|
||||
* optimizing lock_ptr out of the logic below.
|
||||
*/
|
||||
lock_ptr = READ_ONCE(q->lock_ptr);
|
||||
if (lock_ptr != NULL) {
|
||||
spin_lock(lock_ptr);
|
||||
/*
|
||||
|
||||
+57
-75
@@ -123,8 +123,6 @@ static inline int debug_locks_off_graph_unlock(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lockdep_initialized;
|
||||
|
||||
unsigned long nr_list_entries;
|
||||
static struct lock_list list_entries[MAX_LOCKDEP_ENTRIES];
|
||||
|
||||
@@ -433,19 +431,6 @@ unsigned int nr_process_chains;
|
||||
unsigned int max_lockdep_depth;
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCKDEP
|
||||
/*
|
||||
* We cannot printk in early bootup code. Not even early_printk()
|
||||
* might work. So we mark any initialization errors and printk
|
||||
* about it later on, in lockdep_info().
|
||||
*/
|
||||
static int lockdep_init_error;
|
||||
static const char *lock_init_error;
|
||||
static unsigned long lockdep_init_trace_data[20];
|
||||
static struct stack_trace lockdep_init_trace = {
|
||||
.max_entries = ARRAY_SIZE(lockdep_init_trace_data),
|
||||
.entries = lockdep_init_trace_data,
|
||||
};
|
||||
|
||||
/*
|
||||
* Various lockdep statistics:
|
||||
*/
|
||||
@@ -669,20 +654,6 @@ look_up_lock_class(struct lockdep_map *lock, unsigned int subclass)
|
||||
struct hlist_head *hash_head;
|
||||
struct lock_class *class;
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCKDEP
|
||||
/*
|
||||
* If the architecture calls into lockdep before initializing
|
||||
* the hashes then we'll warn about it later. (we cannot printk
|
||||
* right now)
|
||||
*/
|
||||
if (unlikely(!lockdep_initialized)) {
|
||||
lockdep_init();
|
||||
lockdep_init_error = 1;
|
||||
lock_init_error = lock->name;
|
||||
save_stack_trace(&lockdep_init_trace);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (unlikely(subclass >= MAX_LOCKDEP_SUBCLASSES)) {
|
||||
debug_locks_off();
|
||||
printk(KERN_ERR
|
||||
@@ -2010,6 +1981,53 @@ struct lock_class *lock_chain_get_class(struct lock_chain *chain, int i)
|
||||
return lock_classes + chain_hlocks[chain->base + i];
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the index of the first held_lock of the current chain
|
||||
*/
|
||||
static inline int get_first_held_lock(struct task_struct *curr,
|
||||
struct held_lock *hlock)
|
||||
{
|
||||
int i;
|
||||
struct held_lock *hlock_curr;
|
||||
|
||||
for (i = curr->lockdep_depth - 1; i >= 0; i--) {
|
||||
hlock_curr = curr->held_locks + i;
|
||||
if (hlock_curr->irq_context != hlock->irq_context)
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return ++i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks whether the chain and the current held locks are consistent
|
||||
* in depth and also in content. If they are not it most likely means
|
||||
* that there was a collision during the calculation of the chain_key.
|
||||
* Returns: 0 not passed, 1 passed
|
||||
*/
|
||||
static int check_no_collision(struct task_struct *curr,
|
||||
struct held_lock *hlock,
|
||||
struct lock_chain *chain)
|
||||
{
|
||||
#ifdef CONFIG_DEBUG_LOCKDEP
|
||||
int i, j, id;
|
||||
|
||||
i = get_first_held_lock(curr, hlock);
|
||||
|
||||
if (DEBUG_LOCKS_WARN_ON(chain->depth != curr->lockdep_depth - (i - 1)))
|
||||
return 0;
|
||||
|
||||
for (j = 0; j < chain->depth - 1; j++, i++) {
|
||||
id = curr->held_locks[i].class_idx - 1;
|
||||
|
||||
if (DEBUG_LOCKS_WARN_ON(chain_hlocks[chain->base + j] != id))
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up a dependency chain. If the key is not present yet then
|
||||
* add it and return 1 - in this case the new dependency chain is
|
||||
@@ -2023,7 +2041,6 @@ static inline int lookup_chain_cache(struct task_struct *curr,
|
||||
struct lock_class *class = hlock_class(hlock);
|
||||
struct hlist_head *hash_head = chainhashentry(chain_key);
|
||||
struct lock_chain *chain;
|
||||
struct held_lock *hlock_curr;
|
||||
int i, j;
|
||||
|
||||
/*
|
||||
@@ -2041,6 +2058,9 @@ static inline int lookup_chain_cache(struct task_struct *curr,
|
||||
if (chain->chain_key == chain_key) {
|
||||
cache_hit:
|
||||
debug_atomic_inc(chain_lookup_hits);
|
||||
if (!check_no_collision(curr, hlock, chain))
|
||||
return 0;
|
||||
|
||||
if (very_verbose(class))
|
||||
printk("\nhash chain already cached, key: "
|
||||
"%016Lx tail class: [%p] %s\n",
|
||||
@@ -2078,13 +2098,7 @@ cache_hit:
|
||||
chain = lock_chains + nr_lock_chains++;
|
||||
chain->chain_key = chain_key;
|
||||
chain->irq_context = hlock->irq_context;
|
||||
/* Find the first held_lock of current chain */
|
||||
for (i = curr->lockdep_depth - 1; i >= 0; i--) {
|
||||
hlock_curr = curr->held_locks + i;
|
||||
if (hlock_curr->irq_context != hlock->irq_context)
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
i = get_first_held_lock(curr, hlock);
|
||||
chain->depth = curr->lockdep_depth + 1 - i;
|
||||
if (likely(nr_chain_hlocks + chain->depth <= MAX_LOCKDEP_CHAIN_HLOCKS)) {
|
||||
chain->base = nr_chain_hlocks;
|
||||
@@ -2172,7 +2186,7 @@ static void check_chain_key(struct task_struct *curr)
|
||||
{
|
||||
#ifdef CONFIG_DEBUG_LOCKDEP
|
||||
struct held_lock *hlock, *prev_hlock = NULL;
|
||||
unsigned int i, id;
|
||||
unsigned int i;
|
||||
u64 chain_key = 0;
|
||||
|
||||
for (i = 0; i < curr->lockdep_depth; i++) {
|
||||
@@ -2189,17 +2203,16 @@ static void check_chain_key(struct task_struct *curr)
|
||||
(unsigned long long)hlock->prev_chain_key);
|
||||
return;
|
||||
}
|
||||
id = hlock->class_idx - 1;
|
||||
/*
|
||||
* Whoops ran out of static storage again?
|
||||
*/
|
||||
if (DEBUG_LOCKS_WARN_ON(id >= MAX_LOCKDEP_KEYS))
|
||||
if (DEBUG_LOCKS_WARN_ON(hlock->class_idx > MAX_LOCKDEP_KEYS))
|
||||
return;
|
||||
|
||||
if (prev_hlock && (prev_hlock->irq_context !=
|
||||
hlock->irq_context))
|
||||
chain_key = 0;
|
||||
chain_key = iterate_chain_key(chain_key, id);
|
||||
chain_key = iterate_chain_key(chain_key, hlock->class_idx);
|
||||
prev_hlock = hlock;
|
||||
}
|
||||
if (chain_key != curr->curr_chain_key) {
|
||||
@@ -3077,7 +3090,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
|
||||
struct task_struct *curr = current;
|
||||
struct lock_class *class = NULL;
|
||||
struct held_lock *hlock;
|
||||
unsigned int depth, id;
|
||||
unsigned int depth;
|
||||
int chain_head = 0;
|
||||
int class_idx;
|
||||
u64 chain_key;
|
||||
@@ -3180,11 +3193,10 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
|
||||
* The 'key ID' is what is the most compact key value to drive
|
||||
* the hash, not class->key.
|
||||
*/
|
||||
id = class - lock_classes;
|
||||
/*
|
||||
* Whoops, we did it again.. ran straight out of our static allocation.
|
||||
*/
|
||||
if (DEBUG_LOCKS_WARN_ON(id >= MAX_LOCKDEP_KEYS))
|
||||
if (DEBUG_LOCKS_WARN_ON(class_idx > MAX_LOCKDEP_KEYS))
|
||||
return 0;
|
||||
|
||||
chain_key = curr->curr_chain_key;
|
||||
@@ -3202,7 +3214,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
|
||||
chain_key = 0;
|
||||
chain_head = 1;
|
||||
}
|
||||
chain_key = iterate_chain_key(chain_key, id);
|
||||
chain_key = iterate_chain_key(chain_key, class_idx);
|
||||
|
||||
if (nest_lock && !__lock_is_held(nest_lock))
|
||||
return print_lock_nested_lock_not_held(curr, hlock, ip);
|
||||
@@ -4013,28 +4025,6 @@ out_restore:
|
||||
raw_local_irq_restore(flags);
|
||||
}
|
||||
|
||||
void lockdep_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
/*
|
||||
* Some architectures have their own start_kernel()
|
||||
* code which calls lockdep_init(), while we also
|
||||
* call lockdep_init() from the start_kernel() itself,
|
||||
* and we want to initialize the hashes only once:
|
||||
*/
|
||||
if (lockdep_initialized)
|
||||
return;
|
||||
|
||||
for (i = 0; i < CLASSHASH_SIZE; i++)
|
||||
INIT_HLIST_HEAD(classhash_table + i);
|
||||
|
||||
for (i = 0; i < CHAINHASH_SIZE; i++)
|
||||
INIT_HLIST_HEAD(chainhash_table + i);
|
||||
|
||||
lockdep_initialized = 1;
|
||||
}
|
||||
|
||||
void __init lockdep_info(void)
|
||||
{
|
||||
printk("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n");
|
||||
@@ -4061,14 +4051,6 @@ void __init lockdep_info(void)
|
||||
|
||||
printk(" per task-struct memory footprint: %lu bytes\n",
|
||||
sizeof(struct held_lock) * MAX_LOCK_DEPTH);
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCKDEP
|
||||
if (lockdep_init_error) {
|
||||
printk("WARNING: lockdep init error: lock '%s' was acquired before lockdep_init().\n", lock_init_error);
|
||||
printk("Call stack leading to lockdep invocation was:\n");
|
||||
print_stack_trace(&lockdep_init_trace, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
|
||||
@@ -67,7 +67,13 @@ void mcs_spin_lock(struct mcs_spinlock **lock, struct mcs_spinlock *node)
|
||||
node->locked = 0;
|
||||
node->next = NULL;
|
||||
|
||||
prev = xchg_acquire(lock, node);
|
||||
/*
|
||||
* We rely on the full barrier with global transitivity implied by the
|
||||
* below xchg() to order the initialization stores above against any
|
||||
* observation of @node. And to provide the ACQUIRE ordering associated
|
||||
* with a LOCK primitive.
|
||||
*/
|
||||
prev = xchg(lock, node);
|
||||
if (likely(prev == NULL)) {
|
||||
/*
|
||||
* Lock acquired, don't need to set node->locked to 1. Threads
|
||||
|
||||
@@ -716,6 +716,7 @@ static inline void
|
||||
__mutex_unlock_common_slowpath(struct mutex *lock, int nested)
|
||||
{
|
||||
unsigned long flags;
|
||||
WAKE_Q(wake_q);
|
||||
|
||||
/*
|
||||
* As a performance measurement, release the lock before doing other
|
||||
@@ -743,11 +744,11 @@ __mutex_unlock_common_slowpath(struct mutex *lock, int nested)
|
||||
struct mutex_waiter, list);
|
||||
|
||||
debug_mutex_wake_waiter(lock, waiter);
|
||||
|
||||
wake_up_process(waiter->task);
|
||||
wake_q_add(&wake_q, waiter->task);
|
||||
}
|
||||
|
||||
spin_unlock_mutex(&lock->wait_lock, flags);
|
||||
wake_up_q(&wake_q);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -358,8 +358,7 @@ void queued_spin_lock_slowpath(struct qspinlock *lock, u32 val)
|
||||
* sequentiality; this is because not all clear_pending_set_locked()
|
||||
* implementations imply full barriers.
|
||||
*/
|
||||
while ((val = smp_load_acquire(&lock->val.counter)) & _Q_LOCKED_MASK)
|
||||
cpu_relax();
|
||||
smp_cond_acquire(!(atomic_read(&lock->val) & _Q_LOCKED_MASK));
|
||||
|
||||
/*
|
||||
* take ownership and clear the pending bit.
|
||||
@@ -435,7 +434,7 @@ queue:
|
||||
*
|
||||
* The PV pv_wait_head_or_lock function, if active, will acquire
|
||||
* the lock and return a non-zero value. So we have to skip the
|
||||
* smp_load_acquire() call. As the next PV queue head hasn't been
|
||||
* smp_cond_acquire() call. As the next PV queue head hasn't been
|
||||
* designated yet, there is no way for the locked value to become
|
||||
* _Q_SLOW_VAL. So both the set_locked() and the
|
||||
* atomic_cmpxchg_relaxed() calls will be safe.
|
||||
@@ -466,7 +465,7 @@ locked:
|
||||
break;
|
||||
}
|
||||
/*
|
||||
* The smp_load_acquire() call above has provided the necessary
|
||||
* The smp_cond_acquire() call above has provided the necessary
|
||||
* acquire semantics required for locking. At most two
|
||||
* iterations of this loop may be ran.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,11 @@ struct pv_node {
|
||||
u8 state;
|
||||
};
|
||||
|
||||
/*
|
||||
* Include queued spinlock statistics code
|
||||
*/
|
||||
#include "qspinlock_stat.h"
|
||||
|
||||
/*
|
||||
* By replacing the regular queued_spin_trylock() with the function below,
|
||||
* it will be called once when a lock waiter enter the PV slowpath before
|
||||
@@ -65,9 +70,11 @@ struct pv_node {
|
||||
static inline bool pv_queued_spin_steal_lock(struct qspinlock *lock)
|
||||
{
|
||||
struct __qspinlock *l = (void *)lock;
|
||||
int ret = !(atomic_read(&lock->val) & _Q_LOCKED_PENDING_MASK) &&
|
||||
(cmpxchg(&l->locked, 0, _Q_LOCKED_VAL) == 0);
|
||||
|
||||
return !(atomic_read(&lock->val) & _Q_LOCKED_PENDING_MASK) &&
|
||||
(cmpxchg(&l->locked, 0, _Q_LOCKED_VAL) == 0);
|
||||
qstat_inc(qstat_pv_lock_stealing, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -137,11 +144,6 @@ static __always_inline int trylock_clear_pending(struct qspinlock *lock)
|
||||
}
|
||||
#endif /* _Q_PENDING_BITS == 8 */
|
||||
|
||||
/*
|
||||
* Include queued spinlock statistics code
|
||||
*/
|
||||
#include "qspinlock_stat.h"
|
||||
|
||||
/*
|
||||
* Lock and MCS node addresses hash table for fast lookup
|
||||
*
|
||||
@@ -398,6 +400,11 @@ pv_wait_head_or_lock(struct qspinlock *lock, struct mcs_spinlock *node)
|
||||
if (READ_ONCE(pn->state) == vcpu_hashed)
|
||||
lp = (struct qspinlock **)1;
|
||||
|
||||
/*
|
||||
* Tracking # of slowpath locking operations
|
||||
*/
|
||||
qstat_inc(qstat_pv_lock_slowpath, true);
|
||||
|
||||
for (;; waitcnt++) {
|
||||
/*
|
||||
* Set correct vCPU state to be used by queue node wait-early
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
* pv_kick_wake - # of vCPU kicks used for computing pv_latency_wake
|
||||
* pv_latency_kick - average latency (ns) of vCPU kick operation
|
||||
* pv_latency_wake - average latency (ns) from vCPU kick to wakeup
|
||||
* pv_lock_slowpath - # of locking operations via the slowpath
|
||||
* pv_lock_stealing - # of lock stealing operations
|
||||
* pv_spurious_wakeup - # of spurious wakeups
|
||||
* pv_wait_again - # of vCPU wait's that happened after a vCPU kick
|
||||
@@ -45,6 +46,7 @@ enum qlock_stats {
|
||||
qstat_pv_kick_wake,
|
||||
qstat_pv_latency_kick,
|
||||
qstat_pv_latency_wake,
|
||||
qstat_pv_lock_slowpath,
|
||||
qstat_pv_lock_stealing,
|
||||
qstat_pv_spurious_wakeup,
|
||||
qstat_pv_wait_again,
|
||||
@@ -70,6 +72,7 @@ static const char * const qstat_names[qstat_num + 1] = {
|
||||
[qstat_pv_spurious_wakeup] = "pv_spurious_wakeup",
|
||||
[qstat_pv_latency_kick] = "pv_latency_kick",
|
||||
[qstat_pv_latency_wake] = "pv_latency_wake",
|
||||
[qstat_pv_lock_slowpath] = "pv_lock_slowpath",
|
||||
[qstat_pv_lock_stealing] = "pv_lock_stealing",
|
||||
[qstat_pv_wait_again] = "pv_wait_again",
|
||||
[qstat_pv_wait_early] = "pv_wait_early",
|
||||
@@ -279,19 +282,6 @@ static inline void __pv_wait(u8 *ptr, u8 val)
|
||||
#define pv_kick(c) __pv_kick(c)
|
||||
#define pv_wait(p, v) __pv_wait(p, v)
|
||||
|
||||
/*
|
||||
* PV unfair trylock count tracking function
|
||||
*/
|
||||
static inline int qstat_spin_steal_lock(struct qspinlock *lock)
|
||||
{
|
||||
int ret = pv_queued_spin_steal_lock(lock);
|
||||
|
||||
qstat_inc(qstat_pv_lock_stealing, ret);
|
||||
return ret;
|
||||
}
|
||||
#undef queued_spin_trylock
|
||||
#define queued_spin_trylock(l) qstat_spin_steal_lock(l)
|
||||
|
||||
#else /* CONFIG_QUEUED_LOCK_STAT */
|
||||
|
||||
static inline void qstat_inc(enum qlock_stats stat, bool cond) { }
|
||||
|
||||
+4
-5
@@ -105,13 +105,12 @@ void __init call_function_init(void)
|
||||
* previous function call. For multi-cpu calls its even more interesting
|
||||
* as we'll have to ensure no other cpu is observing our csd.
|
||||
*/
|
||||
static void csd_lock_wait(struct call_single_data *csd)
|
||||
static __always_inline void csd_lock_wait(struct call_single_data *csd)
|
||||
{
|
||||
while (smp_load_acquire(&csd->flags) & CSD_FLAG_LOCK)
|
||||
cpu_relax();
|
||||
smp_cond_acquire(!(csd->flags & CSD_FLAG_LOCK));
|
||||
}
|
||||
|
||||
static void csd_lock(struct call_single_data *csd)
|
||||
static __always_inline void csd_lock(struct call_single_data *csd)
|
||||
{
|
||||
csd_lock_wait(csd);
|
||||
csd->flags |= CSD_FLAG_LOCK;
|
||||
@@ -124,7 +123,7 @@ static void csd_lock(struct call_single_data *csd)
|
||||
smp_wmb();
|
||||
}
|
||||
|
||||
static void csd_unlock(struct call_single_data *csd)
|
||||
static __always_inline void csd_unlock(struct call_single_data *csd)
|
||||
{
|
||||
WARN_ON(!(csd->flags & CSD_FLAG_LOCK));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user