Merge branch 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull locking updates from Ingo Molnar: "The main changes in this cycle are: - rwsem scalability improvements, phase #2, by Waiman Long, which are rather impressive: "On a 2-socket 40-core 80-thread Skylake system with 40 reader and writer locking threads, the min/mean/max locking operations done in a 5-second testing window before the patchset were: 40 readers, Iterations Min/Mean/Max = 1,807/1,808/1,810 40 writers, Iterations Min/Mean/Max = 1,807/50,344/151,255 After the patchset, they became: 40 readers, Iterations Min/Mean/Max = 30,057/31,359/32,741 40 writers, Iterations Min/Mean/Max = 94,466/95,845/97,098" There's a lot of changes to the locking implementation that makes it similar to qrwlock, including owner handoff for more fair locking. Another microbenchmark shows how across the spectrum the improvements are: "With a locking microbenchmark running on 5.1 based kernel, the total locking rates (in kops/s) on a 2-socket Skylake system with equal numbers of readers and writers (mixed) before and after this patchset were: # of Threads Before Patch After Patch ------------ ------------ ----------- 2 2,618 4,193 4 1,202 3,726 8 802 3,622 16 729 3,359 32 319 2,826 64 102 2,744" The changes are extensive and the patch-set has been through several iterations addressing various locking workloads. There might be more regressions, but unless they are pathological I believe we want to use this new implementation as the baseline going forward. - jump-label optimizations by Daniel Bristot de Oliveira: the primary motivation was to remove IPI disturbance of isolated RT-workload CPUs, which resulted in the implementation of batched jump-label updates. Beyond the improvement of the real-time characteristics kernel, in one test this patchset improved static key update overhead from 57 msecs to just 1.4 msecs - which is a nice speedup as well. - atomic64_t cross-arch type cleanups by Mark Rutland: over the last ~10 years of atomic64_t existence the various types used by the APIs only had to be self-consistent within each architecture - which means they became wildly inconsistent across architectures. Mark puts and end to this by reworking all the atomic64 implementations to use 's64' as the base type for atomic64_t, and to ensure that this type is consistently used for parameters and return values in the API, avoiding further problems in this area. - A large set of small improvements to lockdep by Yuyang Du: type cleanups, output cleanups, function return type and othr cleanups all around the place. - A set of percpu ops cleanups and fixes by Peter Zijlstra. - Misc other changes - please see the Git log for more details" * 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (82 commits) locking/lockdep: increase size of counters for lockdep statistics locking/atomics: Use sed(1) instead of non-standard head(1) option locking/lockdep: Move mark_lock() inside CONFIG_TRACE_IRQFLAGS && CONFIG_PROVE_LOCKING x86/jump_label: Make tp_vec_nr static x86/percpu: Optimize raw_cpu_xchg() x86/percpu, sched/fair: Avoid local_clock() x86/percpu, x86/irq: Relax {set,get}_irq_regs() x86/percpu: Relax smp_processor_id() x86/percpu: Differentiate this_cpu_{}() and __this_cpu_{}() locking/rwsem: Guard against making count negative locking/rwsem: Adaptive disabling of reader optimistic spinning locking/rwsem: Enable time-based spinning on reader-owned rwsem locking/rwsem: Make rwsem->owner an atomic_long_t locking/rwsem: Enable readers spinning on writer locking/rwsem: Clarify usage of owner's nonspinaable bit locking/rwsem: Wake up almost all readers in wait queue locking/rwsem: More optimal RT task handling of null owner locking/rwsem: Always release wait_lock before waking up tasks locking/rwsem: Implement lock handoff to prevent lock starvation locking/rwsem: Make rwsem_spin_on_owner() return owner state ...
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# and is generally not a function of system call inputs.
|
||||
KCOV_INSTRUMENT := n
|
||||
|
||||
obj-y += mutex.o semaphore.o rwsem.o percpu-rwsem.o rwsem-xadd.o
|
||||
obj-y += mutex.o semaphore.o rwsem.o percpu-rwsem.o
|
||||
|
||||
ifdef CONFIG_FUNCTION_TRACER
|
||||
CFLAGS_REMOVE_lockdep.o = $(CC_FLAGS_FTRACE)
|
||||
|
||||
@@ -31,50 +31,13 @@ enum lock_events {
|
||||
DECLARE_PER_CPU(unsigned long, lockevents[lockevent_num]);
|
||||
|
||||
/*
|
||||
* The purpose of the lock event counting subsystem is to provide a low
|
||||
* overhead way to record the number of specific locking events by using
|
||||
* percpu counters. It is the percpu sum that matters, not specifically
|
||||
* how many of them happens in each cpu.
|
||||
*
|
||||
* It is possible that the same percpu counter may be modified in both
|
||||
* the process and interrupt contexts. For architectures that perform
|
||||
* percpu operation with multiple instructions, it is possible to lose
|
||||
* count if a process context percpu update is interrupted in the middle
|
||||
* and the same counter is updated in the interrupt context. Therefore,
|
||||
* the generated percpu sum may not be precise. The error, if any, should
|
||||
* be small and insignificant.
|
||||
*
|
||||
* For those architectures that do multi-instruction percpu operation,
|
||||
* preemption in the middle and moving the task to another cpu may cause
|
||||
* a larger error in the count. Again, this will be few and far between.
|
||||
* Given the imprecise nature of the count and the possibility of resetting
|
||||
* the count and doing the measurement again, this is not really a big
|
||||
* problem.
|
||||
*
|
||||
* To get a better picture of what is happening under the hood, it is
|
||||
* suggested that a few measurements should be taken with the counts
|
||||
* reset in between to stamp out outliner because of these possible
|
||||
* error conditions.
|
||||
*
|
||||
* To minimize overhead, we use __this_cpu_*() in all cases except when
|
||||
* CONFIG_DEBUG_PREEMPT is defined. In this particular case, this_cpu_*()
|
||||
* will be used to avoid the appearance of unwanted BUG messages.
|
||||
*/
|
||||
#ifdef CONFIG_DEBUG_PREEMPT
|
||||
#define lockevent_percpu_inc(x) this_cpu_inc(x)
|
||||
#define lockevent_percpu_add(x, v) this_cpu_add(x, v)
|
||||
#else
|
||||
#define lockevent_percpu_inc(x) __this_cpu_inc(x)
|
||||
#define lockevent_percpu_add(x, v) __this_cpu_add(x, v)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Increment the PV qspinlock statistical counters
|
||||
* Increment the statistical counters. use raw_cpu_inc() because of lower
|
||||
* overhead and we don't care if we loose the occasional update.
|
||||
*/
|
||||
static inline void __lockevent_inc(enum lock_events event, bool cond)
|
||||
{
|
||||
if (cond)
|
||||
lockevent_percpu_inc(lockevents[event]);
|
||||
raw_cpu_inc(lockevents[event]);
|
||||
}
|
||||
|
||||
#define lockevent_inc(ev) __lockevent_inc(LOCKEVENT_ ##ev, true)
|
||||
@@ -82,7 +45,7 @@ static inline void __lockevent_inc(enum lock_events event, bool cond)
|
||||
|
||||
static inline void __lockevent_add(enum lock_events event, int inc)
|
||||
{
|
||||
lockevent_percpu_add(lockevents[event], inc);
|
||||
raw_cpu_add(lockevents[event], inc);
|
||||
}
|
||||
|
||||
#define lockevent_add(ev, c) __lockevent_add(LOCKEVENT_ ##ev, c)
|
||||
|
||||
@@ -56,12 +56,16 @@ LOCK_EVENT(rwsem_sleep_reader) /* # of reader sleeps */
|
||||
LOCK_EVENT(rwsem_sleep_writer) /* # of writer sleeps */
|
||||
LOCK_EVENT(rwsem_wake_reader) /* # of reader wakeups */
|
||||
LOCK_EVENT(rwsem_wake_writer) /* # of writer wakeups */
|
||||
LOCK_EVENT(rwsem_opt_wlock) /* # of write locks opt-spin acquired */
|
||||
LOCK_EVENT(rwsem_opt_fail) /* # of failed opt-spinnings */
|
||||
LOCK_EVENT(rwsem_opt_rlock) /* # of opt-acquired read locks */
|
||||
LOCK_EVENT(rwsem_opt_wlock) /* # of opt-acquired write locks */
|
||||
LOCK_EVENT(rwsem_opt_fail) /* # of failed optspins */
|
||||
LOCK_EVENT(rwsem_opt_nospin) /* # of disabled optspins */
|
||||
LOCK_EVENT(rwsem_opt_norspin) /* # of disabled reader-only optspins */
|
||||
LOCK_EVENT(rwsem_opt_rlock2) /* # of opt-acquired 2ndary read locks */
|
||||
LOCK_EVENT(rwsem_rlock) /* # of read locks acquired */
|
||||
LOCK_EVENT(rwsem_rlock_fast) /* # of fast read locks acquired */
|
||||
LOCK_EVENT(rwsem_rlock_fail) /* # of failed read lock acquisitions */
|
||||
LOCK_EVENT(rwsem_rtrylock) /* # of read trylock calls */
|
||||
LOCK_EVENT(rwsem_rlock_handoff) /* # of read lock handoffs */
|
||||
LOCK_EVENT(rwsem_wlock) /* # of write locks acquired */
|
||||
LOCK_EVENT(rwsem_wlock_fail) /* # of failed write lock acquisitions */
|
||||
LOCK_EVENT(rwsem_wtrylock) /* # of write trylock calls */
|
||||
LOCK_EVENT(rwsem_wlock_handoff) /* # of write lock handoffs */
|
||||
|
||||
+426
-318
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,6 @@ extern unsigned int nr_hardirq_chains;
|
||||
extern unsigned int nr_softirq_chains;
|
||||
extern unsigned int nr_process_chains;
|
||||
extern unsigned int max_lockdep_depth;
|
||||
extern unsigned int max_recursion_depth;
|
||||
|
||||
extern unsigned int max_bfs_queue_depth;
|
||||
|
||||
@@ -160,25 +159,22 @@ lockdep_count_backward_deps(struct lock_class *class)
|
||||
* and we want to avoid too much cache bouncing.
|
||||
*/
|
||||
struct lockdep_stats {
|
||||
int chain_lookup_hits;
|
||||
int chain_lookup_misses;
|
||||
int hardirqs_on_events;
|
||||
int hardirqs_off_events;
|
||||
int redundant_hardirqs_on;
|
||||
int redundant_hardirqs_off;
|
||||
int softirqs_on_events;
|
||||
int softirqs_off_events;
|
||||
int redundant_softirqs_on;
|
||||
int redundant_softirqs_off;
|
||||
int nr_unused_locks;
|
||||
int nr_redundant_checks;
|
||||
int nr_redundant;
|
||||
int nr_cyclic_checks;
|
||||
int nr_cyclic_check_recursions;
|
||||
int nr_find_usage_forwards_checks;
|
||||
int nr_find_usage_forwards_recursions;
|
||||
int nr_find_usage_backwards_checks;
|
||||
int nr_find_usage_backwards_recursions;
|
||||
unsigned long chain_lookup_hits;
|
||||
unsigned int chain_lookup_misses;
|
||||
unsigned long hardirqs_on_events;
|
||||
unsigned long hardirqs_off_events;
|
||||
unsigned long redundant_hardirqs_on;
|
||||
unsigned long redundant_hardirqs_off;
|
||||
unsigned long softirqs_on_events;
|
||||
unsigned long softirqs_off_events;
|
||||
unsigned long redundant_softirqs_on;
|
||||
unsigned long redundant_softirqs_off;
|
||||
int nr_unused_locks;
|
||||
unsigned int nr_redundant_checks;
|
||||
unsigned int nr_redundant;
|
||||
unsigned int nr_cyclic_checks;
|
||||
unsigned int nr_find_usage_forwards_checks;
|
||||
unsigned int nr_find_usage_backwards_checks;
|
||||
|
||||
/*
|
||||
* Per lock class locking operation stat counts
|
||||
|
||||
@@ -1,745 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* rwsem.c: R/W semaphores: contention handling functions
|
||||
*
|
||||
* Written by David Howells (dhowells@redhat.com).
|
||||
* Derived from arch/i386/kernel/semaphore.c
|
||||
*
|
||||
* Writer lock-stealing by Alex Shi <alex.shi@intel.com>
|
||||
* and Michel Lespinasse <walken@google.com>
|
||||
*
|
||||
* Optimistic spinning by Tim Chen <tim.c.chen@intel.com>
|
||||
* and Davidlohr Bueso <davidlohr@hp.com>. Based on mutexes.
|
||||
*/
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/sched/rt.h>
|
||||
#include <linux/sched/wake_q.h>
|
||||
#include <linux/sched/debug.h>
|
||||
#include <linux/osq_lock.h>
|
||||
|
||||
#include "rwsem.h"
|
||||
|
||||
/*
|
||||
* Guide to the rw_semaphore's count field for common values.
|
||||
* (32-bit case illustrated, similar for 64-bit)
|
||||
*
|
||||
* 0x0000000X (1) X readers active or attempting lock, no writer waiting
|
||||
* X = #active_readers + #readers attempting to lock
|
||||
* (X*ACTIVE_BIAS)
|
||||
*
|
||||
* 0x00000000 rwsem is unlocked, and no one is waiting for the lock or
|
||||
* attempting to read lock or write lock.
|
||||
*
|
||||
* 0xffff000X (1) X readers active or attempting lock, with waiters for lock
|
||||
* X = #active readers + # readers attempting lock
|
||||
* (X*ACTIVE_BIAS + WAITING_BIAS)
|
||||
* (2) 1 writer attempting lock, no waiters for lock
|
||||
* X-1 = #active readers + #readers attempting lock
|
||||
* ((X-1)*ACTIVE_BIAS + ACTIVE_WRITE_BIAS)
|
||||
* (3) 1 writer active, no waiters for lock
|
||||
* X-1 = #active readers + #readers attempting lock
|
||||
* ((X-1)*ACTIVE_BIAS + ACTIVE_WRITE_BIAS)
|
||||
*
|
||||
* 0xffff0001 (1) 1 reader active or attempting lock, waiters for lock
|
||||
* (WAITING_BIAS + ACTIVE_BIAS)
|
||||
* (2) 1 writer active or attempting lock, no waiters for lock
|
||||
* (ACTIVE_WRITE_BIAS)
|
||||
*
|
||||
* 0xffff0000 (1) There are writers or readers queued but none active
|
||||
* or in the process of attempting lock.
|
||||
* (WAITING_BIAS)
|
||||
* Note: writer can attempt to steal lock for this count by adding
|
||||
* ACTIVE_WRITE_BIAS in cmpxchg and checking the old count
|
||||
*
|
||||
* 0xfffe0001 (1) 1 writer active, or attempting lock. Waiters on queue.
|
||||
* (ACTIVE_WRITE_BIAS + WAITING_BIAS)
|
||||
*
|
||||
* Note: Readers attempt to lock by adding ACTIVE_BIAS in down_read and checking
|
||||
* the count becomes more than 0 for successful lock acquisition,
|
||||
* i.e. the case where there are only readers or nobody has lock.
|
||||
* (1st and 2nd case above).
|
||||
*
|
||||
* Writers attempt to lock by adding ACTIVE_WRITE_BIAS in down_write and
|
||||
* checking the count becomes ACTIVE_WRITE_BIAS for successful lock
|
||||
* acquisition (i.e. nobody else has lock or attempts lock). If
|
||||
* unsuccessful, in rwsem_down_write_failed, we'll check to see if there
|
||||
* are only waiters but none active (5th case above), and attempt to
|
||||
* steal the lock.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialize an rwsem:
|
||||
*/
|
||||
void __init_rwsem(struct rw_semaphore *sem, const char *name,
|
||||
struct lock_class_key *key)
|
||||
{
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
/*
|
||||
* Make sure we are not reinitializing a held semaphore:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)sem, sizeof(*sem));
|
||||
lockdep_init_map(&sem->dep_map, name, key, 0);
|
||||
#endif
|
||||
atomic_long_set(&sem->count, RWSEM_UNLOCKED_VALUE);
|
||||
raw_spin_lock_init(&sem->wait_lock);
|
||||
INIT_LIST_HEAD(&sem->wait_list);
|
||||
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
||||
sem->owner = NULL;
|
||||
osq_lock_init(&sem->osq);
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(__init_rwsem);
|
||||
|
||||
enum rwsem_waiter_type {
|
||||
RWSEM_WAITING_FOR_WRITE,
|
||||
RWSEM_WAITING_FOR_READ
|
||||
};
|
||||
|
||||
struct rwsem_waiter {
|
||||
struct list_head list;
|
||||
struct task_struct *task;
|
||||
enum rwsem_waiter_type type;
|
||||
};
|
||||
|
||||
enum rwsem_wake_type {
|
||||
RWSEM_WAKE_ANY, /* Wake whatever's at head of wait list */
|
||||
RWSEM_WAKE_READERS, /* Wake readers only */
|
||||
RWSEM_WAKE_READ_OWNED /* Waker thread holds the read lock */
|
||||
};
|
||||
|
||||
/*
|
||||
* handle the lock release when processes blocked on it that can now run
|
||||
* - if we come here from up_xxxx(), then:
|
||||
* - the 'active part' of count (&0x0000ffff) reached 0 (but may have changed)
|
||||
* - the 'waiting part' of count (&0xffff0000) is -ve (and will still be so)
|
||||
* - there must be someone on the queue
|
||||
* - the wait_lock must be held by the caller
|
||||
* - tasks are marked for wakeup, the caller must later invoke wake_up_q()
|
||||
* to actually wakeup the blocked task(s) and drop the reference count,
|
||||
* preferably when the wait_lock is released
|
||||
* - woken process blocks are discarded from the list after having task zeroed
|
||||
* - writers are only marked woken if downgrading is false
|
||||
*/
|
||||
static void __rwsem_mark_wake(struct rw_semaphore *sem,
|
||||
enum rwsem_wake_type wake_type,
|
||||
struct wake_q_head *wake_q)
|
||||
{
|
||||
struct rwsem_waiter *waiter, *tmp;
|
||||
long oldcount, woken = 0, adjustment = 0;
|
||||
struct list_head wlist;
|
||||
|
||||
/*
|
||||
* Take a peek at the queue head waiter such that we can determine
|
||||
* the wakeup(s) to perform.
|
||||
*/
|
||||
waiter = list_first_entry(&sem->wait_list, struct rwsem_waiter, list);
|
||||
|
||||
if (waiter->type == RWSEM_WAITING_FOR_WRITE) {
|
||||
if (wake_type == RWSEM_WAKE_ANY) {
|
||||
/*
|
||||
* Mark writer at the front of the queue for wakeup.
|
||||
* Until the task is actually later awoken later by
|
||||
* the caller, other writers are able to steal it.
|
||||
* Readers, on the other hand, will block as they
|
||||
* will notice the queued writer.
|
||||
*/
|
||||
wake_q_add(wake_q, waiter->task);
|
||||
lockevent_inc(rwsem_wake_writer);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Writers might steal the lock before we grant it to the next reader.
|
||||
* We prefer to do the first reader grant before counting readers
|
||||
* so we can bail out early if a writer stole the lock.
|
||||
*/
|
||||
if (wake_type != RWSEM_WAKE_READ_OWNED) {
|
||||
adjustment = RWSEM_ACTIVE_READ_BIAS;
|
||||
try_reader_grant:
|
||||
oldcount = atomic_long_fetch_add(adjustment, &sem->count);
|
||||
if (unlikely(oldcount < RWSEM_WAITING_BIAS)) {
|
||||
/*
|
||||
* If the count is still less than RWSEM_WAITING_BIAS
|
||||
* after removing the adjustment, it is assumed that
|
||||
* a writer has stolen the lock. We have to undo our
|
||||
* reader grant.
|
||||
*/
|
||||
if (atomic_long_add_return(-adjustment, &sem->count) <
|
||||
RWSEM_WAITING_BIAS)
|
||||
return;
|
||||
|
||||
/* Last active locker left. Retry waking readers. */
|
||||
goto try_reader_grant;
|
||||
}
|
||||
/*
|
||||
* Set it to reader-owned to give spinners an early
|
||||
* indication that readers now have the lock.
|
||||
*/
|
||||
__rwsem_set_reader_owned(sem, waiter->task);
|
||||
}
|
||||
|
||||
/*
|
||||
* Grant an infinite number of read locks to the readers at the front
|
||||
* of the queue. We know that woken will be at least 1 as we accounted
|
||||
* for above. Note we increment the 'active part' of the count by the
|
||||
* number of readers before waking any processes up.
|
||||
*
|
||||
* We have to do wakeup in 2 passes to prevent the possibility that
|
||||
* the reader count may be decremented before it is incremented. It
|
||||
* is because the to-be-woken waiter may not have slept yet. So it
|
||||
* may see waiter->task got cleared, finish its critical section and
|
||||
* do an unlock before the reader count increment.
|
||||
*
|
||||
* 1) Collect the read-waiters in a separate list, count them and
|
||||
* fully increment the reader count in rwsem.
|
||||
* 2) For each waiters in the new list, clear waiter->task and
|
||||
* put them into wake_q to be woken up later.
|
||||
*/
|
||||
list_for_each_entry(waiter, &sem->wait_list, list) {
|
||||
if (waiter->type == RWSEM_WAITING_FOR_WRITE)
|
||||
break;
|
||||
|
||||
woken++;
|
||||
}
|
||||
list_cut_before(&wlist, &sem->wait_list, &waiter->list);
|
||||
|
||||
adjustment = woken * RWSEM_ACTIVE_READ_BIAS - adjustment;
|
||||
lockevent_cond_inc(rwsem_wake_reader, woken);
|
||||
if (list_empty(&sem->wait_list)) {
|
||||
/* hit end of list above */
|
||||
adjustment -= RWSEM_WAITING_BIAS;
|
||||
}
|
||||
|
||||
if (adjustment)
|
||||
atomic_long_add(adjustment, &sem->count);
|
||||
|
||||
/* 2nd pass */
|
||||
list_for_each_entry_safe(waiter, tmp, &wlist, list) {
|
||||
struct task_struct *tsk;
|
||||
|
||||
tsk = waiter->task;
|
||||
get_task_struct(tsk);
|
||||
|
||||
/*
|
||||
* Ensure calling get_task_struct() before setting the reader
|
||||
* waiter to nil such that rwsem_down_read_failed() cannot
|
||||
* race with do_exit() by always holding a reference count
|
||||
* to the task to wakeup.
|
||||
*/
|
||||
smp_store_release(&waiter->task, NULL);
|
||||
/*
|
||||
* Ensure issuing the wakeup (either by us or someone else)
|
||||
* after setting the reader waiter to nil.
|
||||
*/
|
||||
wake_q_add_safe(wake_q, tsk);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function must be called with the sem->wait_lock held to prevent
|
||||
* race conditions between checking the rwsem wait list and setting the
|
||||
* sem->count accordingly.
|
||||
*/
|
||||
static inline bool rwsem_try_write_lock(long count, struct rw_semaphore *sem)
|
||||
{
|
||||
/*
|
||||
* Avoid trying to acquire write lock if count isn't RWSEM_WAITING_BIAS.
|
||||
*/
|
||||
if (count != RWSEM_WAITING_BIAS)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Acquire the lock by trying to set it to ACTIVE_WRITE_BIAS. If there
|
||||
* are other tasks on the wait list, we need to add on WAITING_BIAS.
|
||||
*/
|
||||
count = list_is_singular(&sem->wait_list) ?
|
||||
RWSEM_ACTIVE_WRITE_BIAS :
|
||||
RWSEM_ACTIVE_WRITE_BIAS + RWSEM_WAITING_BIAS;
|
||||
|
||||
if (atomic_long_cmpxchg_acquire(&sem->count, RWSEM_WAITING_BIAS, count)
|
||||
== RWSEM_WAITING_BIAS) {
|
||||
rwsem_set_owner(sem);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
||||
/*
|
||||
* Try to acquire write lock before the writer has been put on wait queue.
|
||||
*/
|
||||
static inline bool rwsem_try_write_lock_unqueued(struct rw_semaphore *sem)
|
||||
{
|
||||
long count = atomic_long_read(&sem->count);
|
||||
|
||||
while (!count || count == RWSEM_WAITING_BIAS) {
|
||||
if (atomic_long_try_cmpxchg_acquire(&sem->count, &count,
|
||||
count + RWSEM_ACTIVE_WRITE_BIAS)) {
|
||||
rwsem_set_owner(sem);
|
||||
lockevent_inc(rwsem_opt_wlock);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool owner_on_cpu(struct task_struct *owner)
|
||||
{
|
||||
/*
|
||||
* As lock holder preemption issue, we both skip spinning if
|
||||
* task is not on cpu or its cpu is preempted
|
||||
*/
|
||||
return owner->on_cpu && !vcpu_is_preempted(task_cpu(owner));
|
||||
}
|
||||
|
||||
static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
struct task_struct *owner;
|
||||
bool ret = true;
|
||||
|
||||
BUILD_BUG_ON(!rwsem_has_anonymous_owner(RWSEM_OWNER_UNKNOWN));
|
||||
|
||||
if (need_resched())
|
||||
return false;
|
||||
|
||||
rcu_read_lock();
|
||||
owner = READ_ONCE(sem->owner);
|
||||
if (owner) {
|
||||
ret = is_rwsem_owner_spinnable(owner) &&
|
||||
owner_on_cpu(owner);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true only if we can still spin on the owner field of the rwsem.
|
||||
*/
|
||||
static noinline bool rwsem_spin_on_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
struct task_struct *owner = READ_ONCE(sem->owner);
|
||||
|
||||
if (!is_rwsem_owner_spinnable(owner))
|
||||
return false;
|
||||
|
||||
rcu_read_lock();
|
||||
while (owner && (READ_ONCE(sem->owner) == owner)) {
|
||||
/*
|
||||
* Ensure we emit the owner->on_cpu, dereference _after_
|
||||
* checking sem->owner still matches owner, if that fails,
|
||||
* owner might point to free()d memory, if it still matches,
|
||||
* the rcu_read_lock() ensures the memory stays valid.
|
||||
*/
|
||||
barrier();
|
||||
|
||||
/*
|
||||
* abort spinning when need_resched or owner is not running or
|
||||
* owner's cpu is preempted.
|
||||
*/
|
||||
if (need_resched() || !owner_on_cpu(owner)) {
|
||||
rcu_read_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
cpu_relax();
|
||||
}
|
||||
rcu_read_unlock();
|
||||
|
||||
/*
|
||||
* If there is a new owner or the owner is not set, we continue
|
||||
* spinning.
|
||||
*/
|
||||
return is_rwsem_owner_spinnable(READ_ONCE(sem->owner));
|
||||
}
|
||||
|
||||
static bool rwsem_optimistic_spin(struct rw_semaphore *sem)
|
||||
{
|
||||
bool taken = false;
|
||||
|
||||
preempt_disable();
|
||||
|
||||
/* sem->wait_lock should not be held when doing optimistic spinning */
|
||||
if (!rwsem_can_spin_on_owner(sem))
|
||||
goto done;
|
||||
|
||||
if (!osq_lock(&sem->osq))
|
||||
goto done;
|
||||
|
||||
/*
|
||||
* Optimistically spin on the owner field and attempt to acquire the
|
||||
* lock whenever the owner changes. Spinning will be stopped when:
|
||||
* 1) the owning writer isn't running; or
|
||||
* 2) readers own the lock as we can't determine if they are
|
||||
* actively running or not.
|
||||
*/
|
||||
while (rwsem_spin_on_owner(sem)) {
|
||||
/*
|
||||
* Try to acquire the lock
|
||||
*/
|
||||
if (rwsem_try_write_lock_unqueued(sem)) {
|
||||
taken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* When there's no owner, we might have preempted between the
|
||||
* owner acquiring the lock and setting the owner field. If
|
||||
* we're an RT task that will live-lock because we won't let
|
||||
* the owner complete.
|
||||
*/
|
||||
if (!sem->owner && (need_resched() || rt_task(current)))
|
||||
break;
|
||||
|
||||
/*
|
||||
* The cpu_relax() call is a compiler barrier which forces
|
||||
* everything in this loop to be re-loaded. We don't need
|
||||
* memory barriers as we'll eventually observe the right
|
||||
* values at the cost of a few extra spins.
|
||||
*/
|
||||
cpu_relax();
|
||||
}
|
||||
osq_unlock(&sem->osq);
|
||||
done:
|
||||
preempt_enable();
|
||||
lockevent_cond_inc(rwsem_opt_fail, !taken);
|
||||
return taken;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the rwsem has active spinner
|
||||
*/
|
||||
static inline bool rwsem_has_spinner(struct rw_semaphore *sem)
|
||||
{
|
||||
return osq_is_locked(&sem->osq);
|
||||
}
|
||||
|
||||
#else
|
||||
static bool rwsem_optimistic_spin(struct rw_semaphore *sem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool rwsem_has_spinner(struct rw_semaphore *sem)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Wait for the read lock to be granted
|
||||
*/
|
||||
static inline struct rw_semaphore __sched *
|
||||
__rwsem_down_read_failed_common(struct rw_semaphore *sem, int state)
|
||||
{
|
||||
long count, adjustment = -RWSEM_ACTIVE_READ_BIAS;
|
||||
struct rwsem_waiter waiter;
|
||||
DEFINE_WAKE_Q(wake_q);
|
||||
|
||||
waiter.task = current;
|
||||
waiter.type = RWSEM_WAITING_FOR_READ;
|
||||
|
||||
raw_spin_lock_irq(&sem->wait_lock);
|
||||
if (list_empty(&sem->wait_list)) {
|
||||
/*
|
||||
* In case the wait queue is empty and the lock isn't owned
|
||||
* by a writer, this reader can exit the slowpath and return
|
||||
* immediately as its RWSEM_ACTIVE_READ_BIAS has already
|
||||
* been set in the count.
|
||||
*/
|
||||
if (atomic_long_read(&sem->count) >= 0) {
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
rwsem_set_reader_owned(sem);
|
||||
lockevent_inc(rwsem_rlock_fast);
|
||||
return sem;
|
||||
}
|
||||
adjustment += RWSEM_WAITING_BIAS;
|
||||
}
|
||||
list_add_tail(&waiter.list, &sem->wait_list);
|
||||
|
||||
/* we're now waiting on the lock, but no longer actively locking */
|
||||
count = atomic_long_add_return(adjustment, &sem->count);
|
||||
|
||||
/*
|
||||
* If there are no active locks, wake the front queued process(es).
|
||||
*
|
||||
* If there are no writers and we are first in the queue,
|
||||
* wake our own waiter to join the existing active readers !
|
||||
*/
|
||||
if (count == RWSEM_WAITING_BIAS ||
|
||||
(count > RWSEM_WAITING_BIAS &&
|
||||
adjustment != -RWSEM_ACTIVE_READ_BIAS))
|
||||
__rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);
|
||||
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
wake_up_q(&wake_q);
|
||||
|
||||
/* wait to be given the lock */
|
||||
while (true) {
|
||||
set_current_state(state);
|
||||
if (!waiter.task)
|
||||
break;
|
||||
if (signal_pending_state(state, current)) {
|
||||
raw_spin_lock_irq(&sem->wait_lock);
|
||||
if (waiter.task)
|
||||
goto out_nolock;
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
break;
|
||||
}
|
||||
schedule();
|
||||
lockevent_inc(rwsem_sleep_reader);
|
||||
}
|
||||
|
||||
__set_current_state(TASK_RUNNING);
|
||||
lockevent_inc(rwsem_rlock);
|
||||
return sem;
|
||||
out_nolock:
|
||||
list_del(&waiter.list);
|
||||
if (list_empty(&sem->wait_list))
|
||||
atomic_long_add(-RWSEM_WAITING_BIAS, &sem->count);
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
__set_current_state(TASK_RUNNING);
|
||||
lockevent_inc(rwsem_rlock_fail);
|
||||
return ERR_PTR(-EINTR);
|
||||
}
|
||||
|
||||
__visible struct rw_semaphore * __sched
|
||||
rwsem_down_read_failed(struct rw_semaphore *sem)
|
||||
{
|
||||
return __rwsem_down_read_failed_common(sem, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_down_read_failed);
|
||||
|
||||
__visible struct rw_semaphore * __sched
|
||||
rwsem_down_read_failed_killable(struct rw_semaphore *sem)
|
||||
{
|
||||
return __rwsem_down_read_failed_common(sem, TASK_KILLABLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_down_read_failed_killable);
|
||||
|
||||
/*
|
||||
* Wait until we successfully acquire the write lock
|
||||
*/
|
||||
static inline struct rw_semaphore *
|
||||
__rwsem_down_write_failed_common(struct rw_semaphore *sem, int state)
|
||||
{
|
||||
long count;
|
||||
bool waiting = true; /* any queued threads before us */
|
||||
struct rwsem_waiter waiter;
|
||||
struct rw_semaphore *ret = sem;
|
||||
DEFINE_WAKE_Q(wake_q);
|
||||
|
||||
/* undo write bias from down_write operation, stop active locking */
|
||||
count = atomic_long_sub_return(RWSEM_ACTIVE_WRITE_BIAS, &sem->count);
|
||||
|
||||
/* do optimistic spinning and steal lock if possible */
|
||||
if (rwsem_optimistic_spin(sem))
|
||||
return sem;
|
||||
|
||||
/*
|
||||
* Optimistic spinning failed, proceed to the slowpath
|
||||
* and block until we can acquire the sem.
|
||||
*/
|
||||
waiter.task = current;
|
||||
waiter.type = RWSEM_WAITING_FOR_WRITE;
|
||||
|
||||
raw_spin_lock_irq(&sem->wait_lock);
|
||||
|
||||
/* account for this before adding a new element to the list */
|
||||
if (list_empty(&sem->wait_list))
|
||||
waiting = false;
|
||||
|
||||
list_add_tail(&waiter.list, &sem->wait_list);
|
||||
|
||||
/* we're now waiting on the lock, but no longer actively locking */
|
||||
if (waiting) {
|
||||
count = atomic_long_read(&sem->count);
|
||||
|
||||
/*
|
||||
* If there were already threads queued before us and there are
|
||||
* no active writers, the lock must be read owned; so we try to
|
||||
* wake any read locks that were queued ahead of us.
|
||||
*/
|
||||
if (count > RWSEM_WAITING_BIAS) {
|
||||
__rwsem_mark_wake(sem, RWSEM_WAKE_READERS, &wake_q);
|
||||
/*
|
||||
* The wakeup is normally called _after_ the wait_lock
|
||||
* is released, but given that we are proactively waking
|
||||
* readers we can deal with the wake_q overhead as it is
|
||||
* similar to releasing and taking the wait_lock again
|
||||
* for attempting rwsem_try_write_lock().
|
||||
*/
|
||||
wake_up_q(&wake_q);
|
||||
|
||||
/*
|
||||
* Reinitialize wake_q after use.
|
||||
*/
|
||||
wake_q_init(&wake_q);
|
||||
}
|
||||
|
||||
} else
|
||||
count = atomic_long_add_return(RWSEM_WAITING_BIAS, &sem->count);
|
||||
|
||||
/* wait until we successfully acquire the lock */
|
||||
set_current_state(state);
|
||||
while (true) {
|
||||
if (rwsem_try_write_lock(count, sem))
|
||||
break;
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
|
||||
/* Block until there are no active lockers. */
|
||||
do {
|
||||
if (signal_pending_state(state, current))
|
||||
goto out_nolock;
|
||||
|
||||
schedule();
|
||||
lockevent_inc(rwsem_sleep_writer);
|
||||
set_current_state(state);
|
||||
} while ((count = atomic_long_read(&sem->count)) & RWSEM_ACTIVE_MASK);
|
||||
|
||||
raw_spin_lock_irq(&sem->wait_lock);
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
list_del(&waiter.list);
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
lockevent_inc(rwsem_wlock);
|
||||
|
||||
return ret;
|
||||
|
||||
out_nolock:
|
||||
__set_current_state(TASK_RUNNING);
|
||||
raw_spin_lock_irq(&sem->wait_lock);
|
||||
list_del(&waiter.list);
|
||||
if (list_empty(&sem->wait_list))
|
||||
atomic_long_add(-RWSEM_WAITING_BIAS, &sem->count);
|
||||
else
|
||||
__rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);
|
||||
raw_spin_unlock_irq(&sem->wait_lock);
|
||||
wake_up_q(&wake_q);
|
||||
lockevent_inc(rwsem_wlock_fail);
|
||||
|
||||
return ERR_PTR(-EINTR);
|
||||
}
|
||||
|
||||
__visible struct rw_semaphore * __sched
|
||||
rwsem_down_write_failed(struct rw_semaphore *sem)
|
||||
{
|
||||
return __rwsem_down_write_failed_common(sem, TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_down_write_failed);
|
||||
|
||||
__visible struct rw_semaphore * __sched
|
||||
rwsem_down_write_failed_killable(struct rw_semaphore *sem)
|
||||
{
|
||||
return __rwsem_down_write_failed_common(sem, TASK_KILLABLE);
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_down_write_failed_killable);
|
||||
|
||||
/*
|
||||
* handle waking up a waiter on the semaphore
|
||||
* - up_read/up_write has decremented the active part of count if we come here
|
||||
*/
|
||||
__visible
|
||||
struct rw_semaphore *rwsem_wake(struct rw_semaphore *sem)
|
||||
{
|
||||
unsigned long flags;
|
||||
DEFINE_WAKE_Q(wake_q);
|
||||
|
||||
/*
|
||||
* __rwsem_down_write_failed_common(sem)
|
||||
* rwsem_optimistic_spin(sem)
|
||||
* osq_unlock(sem->osq)
|
||||
* ...
|
||||
* atomic_long_add_return(&sem->count)
|
||||
*
|
||||
* - VS -
|
||||
*
|
||||
* __up_write()
|
||||
* if (atomic_long_sub_return_release(&sem->count) < 0)
|
||||
* rwsem_wake(sem)
|
||||
* osq_is_locked(&sem->osq)
|
||||
*
|
||||
* And __up_write() must observe !osq_is_locked() when it observes the
|
||||
* atomic_long_add_return() in order to not miss a wakeup.
|
||||
*
|
||||
* This boils down to:
|
||||
*
|
||||
* [S.rel] X = 1 [RmW] r0 = (Y += 0)
|
||||
* MB RMB
|
||||
* [RmW] Y += 1 [L] r1 = X
|
||||
*
|
||||
* exists (r0=1 /\ r1=0)
|
||||
*/
|
||||
smp_rmb();
|
||||
|
||||
/*
|
||||
* If a spinner is present, it is not necessary to do the wakeup.
|
||||
* Try to do wakeup only if the trylock succeeds to minimize
|
||||
* spinlock contention which may introduce too much delay in the
|
||||
* unlock operation.
|
||||
*
|
||||
* spinning writer up_write/up_read caller
|
||||
* --------------- -----------------------
|
||||
* [S] osq_unlock() [L] osq
|
||||
* MB RMB
|
||||
* [RmW] rwsem_try_write_lock() [RmW] spin_trylock(wait_lock)
|
||||
*
|
||||
* Here, it is important to make sure that there won't be a missed
|
||||
* wakeup while the rwsem is free and the only spinning writer goes
|
||||
* to sleep without taking the rwsem. Even when the spinning writer
|
||||
* is just going to break out of the waiting loop, it will still do
|
||||
* a trylock in rwsem_down_write_failed() before sleeping. IOW, if
|
||||
* rwsem_has_spinner() is true, it will guarantee at least one
|
||||
* trylock attempt on the rwsem later on.
|
||||
*/
|
||||
if (rwsem_has_spinner(sem)) {
|
||||
/*
|
||||
* The smp_rmb() here is to make sure that the spinner
|
||||
* state is consulted before reading the wait_lock.
|
||||
*/
|
||||
smp_rmb();
|
||||
if (!raw_spin_trylock_irqsave(&sem->wait_lock, flags))
|
||||
return sem;
|
||||
goto locked;
|
||||
}
|
||||
raw_spin_lock_irqsave(&sem->wait_lock, flags);
|
||||
locked:
|
||||
|
||||
if (!list_empty(&sem->wait_list))
|
||||
__rwsem_mark_wake(sem, RWSEM_WAKE_ANY, &wake_q);
|
||||
|
||||
raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
|
||||
wake_up_q(&wake_q);
|
||||
|
||||
return sem;
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_wake);
|
||||
|
||||
/*
|
||||
* downgrade a write lock into a read lock
|
||||
* - caller incremented waiting part of count and discovered it still negative
|
||||
* - just wake up any readers at the front of the queue
|
||||
*/
|
||||
__visible
|
||||
struct rw_semaphore *rwsem_downgrade_wake(struct rw_semaphore *sem)
|
||||
{
|
||||
unsigned long flags;
|
||||
DEFINE_WAKE_Q(wake_q);
|
||||
|
||||
raw_spin_lock_irqsave(&sem->wait_lock, flags);
|
||||
|
||||
if (!list_empty(&sem->wait_list))
|
||||
__rwsem_mark_wake(sem, RWSEM_WAKE_READ_OWNED, &wake_q);
|
||||
|
||||
raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
|
||||
wake_up_q(&wake_q);
|
||||
|
||||
return sem;
|
||||
}
|
||||
EXPORT_SYMBOL(rwsem_downgrade_wake);
|
||||
+1426
-27
File diff suppressed because it is too large
Load Diff
+6
-300
@@ -1,304 +1,10 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* The least significant 2 bits of the owner value has the following
|
||||
* meanings when set.
|
||||
* - RWSEM_READER_OWNED (bit 0): The rwsem is owned by readers
|
||||
* - RWSEM_ANONYMOUSLY_OWNED (bit 1): The rwsem is anonymously owned,
|
||||
* i.e. the owner(s) cannot be readily determined. It can be reader
|
||||
* owned or the owning writer is indeterminate.
|
||||
*
|
||||
* When a writer acquires a rwsem, it puts its task_struct pointer
|
||||
* into the owner field. It is cleared after an unlock.
|
||||
*
|
||||
* When a reader acquires a rwsem, it will also puts its task_struct
|
||||
* pointer into the owner field with both the RWSEM_READER_OWNED and
|
||||
* RWSEM_ANONYMOUSLY_OWNED bits set. On unlock, the owner field will
|
||||
* largely be left untouched. So for a free or reader-owned rwsem,
|
||||
* the owner value may contain information about the last reader that
|
||||
* acquires the rwsem. The anonymous bit is set because that particular
|
||||
* reader may or may not still own the lock.
|
||||
*
|
||||
* That information may be helpful in debugging cases where the system
|
||||
* seems to hang on a reader owned rwsem especially if only one reader
|
||||
* is involved. Ideally we would like to track all the readers that own
|
||||
* a rwsem, but the overhead is simply too big.
|
||||
*/
|
||||
#include "lock_events.h"
|
||||
|
||||
#define RWSEM_READER_OWNED (1UL << 0)
|
||||
#define RWSEM_ANONYMOUSLY_OWNED (1UL << 1)
|
||||
#ifndef __INTERNAL_RWSEM_H
|
||||
#define __INTERNAL_RWSEM_H
|
||||
#include <linux/rwsem.h>
|
||||
|
||||
#ifdef CONFIG_DEBUG_RWSEMS
|
||||
# define DEBUG_RWSEMS_WARN_ON(c, sem) do { \
|
||||
if (!debug_locks_silent && \
|
||||
WARN_ONCE(c, "DEBUG_RWSEMS_WARN_ON(%s): count = 0x%lx, owner = 0x%lx, curr 0x%lx, list %sempty\n",\
|
||||
#c, atomic_long_read(&(sem)->count), \
|
||||
(long)((sem)->owner), (long)current, \
|
||||
list_empty(&(sem)->wait_list) ? "" : "not ")) \
|
||||
debug_locks_off(); \
|
||||
} while (0)
|
||||
#else
|
||||
# define DEBUG_RWSEMS_WARN_ON(c, sem)
|
||||
#endif
|
||||
extern void __down_read(struct rw_semaphore *sem);
|
||||
extern void __up_read(struct rw_semaphore *sem);
|
||||
|
||||
/*
|
||||
* R/W semaphores originally for PPC using the stuff in lib/rwsem.c.
|
||||
* Adapted largely from include/asm-i386/rwsem.h
|
||||
* by Paul Mackerras <paulus@samba.org>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* the semaphore definition
|
||||
*/
|
||||
#ifdef CONFIG_64BIT
|
||||
# define RWSEM_ACTIVE_MASK 0xffffffffL
|
||||
#else
|
||||
# define RWSEM_ACTIVE_MASK 0x0000ffffL
|
||||
#endif
|
||||
|
||||
#define RWSEM_ACTIVE_BIAS 0x00000001L
|
||||
#define RWSEM_WAITING_BIAS (-RWSEM_ACTIVE_MASK-1)
|
||||
#define RWSEM_ACTIVE_READ_BIAS RWSEM_ACTIVE_BIAS
|
||||
#define RWSEM_ACTIVE_WRITE_BIAS (RWSEM_WAITING_BIAS + RWSEM_ACTIVE_BIAS)
|
||||
|
||||
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
||||
/*
|
||||
* All writes to owner are protected by WRITE_ONCE() to make sure that
|
||||
* store tearing can't happen as optimistic spinners may read and use
|
||||
* the owner value concurrently without lock. Read from owner, however,
|
||||
* may not need READ_ONCE() as long as the pointer value is only used
|
||||
* for comparison and isn't being dereferenced.
|
||||
*/
|
||||
static inline void rwsem_set_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
WRITE_ONCE(sem->owner, current);
|
||||
}
|
||||
|
||||
static inline void rwsem_clear_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
WRITE_ONCE(sem->owner, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* The task_struct pointer of the last owning reader will be left in
|
||||
* the owner field.
|
||||
*
|
||||
* Note that the owner value just indicates the task has owned the rwsem
|
||||
* previously, it may not be the real owner or one of the real owners
|
||||
* anymore when that field is examined, so take it with a grain of salt.
|
||||
*/
|
||||
static inline void __rwsem_set_reader_owned(struct rw_semaphore *sem,
|
||||
struct task_struct *owner)
|
||||
{
|
||||
unsigned long val = (unsigned long)owner | RWSEM_READER_OWNED
|
||||
| RWSEM_ANONYMOUSLY_OWNED;
|
||||
|
||||
WRITE_ONCE(sem->owner, (struct task_struct *)val);
|
||||
}
|
||||
|
||||
static inline void rwsem_set_reader_owned(struct rw_semaphore *sem)
|
||||
{
|
||||
__rwsem_set_reader_owned(sem, current);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the a rwsem waiter can spin on the rwsem's owner
|
||||
* and steal the lock, i.e. the lock is not anonymously owned.
|
||||
* N.B. !owner is considered spinnable.
|
||||
*/
|
||||
static inline bool is_rwsem_owner_spinnable(struct task_struct *owner)
|
||||
{
|
||||
return !((unsigned long)owner & RWSEM_ANONYMOUSLY_OWNED);
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if rwsem is owned by an anonymous writer or readers.
|
||||
*/
|
||||
static inline bool rwsem_has_anonymous_owner(struct task_struct *owner)
|
||||
{
|
||||
return (unsigned long)owner & RWSEM_ANONYMOUSLY_OWNED;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_RWSEMS
|
||||
/*
|
||||
* With CONFIG_DEBUG_RWSEMS configured, it will make sure that if there
|
||||
* is a task pointer in owner of a reader-owned rwsem, it will be the
|
||||
* real owner or one of the real owners. The only exception is when the
|
||||
* unlock is done by up_read_non_owner().
|
||||
*/
|
||||
#define rwsem_clear_reader_owned rwsem_clear_reader_owned
|
||||
static inline void rwsem_clear_reader_owned(struct rw_semaphore *sem)
|
||||
{
|
||||
unsigned long val = (unsigned long)current | RWSEM_READER_OWNED
|
||||
| RWSEM_ANONYMOUSLY_OWNED;
|
||||
if (READ_ONCE(sem->owner) == (struct task_struct *)val)
|
||||
cmpxchg_relaxed((unsigned long *)&sem->owner, val,
|
||||
RWSEM_READER_OWNED | RWSEM_ANONYMOUSLY_OWNED);
|
||||
}
|
||||
#endif
|
||||
|
||||
#else
|
||||
static inline void rwsem_set_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void rwsem_clear_owner(struct rw_semaphore *sem)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void __rwsem_set_reader_owned(struct rw_semaphore *sem,
|
||||
struct task_struct *owner)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void rwsem_set_reader_owned(struct rw_semaphore *sem)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef rwsem_clear_reader_owned
|
||||
static inline void rwsem_clear_reader_owned(struct rw_semaphore *sem)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
extern struct rw_semaphore *rwsem_down_read_failed(struct rw_semaphore *sem);
|
||||
extern struct rw_semaphore *rwsem_down_read_failed_killable(struct rw_semaphore *sem);
|
||||
extern struct rw_semaphore *rwsem_down_write_failed(struct rw_semaphore *sem);
|
||||
extern struct rw_semaphore *rwsem_down_write_failed_killable(struct rw_semaphore *sem);
|
||||
extern struct rw_semaphore *rwsem_wake(struct rw_semaphore *sem);
|
||||
extern struct rw_semaphore *rwsem_downgrade_wake(struct rw_semaphore *sem);
|
||||
|
||||
/*
|
||||
* lock for reading
|
||||
*/
|
||||
static inline void __down_read(struct rw_semaphore *sem)
|
||||
{
|
||||
if (unlikely(atomic_long_inc_return_acquire(&sem->count) <= 0)) {
|
||||
rwsem_down_read_failed(sem);
|
||||
DEBUG_RWSEMS_WARN_ON(!((unsigned long)sem->owner &
|
||||
RWSEM_READER_OWNED), sem);
|
||||
} else {
|
||||
rwsem_set_reader_owned(sem);
|
||||
}
|
||||
}
|
||||
|
||||
static inline int __down_read_killable(struct rw_semaphore *sem)
|
||||
{
|
||||
if (unlikely(atomic_long_inc_return_acquire(&sem->count) <= 0)) {
|
||||
if (IS_ERR(rwsem_down_read_failed_killable(sem)))
|
||||
return -EINTR;
|
||||
DEBUG_RWSEMS_WARN_ON(!((unsigned long)sem->owner &
|
||||
RWSEM_READER_OWNED), sem);
|
||||
} else {
|
||||
rwsem_set_reader_owned(sem);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __down_read_trylock(struct rw_semaphore *sem)
|
||||
{
|
||||
/*
|
||||
* Optimize for the case when the rwsem is not locked at all.
|
||||
*/
|
||||
long tmp = RWSEM_UNLOCKED_VALUE;
|
||||
|
||||
lockevent_inc(rwsem_rtrylock);
|
||||
do {
|
||||
if (atomic_long_try_cmpxchg_acquire(&sem->count, &tmp,
|
||||
tmp + RWSEM_ACTIVE_READ_BIAS)) {
|
||||
rwsem_set_reader_owned(sem);
|
||||
return 1;
|
||||
}
|
||||
} while (tmp >= 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* lock for writing
|
||||
*/
|
||||
static inline void __down_write(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
tmp = atomic_long_add_return_acquire(RWSEM_ACTIVE_WRITE_BIAS,
|
||||
&sem->count);
|
||||
if (unlikely(tmp != RWSEM_ACTIVE_WRITE_BIAS))
|
||||
rwsem_down_write_failed(sem);
|
||||
rwsem_set_owner(sem);
|
||||
}
|
||||
|
||||
static inline int __down_write_killable(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
tmp = atomic_long_add_return_acquire(RWSEM_ACTIVE_WRITE_BIAS,
|
||||
&sem->count);
|
||||
if (unlikely(tmp != RWSEM_ACTIVE_WRITE_BIAS))
|
||||
if (IS_ERR(rwsem_down_write_failed_killable(sem)))
|
||||
return -EINTR;
|
||||
rwsem_set_owner(sem);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __down_write_trylock(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
lockevent_inc(rwsem_wtrylock);
|
||||
tmp = atomic_long_cmpxchg_acquire(&sem->count, RWSEM_UNLOCKED_VALUE,
|
||||
RWSEM_ACTIVE_WRITE_BIAS);
|
||||
if (tmp == RWSEM_UNLOCKED_VALUE) {
|
||||
rwsem_set_owner(sem);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* unlock after reading
|
||||
*/
|
||||
static inline void __up_read(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
DEBUG_RWSEMS_WARN_ON(!((unsigned long)sem->owner & RWSEM_READER_OWNED),
|
||||
sem);
|
||||
rwsem_clear_reader_owned(sem);
|
||||
tmp = atomic_long_dec_return_release(&sem->count);
|
||||
if (unlikely(tmp < -1 && (tmp & RWSEM_ACTIVE_MASK) == 0))
|
||||
rwsem_wake(sem);
|
||||
}
|
||||
|
||||
/*
|
||||
* unlock after writing
|
||||
*/
|
||||
static inline void __up_write(struct rw_semaphore *sem)
|
||||
{
|
||||
DEBUG_RWSEMS_WARN_ON(sem->owner != current, sem);
|
||||
rwsem_clear_owner(sem);
|
||||
if (unlikely(atomic_long_sub_return_release(RWSEM_ACTIVE_WRITE_BIAS,
|
||||
&sem->count) < 0))
|
||||
rwsem_wake(sem);
|
||||
}
|
||||
|
||||
/*
|
||||
* downgrade write lock to read lock
|
||||
*/
|
||||
static inline void __downgrade_write(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
/*
|
||||
* When downgrading from exclusive to shared ownership,
|
||||
* anything inside the write-locked region cannot leak
|
||||
* into the read side. In contrast, anything in the
|
||||
* read-locked region is ok to be re-ordered into the
|
||||
* write side. As such, rely on RELEASE semantics.
|
||||
*/
|
||||
DEBUG_RWSEMS_WARN_ON(sem->owner != current, sem);
|
||||
tmp = atomic_long_add_return_release(-RWSEM_WAITING_BIAS, &sem->count);
|
||||
rwsem_set_reader_owned(sem);
|
||||
if (tmp < 0)
|
||||
rwsem_downgrade_wake(sem);
|
||||
}
|
||||
#endif /* __INTERNAL_RWSEM_H */
|
||||
|
||||
Reference in New Issue
Block a user