mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Peter Zijlstra](/assets/img/avatar_default.png)
Extend lockdep to validate lock wait-type context. The current wait-types are: LD_WAIT_FREE, /* wait free, rcu etc.. */ LD_WAIT_SPIN, /* spin loops, raw_spinlock_t etc.. */ LD_WAIT_CONFIG, /* CONFIG_PREEMPT_LOCK, spinlock_t etc.. */ LD_WAIT_SLEEP, /* sleeping locks, mutex_t etc.. */ Where lockdep validates that the current lock (the one being acquired) fits in the current wait-context (as generated by the held stack). This ensures that there is no attempt to acquire mutexes while holding spinlocks, to acquire spinlocks while holding raw_spinlocks and so on. In other words, its a more fancy might_sleep(). Obviously RCU made the entire ordeal more complex than a simple single value test because RCU can be acquired in (pretty much) any context and while it presents a context to nested locks it is not the same as it got acquired in. Therefore its necessary to split the wait_type into two values, one representing the acquire (outer) and one representing the nested context (inner). For most 'normal' locks these two are the same. [ To make static initialization easier we have the rule that: .outer == INV means .outer == .inner; because INV == 0. ] It further means that its required to find the minimal .inner of the held stack to compare against the outer of the new lock; because while 'normal' RCU presents a CONFIG type to nested locks, if it is taken while already holding a SPIN type it obviously doesn't relax the rules. Below is an example output generated by the trivial test code: raw_spin_lock(&foo); spin_lock(&bar); spin_unlock(&bar); raw_spin_unlock(&foo); [ BUG: Invalid wait context ] ----------------------------- swapper/0/1 is trying to lock: ffffc90000013f20 (&bar){....}-{3:3}, at: kernel_init+0xdb/0x187 other info that might help us debug this: 1 lock held by swapper/0/1: #0: ffffc90000013ee0 (&foo){+.+.}-{2:2}, at: kernel_init+0xd1/0x187 The way to read it is to look at the new -{n,m} part in the lock description; -{3:3} for the attempted lock, and try and match that up to the held locks, which in this case is the one: -{2,2}. This tells that the acquiring lock requires a more relaxed environment than presented by the lock stack. Currently only the normal locks and RCU are converted, the rest of the lockdep users defaults to .inner = INV which is ignored. More conversions can be done when desired. The check for spinlock_t nesting is not enabled by default. It's a separate config option for now as there are known problems which are currently addressed. The config option allows to identify these problems and to verify that the solutions found are indeed solving them. The config switch will be removed and the checks will permanently enabled once the vast majority of issues has been addressed. [ bigeasy: Move LD_WAIT_FREE,… out of CONFIG_LOCKDEP to avoid compile failure with CONFIG_DEBUG_SPINLOCK + !CONFIG_LOCKDEP] [ tglx: Add the config option ] Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://lkml.kernel.org/r/20200321113242.427089655@linutronix.de
201 lines
6.1 KiB
C
201 lines
6.1 KiB
C
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/* rwsem.h: R/W semaphores, public interface
|
|
*
|
|
* Written by David Howells (dhowells@redhat.com).
|
|
* Derived from asm-i386/semaphore.h
|
|
*/
|
|
|
|
#ifndef _LINUX_RWSEM_H
|
|
#define _LINUX_RWSEM_H
|
|
|
|
#include <linux/linkage.h>
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/err.h>
|
|
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
|
#include <linux/osq_lock.h>
|
|
#endif
|
|
|
|
/*
|
|
* For an uncontended rwsem, count and owner are the only fields a task
|
|
* needs to touch when acquiring the rwsem. So they are put next to each
|
|
* other to increase the chance that they will share the same cacheline.
|
|
*
|
|
* In a contended rwsem, the owner is likely the most frequently accessed
|
|
* field in the structure as the optimistic waiter that holds the osq lock
|
|
* will spin on owner. For an embedded rwsem, other hot fields in the
|
|
* containing structure should be moved further away from the rwsem to
|
|
* reduce the chance that they will share the same cacheline causing
|
|
* cacheline bouncing problem.
|
|
*/
|
|
struct rw_semaphore {
|
|
atomic_long_t count;
|
|
/*
|
|
* Write owner or one of the read owners as well flags regarding
|
|
* the current state of the rwsem. Can be used as a speculative
|
|
* check to see if the write owner is running on the cpu.
|
|
*/
|
|
atomic_long_t owner;
|
|
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
|
struct optimistic_spin_queue osq; /* spinner MCS lock */
|
|
#endif
|
|
raw_spinlock_t wait_lock;
|
|
struct list_head wait_list;
|
|
#ifdef CONFIG_DEBUG_RWSEMS
|
|
void *magic;
|
|
#endif
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
struct lockdep_map dep_map;
|
|
#endif
|
|
};
|
|
|
|
/* In all implementations count != 0 means locked */
|
|
static inline int rwsem_is_locked(struct rw_semaphore *sem)
|
|
{
|
|
return atomic_long_read(&sem->count) != 0;
|
|
}
|
|
|
|
#define RWSEM_UNLOCKED_VALUE 0L
|
|
#define __RWSEM_INIT_COUNT(name) .count = ATOMIC_LONG_INIT(RWSEM_UNLOCKED_VALUE)
|
|
|
|
/* Common initializer macros and functions */
|
|
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
# define __RWSEM_DEP_MAP_INIT(lockname) \
|
|
, .dep_map = { \
|
|
.name = #lockname, \
|
|
.wait_type_inner = LD_WAIT_SLEEP, \
|
|
}
|
|
#else
|
|
# define __RWSEM_DEP_MAP_INIT(lockname)
|
|
#endif
|
|
|
|
#ifdef CONFIG_DEBUG_RWSEMS
|
|
# define __DEBUG_RWSEM_INITIALIZER(lockname) , .magic = &lockname
|
|
#else
|
|
# define __DEBUG_RWSEM_INITIALIZER(lockname)
|
|
#endif
|
|
|
|
#ifdef CONFIG_RWSEM_SPIN_ON_OWNER
|
|
#define __RWSEM_OPT_INIT(lockname) , .osq = OSQ_LOCK_UNLOCKED
|
|
#else
|
|
#define __RWSEM_OPT_INIT(lockname)
|
|
#endif
|
|
|
|
#define __RWSEM_INITIALIZER(name) \
|
|
{ __RWSEM_INIT_COUNT(name), \
|
|
.owner = ATOMIC_LONG_INIT(0), \
|
|
.wait_list = LIST_HEAD_INIT((name).wait_list), \
|
|
.wait_lock = __RAW_SPIN_LOCK_UNLOCKED(name.wait_lock) \
|
|
__RWSEM_OPT_INIT(name) \
|
|
__DEBUG_RWSEM_INITIALIZER(name) \
|
|
__RWSEM_DEP_MAP_INIT(name) }
|
|
|
|
#define DECLARE_RWSEM(name) \
|
|
struct rw_semaphore name = __RWSEM_INITIALIZER(name)
|
|
|
|
extern void __init_rwsem(struct rw_semaphore *sem, const char *name,
|
|
struct lock_class_key *key);
|
|
|
|
#define init_rwsem(sem) \
|
|
do { \
|
|
static struct lock_class_key __key; \
|
|
\
|
|
__init_rwsem((sem), #sem, &__key); \
|
|
} while (0)
|
|
|
|
/*
|
|
* This is the same regardless of which rwsem implementation that is being used.
|
|
* It is just a heuristic meant to be called by somebody alreadying holding the
|
|
* rwsem to see if somebody from an incompatible type is wanting access to the
|
|
* lock.
|
|
*/
|
|
static inline int rwsem_is_contended(struct rw_semaphore *sem)
|
|
{
|
|
return !list_empty(&sem->wait_list);
|
|
}
|
|
|
|
/*
|
|
* lock for reading
|
|
*/
|
|
extern void down_read(struct rw_semaphore *sem);
|
|
extern int __must_check down_read_killable(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* trylock for reading -- returns 1 if successful, 0 if contention
|
|
*/
|
|
extern int down_read_trylock(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* lock for writing
|
|
*/
|
|
extern void down_write(struct rw_semaphore *sem);
|
|
extern int __must_check down_write_killable(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* trylock for writing -- returns 1 if successful, 0 if contention
|
|
*/
|
|
extern int down_write_trylock(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* release a read lock
|
|
*/
|
|
extern void up_read(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* release a write lock
|
|
*/
|
|
extern void up_write(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* downgrade write lock to read lock
|
|
*/
|
|
extern void downgrade_write(struct rw_semaphore *sem);
|
|
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
/*
|
|
* nested locking. NOTE: rwsems are not allowed to recurse
|
|
* (which occurs if the same task tries to acquire the same
|
|
* lock instance multiple times), but multiple locks of the
|
|
* same lock class might be taken, if the order of the locks
|
|
* is always the same. This ordering rule can be expressed
|
|
* to lockdep via the _nested() APIs, but enumerating the
|
|
* subclasses that are used. (If the nesting relationship is
|
|
* static then another method for expressing nested locking is
|
|
* the explicit definition of lock class keys and the use of
|
|
* lockdep_set_class() at lock initialization time.
|
|
* See Documentation/locking/lockdep-design.rst for more details.)
|
|
*/
|
|
extern void down_read_nested(struct rw_semaphore *sem, int subclass);
|
|
extern void down_write_nested(struct rw_semaphore *sem, int subclass);
|
|
extern int down_write_killable_nested(struct rw_semaphore *sem, int subclass);
|
|
extern void _down_write_nest_lock(struct rw_semaphore *sem, struct lockdep_map *nest_lock);
|
|
|
|
# define down_write_nest_lock(sem, nest_lock) \
|
|
do { \
|
|
typecheck(struct lockdep_map *, &(nest_lock)->dep_map); \
|
|
_down_write_nest_lock(sem, &(nest_lock)->dep_map); \
|
|
} while (0);
|
|
|
|
/*
|
|
* Take/release a lock when not the owner will release it.
|
|
*
|
|
* [ This API should be avoided as much as possible - the
|
|
* proper abstraction for this case is completions. ]
|
|
*/
|
|
extern void down_read_non_owner(struct rw_semaphore *sem);
|
|
extern void up_read_non_owner(struct rw_semaphore *sem);
|
|
#else
|
|
# define down_read_nested(sem, subclass) down_read(sem)
|
|
# define down_write_nest_lock(sem, nest_lock) down_write(sem)
|
|
# define down_write_nested(sem, subclass) down_write(sem)
|
|
# define down_write_killable_nested(sem, subclass) down_write_killable(sem)
|
|
# define down_read_non_owner(sem) down_read(sem)
|
|
# define up_read_non_owner(sem) up_read(sem)
|
|
#endif
|
|
|
|
#endif /* _LINUX_RWSEM_H */
|