mirror of
https://github.com/Fishwaldo/Star64_linux.git
synced 2025-06-23 15:11:16 +00:00
Merge branch 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull more locking changes from Ingo Molnar: "This is the second round of locking tree updates for v3.16, offering large system scalability improvements: - optimistic spinning for rwsems, from Davidlohr Bueso. - 'qrwlocks' core code and x86 enablement, from Waiman Long and PeterZ" * 'locking-core-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: x86, locking/rwlocks: Enable qrwlocks on x86 locking/rwlocks: Introduce 'qrwlocks' - fair, queued rwlocks locking/mutexes: Documentation update/rewrite locking/rwsem: Fix checkpatch.pl warnings locking/rwsem: Fix warnings for CONFIG_RWSEM_GENERIC_SPINLOCK locking/rwsem: Support optimistic spinning
This commit is contained in:
commit
c29deef32e
13 changed files with 733 additions and 146 deletions
|
@ -1,139 +1,157 @@
|
||||||
Generic Mutex Subsystem
|
Generic Mutex Subsystem
|
||||||
|
|
||||||
started by Ingo Molnar <mingo@redhat.com>
|
started by Ingo Molnar <mingo@redhat.com>
|
||||||
|
updated by Davidlohr Bueso <davidlohr@hp.com>
|
||||||
|
|
||||||
"Why on earth do we need a new mutex subsystem, and what's wrong
|
What are mutexes?
|
||||||
with semaphores?"
|
-----------------
|
||||||
|
|
||||||
firstly, there's nothing wrong with semaphores. But if the simpler
|
In the Linux kernel, mutexes refer to a particular locking primitive
|
||||||
mutex semantics are sufficient for your code, then there are a couple
|
that enforces serialization on shared memory systems, and not only to
|
||||||
of advantages of mutexes:
|
the generic term referring to 'mutual exclusion' found in academia
|
||||||
|
or similar theoretical text books. Mutexes are sleeping locks which
|
||||||
|
behave similarly to binary semaphores, and were introduced in 2006[1]
|
||||||
|
as an alternative to these. This new data structure provided a number
|
||||||
|
of advantages, including simpler interfaces, and at that time smaller
|
||||||
|
code (see Disadvantages).
|
||||||
|
|
||||||
- 'struct mutex' is smaller on most architectures: E.g. on x86,
|
[1] http://lwn.net/Articles/164802/
|
||||||
'struct semaphore' is 20 bytes, 'struct mutex' is 16 bytes.
|
|
||||||
A smaller structure size means less RAM footprint, and better
|
|
||||||
CPU-cache utilization.
|
|
||||||
|
|
||||||
- tighter code. On x86 i get the following .text sizes when
|
Implementation
|
||||||
switching all mutex-alike semaphores in the kernel to the mutex
|
--------------
|
||||||
subsystem:
|
|
||||||
|
|
||||||
text data bss dec hex filename
|
Mutexes are represented by 'struct mutex', defined in include/linux/mutex.h
|
||||||
3280380 868188 396860 4545428 455b94 vmlinux-semaphore
|
and implemented in kernel/locking/mutex.c. These locks use a three
|
||||||
3255329 865296 396732 4517357 44eded vmlinux-mutex
|
state atomic counter (->count) to represent the different possible
|
||||||
|
transitions that can occur during the lifetime of a lock:
|
||||||
|
|
||||||
that's 25051 bytes of code saved, or a 0.76% win - off the hottest
|
1: unlocked
|
||||||
codepaths of the kernel. (The .data savings are 2892 bytes, or 0.33%)
|
0: locked, no waiters
|
||||||
Smaller code means better icache footprint, which is one of the
|
negative: locked, with potential waiters
|
||||||
major optimization goals in the Linux kernel currently.
|
|
||||||
|
|
||||||
- the mutex subsystem is slightly faster and has better scalability for
|
In its most basic form it also includes a wait-queue and a spinlock
|
||||||
contended workloads. On an 8-way x86 system, running a mutex-based
|
that serializes access to it. CONFIG_SMP systems can also include
|
||||||
kernel and testing creat+unlink+close (of separate, per-task files)
|
a pointer to the lock task owner (->owner) as well as a spinner MCS
|
||||||
in /tmp with 16 parallel tasks, the average number of ops/sec is:
|
lock (->osq), both described below in (ii).
|
||||||
|
|
||||||
Semaphores: Mutexes:
|
When acquiring a mutex, there are three possible paths that can be
|
||||||
|
taken, depending on the state of the lock:
|
||||||
|
|
||||||
$ ./test-mutex V 16 10 $ ./test-mutex V 16 10
|
(i) fastpath: tries to atomically acquire the lock by decrementing the
|
||||||
8 CPUs, running 16 tasks. 8 CPUs, running 16 tasks.
|
counter. If it was already taken by another task it goes to the next
|
||||||
checking VFS performance. checking VFS performance.
|
possible path. This logic is architecture specific. On x86-64, the
|
||||||
avg loops/sec: 34713 avg loops/sec: 84153
|
locking fastpath is 2 instructions:
|
||||||
CPU utilization: 63% CPU utilization: 22%
|
|
||||||
|
|
||||||
i.e. in this workload, the mutex based kernel was 2.4 times faster
|
0000000000000e10 <mutex_lock>:
|
||||||
than the semaphore based kernel, _and_ it also had 2.8 times less CPU
|
e21: f0 ff 0b lock decl (%rbx)
|
||||||
utilization. (In terms of 'ops per CPU cycle', the semaphore kernel
|
e24: 79 08 jns e2e <mutex_lock+0x1e>
|
||||||
performed 551 ops/sec per 1% of CPU time used, while the mutex kernel
|
|
||||||
performed 3825 ops/sec per 1% of CPU time used - it was 6.9 times
|
|
||||||
more efficient.)
|
|
||||||
|
|
||||||
the scalability difference is visible even on a 2-way P4 HT box:
|
|
||||||
|
|
||||||
Semaphores: Mutexes:
|
|
||||||
|
|
||||||
$ ./test-mutex V 16 10 $ ./test-mutex V 16 10
|
|
||||||
4 CPUs, running 16 tasks. 8 CPUs, running 16 tasks.
|
|
||||||
checking VFS performance. checking VFS performance.
|
|
||||||
avg loops/sec: 127659 avg loops/sec: 181082
|
|
||||||
CPU utilization: 100% CPU utilization: 34%
|
|
||||||
|
|
||||||
(the straight performance advantage of mutexes is 41%, the per-cycle
|
|
||||||
efficiency of mutexes is 4.1 times better.)
|
|
||||||
|
|
||||||
- there are no fastpath tradeoffs, the mutex fastpath is just as tight
|
|
||||||
as the semaphore fastpath. On x86, the locking fastpath is 2
|
|
||||||
instructions:
|
|
||||||
|
|
||||||
c0377ccb <mutex_lock>:
|
|
||||||
c0377ccb: f0 ff 08 lock decl (%eax)
|
|
||||||
c0377cce: 78 0e js c0377cde <.text..lock.mutex>
|
|
||||||
c0377cd0: c3 ret
|
|
||||||
|
|
||||||
the unlocking fastpath is equally tight:
|
the unlocking fastpath is equally tight:
|
||||||
|
|
||||||
c0377cd1 <mutex_unlock>:
|
0000000000000bc0 <mutex_unlock>:
|
||||||
c0377cd1: f0 ff 00 lock incl (%eax)
|
bc8: f0 ff 07 lock incl (%rdi)
|
||||||
c0377cd4: 7e 0f jle c0377ce5 <.text..lock.mutex+0x7>
|
bcb: 7f 0a jg bd7 <mutex_unlock+0x17>
|
||||||
c0377cd6: c3 ret
|
|
||||||
|
|
||||||
- 'struct mutex' semantics are well-defined and are enforced if
|
|
||||||
CONFIG_DEBUG_MUTEXES is turned on. Semaphores on the other hand have
|
|
||||||
virtually no debugging code or instrumentation. The mutex subsystem
|
|
||||||
checks and enforces the following rules:
|
|
||||||
|
|
||||||
* - only one task can hold the mutex at a time
|
(ii) midpath: aka optimistic spinning, tries to spin for acquisition
|
||||||
* - only the owner can unlock the mutex
|
while the lock owner is running and there are no other tasks ready
|
||||||
* - multiple unlocks are not permitted
|
to run that have higher priority (need_resched). The rationale is
|
||||||
* - recursive locking is not permitted
|
that if the lock owner is running, it is likely to release the lock
|
||||||
* - a mutex object must be initialized via the API
|
soon. The mutex spinners are queued up using MCS lock so that only
|
||||||
* - a mutex object must not be initialized via memset or copying
|
one spinner can compete for the mutex.
|
||||||
* - task may not exit with mutex held
|
|
||||||
* - memory areas where held locks reside must not be freed
|
|
||||||
* - held mutexes must not be reinitialized
|
|
||||||
* - mutexes may not be used in hardware or software interrupt
|
|
||||||
* contexts such as tasklets and timers
|
|
||||||
|
|
||||||
furthermore, there are also convenience features in the debugging
|
The MCS lock (proposed by Mellor-Crummey and Scott) is a simple spinlock
|
||||||
code:
|
with the desirable properties of being fair and with each cpu trying
|
||||||
|
to acquire the lock spinning on a local variable. It avoids expensive
|
||||||
|
cacheline bouncing that common test-and-set spinlock implementations
|
||||||
|
incur. An MCS-like lock is specially tailored for optimistic spinning
|
||||||
|
for sleeping lock implementation. An important feature of the customized
|
||||||
|
MCS lock is that it has the extra property that spinners are able to exit
|
||||||
|
the MCS spinlock queue when they need to reschedule. This further helps
|
||||||
|
avoid situations where MCS spinners that need to reschedule would continue
|
||||||
|
waiting to spin on mutex owner, only to go directly to slowpath upon
|
||||||
|
obtaining the MCS lock.
|
||||||
|
|
||||||
* - uses symbolic names of mutexes, whenever they are printed in debug output
|
|
||||||
* - point-of-acquire tracking, symbolic lookup of function names
|
(iii) slowpath: last resort, if the lock is still unable to be acquired,
|
||||||
* - list of all locks held in the system, printout of them
|
the task is added to the wait-queue and sleeps until woken up by the
|
||||||
* - owner tracking
|
unlock path. Under normal circumstances it blocks as TASK_UNINTERRUPTIBLE.
|
||||||
* - detects self-recursing locks and prints out all relevant info
|
|
||||||
* - detects multi-task circular deadlocks and prints out all affected
|
While formally kernel mutexes are sleepable locks, it is path (ii) that
|
||||||
* locks and tasks (and only those tasks)
|
makes them more practically a hybrid type. By simply not interrupting a
|
||||||
|
task and busy-waiting for a few cycles instead of immediately sleeping,
|
||||||
|
the performance of this lock has been seen to significantly improve a
|
||||||
|
number of workloads. Note that this technique is also used for rw-semaphores.
|
||||||
|
|
||||||
|
Semantics
|
||||||
|
---------
|
||||||
|
|
||||||
|
The mutex subsystem checks and enforces the following rules:
|
||||||
|
|
||||||
|
- Only one task can hold the mutex at a time.
|
||||||
|
- Only the owner can unlock the mutex.
|
||||||
|
- Multiple unlocks are not permitted.
|
||||||
|
- Recursive locking/unlocking is not permitted.
|
||||||
|
- A mutex must only be initialized via the API (see below).
|
||||||
|
- A task may not exit with a mutex held.
|
||||||
|
- Memory areas where held locks reside must not be freed.
|
||||||
|
- Held mutexes must not be reinitialized.
|
||||||
|
- Mutexes may not be used in hardware or software interrupt
|
||||||
|
contexts such as tasklets and timers.
|
||||||
|
|
||||||
|
These semantics are fully enforced when CONFIG DEBUG_MUTEXES is enabled.
|
||||||
|
In addition, the mutex debugging code also implements a number of other
|
||||||
|
features that make lock debugging easier and faster:
|
||||||
|
|
||||||
|
- Uses symbolic names of mutexes, whenever they are printed
|
||||||
|
in debug output.
|
||||||
|
- Point-of-acquire tracking, symbolic lookup of function names,
|
||||||
|
list of all locks held in the system, printout of them.
|
||||||
|
- Owner tracking.
|
||||||
|
- Detects self-recursing locks and prints out all relevant info.
|
||||||
|
- Detects multi-task circular deadlocks and prints out all affected
|
||||||
|
locks and tasks (and only those tasks).
|
||||||
|
|
||||||
|
|
||||||
|
Interfaces
|
||||||
|
----------
|
||||||
|
Statically define the mutex:
|
||||||
|
DEFINE_MUTEX(name);
|
||||||
|
|
||||||
|
Dynamically initialize the mutex:
|
||||||
|
mutex_init(mutex);
|
||||||
|
|
||||||
|
Acquire the mutex, uninterruptible:
|
||||||
|
void mutex_lock(struct mutex *lock);
|
||||||
|
void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
|
||||||
|
int mutex_trylock(struct mutex *lock);
|
||||||
|
|
||||||
|
Acquire the mutex, interruptible:
|
||||||
|
int mutex_lock_interruptible_nested(struct mutex *lock,
|
||||||
|
unsigned int subclass);
|
||||||
|
int mutex_lock_interruptible(struct mutex *lock);
|
||||||
|
|
||||||
|
Acquire the mutex, interruptible, if dec to 0:
|
||||||
|
int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
|
||||||
|
|
||||||
|
Unlock the mutex:
|
||||||
|
void mutex_unlock(struct mutex *lock);
|
||||||
|
|
||||||
|
Test if the mutex is taken:
|
||||||
|
int mutex_is_locked(struct mutex *lock);
|
||||||
|
|
||||||
Disadvantages
|
Disadvantages
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
The stricter mutex API means you cannot use mutexes the same way you
|
Unlike its original design and purpose, 'struct mutex' is larger than
|
||||||
can use semaphores: e.g. they cannot be used from an interrupt context,
|
most locks in the kernel. E.g: on x86-64 it is 40 bytes, almost twice
|
||||||
nor can they be unlocked from a different context that which acquired
|
as large as 'struct semaphore' (24 bytes) and 8 bytes shy of the
|
||||||
it. [ I'm not aware of any other (e.g. performance) disadvantages from
|
'struct rw_semaphore' variant. Larger structure sizes mean more CPU
|
||||||
using mutexes at the moment, please let me know if you find any. ]
|
cache and memory footprint.
|
||||||
|
|
||||||
Implementation of mutexes
|
When to use mutexes
|
||||||
-------------------------
|
-------------------
|
||||||
|
|
||||||
'struct mutex' is the new mutex type, defined in include/linux/mutex.h and
|
Unless the strict semantics of mutexes are unsuitable and/or the critical
|
||||||
implemented in kernel/locking/mutex.c. It is a counter-based mutex with a
|
region prevents the lock from being shared, always prefer them to any other
|
||||||
spinlock and a wait-list. The counter has 3 states: 1 for "unlocked", 0 for
|
locking primitive.
|
||||||
"locked" and negative numbers (usually -1) for "locked, potential waiters
|
|
||||||
queued".
|
|
||||||
|
|
||||||
the APIs of 'struct mutex' have been streamlined:
|
|
||||||
|
|
||||||
DEFINE_MUTEX(name);
|
|
||||||
|
|
||||||
mutex_init(mutex);
|
|
||||||
|
|
||||||
void mutex_lock(struct mutex *lock);
|
|
||||||
int mutex_lock_interruptible(struct mutex *lock);
|
|
||||||
int mutex_trylock(struct mutex *lock);
|
|
||||||
void mutex_unlock(struct mutex *lock);
|
|
||||||
int mutex_is_locked(struct mutex *lock);
|
|
||||||
void mutex_lock_nested(struct mutex *lock, unsigned int subclass);
|
|
||||||
int mutex_lock_interruptible_nested(struct mutex *lock,
|
|
||||||
unsigned int subclass);
|
|
||||||
int atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock);
|
|
||||||
|
|
|
@ -121,6 +121,7 @@ config X86
|
||||||
select MODULES_USE_ELF_RELA if X86_64
|
select MODULES_USE_ELF_RELA if X86_64
|
||||||
select CLONE_BACKWARDS if X86_32
|
select CLONE_BACKWARDS if X86_32
|
||||||
select ARCH_USE_BUILTIN_BSWAP
|
select ARCH_USE_BUILTIN_BSWAP
|
||||||
|
select ARCH_USE_QUEUE_RWLOCK
|
||||||
select OLD_SIGSUSPEND3 if X86_32 || IA32_EMULATION
|
select OLD_SIGSUSPEND3 if X86_32 || IA32_EMULATION
|
||||||
select OLD_SIGACTION if X86_32
|
select OLD_SIGACTION if X86_32
|
||||||
select COMPAT_OLD_SIGACTION if IA32_EMULATION
|
select COMPAT_OLD_SIGACTION if IA32_EMULATION
|
||||||
|
|
17
arch/x86/include/asm/qrwlock.h
Normal file
17
arch/x86/include/asm/qrwlock.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#ifndef _ASM_X86_QRWLOCK_H
|
||||||
|
#define _ASM_X86_QRWLOCK_H
|
||||||
|
|
||||||
|
#include <asm-generic/qrwlock_types.h>
|
||||||
|
|
||||||
|
#if !defined(CONFIG_X86_OOSTORE) && !defined(CONFIG_X86_PPRO_FENCE)
|
||||||
|
#define queue_write_unlock queue_write_unlock
|
||||||
|
static inline void queue_write_unlock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
barrier();
|
||||||
|
ACCESS_ONCE(*(u8 *)&lock->cnts) = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <asm-generic/qrwlock.h>
|
||||||
|
|
||||||
|
#endif /* _ASM_X86_QRWLOCK_H */
|
|
@ -187,6 +187,7 @@ static inline void arch_spin_unlock_wait(arch_spinlock_t *lock)
|
||||||
cpu_relax();
|
cpu_relax();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef CONFIG_QUEUE_RWLOCK
|
||||||
/*
|
/*
|
||||||
* Read-write spinlocks, allowing multiple readers
|
* Read-write spinlocks, allowing multiple readers
|
||||||
* but only one writer.
|
* but only one writer.
|
||||||
|
@ -269,6 +270,9 @@ static inline void arch_write_unlock(arch_rwlock_t *rw)
|
||||||
asm volatile(LOCK_PREFIX WRITE_LOCK_ADD(%1) "%0"
|
asm volatile(LOCK_PREFIX WRITE_LOCK_ADD(%1) "%0"
|
||||||
: "+m" (rw->write) : "i" (RW_LOCK_BIAS) : "memory");
|
: "+m" (rw->write) : "i" (RW_LOCK_BIAS) : "memory");
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
#include <asm/qrwlock.h>
|
||||||
|
#endif /* CONFIG_QUEUE_RWLOCK */
|
||||||
|
|
||||||
#define arch_read_lock_flags(lock, flags) arch_read_lock(lock)
|
#define arch_read_lock_flags(lock, flags) arch_read_lock(lock)
|
||||||
#define arch_write_lock_flags(lock, flags) arch_write_lock(lock)
|
#define arch_write_lock_flags(lock, flags) arch_write_lock(lock)
|
||||||
|
|
|
@ -34,6 +34,10 @@ typedef struct arch_spinlock {
|
||||||
|
|
||||||
#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } }
|
#define __ARCH_SPIN_LOCK_UNLOCKED { { 0 } }
|
||||||
|
|
||||||
|
#ifdef CONFIG_QUEUE_RWLOCK
|
||||||
|
#include <asm-generic/qrwlock_types.h>
|
||||||
|
#else
|
||||||
#include <asm/rwlock.h>
|
#include <asm/rwlock.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif /* _ASM_X86_SPINLOCK_TYPES_H */
|
#endif /* _ASM_X86_SPINLOCK_TYPES_H */
|
||||||
|
|
166
include/asm-generic/qrwlock.h
Normal file
166
include/asm-generic/qrwlock.h
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
/*
|
||||||
|
* Queue read/write lock
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
*
|
||||||
|
* Authors: Waiman Long <waiman.long@hp.com>
|
||||||
|
*/
|
||||||
|
#ifndef __ASM_GENERIC_QRWLOCK_H
|
||||||
|
#define __ASM_GENERIC_QRWLOCK_H
|
||||||
|
|
||||||
|
#include <linux/atomic.h>
|
||||||
|
#include <asm/barrier.h>
|
||||||
|
#include <asm/processor.h>
|
||||||
|
|
||||||
|
#include <asm-generic/qrwlock_types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writer states & reader shift and bias
|
||||||
|
*/
|
||||||
|
#define _QW_WAITING 1 /* A writer is waiting */
|
||||||
|
#define _QW_LOCKED 0xff /* A writer holds the lock */
|
||||||
|
#define _QW_WMASK 0xff /* Writer mask */
|
||||||
|
#define _QR_SHIFT 8 /* Reader count shift */
|
||||||
|
#define _QR_BIAS (1U << _QR_SHIFT)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* External function declarations
|
||||||
|
*/
|
||||||
|
extern void queue_read_lock_slowpath(struct qrwlock *lock);
|
||||||
|
extern void queue_write_lock_slowpath(struct qrwlock *lock);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_read_can_lock- would read_trylock() succeed?
|
||||||
|
* @lock: Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline int queue_read_can_lock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
return !(atomic_read(&lock->cnts) & _QW_WMASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_write_can_lock- would write_trylock() succeed?
|
||||||
|
* @lock: Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline int queue_write_can_lock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
return !atomic_read(&lock->cnts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_read_trylock - try to acquire read lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
* Return: 1 if lock acquired, 0 if failed
|
||||||
|
*/
|
||||||
|
static inline int queue_read_trylock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
u32 cnts;
|
||||||
|
|
||||||
|
cnts = atomic_read(&lock->cnts);
|
||||||
|
if (likely(!(cnts & _QW_WMASK))) {
|
||||||
|
cnts = (u32)atomic_add_return(_QR_BIAS, &lock->cnts);
|
||||||
|
if (likely(!(cnts & _QW_WMASK)))
|
||||||
|
return 1;
|
||||||
|
atomic_sub(_QR_BIAS, &lock->cnts);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_write_trylock - try to acquire write lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
* Return: 1 if lock acquired, 0 if failed
|
||||||
|
*/
|
||||||
|
static inline int queue_write_trylock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
u32 cnts;
|
||||||
|
|
||||||
|
cnts = atomic_read(&lock->cnts);
|
||||||
|
if (unlikely(cnts))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return likely(atomic_cmpxchg(&lock->cnts,
|
||||||
|
cnts, cnts | _QW_LOCKED) == cnts);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* queue_read_lock - acquire read lock of a queue rwlock
|
||||||
|
* @lock: Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline void queue_read_lock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
u32 cnts;
|
||||||
|
|
||||||
|
cnts = atomic_add_return(_QR_BIAS, &lock->cnts);
|
||||||
|
if (likely(!(cnts & _QW_WMASK)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* The slowpath will decrement the reader count, if necessary. */
|
||||||
|
queue_read_lock_slowpath(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_write_lock - acquire write lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline void queue_write_lock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
/* Optimize for the unfair lock case where the fair flag is 0. */
|
||||||
|
if (atomic_cmpxchg(&lock->cnts, 0, _QW_LOCKED) == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
queue_write_lock_slowpath(lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_read_unlock - release read lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline void queue_read_unlock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Atomically decrement the reader count
|
||||||
|
*/
|
||||||
|
smp_mb__before_atomic();
|
||||||
|
atomic_sub(_QR_BIAS, &lock->cnts);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef queue_write_unlock
|
||||||
|
/**
|
||||||
|
* queue_write_unlock - release write lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
static inline void queue_write_unlock(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If the writer field is atomic, it can be cleared directly.
|
||||||
|
* Otherwise, an atomic subtraction will be used to clear it.
|
||||||
|
*/
|
||||||
|
smp_mb__before_atomic();
|
||||||
|
atomic_sub(_QW_LOCKED, &lock->cnts);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remapping rwlock architecture specific functions to the corresponding
|
||||||
|
* queue rwlock functions.
|
||||||
|
*/
|
||||||
|
#define arch_read_can_lock(l) queue_read_can_lock(l)
|
||||||
|
#define arch_write_can_lock(l) queue_write_can_lock(l)
|
||||||
|
#define arch_read_lock(l) queue_read_lock(l)
|
||||||
|
#define arch_write_lock(l) queue_write_lock(l)
|
||||||
|
#define arch_read_trylock(l) queue_read_trylock(l)
|
||||||
|
#define arch_write_trylock(l) queue_write_trylock(l)
|
||||||
|
#define arch_read_unlock(l) queue_read_unlock(l)
|
||||||
|
#define arch_write_unlock(l) queue_write_unlock(l)
|
||||||
|
|
||||||
|
#endif /* __ASM_GENERIC_QRWLOCK_H */
|
21
include/asm-generic/qrwlock_types.h
Normal file
21
include/asm-generic/qrwlock_types.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef __ASM_GENERIC_QRWLOCK_TYPES_H
|
||||||
|
#define __ASM_GENERIC_QRWLOCK_TYPES_H
|
||||||
|
|
||||||
|
#include <linux/types.h>
|
||||||
|
#include <asm/spinlock_types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The queue read/write lock data structure
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct qrwlock {
|
||||||
|
atomic_t cnts;
|
||||||
|
arch_spinlock_t lock;
|
||||||
|
} arch_rwlock_t;
|
||||||
|
|
||||||
|
#define __ARCH_RW_LOCK_UNLOCKED { \
|
||||||
|
.cnts = ATOMIC_INIT(0), \
|
||||||
|
.lock = __ARCH_SPIN_LOCK_UNLOCKED, \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __ASM_GENERIC_QRWLOCK_TYPES_H */
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
|
|
||||||
|
struct optimistic_spin_queue;
|
||||||
struct rw_semaphore;
|
struct rw_semaphore;
|
||||||
|
|
||||||
#ifdef CONFIG_RWSEM_GENERIC_SPINLOCK
|
#ifdef CONFIG_RWSEM_GENERIC_SPINLOCK
|
||||||
|
@ -23,9 +24,17 @@ struct rw_semaphore;
|
||||||
#else
|
#else
|
||||||
/* All arch specific implementations share the same struct */
|
/* All arch specific implementations share the same struct */
|
||||||
struct rw_semaphore {
|
struct rw_semaphore {
|
||||||
long count;
|
long count;
|
||||||
raw_spinlock_t wait_lock;
|
raw_spinlock_t wait_lock;
|
||||||
struct list_head wait_list;
|
struct list_head wait_list;
|
||||||
|
#ifdef CONFIG_SMP
|
||||||
|
/*
|
||||||
|
* Write owner. Used as a speculative check to see
|
||||||
|
* if the owner is running on the cpu.
|
||||||
|
*/
|
||||||
|
struct task_struct *owner;
|
||||||
|
struct optimistic_spin_queue *osq; /* spinner MCS lock */
|
||||||
|
#endif
|
||||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||||
struct lockdep_map dep_map;
|
struct lockdep_map dep_map;
|
||||||
#endif
|
#endif
|
||||||
|
@ -55,11 +64,21 @@ static inline int rwsem_is_locked(struct rw_semaphore *sem)
|
||||||
# define __RWSEM_DEP_MAP_INIT(lockname)
|
# define __RWSEM_DEP_MAP_INIT(lockname)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_SMP) && !defined(CONFIG_RWSEM_GENERIC_SPINLOCK)
|
||||||
|
#define __RWSEM_INITIALIZER(name) \
|
||||||
|
{ RWSEM_UNLOCKED_VALUE, \
|
||||||
|
__RAW_SPIN_LOCK_UNLOCKED(name.wait_lock), \
|
||||||
|
LIST_HEAD_INIT((name).wait_list), \
|
||||||
|
NULL, /* owner */ \
|
||||||
|
NULL /* mcs lock */ \
|
||||||
|
__RWSEM_DEP_MAP_INIT(name) }
|
||||||
|
#else
|
||||||
#define __RWSEM_INITIALIZER(name) \
|
#define __RWSEM_INITIALIZER(name) \
|
||||||
{ RWSEM_UNLOCKED_VALUE, \
|
{ RWSEM_UNLOCKED_VALUE, \
|
||||||
__RAW_SPIN_LOCK_UNLOCKED(name.wait_lock), \
|
__RAW_SPIN_LOCK_UNLOCKED(name.wait_lock), \
|
||||||
LIST_HEAD_INIT((name).wait_list) \
|
LIST_HEAD_INIT((name).wait_list) \
|
||||||
__RWSEM_DEP_MAP_INIT(name) }
|
__RWSEM_DEP_MAP_INIT(name) }
|
||||||
|
#endif
|
||||||
|
|
||||||
#define DECLARE_RWSEM(name) \
|
#define DECLARE_RWSEM(name) \
|
||||||
struct rw_semaphore name = __RWSEM_INITIALIZER(name)
|
struct rw_semaphore name = __RWSEM_INITIALIZER(name)
|
||||||
|
|
|
@ -223,3 +223,10 @@ endif
|
||||||
config MUTEX_SPIN_ON_OWNER
|
config MUTEX_SPIN_ON_OWNER
|
||||||
def_bool y
|
def_bool y
|
||||||
depends on SMP && !DEBUG_MUTEXES
|
depends on SMP && !DEBUG_MUTEXES
|
||||||
|
|
||||||
|
config ARCH_USE_QUEUE_RWLOCK
|
||||||
|
bool
|
||||||
|
|
||||||
|
config QUEUE_RWLOCK
|
||||||
|
def_bool y if ARCH_USE_QUEUE_RWLOCK
|
||||||
|
depends on SMP
|
||||||
|
|
|
@ -24,4 +24,5 @@ obj-$(CONFIG_DEBUG_SPINLOCK) += spinlock_debug.o
|
||||||
obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o
|
obj-$(CONFIG_RWSEM_GENERIC_SPINLOCK) += rwsem-spinlock.o
|
||||||
obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.o
|
obj-$(CONFIG_RWSEM_XCHGADD_ALGORITHM) += rwsem-xadd.o
|
||||||
obj-$(CONFIG_PERCPU_RWSEM) += percpu-rwsem.o
|
obj-$(CONFIG_PERCPU_RWSEM) += percpu-rwsem.o
|
||||||
|
obj-$(CONFIG_QUEUE_RWLOCK) += qrwlock.o
|
||||||
obj-$(CONFIG_LOCK_TORTURE_TEST) += locktorture.o
|
obj-$(CONFIG_LOCK_TORTURE_TEST) += locktorture.o
|
||||||
|
|
133
kernel/locking/qrwlock.c
Normal file
133
kernel/locking/qrwlock.c
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
/*
|
||||||
|
* Queue read/write lock
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* (C) Copyright 2013-2014 Hewlett-Packard Development Company, L.P.
|
||||||
|
*
|
||||||
|
* Authors: Waiman Long <waiman.long@hp.com>
|
||||||
|
*/
|
||||||
|
#include <linux/smp.h>
|
||||||
|
#include <linux/bug.h>
|
||||||
|
#include <linux/cpumask.h>
|
||||||
|
#include <linux/percpu.h>
|
||||||
|
#include <linux/hardirq.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <asm/qrwlock.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rspin_until_writer_unlock - inc reader count & spin until writer is gone
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
* @writer: Current queue rwlock writer status byte
|
||||||
|
*
|
||||||
|
* In interrupt context or at the head of the queue, the reader will just
|
||||||
|
* increment the reader count & wait until the writer releases the lock.
|
||||||
|
*/
|
||||||
|
static __always_inline void
|
||||||
|
rspin_until_writer_unlock(struct qrwlock *lock, u32 cnts)
|
||||||
|
{
|
||||||
|
while ((cnts & _QW_WMASK) == _QW_LOCKED) {
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
cnts = smp_load_acquire((u32 *)&lock->cnts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_read_lock_slowpath - acquire read lock of a queue rwlock
|
||||||
|
* @lock: Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
void queue_read_lock_slowpath(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
u32 cnts;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Readers come here when they cannot get the lock without waiting
|
||||||
|
*/
|
||||||
|
if (unlikely(in_interrupt())) {
|
||||||
|
/*
|
||||||
|
* Readers in interrupt context will spin until the lock is
|
||||||
|
* available without waiting in the queue.
|
||||||
|
*/
|
||||||
|
cnts = smp_load_acquire((u32 *)&lock->cnts);
|
||||||
|
rspin_until_writer_unlock(lock, cnts);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
atomic_sub(_QR_BIAS, &lock->cnts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Put the reader into the wait queue
|
||||||
|
*/
|
||||||
|
arch_spin_lock(&lock->lock);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* At the head of the wait queue now, wait until the writer state
|
||||||
|
* goes to 0 and then try to increment the reader count and get
|
||||||
|
* the lock. It is possible that an incoming writer may steal the
|
||||||
|
* lock in the interim, so it is necessary to check the writer byte
|
||||||
|
* to make sure that the write lock isn't taken.
|
||||||
|
*/
|
||||||
|
while (atomic_read(&lock->cnts) & _QW_WMASK)
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
|
||||||
|
cnts = atomic_add_return(_QR_BIAS, &lock->cnts) - _QR_BIAS;
|
||||||
|
rspin_until_writer_unlock(lock, cnts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Signal the next one in queue to become queue head
|
||||||
|
*/
|
||||||
|
arch_spin_unlock(&lock->lock);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(queue_read_lock_slowpath);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* queue_write_lock_slowpath - acquire write lock of a queue rwlock
|
||||||
|
* @lock : Pointer to queue rwlock structure
|
||||||
|
*/
|
||||||
|
void queue_write_lock_slowpath(struct qrwlock *lock)
|
||||||
|
{
|
||||||
|
u32 cnts;
|
||||||
|
|
||||||
|
/* Put the writer into the wait queue */
|
||||||
|
arch_spin_lock(&lock->lock);
|
||||||
|
|
||||||
|
/* Try to acquire the lock directly if no reader is present */
|
||||||
|
if (!atomic_read(&lock->cnts) &&
|
||||||
|
(atomic_cmpxchg(&lock->cnts, 0, _QW_LOCKED) == 0))
|
||||||
|
goto unlock;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the waiting flag to notify readers that a writer is pending,
|
||||||
|
* or wait for a previous writer to go away.
|
||||||
|
*/
|
||||||
|
for (;;) {
|
||||||
|
cnts = atomic_read(&lock->cnts);
|
||||||
|
if (!(cnts & _QW_WMASK) &&
|
||||||
|
(atomic_cmpxchg(&lock->cnts, cnts,
|
||||||
|
cnts | _QW_WAITING) == cnts))
|
||||||
|
break;
|
||||||
|
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When no more readers, set the locked flag */
|
||||||
|
for (;;) {
|
||||||
|
cnts = atomic_read(&lock->cnts);
|
||||||
|
if ((cnts == _QW_WAITING) &&
|
||||||
|
(atomic_cmpxchg(&lock->cnts, _QW_WAITING,
|
||||||
|
_QW_LOCKED) == _QW_WAITING))
|
||||||
|
break;
|
||||||
|
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
}
|
||||||
|
unlock:
|
||||||
|
arch_spin_unlock(&lock->lock);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL(queue_write_lock_slowpath);
|
|
@ -5,11 +5,17 @@
|
||||||
*
|
*
|
||||||
* Writer lock-stealing by Alex Shi <alex.shi@intel.com>
|
* Writer lock-stealing by Alex Shi <alex.shi@intel.com>
|
||||||
* and Michel Lespinasse <walken@google.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/rwsem.h>
|
||||||
#include <linux/sched.h>
|
#include <linux/sched.h>
|
||||||
#include <linux/init.h>
|
#include <linux/init.h>
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
|
#include <linux/sched/rt.h>
|
||||||
|
|
||||||
|
#include "mcs_spinlock.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Guide to the rw_semaphore's count field for common values.
|
* Guide to the rw_semaphore's count field for common values.
|
||||||
|
@ -76,6 +82,10 @@ void __init_rwsem(struct rw_semaphore *sem, const char *name,
|
||||||
sem->count = RWSEM_UNLOCKED_VALUE;
|
sem->count = RWSEM_UNLOCKED_VALUE;
|
||||||
raw_spin_lock_init(&sem->wait_lock);
|
raw_spin_lock_init(&sem->wait_lock);
|
||||||
INIT_LIST_HEAD(&sem->wait_list);
|
INIT_LIST_HEAD(&sem->wait_list);
|
||||||
|
#ifdef CONFIG_SMP
|
||||||
|
sem->owner = NULL;
|
||||||
|
sem->osq = NULL;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(__init_rwsem);
|
EXPORT_SYMBOL(__init_rwsem);
|
||||||
|
@ -190,7 +200,7 @@ __rwsem_do_wake(struct rw_semaphore *sem, enum rwsem_wake_type wake_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* wait for the read lock to be granted
|
* Wait for the read lock to be granted
|
||||||
*/
|
*/
|
||||||
__visible
|
__visible
|
||||||
struct rw_semaphore __sched *rwsem_down_read_failed(struct rw_semaphore *sem)
|
struct rw_semaphore __sched *rwsem_down_read_failed(struct rw_semaphore *sem)
|
||||||
|
@ -237,64 +247,221 @@ struct rw_semaphore __sched *rwsem_down_read_failed(struct rw_semaphore *sem)
|
||||||
return sem;
|
return sem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool rwsem_try_write_lock(long count, struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
if (!(count & RWSEM_ACTIVE_MASK)) {
|
||||||
|
/* try acquiring the write lock */
|
||||||
|
if (sem->count == RWSEM_WAITING_BIAS &&
|
||||||
|
cmpxchg(&sem->count, RWSEM_WAITING_BIAS,
|
||||||
|
RWSEM_ACTIVE_WRITE_BIAS) == RWSEM_WAITING_BIAS) {
|
||||||
|
if (!list_is_singular(&sem->wait_list))
|
||||||
|
rwsem_atomic_update(RWSEM_WAITING_BIAS, sem);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_SMP
|
||||||
/*
|
/*
|
||||||
* wait until we successfully acquire the write lock
|
* 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 old, count = ACCESS_ONCE(sem->count);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (!(count == 0 || count == RWSEM_WAITING_BIAS))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
old = cmpxchg(&sem->count, count, count + RWSEM_ACTIVE_WRITE_BIAS);
|
||||||
|
if (old == count)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
count = old;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
struct task_struct *owner;
|
||||||
|
bool on_cpu = true;
|
||||||
|
|
||||||
|
if (need_resched())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
owner = ACCESS_ONCE(sem->owner);
|
||||||
|
if (owner)
|
||||||
|
on_cpu = owner->on_cpu;
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If sem->owner is not set, the rwsem owner may have
|
||||||
|
* just acquired it and not set the owner yet or the rwsem
|
||||||
|
* has been released.
|
||||||
|
*/
|
||||||
|
return on_cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool owner_running(struct rw_semaphore *sem,
|
||||||
|
struct task_struct *owner)
|
||||||
|
{
|
||||||
|
if (sem->owner != owner)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
|
||||||
|
return owner->on_cpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
static noinline
|
||||||
|
bool rwsem_spin_on_owner(struct rw_semaphore *sem, struct task_struct *owner)
|
||||||
|
{
|
||||||
|
rcu_read_lock();
|
||||||
|
while (owner_running(sem, owner)) {
|
||||||
|
if (need_resched())
|
||||||
|
break;
|
||||||
|
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
}
|
||||||
|
rcu_read_unlock();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We break out the loop above on need_resched() or when the
|
||||||
|
* owner changed, which is a sign for heavy contention. Return
|
||||||
|
* success only when sem->owner is NULL.
|
||||||
|
*/
|
||||||
|
return sem->owner == NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool rwsem_optimistic_spin(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
struct task_struct *owner;
|
||||||
|
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;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
owner = ACCESS_ONCE(sem->owner);
|
||||||
|
if (owner && !rwsem_spin_on_owner(sem, owner))
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* wait_lock will be acquired if write_lock is obtained */
|
||||||
|
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 (!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.
|
||||||
|
*/
|
||||||
|
arch_mutex_cpu_relax();
|
||||||
|
}
|
||||||
|
osq_unlock(&sem->osq);
|
||||||
|
done:
|
||||||
|
preempt_enable();
|
||||||
|
return taken;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
static bool rwsem_optimistic_spin(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wait until we successfully acquire the write lock
|
||||||
*/
|
*/
|
||||||
__visible
|
__visible
|
||||||
struct rw_semaphore __sched *rwsem_down_write_failed(struct rw_semaphore *sem)
|
struct rw_semaphore __sched *rwsem_down_write_failed(struct rw_semaphore *sem)
|
||||||
{
|
{
|
||||||
long count, adjustment = -RWSEM_ACTIVE_WRITE_BIAS;
|
long count;
|
||||||
|
bool waiting = true; /* any queued threads before us */
|
||||||
struct rwsem_waiter waiter;
|
struct rwsem_waiter waiter;
|
||||||
struct task_struct *tsk = current;
|
|
||||||
|
|
||||||
/* set up my own style of waitqueue */
|
/* undo write bias from down_write operation, stop active locking */
|
||||||
waiter.task = tsk;
|
count = rwsem_atomic_update(-RWSEM_ACTIVE_WRITE_BIAS, sem);
|
||||||
|
|
||||||
|
/* 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;
|
waiter.type = RWSEM_WAITING_FOR_WRITE;
|
||||||
|
|
||||||
raw_spin_lock_irq(&sem->wait_lock);
|
raw_spin_lock_irq(&sem->wait_lock);
|
||||||
|
|
||||||
|
/* account for this before adding a new element to the list */
|
||||||
if (list_empty(&sem->wait_list))
|
if (list_empty(&sem->wait_list))
|
||||||
adjustment += RWSEM_WAITING_BIAS;
|
waiting = false;
|
||||||
|
|
||||||
list_add_tail(&waiter.list, &sem->wait_list);
|
list_add_tail(&waiter.list, &sem->wait_list);
|
||||||
|
|
||||||
/* we're now waiting on the lock, but no longer actively locking */
|
/* we're now waiting on the lock, but no longer actively locking */
|
||||||
count = rwsem_atomic_update(adjustment, sem);
|
if (waiting) {
|
||||||
|
count = ACCESS_ONCE(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
|
* If there were already threads queued before us and there are
|
||||||
* any read locks that were queued ahead of us. */
|
* no active writers, the lock must be read owned; so we try to
|
||||||
if (count > RWSEM_WAITING_BIAS &&
|
* wake any read locks that were queued ahead of us.
|
||||||
adjustment == -RWSEM_ACTIVE_WRITE_BIAS)
|
*/
|
||||||
sem = __rwsem_do_wake(sem, RWSEM_WAKE_READERS);
|
if (count > RWSEM_WAITING_BIAS)
|
||||||
|
sem = __rwsem_do_wake(sem, RWSEM_WAKE_READERS);
|
||||||
|
|
||||||
|
} else
|
||||||
|
count = rwsem_atomic_update(RWSEM_WAITING_BIAS, sem);
|
||||||
|
|
||||||
/* wait until we successfully acquire the lock */
|
/* wait until we successfully acquire the lock */
|
||||||
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!(count & RWSEM_ACTIVE_MASK)) {
|
if (rwsem_try_write_lock(count, sem))
|
||||||
/* Try acquiring the write lock. */
|
break;
|
||||||
count = RWSEM_ACTIVE_WRITE_BIAS;
|
|
||||||
if (!list_is_singular(&sem->wait_list))
|
|
||||||
count += RWSEM_WAITING_BIAS;
|
|
||||||
|
|
||||||
if (sem->count == RWSEM_WAITING_BIAS &&
|
|
||||||
cmpxchg(&sem->count, RWSEM_WAITING_BIAS, count) ==
|
|
||||||
RWSEM_WAITING_BIAS)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
raw_spin_unlock_irq(&sem->wait_lock);
|
raw_spin_unlock_irq(&sem->wait_lock);
|
||||||
|
|
||||||
/* Block until there are no active lockers. */
|
/* Block until there are no active lockers. */
|
||||||
do {
|
do {
|
||||||
schedule();
|
schedule();
|
||||||
set_task_state(tsk, TASK_UNINTERRUPTIBLE);
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||||
} while ((count = sem->count) & RWSEM_ACTIVE_MASK);
|
} while ((count = sem->count) & RWSEM_ACTIVE_MASK);
|
||||||
|
|
||||||
raw_spin_lock_irq(&sem->wait_lock);
|
raw_spin_lock_irq(&sem->wait_lock);
|
||||||
}
|
}
|
||||||
|
__set_current_state(TASK_RUNNING);
|
||||||
|
|
||||||
list_del(&waiter.list);
|
list_del(&waiter.list);
|
||||||
raw_spin_unlock_irq(&sem->wait_lock);
|
raw_spin_unlock_irq(&sem->wait_lock);
|
||||||
tsk->state = TASK_RUNNING;
|
|
||||||
|
|
||||||
return sem;
|
return sem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,27 @@
|
||||||
|
|
||||||
#include <linux/atomic.h>
|
#include <linux/atomic.h>
|
||||||
|
|
||||||
|
#if defined(CONFIG_SMP) && defined(CONFIG_RWSEM_XCHGADD_ALGORITHM)
|
||||||
|
static inline void rwsem_set_owner(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
sem->owner = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rwsem_clear_owner(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
sem->owner = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
static inline void rwsem_set_owner(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void rwsem_clear_owner(struct rw_semaphore *sem)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* lock for reading
|
* lock for reading
|
||||||
*/
|
*/
|
||||||
|
@ -48,6 +69,7 @@ void __sched down_write(struct rw_semaphore *sem)
|
||||||
rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
|
rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
|
||||||
|
|
||||||
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
||||||
|
rwsem_set_owner(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(down_write);
|
EXPORT_SYMBOL(down_write);
|
||||||
|
@ -59,8 +81,11 @@ int down_write_trylock(struct rw_semaphore *sem)
|
||||||
{
|
{
|
||||||
int ret = __down_write_trylock(sem);
|
int ret = __down_write_trylock(sem);
|
||||||
|
|
||||||
if (ret == 1)
|
if (ret == 1) {
|
||||||
rwsem_acquire(&sem->dep_map, 0, 1, _RET_IP_);
|
rwsem_acquire(&sem->dep_map, 0, 1, _RET_IP_);
|
||||||
|
rwsem_set_owner(sem);
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +110,7 @@ void up_write(struct rw_semaphore *sem)
|
||||||
{
|
{
|
||||||
rwsem_release(&sem->dep_map, 1, _RET_IP_);
|
rwsem_release(&sem->dep_map, 1, _RET_IP_);
|
||||||
|
|
||||||
|
rwsem_clear_owner(sem);
|
||||||
__up_write(sem);
|
__up_write(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +125,7 @@ void downgrade_write(struct rw_semaphore *sem)
|
||||||
* lockdep: a downgraded write will live on as a write
|
* lockdep: a downgraded write will live on as a write
|
||||||
* dependency.
|
* dependency.
|
||||||
*/
|
*/
|
||||||
|
rwsem_clear_owner(sem);
|
||||||
__downgrade_write(sem);
|
__downgrade_write(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,6 +149,7 @@ void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest)
|
||||||
rwsem_acquire_nest(&sem->dep_map, 0, 0, nest, _RET_IP_);
|
rwsem_acquire_nest(&sem->dep_map, 0, 0, nest, _RET_IP_);
|
||||||
|
|
||||||
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
||||||
|
rwsem_set_owner(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(_down_write_nest_lock);
|
EXPORT_SYMBOL(_down_write_nest_lock);
|
||||||
|
@ -141,6 +169,7 @@ void down_write_nested(struct rw_semaphore *sem, int subclass)
|
||||||
rwsem_acquire(&sem->dep_map, subclass, 0, _RET_IP_);
|
rwsem_acquire(&sem->dep_map, subclass, 0, _RET_IP_);
|
||||||
|
|
||||||
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
LOCK_CONTENDED(sem, __down_write_trylock, __down_write);
|
||||||
|
rwsem_set_owner(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
EXPORT_SYMBOL(down_write_nested);
|
EXPORT_SYMBOL(down_write_nested);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue