Patch series "extend hung task blocker tracking to rwsems". Inspired by mutex blocker tracking[1], and having already extended it to semaphores, let's now add support for reader-writer semaphores (rwsems). The approach is simple: when a task enters TASK_UNINTERRUPTIBLE while waiting for an rwsem, we just call hung_task_set_blocker(). The hung task detector can then query the rwsem's owner to identify the lock holder. Tracking works reliably for writers, as there can only be a single writer holding the lock, and its task struct is stored in the owner field. The main challenge lies with readers. The owner field points to only one of many concurrent readers, so we might lose track of the blocker if that specific reader unlocks, even while others remain. This is not a significant issue, however. In practice, long-lasting lock contention is almost always caused by a writer. Therefore, reliably tracking the writer is the primary goal of this patch series ;) With this change, the hung task detector can now show blocker task's info like below: [Fri Jun 27 15:21:34 2025] INFO: task cat:28631 blocked for more than 122 seconds. [Fri Jun 27 15:21:34 2025] Tainted: G S 6.16.0-rc3 #8 [Fri Jun 27 15:21:34 2025] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message. [Fri Jun 27 15:21:34 2025] task:cat state:D stack:0 pid:28631 tgid:28631 ppid:28501 task_flags:0x400000 flags:0x00004000 [Fri Jun 27 15:21:34 2025] Call Trace: [Fri Jun 27 15:21:34 2025] <TASK> [Fri Jun 27 15:21:34 2025] __schedule+0x7c7/0x1930 [Fri Jun 27 15:21:34 2025] ? __pfx___schedule+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? policy_nodemask+0x215/0x340 [Fri Jun 27 15:21:34 2025] ? _raw_spin_lock_irq+0x8a/0xe0 [Fri Jun 27 15:21:34 2025] ? __pfx__raw_spin_lock_irq+0x10/0x10 [Fri Jun 27 15:21:34 2025] schedule+0x6a/0x180 [Fri Jun 27 15:21:34 2025] schedule_preempt_disabled+0x15/0x30 [Fri Jun 27 15:21:34 2025] rwsem_down_read_slowpath+0x55e/0xe10 [Fri Jun 27 15:21:34 2025] ? __pfx_rwsem_down_read_slowpath+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? __pfx___might_resched+0x10/0x10 [Fri Jun 27 15:21:34 2025] down_read+0xc9/0x230 [Fri Jun 27 15:21:34 2025] ? __pfx_down_read+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? __debugfs_file_get+0x14d/0x700 [Fri Jun 27 15:21:34 2025] ? __pfx___debugfs_file_get+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? handle_pte_fault+0x52a/0x710 [Fri Jun 27 15:21:34 2025] ? selinux_file_permission+0x3a9/0x590 [Fri Jun 27 15:21:34 2025] read_dummy_rwsem_read+0x4a/0x90 [Fri Jun 27 15:21:34 2025] full_proxy_read+0xff/0x1c0 [Fri Jun 27 15:21:34 2025] ? rw_verify_area+0x6d/0x410 [Fri Jun 27 15:21:34 2025] vfs_read+0x177/0xa50 [Fri Jun 27 15:21:34 2025] ? __pfx_vfs_read+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? fdget_pos+0x1cf/0x4c0 [Fri Jun 27 15:21:34 2025] ksys_read+0xfc/0x1d0 [Fri Jun 27 15:21:34 2025] ? __pfx_ksys_read+0x10/0x10 [Fri Jun 27 15:21:34 2025] do_syscall_64+0x66/0x2d0 [Fri Jun 27 15:21:34 2025] entry_SYSCALL_64_after_hwframe+0x76/0x7e [Fri Jun 27 15:21:34 2025] RIP: 0033:0x7f3f8faefb40 [Fri Jun 27 15:21:34 2025] RSP: 002b:00007ffdeda5ab98 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 [Fri Jun 27 15:21:34 2025] RAX: ffffffffffffffda RBX: 0000000000010000 RCX: 00007f3f8faefb40 [Fri Jun 27 15:21:34 2025] RDX: 0000000000010000 RSI: 00000000010fa000 RDI: 0000000000000003 [Fri Jun 27 15:21:34 2025] RBP: 00000000010fa000 R08: 0000000000000000 R09: 0000000000010fff [Fri Jun 27 15:21:34 2025] R10: 00007ffdeda59fe0 R11: 0000000000000246 R12: 00000000010fa000 [Fri Jun 27 15:21:34 2025] R13: 0000000000000003 R14: 0000000000000000 R15: 0000000000000fff [Fri Jun 27 15:21:34 2025] </TASK> [Fri Jun 27 15:21:34 2025] INFO: task cat:28631 <reader> blocked on an rw-semaphore likely owned by task cat:28630 <writer> [Fri Jun 27 15:21:34 2025] task:cat state:S stack:0 pid:28630 tgid:28630 ppid:28501 task_flags:0x400000 flags:0x00004000 [Fri Jun 27 15:21:34 2025] Call Trace: [Fri Jun 27 15:21:34 2025] <TASK> [Fri Jun 27 15:21:34 2025] __schedule+0x7c7/0x1930 [Fri Jun 27 15:21:34 2025] ? __pfx___schedule+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? __mod_timer+0x304/0xa80 [Fri Jun 27 15:21:34 2025] schedule+0x6a/0x180 [Fri Jun 27 15:21:34 2025] schedule_timeout+0xfb/0x230 [Fri Jun 27 15:21:34 2025] ? __pfx_schedule_timeout+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? __pfx_process_timeout+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? down_write+0xc4/0x140 [Fri Jun 27 15:21:34 2025] msleep_interruptible+0xbe/0x150 [Fri Jun 27 15:21:34 2025] read_dummy_rwsem_write+0x54/0x90 [Fri Jun 27 15:21:34 2025] full_proxy_read+0xff/0x1c0 [Fri Jun 27 15:21:34 2025] ? rw_verify_area+0x6d/0x410 [Fri Jun 27 15:21:34 2025] vfs_read+0x177/0xa50 [Fri Jun 27 15:21:34 2025] ? __pfx_vfs_read+0x10/0x10 [Fri Jun 27 15:21:34 2025] ? fdget_pos+0x1cf/0x4c0 [Fri Jun 27 15:21:34 2025] ksys_read+0xfc/0x1d0 [Fri Jun 27 15:21:34 2025] ? __pfx_ksys_read+0x10/0x10 [Fri Jun 27 15:21:34 2025] do_syscall_64+0x66/0x2d0 [Fri Jun 27 15:21:34 2025] entry_SYSCALL_64_after_hwframe+0x76/0x7e [Fri Jun 27 15:21:34 2025] RIP: 0033:0x7f8f288efb40 [Fri Jun 27 15:21:34 2025] RSP: 002b:00007ffffb631038 EFLAGS: 00000246 ORIG_RAX: 0000000000000000 [Fri Jun 27 15:21:34 2025] RAX: ffffffffffffffda RBX: 0000000000010000 RCX: 00007f8f288efb40 [Fri Jun 27 15:21:34 2025] RDX: 0000000000010000 RSI: 000000002a4b5000 RDI: 0000000000000003 [Fri Jun 27 15:21:34 2025] RBP: 000000002a4b5000 R08: 0000000000000000 R09: 0000000000010fff [Fri Jun 27 15:21:34 2025] R10: 00007ffffb630460 R11: 0000000000000246 R12: 000000002a4b5000 [Fri Jun 27 15:21:34 2025] R13: 0000000000000003 R14: 0000000000000000 R15: 0000000000000fff [Fri Jun 27 15:21:34 2025] </TASK> This patch (of 3): In preparation for extending blocker tracking to support rwsems, make the rwsem_owner() and is_rwsem_reader_owned() helpers globally available for determining if the blocker is a writer or one of the readers. Additionally, a stale owner pointer in a reader-owned rwsem can lead to false positives in blocker tracking when CONFIG_DETECT_HUNG_TASK_BLOCKER is enabled. To mitigate this, clear the owner field on the reader unlock path, similar to what CONFIG_DEBUG_RWSEMS does. A NULL owner is better than a stale one for diagnostics. Link: https://lkml.kernel.org/r/20250627072924.36567-1-lance.yang@linux.dev Link: https://lkml.kernel.org/r/20250627072924.36567-2-lance.yang@linux.dev Link: https://lore.kernel.org/all/174046694331.2194069.15472952050240807469.stgit@mhiramat.tok.corp.google.com/ [1] Signed-off-by: Lance Yang <lance.yang@linux.dev> Reviewed-by: Masami Hiramatsu (Google) <mhiramat@kernel.org> Cc: Anna Schumaker <anna.schumaker@oracle.com> Cc: Boqun Feng <boqun.feng@gmail.com> Cc: Ingo Molnar <mingo@redhat.com> Cc: Joel Granados <joel.granados@kernel.org> Cc: John Stultz <jstultz@google.com> Cc: Kent Overstreet <kent.overstreet@linux.dev> Cc: Mingzhe Yang <mingzhe.yang@ly.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Sergey Senozhatsky <senozhatsky@chromium.org> Cc: Steven Rostedt <rostedt@goodmis.org> Cc: Tomasz Figa <tfiga@chromium.org> Cc: Waiman Long <longman@redhat.com> Cc: Will Deacon <will@kernel.org> Cc: Yongliang Gao <leonylgao@tencent.com> Cc: Zi Li <zi.li@linux.dev> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
310 lines
8.9 KiB
C
310 lines
8.9 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/list.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/err.h>
|
|
#include <linux/cleanup.h>
|
|
|
|
#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
|
|
|
|
#ifndef CONFIG_PREEMPT_RT
|
|
|
|
#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
|
|
};
|
|
|
|
#define RWSEM_UNLOCKED_VALUE 0UL
|
|
#define RWSEM_WRITER_LOCKED (1UL << 0)
|
|
#define __RWSEM_COUNT_INIT(name) .count = ATOMIC_LONG_INIT(RWSEM_UNLOCKED_VALUE)
|
|
|
|
static inline int rwsem_is_locked(struct rw_semaphore *sem)
|
|
{
|
|
return atomic_long_read(&sem->count) != RWSEM_UNLOCKED_VALUE;
|
|
}
|
|
|
|
static inline void rwsem_assert_held_nolockdep(const struct rw_semaphore *sem)
|
|
{
|
|
WARN_ON(atomic_long_read(&sem->count) == RWSEM_UNLOCKED_VALUE);
|
|
}
|
|
|
|
static inline void rwsem_assert_held_write_nolockdep(const struct rw_semaphore *sem)
|
|
{
|
|
WARN_ON(!(atomic_long_read(&sem->count) & RWSEM_WRITER_LOCKED));
|
|
}
|
|
|
|
/* Common initializer macros and functions */
|
|
|
|
#ifdef CONFIG_DEBUG_RWSEMS
|
|
# define __RWSEM_DEBUG_INIT(lockname) .magic = &lockname,
|
|
#else
|
|
# define __RWSEM_DEBUG_INIT(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_COUNT_INIT(name), \
|
|
.owner = ATOMIC_LONG_INIT(0), \
|
|
__RWSEM_OPT_INIT(name) \
|
|
.wait_lock = __RAW_SPIN_LOCK_UNLOCKED(name.wait_lock),\
|
|
.wait_list = LIST_HEAD_INIT((name).wait_list), \
|
|
__RWSEM_DEBUG_INIT(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 already 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);
|
|
}
|
|
|
|
#if defined(CONFIG_DEBUG_RWSEMS) || defined(CONFIG_DETECT_HUNG_TASK_BLOCKER)
|
|
/*
|
|
* Return just the real task structure pointer of the owner
|
|
*/
|
|
extern struct task_struct *rwsem_owner(struct rw_semaphore *sem);
|
|
|
|
/*
|
|
* Return true if the rwsem is owned by a reader.
|
|
*/
|
|
extern bool is_rwsem_reader_owned(struct rw_semaphore *sem);
|
|
#endif
|
|
|
|
#else /* !CONFIG_PREEMPT_RT */
|
|
|
|
#include <linux/rwbase_rt.h>
|
|
|
|
struct rw_semaphore {
|
|
struct rwbase_rt rwbase;
|
|
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
|
struct lockdep_map dep_map;
|
|
#endif
|
|
};
|
|
|
|
#define __RWSEM_INITIALIZER(name) \
|
|
{ \
|
|
.rwbase = __RWBASE_INITIALIZER(name), \
|
|
__RWSEM_DEP_MAP_INIT(name) \
|
|
}
|
|
|
|
#define DECLARE_RWSEM(lockname) \
|
|
struct rw_semaphore lockname = __RWSEM_INITIALIZER(lockname)
|
|
|
|
extern void __init_rwsem(struct rw_semaphore *rwsem, 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)
|
|
|
|
static __always_inline int rwsem_is_locked(const struct rw_semaphore *sem)
|
|
{
|
|
return rw_base_is_locked(&sem->rwbase);
|
|
}
|
|
|
|
static __always_inline void rwsem_assert_held_nolockdep(const struct rw_semaphore *sem)
|
|
{
|
|
WARN_ON(!rwsem_is_locked(sem));
|
|
}
|
|
|
|
static __always_inline void rwsem_assert_held_write_nolockdep(const struct rw_semaphore *sem)
|
|
{
|
|
WARN_ON(!rw_base_is_write_locked(&sem->rwbase));
|
|
}
|
|
|
|
static __always_inline int rwsem_is_contended(struct rw_semaphore *sem)
|
|
{
|
|
return rw_base_is_contended(&sem->rwbase);
|
|
}
|
|
|
|
#endif /* CONFIG_PREEMPT_RT */
|
|
|
|
/*
|
|
* The functions below are the same for all rwsem implementations including
|
|
* the RT specific variant.
|
|
*/
|
|
|
|
static inline void rwsem_assert_held(const struct rw_semaphore *sem)
|
|
{
|
|
if (IS_ENABLED(CONFIG_LOCKDEP))
|
|
lockdep_assert_held(sem);
|
|
else
|
|
rwsem_assert_held_nolockdep(sem);
|
|
}
|
|
|
|
static inline void rwsem_assert_held_write(const struct rw_semaphore *sem)
|
|
{
|
|
if (IS_ENABLED(CONFIG_LOCKDEP))
|
|
lockdep_assert_held_write(sem);
|
|
else
|
|
rwsem_assert_held_write_nolockdep(sem);
|
|
}
|
|
|
|
/*
|
|
* lock for reading
|
|
*/
|
|
extern void down_read(struct rw_semaphore *sem);
|
|
extern int __must_check down_read_interruptible(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);
|
|
|
|
DEFINE_GUARD(rwsem_read, struct rw_semaphore *, down_read(_T), up_read(_T))
|
|
DEFINE_GUARD_COND(rwsem_read, _try, down_read_trylock(_T))
|
|
DEFINE_GUARD_COND(rwsem_read, _intr, down_read_interruptible(_T) == 0)
|
|
|
|
DEFINE_GUARD(rwsem_write, struct rw_semaphore *, down_write(_T), up_write(_T))
|
|
DEFINE_GUARD_COND(rwsem_write, _try, down_write_trylock(_T))
|
|
|
|
/*
|
|
* 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 int __must_check down_read_killable_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_read_killable_nested(sem, subclass) down_read_killable(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 */
|