* [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool
@ 2026-08-27 3:58 Jim Cromie
2026-08-27 3:58 ` [PATCH 1/8] lockdep: Traverse adjacency lists directly in zap_class() Jim Cromie
` (8 more replies)
0 siblings, 9 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Lockdep cannot rely upon any other subsystem that uses locks, so since
inception, its graph-db has been stored in static arrays, pinning ~10
MB in .bss. This is a hardcoded compromise between embedded and
enterprise hardware.
However, if it acts early, lockdep can pre-allocate a pool of slabs
from memblock_alloc(), enough for its lifetime of anticipated workloads.
Then it can allocate them as needed to provide new segments/slabs to
the graph-db.
With that idea, we:
0. Add lockdep_early_init() hook in start_kernel() right before
mm_core_init() to grab a private pool of 64 KB slabs from memblock.
1. Add DECLARE_CHUNKED_ARRAY() to build 2D chunk pointer tables.
Indexing uses a compile-time hybrid:
- Power-of-2 tables (lock_chains @ 2,048/slab, chain_hlocks @ 32,768/slab)
use single-cycle bit shifts (idx >> SHIFT) and masks (idx & MASK)
for zero-overhead cache verification.
- Non-power-of-2 structs (lock_classes @ 409/slab, list_entries @ 1,365/slab)
use Granlund-Montgomery reciprocal divide to achieve >99.8% slab
packing density, avoiding 1.75 MB of internal dead padding.
2. Deploy chunked arrays across the 5 graph-db tables:
- lock_classes: struct lock_class (160 B) -> lock_class_chunk0 (409 / slab)
- list_entries: struct lock_list (48 B) -> list_entries_chunk0 (1,365 / slab)
- lock_chains: struct lock_chain (32 B) -> lock_chain_chunk0 (2,048 / slab)
- chain_hlocks: u16 (2 B) -> chain_hlock_chunk0 (32,768 / slab)
- stack_trace: unsigned long (8 B) -> stack_trace_chunk0 (8,192 / slab)
Each static name##_chunk0 in .bss (~320 kB total) provisions the graph-db
with initial storage to cover early boot until memblock is up.
3. Embed struct lock_class.class_idx and struct lock_chain.chain_idx to
replace flat pointer arithmetic (ptr - base) with O(1) index queries
across disjoint 2D slabs.
4. Dole slabs out on demand to the 5 consumers via an index bump under
graph_lock (zero allocator locks, zero recursion risk).
5. Auto-tune the pool size based on RAM and accept boot overrides via
lockdep_slabs=N and lockdep_headroom=M%.
6. At late_initcall, satisfy both constraints (slabs >= N and headroom
>= M%), and return all unused excess slabs to the buddy allocator
via free_reserved_page().
7. Expose pool usage and remaining headroom via /proc/lockdep_stats and
log lifetime usage via a reboot notifier.
8. On debug_locks_off() or OOM, immediately sacrifice all dynamically
claimed slabs back to the buddy allocator.
Static .bss Memory Savings (vmlinux x86_64 defconfig):
Kernel .bss Section Size Notes
----------------------------------------------------------------------
Upstream (Static) 12.46 MB (13061164 B) Fixed max-sized arrays
Patched (Memblock) 2.30 MB ( 2410988 B) 5 * 64 kB Chunk 0s in .bss
----------------------------------------------------------------------
Net Savings -10.16 MB (81.5% reduction in .bss)
Memblock-Pool Elasticity & Buddy Return:
[ 0.850318] lockdep: boot complete : 9/64 slabs used, 41 kept (355% headroom), 23 returned to buddy (1472 kB freed)
That VM boot consumed 9 slabs (576 kB), keeps 41 slabs (2624 kB,
355% headroom) for runtime growth, and returns 23 slabs (1472 kB) to
buddy at late_initcall:
The boot-args let user specify the reserved-slab-pool size:
lockdep_slabs=N # min ct of 64kb slabs kept
lockdep_headroom=N% # added % to boot-complete numbers, default is 100%
Workload Stress Performance & CPU Overheads (perf stat, 4 vCPUs):
Benchmark Metric Upstream (Base) Patched (Memblock) Delta
---------------------------------------------------------------------------
hackbench Runtime 8.482 s 8.278 s -2.40%
hackbench Cycles 52899510936 52033166458 -1.64%
hackbench Instructions 29008189069 31698626928 +9.27%
netns Runtime 9.704 s 13.321 s +37.28%
netns Cycles 3941156282 5077381493 +28.83%
netns Instructions 2758079004 3008859260 +9.09%
vfs Runtime 10.544 s 10.608 s +0.60%
vfs Cycles 48150692681 48840696114 +1.43%
vfs Instructions 32621052088 35448537908 +8.67%
modstorm Runtime 0.611 s 0.585 s -4.17%
modstorm Cycles 512425082 514784953 +0.46%
modstorm Instructions 349890783 383054584 +9.48%
Under heavy lock contention (hackbench), the power-of-2 fast-path on
chain_hlocks and lock_chains brings total cycle consumption to parity
with or slightly faster than upstream baseline (-1.64% cycles).
Workload specifics (virtme-ng, 4 vCPUs, 4 GB RAM):
- hackbench: hackbench -p -g 8 -l 1000
- netns: 40 netns add/del cycles with paired veth interfaces
- vfs: 8 parallel workers creating 200 dirs, files, symlinks + rm -rf
- modstorm: 10 sequential rounds of batch modprobe/rmmod (dummy loop null_blk brd tun)
What's Unchanged:
- All lockdep validation invariants, BFS graph algorithms, and deadlock
detection logic are completely unmodified.
- RCU iteration semantics across lock classes and chains remain intact.
- /proc/lockdep and /proc/lockdep_stats formatting is fully preserved.
Series Structure:
- Patch 1: Optimize zap_class() to traverse adjacency lists directly
rather than scanning the global bitmap.
- Patch 2: Add chunked array infrastructure and embedded indices.
- Patch 3: Pre-reserve early memblock slab pool for dynamic tables.
- Patch 4: Convert 5 graph arrays to chunked tables backed by slab pool.
- Patch 5: Fast-path power-of-2 tables with shift/mask indexing.
- Patch 6: Free unused reservation slabs to buddy allocator at late boot.
- Patch 7: Expose slab pool telemetry in /proc/lockdep_stats and initcalls.
- Patch 8: On debug_locks_off or OOM, recycle all slabs to buddy.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Jim Cromie (8):
lockdep: Traverse adjacency lists directly in zap_class()
lockdep: Add chunked array infrastructure and embedded indices
lockdep: Pre-reserve early memblock slab pool for dynamic tables
lockdep: Convert 5 graph arrays to chunked tables backed by slab pool
lockdep: Fast-path power-of-2 tables with shift/mask indexing
lockdep: Free unused reservation slabs to buddy allocator at late boot
lockdep: Expose slab pool telemetry in /proc/lockdep_stats and initcalls
lockdep: on debug_locks_off or OOM, recycle all slabs to buddy
include/linux/lockdep.h | 4 +-
include/linux/lockdep_types.h | 1 +
init/main.c | 1 +
kernel/locking/lockdep.c | 951 ++++++++++++++++++++++++++++---------
kernel/locking/lockdep_internals.h | 95 +++-
kernel/locking/lockdep_proc.c | 63 ++-
6 files changed, 875 insertions(+), 240 deletions(-)
---
base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
change-id: 20260825-lockdep-memblock-v1-12c083225ef9
Best regards,
--
Jim Cromie <jim.cromie@gmail.com>
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 1/8] lockdep: Traverse adjacency lists directly in zap_class()
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 2/8] lockdep: Add chunked array infrastructure and embedded indices Jim Cromie
` (7 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Lockdep's canonical graph representation is its per-class adjacency
lists (locks_after and locks_before). However, zap_class() operates on
a flat storage-layer projection of the graph: it scans the global
list_entries_in_use bitmap across the entire edge pool.
This global scan has a few defects:
0. Search Inefficiency:
On a typical booted laptop with ~2,000 lock classes and ~6,500 active
dependency list entries, zapping a single class forces 6,500+ table
inspections under graph_lock across 4 KB of bitmap. Zapping a batch of
classes during module unload multiplies this into tens of thousands of
global array iterations.
1. Projection maintenance: the bitmap must be kept up-to-date.
given 0, its a net burden, but we still need the bitmap elsewhere.
2. Incompatible with Array Segmentation:
The loop relies on contiguous pointer arithmetic (list_entries + i) to
map bitmap indices back to entries. This completely breaks once
list_entries is segmented into dynamic 64 kB memblock slabs residing
on disjoint memory pages.
So just implement the adjacency check literally, per graph-theory.
Real-world lock classes have very short adjacency lists: 3..5 entries
on average for class->locks_after and class->locks_before, rarely
exceeding 15.
Directly walking these lists visits only ~10..15 nodes per zapped
class, replacing 6,500+ global table dereferences with a handful of
cacheline-local pointer hops (>99.8% reduction in loop iterations).
Note: We continue clearing bits in list_entries_in_use for now, as
alloc_list_entry() still queries the bitmap in this commit. The bitmap
itself is eliminated soon
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
kernel/locking/lockdep.c | 31 ++++++++++++++++++++++++-------
1 file changed, 24 insertions(+), 7 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 2d4c5bab5af8..6a4f21f3e9c8 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -6243,8 +6243,7 @@ static void remove_class_from_lock_chains(struct pending_free *pf,
*/
static void zap_class(struct pending_free *pf, struct lock_class *class)
{
- struct lock_list *entry;
- int i;
+ struct lock_list *entry, *tmp, *other, *other_tmp;
WARN_ON_ONCE(!class->key);
@@ -6252,11 +6251,29 @@ static void zap_class(struct pending_free *pf, struct lock_class *class)
* Remove all dependencies this lock is
* involved in:
*/
- for_each_set_bit(i, list_entries_in_use, ARRAY_SIZE(list_entries)) {
- entry = list_entries + i;
- if (entry->class != class && entry->links_to != class)
- continue;
- __clear_bit(i, list_entries_in_use);
+ list_for_each_entry_safe(entry, tmp, &class->locks_after, entry) {
+ list_for_each_entry_safe(other, other_tmp, &entry->links_to->locks_before, entry) {
+ if (other->links_to == class) {
+ __clear_bit(other - list_entries, list_entries_in_use);
+ nr_list_entries--;
+ list_del_rcu(&other->entry);
+ break;
+ }
+ }
+ __clear_bit(entry - list_entries, list_entries_in_use);
+ nr_list_entries--;
+ list_del_rcu(&entry->entry);
+ }
+ list_for_each_entry_safe(entry, tmp, &class->locks_before, entry) {
+ list_for_each_entry_safe(other, other_tmp, &entry->links_to->locks_after, entry) {
+ if (other->links_to == class) {
+ __clear_bit(other - list_entries, list_entries_in_use);
+ nr_list_entries--;
+ list_del_rcu(&other->entry);
+ break;
+ }
+ }
+ __clear_bit(entry - list_entries, list_entries_in_use);
nr_list_entries--;
list_del_rcu(&entry->entry);
}
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 2/8] lockdep: Add chunked array infrastructure and embedded indices
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
2026-08-27 3:58 ` [PATCH 1/8] lockdep: Traverse adjacency lists directly in zap_class() Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 3/8] lockdep: Pre-reserve early memblock slab pool for dynamic tables Jim Cromie
` (6 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Lockdep's dependency graph has historically relied on flat static
arrays in .bss. To transition these tables to dynamically allocated
slabs without incurring division instructions, introduce the
DECLARE_CHUNKED_ARRAY() and DEFINE_CHUNKED_ARRAY() macros.
These macros construct 2-tier chunked arrays (Array-of-Arrays) indexed
via Granlund-Montgomery reciprocal divide (reciprocal_divide()),
mapping indices to (chunk, offset) tuples in constant time (~3 cycles).
Also embed class_idx into struct lock_class and chain_idx into struct
lock_chain to replace flat pointer arithmetic (ptr - base) with O(1)
index lookups across disjoint slab chunks.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
include/linux/lockdep.h | 3 ++-
include/linux/lockdep_types.h | 1 +
kernel/locking/lockdep_internals.h | 48 ++++++++++++++++++++++++++++++++++++--
3 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 621566345406..4c96959d8ad7 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -77,7 +77,7 @@ struct lock_chain {
unsigned int irq_context : 2,
depth : 6,
base : 24;
- /* 4 byte hole */
+ unsigned int chain_idx;
struct hlist_node entry;
u64 chain_key;
};
@@ -85,6 +85,7 @@ struct lock_chain {
/*
* Initialization, self-test and debugging-output methods:
*/
+extern void lockdep_early_init(void);
extern void lockdep_init(void);
extern void lockdep_reset(void);
extern void lockdep_reset_lock(struct lockdep_map *lock);
diff --git a/include/linux/lockdep_types.h b/include/linux/lockdep_types.h
index eae115a26488..8acac0b59f69 100644
--- a/include/linux/lockdep_types.h
+++ b/include/linux/lockdep_types.h
@@ -121,6 +121,7 @@ struct lock_class {
unsigned int subclass;
unsigned int dep_gen_id;
+ unsigned int class_idx;
/*
* IRQ/softirq usage tracking bits:
diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index 0e5e6ffe91a3..3d8bce0dc9f9 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -122,9 +122,53 @@ enum {
#define MAX_LOCKDEP_CHAINS (1UL << MAX_LOCKDEP_CHAINS_BITS)
#define AVG_LOCKDEP_CHAIN_DEPTH 5
-#define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS * AVG_LOCKDEP_CHAIN_DEPTH)
+#include <linux/reciprocal_div.h>
-extern struct lock_chain lock_chains[];
+#define LOCKDEP_SLAB_SIZE (64 * 1024)
+#define LOCKDEP_MAX_SLABS 64
+
+/*
+ * Chunked Array Tables:
+ * Replaces flat monolithic BSS arrays with 2D chunk pointer matrices.
+ * Chunk 0 is statically allocated in BSS for early boot, while subsequent
+ * chunks are claimed from the memblock reservoir via lockdep_claim_slab().
+ * Indexing uses compile-time Granlund-Montgomery reciprocal divide
+ * (~3-cycle multiply+shift, zero division instructions).
+ */
+#define DECLARE_CHUNKED_ARRAY(name, type) \
+ enum { \
+ name##_PER_CHUNK = (LOCKDEP_SLAB_SIZE / sizeof(type)), \
+ }; \
+ extern type * name##_chunks[LOCKDEP_MAX_SLABS]; \
+ extern const struct reciprocal_value name##_rv; \
+ static __always_inline type *idx_to_##name(unsigned int idx) \
+ { \
+ unsigned int chunk = reciprocal_divide(idx, name##_rv); \
+ unsigned int offset = idx - (chunk * name##_PER_CHUNK); \
+ type *chunk_ptr; \
+ if (unlikely(chunk >= LOCKDEP_MAX_SLABS)) \
+ return NULL; \
+ /* Pairs with smp_store_release() when new chunk slabs are published */ \
+ chunk_ptr = smp_load_acquire(&name##_chunks[chunk]); \
+ if (unlikely(!chunk_ptr)) \
+ return NULL; \
+ return &chunk_ptr[offset]; \
+ }
+
+#define DEFINE_CHUNKED_ARRAY(name, type) \
+ static type name##_chunk0[name##_PER_CHUNK]; \
+ type *name##_chunks[LOCKDEP_MAX_SLABS] = { name##_chunk0 }; \
+ static unsigned int nr_##name##_chunks = 1; \
+ const struct reciprocal_value name##_rv = \
+ RECIPROCAL_VALUE_INIT(name##_PER_CHUNK)
+
+struct lockdep_slab_usage {
+ unsigned int lock_classes;
+ unsigned int direct_deps;
+ unsigned int lock_chains;
+ unsigned int chain_hlocks;
+ unsigned int stack_traces;
+};
#define LOCK_USAGE_CHARS (2*XXX_LOCK_USAGE_STATES + 1)
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 3/8] lockdep: Pre-reserve early memblock slab pool for dynamic tables
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
2026-08-27 3:58 ` [PATCH 1/8] lockdep: Traverse adjacency lists directly in zap_class() Jim Cromie
2026-08-27 3:58 ` [PATCH 2/8] lockdep: Add chunked array infrastructure and embedded indices Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 4/8] lockdep: Convert 5 graph arrays to chunked tables backed by slab pool Jim Cromie
` (5 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Lockdep cannot allocate memory dynamically during normal runtime because
it cannot recurse into allocator locks. However, before mm_core_init()
brings up the buddy allocator, lockdep can claim a contiguous pool of
64 KB slabs directly from early memblock.
Add lockdep_early_init() to start_kernel() right before mm_core_init()
to reserve a private pool of 64 KB slabs. Auto-tune the pool based on
physical RAM (2 MB on <512 MB systems, 4 MB default, 8 MB on >64 GB
servers) and accept overrides via lockdep_slabs=N and
lockdep_headroom=M%.
Provide lockdep_claim_slab() and lockdep_release_slab() to dole out and
recycle slabs under graph_lock without invoking external locks.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
init/main.c | 1 +
kernel/locking/lockdep.c | 131 +++++++++++++++++++++++++++++++++++++
kernel/locking/lockdep_internals.h | 3 +-
3 files changed, 134 insertions(+), 1 deletion(-)
diff --git a/init/main.c b/init/main.c
index e363232b428b..12280dfe1d11 100644
--- a/init/main.c
+++ b/init/main.c
@@ -1031,6 +1031,7 @@ void start_kernel(void)
vfs_caches_init_early();
sort_main_extable();
trap_init();
+ lockdep_early_init();
mm_core_init();
maple_tree_init();
poking_init();
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 6a4f21f3e9c8..68d82e46cbf6 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -58,12 +58,96 @@
#include <linux/context_tracking.h>
#include <linux/console.h>
#include <linux/kasan.h>
+#include <linux/memblock.h>
#include <asm/sections.h>
#include "lockdep_internals.h"
#include "lock_events.h"
+static void *lockdep_slabs[LOCKDEP_MAX_SLABS];
+static unsigned int lockdep_nr_slabs;
+static unsigned int lockdep_slabs_used;
+static struct lockdep_slab_usage ld_slabs;
+
+static unsigned int requested_lockdep_slabs;
+static unsigned int requested_lockdep_headroom_pct = 100; /* default 100% headroom */
+static bool lockdep_headroom_specified;
+static bool lockdep_disabled_early;
+
+static int __init setup_lockdep_slabs(char *str)
+{
+ unsigned long val;
+
+ if (!str)
+ return -EINVAL;
+
+ if (!strcmp(str, "off") || !strcmp(str, "0")) {
+ lockdep_disabled_early = true;
+ return 0;
+ }
+
+ if (kstrtoul(str, 0, &val))
+ return -EINVAL;
+
+ if (val > 10000) {
+ pr_warn("lockdep: ignoring unrealistic lockdep_slabs=%lu\n",
+ val);
+ return -EINVAL;
+ }
+
+ requested_lockdep_slabs = clamp_t(unsigned int, val, 2, LOCKDEP_MAX_SLABS);
+ return 0;
+}
+early_param("lockdep_slabs", setup_lockdep_slabs);
+
+static int __init setup_lockdep_headroom(char *str)
+{
+ unsigned long val;
+
+ if (!str || kstrtoul(str, 0, &val))
+ return -EINVAL;
+
+ requested_lockdep_headroom_pct = clamp_t(unsigned int, val, 10, 900);
+ lockdep_headroom_specified = true;
+ return 0;
+}
+early_param("lockdep_headroom", setup_lockdep_headroom);
+
+static void *lockdep_free_slabs[LOCKDEP_MAX_SLABS];
+static unsigned int lockdep_nr_free_slabs;
+
+/*
+ * Claim a 64KB slab from the pre-allocated memblock reservoir.
+ * Must be called with graph_lock held. Completely lockless and deadlock-free.
+ */
+static void *lockdep_claim_slab(unsigned int *table_counter)
+{
+ void *slab;
+
+ if (lockdep_nr_free_slabs > 0)
+ slab = lockdep_free_slabs[--lockdep_nr_free_slabs];
+ else if (lockdep_slabs_used < lockdep_nr_slabs)
+ slab = lockdep_slabs[lockdep_slabs_used++];
+ else
+ return NULL;
+
+ if (table_counter)
+ (*table_counter)++;
+
+ return slab;
+}
+
+static void lockdep_release_slab(void *slab, unsigned int *table_counter)
+{
+ if (!slab || lockdep_nr_free_slabs >= LOCKDEP_MAX_SLABS)
+ return;
+
+ lockdep_free_slabs[lockdep_nr_free_slabs++] = slab;
+ if (table_counter && *table_counter > 0)
+ (*table_counter)--;
+}
+
#include <trace/events/lock.h>
#ifdef CONFIG_PROVE_LOCKING
@@ -6646,6 +6730,53 @@ void lockdep_unregister_key(struct lock_class_key *key)
}
EXPORT_SYMBOL_GPL(lockdep_unregister_key);
+void __init lockdep_early_init(void)
+{
+ unsigned int nr_slabs, i;
+ phys_addr_t phys_mem;
+ size_t slab_bytes;
+ void *pool;
+
+ if (lockdep_disabled_early) {
+ pr_info("lockdep: disabled by early boot parameter, 0 bytes reserved\n");
+ return;
+ }
+
+ phys_mem = memblock_phys_mem_size();
+
+ /* Auto-tune based on physical memory and CPU count */
+ if (phys_mem && phys_mem < (512ULL << 20))
+ nr_slabs = 32; /* 2 MB on small systems (<512MB RAM) */
+ else if (num_possible_cpus() >= 64 || phys_mem > (64ULL << 30))
+ nr_slabs = 128; /* 8 MB on large servers (>64GB RAM or >64 CPUs) */
+ else
+ nr_slabs = LOCKDEP_DEFAULT_SLABS; /* 64 slabs = 4 MB default */
+
+ /* Ensure initial reservation satisfies requested floor or headroom */
+ if (requested_lockdep_slabs > nr_slabs)
+ nr_slabs = requested_lockdep_slabs;
+
+ if (lockdep_headroom_specified && requested_lockdep_headroom_pct > 100)
+ nr_slabs = (nr_slabs * (100 + requested_lockdep_headroom_pct)) / 100;
+
+ nr_slabs = clamp_t(unsigned int, nr_slabs, 8, LOCKDEP_MAX_SLABS);
+
+ slab_bytes = (size_t)nr_slabs * LOCKDEP_SLAB_SIZE;
+ pool = memblock_alloc(slab_bytes, PAGE_SIZE);
+ if (!pool) {
+ pr_err("lockdep: failed to allocate %u slabs (%zu KB) from memblock\n",
+ nr_slabs, slab_bytes / 1024);
+ return;
+ }
+
+ for (i = 0; i < nr_slabs; i++)
+ lockdep_slabs[i] = (char *)pool + (i * LOCKDEP_SLAB_SIZE);
+
+ lockdep_nr_slabs = nr_slabs;
+ pr_info("lockdep: reserved %u slabs (%zu KB) from memblock\n",
+ nr_slabs, slab_bytes / 1024);
+}
+
void __init lockdep_init(void)
{
pr_info("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n");
diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index 3d8bce0dc9f9..3344361a1c3b 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -125,7 +125,8 @@ enum {
#include <linux/reciprocal_div.h>
#define LOCKDEP_SLAB_SIZE (64 * 1024)
-#define LOCKDEP_MAX_SLABS 64
+#define LOCKDEP_MAX_SLABS 512
+#define LOCKDEP_DEFAULT_SLABS 64
/*
* Chunked Array Tables:
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 4/8] lockdep: Convert 5 graph arrays to chunked tables backed by slab pool
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (2 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 3/8] lockdep: Pre-reserve early memblock slab pool for dynamic tables Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 5/8] lockdep: Fast-path power-of-2 tables with shift/mask indexing Jim Cromie
` (4 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Replace the monolithic static .bss tables for lock_classes, list_entries,
lock_chains, chain_hlocks, and stack_trace with 2D chunked arrays
(DECLARE_CHUNKED_ARRAY) backed by the early memblock slab pool.
Retain 1 static chunk (Chunk 0) in .bss per consumer (~320 kB total) to
cover early bootstrap locking before memblock is initialized:
- lock_classes: 409 items in Chunk 0 (160 B each)
- list_entries: 1,365 items in Chunk 0 (48 B each)
- lock_chains: 2,048 items in Chunk 0 (32 B each)
- chain_hlocks: 32,768 items in Chunk 0 (2 B each)
- stack_trace: 8,192 items in Chunk 0 (8 B each)
Update allocators (alloc_lock_chain(), alloc_chain_hlocks(),
alloc_list_entry(), save_trace()) to claim new 64 KB slabs dynamically
from the memblock reservoir under graph_lock when chunk capacity is
exhausted.
Replace flat array pointer arithmetic with embedded struct indices
(class->class_idx and chain->chain_idx).
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
include/linux/lockdep.h | 1 +
include/linux/lockdep_types.h | 2 +-
kernel/locking/lockdep.c | 600 ++++++++++++++++++++++++-------------
kernel/locking/lockdep_internals.h | 63 ++--
4 files changed, 436 insertions(+), 230 deletions(-)
diff --git a/include/linux/lockdep.h b/include/linux/lockdep.h
index 4c96959d8ad7..0ebb9a3e1bea 100644
--- a/include/linux/lockdep.h
+++ b/include/linux/lockdep.h
@@ -340,6 +340,7 @@ static inline void lockdep_set_selftest_task(struct task_struct *task)
# define lock_set_class(l, n, key, s, i) do { (void)(key); } while (0)
# define lock_set_novalidate_class(l, n, i) do { } while (0)
# define lock_set_subclass(l, s, i) do { } while (0)
+# define lockdep_early_init() do { } while (0)
# define lockdep_init() do { } while (0)
# define lockdep_init_map_type(lock, name, key, sub, inner, outer, type) \
do { (void)(name); (void)(key); } while (0)
diff --git a/include/linux/lockdep_types.h b/include/linux/lockdep_types.h
index 8acac0b59f69..fa0dcf3c3f70 100644
--- a/include/linux/lockdep_types.h
+++ b/include/linux/lockdep_types.h
@@ -114,6 +114,7 @@ struct lock_class {
* "backward" graph nodes.
*/
struct list_head locks_after, locks_before;
+ unsigned int class_idx;
const struct lockdep_subclass_key *key;
lock_cmp_fn cmp_fn;
@@ -121,7 +122,6 @@ struct lock_class {
unsigned int subclass;
unsigned int dep_gen_id;
- unsigned int class_idx;
/*
* IRQ/softirq usage tracking bits:
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 68d82e46cbf6..1c8db52af1ac 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -58,7 +58,9 @@
#include <linux/context_tracking.h>
#include <linux/console.h>
#include <linux/kasan.h>
+#include <linux/mm.h>
#include <linux/memblock.h>
+#include <linux/reboot.h>
#include <asm/sections.h>
@@ -148,6 +150,28 @@ static void lockdep_release_slab(void *slab, unsigned int *table_counter)
(*table_counter)--;
}
+void lockdep_get_slab_stats(struct lockdep_slab_stats *st)
+{
+ st->total_slabs = lockdep_nr_slabs;
+ st->used_slabs = lockdep_slabs_used - lockdep_nr_free_slabs;
+ st->usage = ld_slabs;
+}
+
+#define BOOTSTRAP_LOCKDEP_ENTRIES 1024UL
+
+static struct lock_list list_entries[BOOTSTRAP_LOCKDEP_ENTRIES];
+static struct lock_list *list_entries_freelist;
+static struct lock_list *list_entries_cur = list_entries;
+static unsigned int list_entries_remaining = BOOTSTRAP_LOCKDEP_ENTRIES;
+
+static inline void free_list_entry(struct lock_list *entry)
+{
+ if (!entry)
+ return;
+ *(void **)entry = list_entries_freelist;
+ list_entries_freelist = entry;
+}
+
#include <trace/events/lock.h>
#ifdef CONFIG_PROVE_LOCKING
@@ -289,8 +313,6 @@ static inline int debug_locks_off_graph_unlock(void)
}
unsigned long nr_list_entries;
-static struct lock_list list_entries[MAX_LOCKDEP_ENTRIES];
-static DECLARE_BITMAP(list_entries_in_use, MAX_LOCKDEP_ENTRIES);
/*
* All data structures here are protected by the global debug_lock.
@@ -305,29 +327,25 @@ unsigned long nr_lock_classes;
unsigned long nr_zapped_classes;
unsigned long nr_dynamic_keys;
unsigned long max_lock_class_idx;
-struct lock_class lock_classes[MAX_LOCKDEP_KEYS];
+
+DEFINE_CHUNKED_ARRAY(lock_class, struct lock_class);
+
+static void lockdep_print_watermarks(const char *bug_msg);
+
DECLARE_BITMAP(lock_classes_in_use, MAX_LOCKDEP_KEYS);
static inline struct lock_class *hlock_class(struct held_lock *hlock)
{
unsigned int class_idx = hlock->class_idx;
- /* Don't re-read hlock->class_idx, can't use READ_ONCE() on bitfield */
barrier();
if (!test_bit(class_idx, lock_classes_in_use)) {
- /*
- * Someone passed in garbage, we give up.
- */
DEBUG_LOCKS_WARN_ON(1);
return NULL;
}
- /*
- * At this point, if the passed hlock->class_idx is still garbage,
- * we just have to live with it
- */
- return lock_classes + class_idx;
+ return idx_to_lock_class(class_idx);
}
#ifdef CONFIG_LOCK_STAT
@@ -388,7 +406,7 @@ void lock_stats(struct lock_class *class, struct lock_class_stats *stats)
memset(stats, 0, sizeof(struct lock_class_stats));
for_each_possible_cpu(cpu) {
struct lock_class_stats *pcs =
- &per_cpu(cpu_lock_stats, cpu)[class - lock_classes];
+ &per_cpu(cpu_lock_stats, cpu)[class->class_idx];
for (i = 0; i < ARRAY_SIZE(stats->contention_point); i++)
stats->contention_point[i] += pcs->contention_point[i];
@@ -413,7 +431,7 @@ void clear_lock_stats(struct lock_class *class)
for_each_possible_cpu(cpu) {
struct lock_class_stats *cpu_stats =
- &per_cpu(cpu_lock_stats, cpu)[class - lock_classes];
+ &per_cpu(cpu_lock_stats, cpu)[class->class_idx];
memset(cpu_stats, 0, sizeof(struct lock_class_stats));
}
@@ -423,7 +441,7 @@ void clear_lock_stats(struct lock_class *class)
static struct lock_class_stats *get_lock_stats(struct lock_class *class)
{
- return &this_cpu_ptr(cpu_lock_stats)[class - lock_classes];
+ return &this_cpu_ptr(cpu_lock_stats)[class->class_idx];
}
static void lock_release_holdtime(struct held_lock *hlock)
@@ -555,9 +573,26 @@ static __always_inline void lockdep_recursion_finish(void)
__this_cpu_write(lockdep_recursion, 0);
}
+static void lockdep_selftest_trace_start(void);
+static void lockdep_selftest_trace_finish(void);
+static void lockdep_report_stage(const char *domain, const char *stage_name);
+
void lockdep_set_selftest_task(struct task_struct *task)
{
- lockdep_selftest_task_struct = task;
+ unsigned long flags;
+
+ if (task) {
+ lockdep_selftest_task_struct = task;
+ lockdep_selftest_trace_start();
+ return;
+ }
+
+ lockdep_selftest_task_struct = NULL;
+ raw_local_irq_save(flags);
+ lockdep_lock();
+ lockdep_selftest_trace_finish();
+ lockdep_unlock();
+ raw_local_irq_restore(flags);
}
/*
@@ -622,6 +657,7 @@ unsigned long nr_stack_trace_entries;
* @nr_entries: Number of entries in @entries.
* @entries: Actual stack backtrace.
*/
+#define STACK_TRACE_HASH_SIZE (1 << CONFIG_LOCKDEP_STACK_TRACE_HASH_BITS)
struct lock_trace {
struct hlist_node hash_entry;
u32 hash;
@@ -630,17 +666,80 @@ struct lock_trace {
};
#define LOCK_TRACE_SIZE_IN_LONGS \
(sizeof(struct lock_trace) / sizeof(unsigned long))
-/*
- * Stack-trace: sequence of lock_trace structures. Protected by the graph_lock.
- */
-static unsigned long stack_trace[MAX_STACK_TRACE_ENTRIES];
+#define BOOTSTRAP_STACK_TRACE_ENTRIES 4096UL
+#define MAX_LOCKDEP_TRACE_DEPTH 48
+
+static unsigned long stack_trace[BOOTSTRAP_STACK_TRACE_ENTRIES];
+static unsigned long *trace_free_ptr = stack_trace;
+static size_t trace_remaining_longs = BOOTSTRAP_STACK_TRACE_ENTRIES;
+
+static void *trace_slabs[LOCKDEP_MAX_SLABS];
+static unsigned int nr_trace_slabs;
+
static struct hlist_head stack_trace_hash[STACK_TRACE_HASH_SIZE];
-static bool traces_identical(struct lock_trace *t1, struct lock_trace *t2)
+struct lockdep_selftest_snap {
+ unsigned int nr_trace_slabs;
+ unsigned long *trace_free_ptr;
+ size_t trace_remaining_longs;
+ unsigned long nr_trace_entries;
+};
+static struct lockdep_selftest_snap selftest_snap;
+
+static void lockdep_selftest_trace_start(void)
+{
+ selftest_snap.nr_trace_slabs = nr_trace_slabs;
+ selftest_snap.trace_free_ptr = trace_free_ptr;
+ selftest_snap.trace_remaining_longs = trace_remaining_longs;
+ selftest_snap.nr_trace_entries = nr_stack_trace_entries;
+
+ lockdep_report_stage("selftest", "pre-test");
+}
+
+static void lockdep_selftest_trace_finish(void)
+{
+ unsigned int reclaimed_slabs = 0;
+ unsigned int i;
+
+ if (!debug_locks || !selftest_snap.trace_free_ptr)
+ return;
+
+ lockdep_report_stage("selftest", "peak-test");
+
+ for (i = selftest_snap.nr_trace_slabs; i < nr_trace_slabs; i++) {
+ lockdep_release_slab(trace_slabs[i], &ld_slabs.stack_traces);
+ trace_slabs[i] = NULL;
+ reclaimed_slabs++;
+ }
+ nr_trace_slabs = selftest_snap.nr_trace_slabs;
+ trace_free_ptr = selftest_snap.trace_free_ptr;
+ trace_remaining_longs = selftest_snap.trace_remaining_longs;
+ nr_stack_trace_entries = selftest_snap.nr_trace_entries;
+ memset(stack_trace_hash, 0, sizeof(stack_trace_hash));
+
+ if (reclaimed_slabs)
+ pr_info("lockdep: selftest complete : recycled %u trace slabs (%u kB) to pool\n",
+ reclaimed_slabs, (reclaimed_slabs * LOCKDEP_SLAB_SIZE) / 1024);
+
+ lockdep_report_stage("selftest", "post-test");
+}
+
+static inline void lock_trace_discard(struct lock_trace *trace, unsigned int max_entries)
+{
+ size_t needed_longs = LOCK_TRACE_SIZE_IN_LONGS + max_entries;
+
+ if ((unsigned long *)trace + needed_longs == trace_free_ptr) {
+ trace_free_ptr = (unsigned long *)trace;
+ trace_remaining_longs += needed_longs;
+ }
+}
+
+static inline void lock_trace_trim(struct lock_trace *trace, unsigned int unused_entries)
{
- return t1->hash == t2->hash && t1->nr_entries == t2->nr_entries &&
- memcmp(t1->entries, t2->entries,
- t1->nr_entries * sizeof(t1->entries[0])) == 0;
+ if (unused_entries && trace_free_ptr) {
+ trace_free_ptr -= unused_entries;
+ trace_remaining_longs += unused_entries;
+ }
}
static struct lock_trace *save_trace(void)
@@ -648,40 +747,59 @@ static struct lock_trace *save_trace(void)
struct lock_trace *trace, *t2;
struct hlist_head *hash_head;
u32 hash;
- int max_entries;
+ size_t needed_longs = LOCK_TRACE_SIZE_IN_LONGS + MAX_LOCKDEP_TRACE_DEPTH;
BUILD_BUG_ON_NOT_POWER_OF_2(STACK_TRACE_HASH_SIZE);
- BUILD_BUG_ON(LOCK_TRACE_SIZE_IN_LONGS >= MAX_STACK_TRACE_ENTRIES);
-
- trace = (struct lock_trace *)(stack_trace + nr_stack_trace_entries);
- max_entries = MAX_STACK_TRACE_ENTRIES - nr_stack_trace_entries -
- LOCK_TRACE_SIZE_IN_LONGS;
- if (max_entries <= 0) {
- if (!debug_locks_off_graph_unlock())
- return NULL;
+ if (trace_remaining_longs < needed_longs) {
+ unsigned long *slab = lockdep_claim_slab(&ld_slabs.stack_traces);
- nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_STACK_TRACE_ENTRIES too low!");
- dump_stack();
- nbcon_cpu_emergency_exit();
+ if (unlikely(!slab))
+ goto out_fail;
- return NULL;
+ trace_slabs[nr_trace_slabs++] = slab;
+ trace_free_ptr = slab;
+ trace_remaining_longs = LOCKDEP_SLAB_SIZE / sizeof(unsigned long);
}
- trace->nr_entries = stack_trace_save(trace->entries, max_entries, 3);
- hash = jhash(trace->entries, trace->nr_entries *
- sizeof(trace->entries[0]), 0);
+ trace = (struct lock_trace *)trace_free_ptr;
+ trace_free_ptr += needed_longs;
+ trace_remaining_longs -= needed_longs;
+
+ trace->nr_entries = stack_trace_save(trace->entries, MAX_LOCKDEP_TRACE_DEPTH, 3);
+ hash = jhash(trace->entries, trace->nr_entries * sizeof(unsigned long), 0);
trace->hash = hash;
hash_head = stack_trace_hash + (hash & (STACK_TRACE_HASH_SIZE - 1));
+
hlist_for_each_entry(t2, hash_head, hash_entry) {
- if (traces_identical(trace, t2))
+ if (t2->hash == hash && t2->nr_entries == trace->nr_entries &&
+ !memcmp(t2->entries, trace->entries,
+ trace->nr_entries * sizeof(unsigned long))) {
+ /* Duplicate hit: rewind speculative allocation */
+ lock_trace_discard(trace, MAX_LOCKDEP_TRACE_DEPTH);
return t2;
+ }
}
- nr_stack_trace_entries += LOCK_TRACE_SIZE_IN_LONGS + trace->nr_entries;
+
+ /* Novel trace: trim unused tail frames */
+ if (trace->nr_entries < MAX_LOCKDEP_TRACE_DEPTH)
+ lock_trace_trim(trace, MAX_LOCKDEP_TRACE_DEPTH - trace->nr_entries);
+
hlist_add_head(&trace->hash_entry, hash_head);
+ nr_stack_trace_entries += LOCK_TRACE_SIZE_IN_LONGS + trace->nr_entries;
return trace;
+
+out_fail:
+ if (!debug_locks_off_graph_unlock())
+ return NULL;
+
+ nbcon_cpu_emergency_enter();
+ lockdep_print_watermarks("BUG: lockdep stack trace allocation failed!");
+ dump_stack();
+ nbcon_cpu_emergency_exit();
+
+ return NULL;
}
/* Return the number of stack traces in the stack_trace[] array. */
@@ -1075,46 +1193,15 @@ static bool assign_lock_key(struct lockdep_map *lock)
#ifdef CONFIG_DEBUG_LOCKDEP
-/* Check whether element @e occurs in list @h */
-static bool in_list(struct list_head *e, struct list_head *h)
-{
- struct list_head *f;
-
- list_for_each(f, h) {
- if (e == f)
- return true;
- }
-
- return false;
-}
-
-/*
- * Check whether entry @e occurs in any of the locks_after or locks_before
- * lists.
- */
-static bool in_any_class_list(struct list_head *e)
-{
- struct lock_class *class;
- int i;
-
- for (i = 0; i < ARRAY_SIZE(lock_classes); i++) {
- class = &lock_classes[i];
- if (in_list(e, &class->locks_after) ||
- in_list(e, &class->locks_before))
- return true;
- }
- return false;
-}
-
static bool class_lock_list_valid(struct lock_class *c, struct list_head *h)
{
struct lock_list *e;
list_for_each_entry(e, h, entry) {
if (e->links_to != c) {
- printk(KERN_INFO "class %s: mismatch for lock entry %ld; class %s <> %s",
+ pr_info("class %s: mismatch for lock entry %p; class %s <> %s",
c->name ? : "(?)",
- (unsigned long)(e - list_entries),
+ e,
e->links_to && e->links_to->name ?
e->links_to->name : "(?)",
e->class && e->class->name ? e->class->name :
@@ -1126,7 +1213,8 @@ static bool class_lock_list_valid(struct lock_class *c, struct list_head *h)
}
#ifdef CONFIG_PROVE_LOCKING
-static u16 chain_hlocks[MAX_LOCKDEP_CHAIN_HLOCKS];
+static u16 get_chain_hlock(unsigned int offset);
+static void set_chain_hlock(unsigned int offset, u16 val);
#endif
static bool check_lock_chain_key(struct lock_chain *chain)
@@ -1136,14 +1224,14 @@ static bool check_lock_chain_key(struct lock_chain *chain)
int i;
for (i = chain->base; i < chain->base + chain->depth; i++)
- chain_key = iterate_chain_key(chain_key, chain_hlocks[i]);
+ chain_key = iterate_chain_key(chain_key, get_chain_hlock(i));
/*
* The 'unsigned long long' casts avoid that a compiler warning
* is reported when building tools/lib/lockdep.
*/
if (chain->chain_key != chain_key) {
printk(KERN_INFO "chain %lld: key %#llx <> %#llx\n",
- (unsigned long long)(chain - lock_chains),
+ (unsigned long long)chain->chain_idx,
(unsigned long long)chain->chain_key,
(unsigned long long)chain_key);
return false;
@@ -1152,42 +1240,15 @@ static bool check_lock_chain_key(struct lock_chain *chain)
return true;
}
-static bool in_any_zapped_class_list(struct lock_class *class)
-{
- struct pending_free *pf;
- int i;
-
- for (i = 0, pf = delayed_free.pf; i < ARRAY_SIZE(delayed_free.pf); i++, pf++) {
- if (in_list(&class->lock_entry, &pf->zapped))
- return true;
- }
-
- return false;
-}
-
static bool __check_data_structures(void)
{
struct lock_class *class;
struct lock_chain *chain;
struct hlist_head *head;
- struct lock_list *e;
int i;
- /* Check whether all classes occur in a lock list. */
- for (i = 0; i < ARRAY_SIZE(lock_classes); i++) {
- class = &lock_classes[i];
- if (!in_list(&class->lock_entry, &all_lock_classes) &&
- !in_list(&class->lock_entry, &free_lock_classes) &&
- !in_any_zapped_class_list(class)) {
- printk(KERN_INFO "class %px/%s is not in any class list\n",
- class, class->name ? : "(?)");
- return false;
- }
- }
-
/* Check whether all classes have valid lock lists. */
- for (i = 0; i < ARRAY_SIZE(lock_classes); i++) {
- class = &lock_classes[i];
+ list_for_each_entry(class, &all_lock_classes, lock_entry) {
if (!class_lock_list_valid(class, &class->locks_before))
return false;
if (!class_lock_list_valid(class, &class->locks_after))
@@ -1203,38 +1264,6 @@ static bool __check_data_structures(void)
}
}
- /*
- * Check whether all list entries that are in use occur in a class
- * lock list.
- */
- for_each_set_bit(i, list_entries_in_use, ARRAY_SIZE(list_entries)) {
- e = list_entries + i;
- if (!in_any_class_list(&e->entry)) {
- printk(KERN_INFO "list entry %d is not in any class list; class %s <> %s\n",
- (unsigned int)(e - list_entries),
- e->class->name ? : "(?)",
- e->links_to->name ? : "(?)");
- return false;
- }
- }
-
- /*
- * Check whether all list entries that are not in use do not occur in
- * a class lock list.
- */
- for_each_clear_bit(i, list_entries_in_use, ARRAY_SIZE(list_entries)) {
- e = list_entries + i;
- if (in_any_class_list(&e->entry)) {
- printk(KERN_INFO "list entry %d occurs in a class list; class %s <> %s\n",
- (unsigned int)(e - list_entries),
- e->class && e->class->name ? e->class->name :
- "(?)",
- e->links_to && e->links_to->name ?
- e->links_to->name : "(?)");
- return false;
- }
- }
-
return true;
}
@@ -1286,11 +1315,15 @@ static void init_data_structures_once(void)
INIT_LIST_HEAD(&delayed_free.pf[0].zapped);
INIT_LIST_HEAD(&delayed_free.pf[1].zapped);
- for (i = 0; i < ARRAY_SIZE(lock_classes); i++) {
- list_add_tail(&lock_classes[i].lock_entry, &free_lock_classes);
- INIT_LIST_HEAD(&lock_classes[i].locks_after);
- INIT_LIST_HEAD(&lock_classes[i].locks_before);
+ for (i = 0; i < lock_class_PER_CHUNK; i++) {
+ struct lock_class *class = &lock_class_chunk0[i];
+
+ class->class_idx = i;
+ list_add_tail(&class->lock_entry, &free_lock_classes);
+ INIT_LIST_HEAD(&class->locks_after);
+ INIT_LIST_HEAD(&class->locks_before);
}
+
init_chain_block_buckets();
}
@@ -1371,7 +1404,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
struct lockdep_subclass_key *key;
struct hlist_head *hash_head;
struct lock_class *class;
- int idx;
+ int idx, i;
DEBUG_LOCKS_WARN_ON(!irqs_disabled());
@@ -1406,19 +1439,43 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
/* Allocate a new lock class and add it to the hash. */
class = list_first_entry_or_null(&free_lock_classes, typeof(*class),
lock_entry);
+ if (!class) {
+ if (nr_lock_class_chunks < LOCKDEP_MAX_SLABS) {
+ struct lock_class *chunk;
+ unsigned int chunk_idx = nr_lock_class_chunks;
+
+ chunk = lockdep_claim_slab(&ld_slabs.lock_classes);
+ if (chunk) {
+ memset(chunk, 0, sizeof(struct lock_class) * lock_class_PER_CHUNK);
+ for (i = 0; i < lock_class_PER_CHUNK; i++) {
+ struct lock_class *c = &chunk[i];
+
+ c->class_idx = chunk_idx * lock_class_PER_CHUNK + i;
+ INIT_LIST_HEAD(&c->locks_after);
+ INIT_LIST_HEAD(&c->locks_before);
+ list_add_tail(&c->lock_entry, &free_lock_classes);
+ }
+ /* Pairs with smp_load_acquire() in idx_to_lock_class() */
+ smp_store_release(&lock_class_chunks[chunk_idx], chunk);
+ nr_lock_class_chunks++;
+ class = list_first_entry_or_null(&free_lock_classes, typeof(*class),
+ lock_entry);
+ }
+ }
+ }
if (!class) {
if (!debug_locks_off_graph_unlock()) {
return NULL;
}
nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_LOCKDEP_KEYS too low!");
+ lockdep_print_watermarks("BUG: MAX_LOCKDEP_KEYS too low!");
dump_stack();
nbcon_cpu_emergency_exit();
return NULL;
}
nr_lock_classes++;
- __set_bit(class - lock_classes, lock_classes_in_use);
+ __set_bit(class->class_idx, lock_classes_in_use);
debug_atomic_inc(nr_unused_locks);
class->key = key;
class->name = lock->name;
@@ -1439,7 +1496,7 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
* of classes.
*/
list_move_tail(&class->lock_entry, &all_lock_classes);
- idx = class - lock_classes;
+ idx = class->class_idx;
if (idx > max_lock_class_idx)
max_lock_class_idx = idx;
@@ -1484,22 +1541,39 @@ register_lock_class(struct lockdep_map *lock, unsigned int subclass, int force)
*/
static struct lock_list *alloc_list_entry(void)
{
- int idx = find_first_zero_bit(list_entries_in_use,
- ARRAY_SIZE(list_entries));
+ struct lock_list *entry;
- if (idx >= ARRAY_SIZE(list_entries)) {
- if (!debug_locks_off_graph_unlock())
- return NULL;
+ if (list_entries_freelist) {
+ entry = list_entries_freelist;
+ list_entries_freelist = *(void **)entry;
+ } else if (list_entries_remaining > 0) {
+ entry = list_entries_cur++;
+ list_entries_remaining--;
+ } else {
+ struct lock_list *slab = lockdep_claim_slab(&ld_slabs.direct_deps);
- nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_LOCKDEP_ENTRIES too low!");
- dump_stack();
- nbcon_cpu_emergency_exit();
- return NULL;
+ if (unlikely(!slab))
+ goto out_fail;
+
+ list_entries_cur = slab;
+ list_entries_remaining = LOCKDEP_SLAB_SIZE / sizeof(struct lock_list);
+ entry = list_entries_cur++;
+ list_entries_remaining--;
}
+
+ memset(entry, 0, sizeof(*entry));
nr_list_entries++;
- __set_bit(idx, list_entries_in_use);
- return list_entries + idx;
+ return entry;
+
+out_fail:
+ if (!debug_locks_off_graph_unlock())
+ return NULL;
+
+ nbcon_cpu_emergency_enter();
+ print_lockdep_off("BUG: lockdep pool exhausted!");
+ dump_stack();
+ nbcon_cpu_emergency_exit();
+ return NULL;
}
/*
@@ -3407,9 +3481,32 @@ check_prevs_add(struct task_struct *curr, struct held_lock *next)
return 0;
}
-struct lock_chain lock_chains[MAX_LOCKDEP_CHAINS];
+DEFINE_CHUNKED_ARRAY(lock_chain, struct lock_chain);
static DECLARE_BITMAP(lock_chains_in_use, MAX_LOCKDEP_CHAINS);
-static u16 chain_hlocks[MAX_LOCKDEP_CHAIN_HLOCKS];
+
+DEFINE_CHUNKED_ARRAY(chain_hlock, u16);
+static unsigned int total_chain_hlocks_capacity = chain_hlock_PER_CHUNK;
+
+unsigned int chain_hlocks_used(void)
+{
+ return total_chain_hlocks_capacity - (nr_free_chain_hlocks + nr_lost_chain_hlocks);
+}
+
+static inline u16 get_chain_hlock(unsigned int offset)
+{
+ u16 *p = idx_to_chain_hlock(offset);
+
+ return p ? *p : 0;
+}
+
+static inline void set_chain_hlock(unsigned int offset, u16 val)
+{
+ u16 *p = idx_to_chain_hlock(offset);
+
+ if (p)
+ *p = val;
+}
+
unsigned long nr_zapped_lock_chains;
unsigned int nr_free_chain_hlocks; /* Free chain_hlocks in buckets */
unsigned int nr_lost_chain_hlocks; /* Lost chain_hlocks */
@@ -3459,7 +3556,7 @@ static inline int size_to_bucket(int size)
*/
static inline int chain_block_next(int offset)
{
- int next = chain_hlocks[offset];
+ int next = get_chain_hlock(offset);
WARN_ON_ONCE(!(next & CHAIN_BLK_FLAG));
@@ -3468,7 +3565,7 @@ static inline int chain_block_next(int offset)
next &= ~CHAIN_BLK_FLAG;
next <<= 16;
- next |= chain_hlocks[offset + 1];
+ next |= get_chain_hlock(offset + 1);
return next;
}
@@ -3478,17 +3575,17 @@ static inline int chain_block_next(int offset)
*/
static inline int chain_block_size(int offset)
{
- return (chain_hlocks[offset + 2] << 16) | chain_hlocks[offset + 3];
+ return (get_chain_hlock(offset + 2) << 16) | get_chain_hlock(offset + 3);
}
static inline void init_chain_block(int offset, int next, int bucket, int size)
{
- chain_hlocks[offset] = (next >> 16) | CHAIN_BLK_FLAG;
- chain_hlocks[offset + 1] = (u16)next;
+ set_chain_hlock(offset, (next >> 16) | CHAIN_BLK_FLAG);
+ set_chain_hlock(offset + 1, (u16)next);
if (size && !bucket) {
- chain_hlocks[offset + 2] = size >> 16;
- chain_hlocks[offset + 3] = (u16)size;
+ set_chain_hlock(offset + 2, size >> 16);
+ set_chain_hlock(offset + 3, (u16)size);
}
}
@@ -3563,7 +3660,7 @@ static void init_chain_block_buckets(void)
for (i = 0; i < MAX_CHAIN_BUCKETS; i++)
chain_block_buckets[i] = -1;
- add_chain_block(0, ARRAY_SIZE(chain_hlocks));
+ add_chain_block(0, chain_hlock_PER_CHUNK);
}
/*
@@ -3584,14 +3681,33 @@ static int alloc_chain_hlocks(int req)
init_data_structures_once();
- if (nr_free_chain_hlocks < req)
- return -1;
-
/*
* We require a minimum of 2 (u16) entries to encode a freelist
* 'pointer'.
*/
req = max(req, 2);
+
+retry:
+ if (nr_free_chain_hlocks < req) {
+ if (nr_chain_hlock_chunks < LOCKDEP_MAX_SLABS) {
+ unsigned int chunk_idx = nr_chain_hlock_chunks;
+ unsigned int base_offset = chunk_idx * chain_hlock_PER_CHUNK;
+ u16 *chunk;
+
+ chunk = lockdep_claim_slab(&ld_slabs.chain_hlocks);
+ if (chunk) {
+ memset(chunk, 0, sizeof(u16) * chain_hlock_PER_CHUNK);
+ /* Pairs with smp_load_acquire() in idx_to_chain_hlock() */
+ smp_store_release(&chain_hlock_chunks[chunk_idx], chunk);
+ nr_chain_hlock_chunks++;
+ total_chain_hlocks_capacity += chain_hlock_PER_CHUNK;
+ add_chain_block(base_offset, chain_hlock_PER_CHUNK);
+ }
+ }
+ if (nr_free_chain_hlocks < req)
+ return -1;
+ }
+
bucket = size_to_bucket(req);
curr = chain_block_buckets[bucket];
@@ -3632,6 +3748,24 @@ static int alloc_chain_hlocks(int req)
return curr;
}
+ /* If fragmented and chunks remain, expand with a new chunk */
+ if (nr_chain_hlock_chunks < LOCKDEP_MAX_SLABS) {
+ unsigned int chunk_idx = nr_chain_hlock_chunks;
+ unsigned int base_offset = chunk_idx * chain_hlock_PER_CHUNK;
+ u16 *chunk;
+
+ chunk = lockdep_claim_slab(&ld_slabs.chain_hlocks);
+ if (chunk) {
+ memset(chunk, 0, sizeof(u16) * chain_hlock_PER_CHUNK);
+ /* Pairs with smp_load_acquire() in idx_to_chain_hlock() */
+ smp_store_release(&chain_hlock_chunks[chunk_idx], chunk);
+ nr_chain_hlock_chunks++;
+ total_chain_hlocks_capacity += chain_hlock_PER_CHUNK;
+ add_chain_block(base_offset, chain_hlock_PER_CHUNK);
+ goto retry;
+ }
+ }
+
return -1;
}
@@ -3642,10 +3776,10 @@ static inline void free_chain_hlocks(int base, int size)
struct lock_class *lock_chain_get_class(struct lock_chain *chain, int i)
{
- u16 chain_hlock = chain_hlocks[chain->base + i];
+ u16 chain_hlock = get_chain_hlock(chain->base + i);
unsigned int class_idx = chain_hlock_class_idx(chain_hlock);
- return lock_classes + class_idx;
+ return idx_to_lock_class(class_idx);
}
/*
@@ -3710,10 +3844,10 @@ static void print_chain_keys_chain(struct lock_chain *chain)
printk("depth: %u\n", chain->depth);
for (i = 0; i < chain->depth; i++) {
- hlock_id = chain_hlocks[chain->base + i];
+ hlock_id = get_chain_hlock(chain->base + i);
chain_key = print_chain_key_iteration(hlock_id, chain_key);
- print_lock_name(NULL, lock_classes + chain_hlock_class_idx(hlock_id));
+ print_lock_name(NULL, idx_to_lock_class(chain_hlock_class_idx(hlock_id)));
printk("\n");
}
}
@@ -3768,7 +3902,7 @@ static int check_no_collision(struct task_struct *curr,
for (j = 0; j < chain->depth - 1; j++, i++) {
id = hlock_id(&curr->held_locks[i]);
- if (DEBUG_LOCKS_WARN_ON(chain_hlocks[chain->base + j] != id)) {
+ if (DEBUG_LOCKS_WARN_ON(get_chain_hlock(chain->base + j) != id)) {
print_collision(curr, hlock, chain);
return 0;
}
@@ -3783,25 +3917,64 @@ static int check_no_collision(struct task_struct *curr,
*/
long lockdep_next_lockchain(long i)
{
- i = find_next_bit(lock_chains_in_use, ARRAY_SIZE(lock_chains), i + 1);
- return i < ARRAY_SIZE(lock_chains) ? i : -2;
+ i = find_next_bit(lock_chains_in_use, MAX_LOCKDEP_CHAINS, i + 1);
+ return i < MAX_LOCKDEP_CHAINS ? i : -2;
}
unsigned long lock_chain_count(void)
{
- return bitmap_weight(lock_chains_in_use, ARRAY_SIZE(lock_chains));
+ return bitmap_weight(lock_chains_in_use, MAX_LOCKDEP_CHAINS);
+}
+
+static void lockdep_print_watermarks(const char *bug_msg)
+{
+ print_lockdep_off(bug_msg);
+ pr_err("Lockdep Stats: classes=%lu (chunks=%u), entries=%lu, chains=%lu (chunks=%u), hlocks=%u (chunks=%u)\n",
+ nr_lock_classes, nr_lock_class_chunks,
+ nr_list_entries,
+ lock_chain_count(), nr_lock_chain_chunks,
+ chain_hlocks_used(), nr_chain_hlock_chunks);
+ pr_err("Lockdep Slabs: total=%u, used=%u (classes=%u, entries=%u, chains=%u, hlocks=%u, trace=%u), free=%u\n",
+ lockdep_nr_slabs, lockdep_slabs_used,
+ ld_slabs.lock_classes, ld_slabs.direct_deps,
+ ld_slabs.lock_chains, ld_slabs.chain_hlocks,
+ ld_slabs.stack_traces,
+ lockdep_nr_slabs > lockdep_slabs_used ? lockdep_nr_slabs - lockdep_slabs_used : 0);
+ show_mem();
}
/* Must be called with the graph lock held. */
static struct lock_chain *alloc_lock_chain(void)
{
- int idx = find_first_zero_bit(lock_chains_in_use,
- ARRAY_SIZE(lock_chains));
+ int idx = find_first_zero_bit(lock_chains_in_use, MAX_LOCKDEP_CHAINS);
+ unsigned int chunk_idx;
+ struct lock_chain *chain;
- if (unlikely(idx >= ARRAY_SIZE(lock_chains)))
+ if (unlikely(idx >= MAX_LOCKDEP_CHAINS))
return NULL;
+
+ chunk_idx = reciprocal_divide(idx, lock_chain_rv);
+ if (chunk_idx >= LOCKDEP_MAX_SLABS)
+ return NULL;
+
+ if (chunk_idx >= nr_lock_chain_chunks) {
+ struct lock_chain *chunk;
+
+ chunk = lockdep_claim_slab(&ld_slabs.lock_chains);
+ if (!chunk)
+ return NULL;
+
+ memset(chunk, 0, sizeof(struct lock_chain) * lock_chain_PER_CHUNK);
+ /* Pairs with smp_load_acquire() in idx_to_lock_chain() */
+ smp_store_release(&lock_chain_chunks[chunk_idx], chunk);
+ nr_lock_chain_chunks = chunk_idx + 1;
+ }
+
__set_bit(idx, lock_chains_in_use);
- return lock_chains + idx;
+ chain = idx_to_lock_chain(idx);
+ memset(chain, 0, sizeof(*chain));
+ chain->chain_idx = idx;
+ return chain;
}
/*
@@ -3833,7 +4006,7 @@ static inline int add_chain_cache(struct task_struct *curr,
return 0;
nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_LOCKDEP_CHAINS too low!");
+ lockdep_print_watermarks("BUG: MAX_LOCKDEP_CHAINS too low!");
dump_stack();
nbcon_cpu_emergency_exit();
return 0;
@@ -3843,9 +4016,9 @@ static inline int add_chain_cache(struct task_struct *curr,
i = get_first_held_lock(curr, hlock);
chain->depth = curr->lockdep_depth + 1 - i;
- BUILD_BUG_ON((1UL << 24) <= ARRAY_SIZE(chain_hlocks));
+ BUILD_BUG_ON((1UL << 24) <= MAX_LOCKDEP_CHAIN_HLOCKS);
BUILD_BUG_ON((1UL << 6) <= ARRAY_SIZE(curr->held_locks));
- BUILD_BUG_ON((1UL << 8*sizeof(chain_hlocks[0])) <= ARRAY_SIZE(lock_classes));
+ BUILD_BUG_ON((1UL << (8 * sizeof(u16))) <= MAX_LOCKDEP_KEYS);
j = alloc_chain_hlocks(chain->depth);
if (j < 0) {
@@ -3853,7 +4026,7 @@ static inline int add_chain_cache(struct task_struct *curr,
return 0;
nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_LOCKDEP_CHAIN_HLOCKS too low!");
+ lockdep_print_watermarks("BUG: MAX_LOCKDEP_CHAIN_HLOCKS too low!");
dump_stack();
nbcon_cpu_emergency_exit();
return 0;
@@ -3863,9 +4036,9 @@ static inline int add_chain_cache(struct task_struct *curr,
for (j = 0; j < chain->depth - 1; j++, i++) {
int lock_id = hlock_id(curr->held_locks + i);
- chain_hlocks[chain->base + j] = lock_id;
+ set_chain_hlock(chain->base + j, lock_id);
}
- chain_hlocks[chain->base + j] = hlock_id(hlock);
+ set_chain_hlock(chain->base + j, hlock_id(hlock));
hlist_add_head_rcu(&chain->entry, hash_head);
debug_atomic_inc(chain_lookup_misses);
inc_chains(chain->irq_context);
@@ -5222,7 +5395,7 @@ static int __lock_acquire(struct lockdep_map *lock, unsigned int subclass,
if (DEBUG_LOCKS_WARN_ON(depth >= MAX_LOCK_DEPTH))
return 0;
- class_idx = class - lock_classes;
+ class_idx = class->class_idx;
if (depth && !sync) {
/* we're holding locks and the new held lock is not a sync */
@@ -5413,7 +5586,7 @@ static noinstr int match_held_lock(const struct held_lock *hlock,
if (DEBUG_LOCKS_WARN_ON(!hlock->nest_lock))
return 0;
- if (hlock->class_idx == class - lock_classes)
+ if (hlock->class_idx == class->class_idx)
return 1;
}
@@ -5521,7 +5694,7 @@ __lock_set_class(struct lockdep_map *lock, const char *name,
lock->wait_type_outer,
lock->lock_type);
class = register_lock_class(lock, subclass, 0);
- hlock->class_idx = class - lock_classes;
+ hlock->class_idx = class->class_idx;
curr->lockdep_depth = i;
curr->curr_chain_key = hlock->prev_chain_key;
@@ -5871,7 +6044,7 @@ static void verify_lock_unused(struct lockdep_map *lock, struct held_lock *hlock
if (!(class->usage_mask & mask))
return;
- hlock->class_idx = class - lock_classes;
+ hlock->class_idx = class->class_idx;
print_usage_bug(current, hlock, LOCK_USED, LOCK_USAGE_STATES);
#endif
@@ -6279,7 +6452,7 @@ static void remove_class_from_lock_chain(struct pending_free *pf,
int i;
for (i = chain->base; i < chain->base + chain->depth; i++) {
- if (chain_hlock_class_idx(chain_hlocks[i]) != class - lock_classes)
+ if (chain_hlock_class_idx(get_chain_hlock(i)) != class->class_idx)
continue;
/*
* Each lock class occurs at most once in a lock chain so once
@@ -6301,7 +6474,7 @@ static void remove_class_from_lock_chain(struct pending_free *pf,
* hlist_for_each_entry_rcu() loop is safe.
*/
hlist_del_rcu(&chain->entry);
- __set_bit(chain - lock_chains, pf->lock_chains_being_freed);
+ __set_bit(chain->chain_idx, pf->lock_chains_being_freed);
nr_zapped_lock_chains++;
#endif
}
@@ -6338,28 +6511,28 @@ static void zap_class(struct pending_free *pf, struct lock_class *class)
list_for_each_entry_safe(entry, tmp, &class->locks_after, entry) {
list_for_each_entry_safe(other, other_tmp, &entry->links_to->locks_before, entry) {
if (other->links_to == class) {
- __clear_bit(other - list_entries, list_entries_in_use);
nr_list_entries--;
list_del_rcu(&other->entry);
+ free_list_entry(other);
break;
}
}
- __clear_bit(entry - list_entries, list_entries_in_use);
nr_list_entries--;
list_del_rcu(&entry->entry);
+ free_list_entry(entry);
}
list_for_each_entry_safe(entry, tmp, &class->locks_before, entry) {
list_for_each_entry_safe(other, other_tmp, &entry->links_to->locks_after, entry) {
if (other->links_to == class) {
- __clear_bit(other - list_entries, list_entries_in_use);
nr_list_entries--;
list_del_rcu(&other->entry);
+ free_list_entry(other);
break;
}
}
- __clear_bit(entry - list_entries, list_entries_in_use);
nr_list_entries--;
list_del_rcu(&entry->entry);
+ free_list_entry(entry);
}
if (list_empty(&class->locks_after) &&
list_empty(&class->locks_before)) {
@@ -6371,8 +6544,8 @@ static void zap_class(struct pending_free *pf, struct lock_class *class)
if (class->usage_mask == 0)
debug_atomic_dec(nr_unused_locks);
nr_lock_classes--;
- __clear_bit(class - lock_classes, lock_classes_in_use);
- if (class - lock_classes == max_lock_class_idx)
+ __clear_bit(class->class_idx, lock_classes_in_use);
+ if (class->class_idx == max_lock_class_idx)
max_lock_class_idx--;
} else {
WARN_ONCE(true, "%s() failed for class %s\n", __func__,
@@ -6450,8 +6623,8 @@ static void __free_zapped_classes(struct pending_free *pf)
#ifdef CONFIG_PROVE_LOCKING
bitmap_andnot(lock_chains_in_use, lock_chains_in_use,
- pf->lock_chains_being_freed, ARRAY_SIZE(lock_chains));
- bitmap_clear(pf->lock_chains_being_freed, 0, ARRAY_SIZE(lock_chains));
+ pf->lock_chains_being_freed, MAX_LOCKDEP_CHAINS);
+ bitmap_clear(pf->lock_chains_being_freed, 0, MAX_LOCKDEP_CHAINS);
#endif
}
@@ -6773,12 +6946,16 @@ void __init lockdep_early_init(void)
lockdep_slabs[i] = (char *)pool + (i * LOCKDEP_SLAB_SIZE);
lockdep_nr_slabs = nr_slabs;
- pr_info("lockdep: reserved %u slabs (%zu KB) from memblock\n",
+ lockdep_slabs_used = 0;
+
+ pr_info("lockdep: reserved %u slabs (%zu KB) from memblock for dynamic tables\n",
nr_slabs, slab_bytes / 1024);
}
void __init lockdep_init(void)
{
+ init_data_structures_once();
+
pr_info("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n");
pr_info("... MAX_LOCKDEP_SUBCLASSES: %lu\n", MAX_LOCKDEP_SUBCLASSES);
@@ -6789,26 +6966,25 @@ void __init lockdep_init(void)
pr_info("... MAX_LOCKDEP_CHAINS: %lu\n", MAX_LOCKDEP_CHAINS);
pr_info("... CHAINHASH_SIZE: %lu\n", CHAINHASH_SIZE);
- pr_info(" memory used by lock dependency info: %zu kB\n",
- (sizeof(lock_classes) +
+ pr_info(" memory used by lock dependency info: dynamic (bootstrap %zu kB)\n",
+ (sizeof(lock_class_chunk0) +
sizeof(lock_classes_in_use) +
sizeof(classhash_table) +
sizeof(list_entries) +
- sizeof(list_entries_in_use) +
sizeof(chainhash_table) +
sizeof(delayed_free)
#ifdef CONFIG_PROVE_LOCKING
+ sizeof(lock_cq)
- + sizeof(lock_chains)
+ + sizeof(lock_chain_chunk0)
+ sizeof(lock_chains_in_use)
- + sizeof(chain_hlocks)
+ + sizeof(chain_hlock_chunk0)
#endif
) / 1024
);
#if defined(CONFIG_TRACE_IRQFLAGS) && defined(CONFIG_PROVE_LOCKING)
- pr_info(" memory used for stack traces: %zu kB\n",
- (sizeof(stack_trace) + sizeof(stack_trace_hash)) / 1024
+ pr_info(" memory used for stack traces: dynamic (bootstrap %zu kB)\n",
+ sizeof(stack_trace) / 1024
);
#endif
diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index 3344361a1c3b..eaa23d9b4dd5 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -7,6 +7,15 @@
* lockdep subsystem internal functions and variables.
*/
+#include <linux/types.h>
+#include <linux/reciprocal_div.h>
+#include <linux/log2.h>
+#include <asm/barrier.h>
+
+#define LOCKDEP_SLAB_SIZE (64 * 1024)
+#define LOCKDEP_MAX_SLABS 512
+#define LOCKDEP_DEFAULT_SLABS 64
+
/*
* Lock-class usage-state bits:
*/
@@ -122,19 +131,25 @@ enum {
#define MAX_LOCKDEP_CHAINS (1UL << MAX_LOCKDEP_CHAINS_BITS)
#define AVG_LOCKDEP_CHAIN_DEPTH 5
-#include <linux/reciprocal_div.h>
+#define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS * AVG_LOCKDEP_CHAIN_DEPTH)
-#define LOCKDEP_SLAB_SIZE (64 * 1024)
-#define LOCKDEP_MAX_SLABS 512
-#define LOCKDEP_DEFAULT_SLABS 64
+/*
+ * Compile-time precomputation of struct reciprocal_value using the canonical
+ * Granlund-Montgomery algorithm matching lib/math/reciprocal_div.c.
+ */
+#define RECIPROCAL_VALUE_INIT(d) { \
+ .m = (u32)((((1ULL << 32) * ((1ULL << (ilog2((d) - 1) + 1)) - (d))) / (d)) + 1), \
+ .sh1 = (ilog2((d) - 1) + 1) > 0 ? 1 : 0, \
+ .sh2 = (ilog2((d) - 1) + 1) > 1 ? (ilog2((d) - 1) + 1) - 1 : 0, \
+}
/*
* Chunked Array Tables:
* Replaces flat monolithic BSS arrays with 2D chunk pointer matrices.
* Chunk 0 is statically allocated in BSS for early boot, while subsequent
* chunks are claimed from the memblock reservoir via lockdep_claim_slab().
- * Indexing uses compile-time Granlund-Montgomery reciprocal divide
- * (~3-cycle multiply+shift, zero division instructions).
+ * Indexing uses a compile-time hybrid: single-cycle bit shifts for power-of-2
+ * elements, and Granlund-Montgomery reciprocal divide for non-power-of-2.
*/
#define DECLARE_CHUNKED_ARRAY(name, type) \
enum { \
@@ -156,20 +171,16 @@ enum {
return &chunk_ptr[offset]; \
}
-#define DEFINE_CHUNKED_ARRAY(name, type) \
+#define DEFINE_CHUNKED_ARRAY(name, type) \
static type name##_chunk0[name##_PER_CHUNK]; \
type *name##_chunks[LOCKDEP_MAX_SLABS] = { name##_chunk0 }; \
static unsigned int nr_##name##_chunks = 1; \
const struct reciprocal_value name##_rv = \
RECIPROCAL_VALUE_INIT(name##_PER_CHUNK)
-struct lockdep_slab_usage {
- unsigned int lock_classes;
- unsigned int direct_deps;
- unsigned int lock_chains;
- unsigned int chain_hlocks;
- unsigned int stack_traces;
-};
+DECLARE_CHUNKED_ARRAY(lock_chain, struct lock_chain);
+DECLARE_CHUNKED_ARRAY(chain_hlock, u16);
+void lockdep_chain_stats(unsigned int *nr_chunks, size_t *chunk_size, size_t *tail_used);
#define LOCK_USAGE_CHARS (2*XXX_LOCK_USAGE_STATES + 1)
@@ -201,9 +212,27 @@ extern unsigned int max_lockdep_depth;
extern unsigned int max_bfs_queue_depth;
extern unsigned long max_lock_class_idx;
-extern struct lock_class lock_classes[MAX_LOCKDEP_KEYS];
+DECLARE_2D_RADIX(lock_class, struct lock_class);
extern unsigned long lock_classes_in_use[];
+struct lockdep_slab_usage {
+ unsigned int lock_classes;
+ unsigned int direct_deps;
+ unsigned int lock_chains;
+ unsigned int chain_hlocks;
+ unsigned int stack_traces;
+};
+
+struct lockdep_slab_stats {
+ unsigned int total_slabs;
+ unsigned int used_slabs;
+ struct lockdep_slab_usage usage;
+};
+
+unsigned int chain_hlocks_used(void);
+unsigned long lock_chain_count(void);
+void lockdep_get_slab_stats(struct lockdep_slab_stats *st);
+
#ifdef CONFIG_PROVE_LOCKING
extern unsigned long lockdep_count_forward_deps(struct lock_class *);
extern unsigned long lockdep_count_backward_deps(struct lock_class *);
@@ -286,7 +315,7 @@ static inline void debug_class_ops_inc(struct lock_class *class)
{
int idx;
- idx = class - lock_classes;
+ idx = class->class_idx;
__debug_atomic_inc(lock_class_ops[idx]);
}
@@ -295,7 +324,7 @@ static inline unsigned long debug_class_ops_read(struct lock_class *class)
int idx, cpu;
unsigned long ops = 0;
- idx = class - lock_classes;
+ idx = class->class_idx;
for_each_possible_cpu(cpu)
ops += per_cpu(lockdep_stats.lock_class_ops[idx], cpu);
return ops;
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 5/8] lockdep: Fast-path power-of-2 tables with shift/mask indexing
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (3 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 4/8] lockdep: Convert 5 graph arrays to chunked tables backed by slab pool Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 6/8] lockdep: Free unused reservation slabs to buddy allocator at late boot Jim Cromie
` (3 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
The 2D chunked arrays (DECLARE_CHUNKED_ARRAY) use Granlund-Montgomery
reciprocal division (reciprocal_divide()) to map indices to (chunk, offset)
tuples across 64 KB slabs. This achieves >99.8% packing density for
non-power-of-2 structs (lock_classes @ 160 B and list_entries @ 48 B).
However, the ultra-hot cache-verification tables (lock_chains @ 32 B and
chain_hlocks @ 2 B) have exact power-of-2 chunk counts (2,048 and 32,768
elements per 64 KB slab).
Add a compile-time branch in DECLARE_CHUNKED_ARRAY() using __builtin_ctz():
for power-of-2 tables, GCC/Clang folds translation into single-cycle bit
shifts (idx >> SHIFT) and masks (idx & MASK), eliminating reciprocal
multiplication overhead entirely from the hot acquire validation path.
Workload Progression (hackbench -p -g 8 -l 1000, 4 vCPUs):
Metric Upstream (1D) Generic (P2) Fast-Path (P3) Delta
====================================================================
Runtime 8.482 s 8.895 s (+4.8%) 8.278 s -2.40%
Cycles 52899510936 55428687460 52033166458 -1.64%
Instructions 29008189069 31932214532 31698626928 +9.27%
By replacing G-M multiplication with single-cycle bit shifts on the hot
cache verification tables, cycle overhead drops by ~6.4% relative to
Patch 2, bringing total cycles to parity with or slightly faster than
upstream baseline (-1.64% cycles).
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
kernel/locking/lockdep.c | 2 +-
kernel/locking/lockdep_internals.h | 15 +++++++++++++--
2 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 1c8db52af1ac..b2dc7619a5e3 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -3953,7 +3953,7 @@ static struct lock_chain *alloc_lock_chain(void)
if (unlikely(idx >= MAX_LOCKDEP_CHAINS))
return NULL;
- chunk_idx = reciprocal_divide(idx, lock_chain_rv);
+ chunk_idx = idx / lock_chain_PER_CHUNK;
if (chunk_idx >= LOCKDEP_MAX_SLABS)
return NULL;
diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index eaa23d9b4dd5..ccd7343af672 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -154,14 +154,25 @@ enum {
#define DECLARE_CHUNKED_ARRAY(name, type) \
enum { \
name##_PER_CHUNK = (LOCKDEP_SLAB_SIZE / sizeof(type)), \
+ name##_IS_P2 = (!(name##_PER_CHUNK & (name##_PER_CHUNK - 1))), \
+ name##_SHIFT = (__builtin_ctz(name##_PER_CHUNK)), \
+ name##_MASK = (name##_PER_CHUNK - 1), \
}; \
extern type * name##_chunks[LOCKDEP_MAX_SLABS]; \
extern const struct reciprocal_value name##_rv; \
static __always_inline type *idx_to_##name(unsigned int idx) \
{ \
- unsigned int chunk = reciprocal_divide(idx, name##_rv); \
- unsigned int offset = idx - (chunk * name##_PER_CHUNK); \
+ unsigned int chunk, offset; \
type *chunk_ptr; \
+ \
+ if (name##_IS_P2) { \
+ chunk = idx >> name##_SHIFT; \
+ offset = idx & name##_MASK; \
+ } else { \
+ chunk = reciprocal_divide(idx, name##_rv); \
+ offset = idx - (chunk * name##_PER_CHUNK); \
+ } \
+ \
if (unlikely(chunk >= LOCKDEP_MAX_SLABS)) \
return NULL; \
/* Pairs with smp_store_release() when new chunk slabs are published */ \
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 6/8] lockdep: Free unused reservation slabs to buddy allocator at late boot
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (4 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 5/8] lockdep: Fast-path power-of-2 tables with shift/mask indexing Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 7/8] lockdep: Expose slab pool telemetry in /proc/lockdep_stats and initcalls Jim Cromie
` (2 subsequent siblings)
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
During early boot, lockdep reserves a generous slab pool (e.g. 64 slabs =
4 MB) from memblock to guarantee uninterrupted initialization. Once
the kernel reaches late_initcall, the boot locking storm is complete,
and lockdep's steady-state working set is known.
Add lockdep_post_init_trim() as a late_initcall_sync handler. Calculate
required runtime headroom (default 100% headroom over boot usage, with
a floor of 32 slabs, or custom lockdep_slabs=N / lockdep_headroom=M%),
and return all excess slabs to the buddy page allocator via
free_reserved_page().
Also register a reboot notifier to log total lifetime slab consumption
and remaining headroom on clean system shutdown.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
kernel/locking/lockdep.c | 71 +++++++++++++++++++++++++++++++++++++++++++
kernel/locking/lockdep_proc.c | 23 +++++++-------
2 files changed, 83 insertions(+), 11 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index b2dc7619a5e3..3fb6c07cea36 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -6992,6 +6992,77 @@ void __init lockdep_init(void)
sizeof(((struct task_struct *)NULL)->held_locks));
}
+static int lockdep_shutdown_notify(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ unsigned int used = lockdep_slabs_used - lockdep_nr_free_slabs;
+ unsigned int total = lockdep_nr_slabs;
+ unsigned int free_slabs = total > used ? total - used : 0;
+ unsigned int headroom_pct = used ? (free_slabs * 100) / used : 0;
+
+ pr_info("lockdep: shutdown summary : %u/%u slabs (%u kB/%u kB, %u%% headroom left), %lu classes, %lu chains, %u hlocks\n",
+ used, total,
+ (used * LOCKDEP_SLAB_SIZE) / 1024,
+ (total * LOCKDEP_SLAB_SIZE) / 1024,
+ headroom_pct,
+ nr_lock_classes, lock_chain_count(), chain_hlocks_used());
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block lockdep_reboot_nb = {
+ .notifier_call = lockdep_shutdown_notify,
+};
+
+static int __init lockdep_post_init_trim(void)
+{
+ unsigned int used = lockdep_slabs_used - lockdep_nr_free_slabs;
+ unsigned int initial_slabs = lockdep_nr_slabs;
+ unsigned int target_slabs, kept_headroom_slabs;
+ unsigned int freed_slabs = 0;
+ unsigned int i;
+
+ if (!lockdep_nr_slabs)
+ return 0;
+
+ /* Compute headroom requirement (default 100% or custom percentage) with 32-slab floor */
+ kept_headroom_slabs = DIV_ROUND_UP(used * requested_lockdep_headroom_pct, 100);
+ if (kept_headroom_slabs < 32)
+ kept_headroom_slabs = 32;
+
+ target_slabs = used + kept_headroom_slabs;
+
+ /* Satisfy both: target_slabs >= requested_lockdep_slabs AND headroom >= M% */
+ if (requested_lockdep_slabs > target_slabs)
+ target_slabs = requested_lockdep_slabs;
+
+ target_slabs = clamp_t(unsigned int, target_slabs, used, lockdep_nr_slabs);
+
+ /* Release excess slabs to buddy allocator */
+ if (target_slabs < lockdep_nr_slabs) {
+ for (i = target_slabs; i < lockdep_nr_slabs; i++) {
+ struct page *page = virt_to_page(lockdep_slabs[i]);
+ unsigned long p;
+
+ for (p = 0; p < (LOCKDEP_SLAB_SIZE >> PAGE_SHIFT); p++)
+ free_reserved_page(page + p);
+
+ lockdep_slabs[i] = NULL;
+ }
+ freed_slabs = lockdep_nr_slabs - target_slabs;
+ lockdep_nr_slabs = target_slabs;
+ }
+
+ pr_info("lockdep: boot complete : %u/%u slabs used, %u kept (%u%% headroom), %u returned to buddy (%u kB freed)\n",
+ used, initial_slabs, lockdep_nr_slabs,
+ lockdep_nr_slabs > used ? ((lockdep_nr_slabs - used) * 100) / used : 0,
+ freed_slabs, (freed_slabs * LOCKDEP_SLAB_SIZE) / 1024);
+
+ register_reboot_notifier(&lockdep_reboot_nb);
+ return 0;
+}
+late_initcall_sync(lockdep_post_init_trim);
+
static void
print_freed_lock_bug(struct task_struct *curr, const void *mem_from,
const void *mem_to, struct held_lock *hlock)
diff --git a/kernel/locking/lockdep_proc.c b/kernel/locking/lockdep_proc.c
index 1916db9aa46b..95b76047918d 100644
--- a/kernel/locking/lockdep_proc.c
+++ b/kernel/locking/lockdep_proc.c
@@ -32,16 +32,17 @@
* bitmap and max_lock_class_idx.
*/
#define iterate_lock_classes(idx, class) \
- for (idx = 0, class = lock_classes; idx <= max_lock_class_idx; \
- idx++, class++)
+ for (idx = 0, class = idx_to_lock_class(0); \
+ idx <= max_lock_class_idx; \
+ idx++, class = idx_to_lock_class(idx))
static void *l_next(struct seq_file *m, void *v, loff_t *pos)
{
- struct lock_class *class = v;
+ unsigned long idx = ++*pos;
- ++class;
- *pos = class - lock_classes;
- return (*pos > max_lock_class_idx) ? NULL : class;
+ if (idx > max_lock_class_idx)
+ return NULL;
+ return idx_to_lock_class(idx);
}
static void *l_start(struct seq_file *m, loff_t *pos)
@@ -50,7 +51,7 @@ static void *l_start(struct seq_file *m, loff_t *pos)
if (idx > max_lock_class_idx)
return NULL;
- return lock_classes + idx;
+ return idx_to_lock_class(idx);
}
static void l_stop(struct seq_file *m, void *v)
@@ -59,7 +60,7 @@ static void l_stop(struct seq_file *m, void *v)
static void print_name(struct seq_file *m, struct lock_class *class)
{
- char str[KSYM_NAME_LEN];
+ char str[128];
const char *name = class->name;
if (!name) {
@@ -79,9 +80,9 @@ static int l_show(struct seq_file *m, void *v)
struct lock_class *class = v;
struct lock_list *entry;
char usage[LOCK_USAGE_CHARS];
- int idx = class - lock_classes;
+ int idx = class->class_idx;
- if (v == lock_classes)
+ if (idx == 0)
seq_printf(m, "all lock classes:\n");
if (!test_bit(idx, lock_classes_in_use))
@@ -133,7 +134,7 @@ static void *lc_start(struct seq_file *m, loff_t *pos)
if (*pos == 0)
return SEQ_START_TOKEN;
- return lock_chains + (*pos - 1);
+ return idx_to_lock_chain(*pos - 1);
}
static void *lc_next(struct seq_file *m, void *v, loff_t *pos)
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 7/8] lockdep: Expose slab pool telemetry in /proc/lockdep_stats and initcalls
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (5 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 6/8] lockdep: Free unused reservation slabs to buddy allocator at late boot Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 3:58 ` [PATCH 8/8] lockdep: on debug_locks_off or OOM, recycle all slabs to buddy Jim Cromie
2026-08-27 6:46 ` [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Peter Zijlstra
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
Extend lockdep observability to track slab allocation dynamics:
0. In /proc/lockdep_stats, display total reserved slabs, used slabs,
free slabs (headroom), and a per-consumer slab breakdown (lock_classes,
direct deps, dependency chains, chain hlocks, stack traces).
1. In lockdep.c, add lockdep_report_stage() hooks registered across
initcall milestones (core, postcore, arch, subsys, fs, device, late)
to log slab consumption progress throughout kernel initialization.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
kernel/locking/lockdep.c | 47 ++++++++++++++++++++++++++++++++++++++
kernel/locking/lockdep_internals.h | 4 +++-
kernel/locking/lockdep_proc.c | 40 ++++++++++++++++++++++++--------
3 files changed, 80 insertions(+), 11 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 3fb6c07cea36..b6048e7c8b56 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -6992,6 +6992,53 @@ void __init lockdep_init(void)
sizeof(((struct task_struct *)NULL)->held_locks));
}
+static void lockdep_report_stage(const char *domain, const char *stage_name)
+{
+ unsigned int used = lockdep_slabs_used - lockdep_nr_free_slabs;
+ unsigned int class_per_chunk = lock_class_PER_CHUNK;
+ unsigned int entry_per_chunk = LOCKDEP_SLAB_SIZE / sizeof(struct lock_list);
+ unsigned int chain_per_chunk = lock_chain_PER_CHUNK;
+ unsigned int hlock_per_chunk = chain_hlock_PER_CHUNK;
+ unsigned int trace_per_chunk = LOCKDEP_SLAB_SIZE / sizeof(unsigned long);
+
+ unsigned int cl_w = nr_lock_classes / class_per_chunk;
+ unsigned int cl_f = ((nr_lock_classes % class_per_chunk) * 100) / class_per_chunk;
+
+ unsigned int en_w = nr_list_entries / entry_per_chunk;
+ unsigned int en_f = ((nr_list_entries % entry_per_chunk) * 100) / entry_per_chunk;
+
+ unsigned long chains = lock_chain_count();
+ unsigned int ch_w = chains / chain_per_chunk;
+ unsigned int ch_f = ((chains % chain_per_chunk) * 100) / chain_per_chunk;
+
+ unsigned int hlocks = chain_hlocks_used();
+ unsigned int hl_w = hlocks / hlock_per_chunk;
+ unsigned int hl_f = ((hlocks % hlock_per_chunk) * 100) / hlock_per_chunk;
+
+ unsigned int tr_w = nr_stack_trace_entries / trace_per_chunk;
+ unsigned int tr_f = ((nr_stack_trace_entries % trace_per_chunk) * 100) / trace_per_chunk;
+
+ pr_info("lockdep: %-8s [%-9s] : %u/%u slabs : classes=%u.%02u entries=%u.%02u chains=%u.%02u hlocks=%u.%02u trace=%u.%02u\n",
+ domain, stage_name, used, lockdep_nr_slabs,
+ cl_w, cl_f, en_w, en_f, ch_w, ch_f, hl_w, hl_f, tr_w, tr_f);
+}
+
+#define DEFINE_LOCKDEP_LEVEL_REPORT(lvl, name) \
+ static int __init lockdep_report_##name(void) \
+ { \
+ lockdep_report_stage("initcall", #name); \
+ return 0; \
+ } \
+ lvl(lockdep_report_##name)
+
+DEFINE_LOCKDEP_LEVEL_REPORT(core_initcall_sync, core);
+DEFINE_LOCKDEP_LEVEL_REPORT(postcore_initcall_sync, postcore);
+DEFINE_LOCKDEP_LEVEL_REPORT(arch_initcall_sync, arch);
+DEFINE_LOCKDEP_LEVEL_REPORT(subsys_initcall_sync, subsys);
+DEFINE_LOCKDEP_LEVEL_REPORT(fs_initcall_sync, fs);
+DEFINE_LOCKDEP_LEVEL_REPORT(device_initcall_sync, device);
+DEFINE_LOCKDEP_LEVEL_REPORT(late_initcall_sync, late);
+
static int lockdep_shutdown_notify(struct notifier_block *nb,
unsigned long code, void *unused)
{
diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index ccd7343af672..6fb776618ac6 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -223,7 +223,7 @@ extern unsigned int max_lockdep_depth;
extern unsigned int max_bfs_queue_depth;
extern unsigned long max_lock_class_idx;
-DECLARE_2D_RADIX(lock_class, struct lock_class);
+DECLARE_CHUNKED_ARRAY(lock_class, struct lock_class);
extern unsigned long lock_classes_in_use[];
struct lockdep_slab_usage {
@@ -240,6 +240,8 @@ struct lockdep_slab_stats {
struct lockdep_slab_usage usage;
};
+void lockdep_get_slab_stats(struct lockdep_slab_stats *st);
+
unsigned int chain_hlocks_used(void);
unsigned long lock_chain_count(void);
void lockdep_get_slab_stats(struct lockdep_slab_stats *st);
diff --git a/kernel/locking/lockdep_proc.c b/kernel/locking/lockdep_proc.c
index 95b76047918d..76e32fca8c54 100644
--- a/kernel/locking/lockdep_proc.c
+++ b/kernel/locking/lockdep_proc.c
@@ -380,17 +380,37 @@ static int lockdep_stats_show(struct seq_file *m, void *v)
debug_locks);
/*
- * Zapped classes and lockdep data buffers reuse statistics.
+ * Shared Memblock Slab Reservoir Statistics
*/
- seq_puts(m, "\n");
- seq_printf(m, " zapped classes: %11lu\n",
- nr_zapped_classes);
-#ifdef CONFIG_PROVE_LOCKING
- seq_printf(m, " zapped lock chains: %11lu\n",
- nr_zapped_lock_chains);
- seq_printf(m, " large chain blocks: %11u\n",
- nr_large_chain_blocks);
-#endif
+ {
+ struct lockdep_slab_stats st;
+
+ lockdep_get_slab_stats(&st);
+ if (st.total_slabs) {
+ unsigned int free_slabs = st.total_slabs > st.used_slabs ?
+ st.total_slabs - st.used_slabs : 0;
+ unsigned int headroom_pct = (free_slabs * 100) / st.total_slabs;
+
+ seq_puts(m, "\n lockdep memblock slab reservoir:\n");
+ seq_printf(m, " total slabs: %11u (%zu kB)\n",
+ st.total_slabs, (size_t)st.total_slabs * 64);
+ seq_printf(m, " used slabs: %11u (%zu kB)\n",
+ st.used_slabs, (size_t)st.used_slabs * 64);
+ seq_printf(m, " - lock_classes slabs: %11u (%zu kB)\n",
+ st.usage.lock_classes, (size_t)st.usage.lock_classes * 64);
+ seq_printf(m, " - direct deps slabs: %11u (%zu kB)\n",
+ st.usage.direct_deps, (size_t)st.usage.direct_deps * 64);
+ seq_printf(m, " - dependency chains slabs: %11u (%zu kB)\n",
+ st.usage.lock_chains, (size_t)st.usage.lock_chains * 64);
+ seq_printf(m, " - chain hlocks slabs: %11u (%zu kB)\n",
+ st.usage.chain_hlocks, (size_t)st.usage.chain_hlocks * 64);
+ seq_printf(m, " - stack_trace slabs: %11u (%zu kB)\n",
+ st.usage.stack_traces, (size_t)st.usage.stack_traces * 64);
+ seq_printf(m, " free slabs (headroom): %11u (%zu kB, %u%%)\n",
+ free_slabs, (size_t)free_slabs * 64, headroom_pct);
+ }
+ }
+
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH 8/8] lockdep: on debug_locks_off or OOM, recycle all slabs to buddy
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (6 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 7/8] lockdep: Expose slab pool telemetry in /proc/lockdep_stats and initcalls Jim Cromie
@ 2026-08-27 3:58 ` Jim Cromie
2026-08-27 6:46 ` [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Peter Zijlstra
8 siblings, 0 replies; 13+ messages in thread
From: Jim Cromie @ 2026-08-27 3:58 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long
Cc: linux-kernel, Jim Cromie
If lockdep breaks, by assertion failure or for ENOMEM-ish reasons, we
can no longer use the graph-db. Since most of the graph-db is now
allocated from memblock_alloc() slabs, we can release them all back to
buddy, and hope that freeing ~1.5MB will help. (v7.2 has ~10MB tied up
in .bss).
0. Hook debug_locks_off() / print_lockdep_off() via an asynchronous
work item (lockdep_sacrifice_work) to release all held slabs back
to the buddy allocator via free_reserved_page() in process context.
1. Register an OOM notifier (lockdep_oom_nb) at late_initcall. If the
system encounters an out-of-memory emergency, lockdep sacrifices its
entire dynamic slab pool (1..8 MB of physical RAM), reporting freed
pages to the OOM killer to avoid terminating user processes.
2. Clear lockdep_slabs[] pointers and zero lockdep_nr_slabs under
graph_lock to seal off subsequent allocations.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
kernel/locking/lockdep.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index b6048e7c8b56..9f28530b2f1d 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -60,7 +60,9 @@
#include <linux/kasan.h>
#include <linux/mm.h>
#include <linux/memblock.h>
+#include <linux/oom.h>
#include <linux/reboot.h>
+#include <linux/workqueue.h>
#include <asm/sections.h>
@@ -638,6 +640,82 @@ static int verbose(struct lock_class *class)
return 0;
}
+/*
+ * Release all memblock slabs (used and unused) back to the buddy allocator
+ * when lockdep is disabled or during system OOM emergencies.
+ * Must run in process context (workqueue or OOM notifier).
+ */
+static unsigned int lockdep_release_slabs_to_buddy(void)
+{
+ unsigned int freed_slabs = 0;
+ unsigned int nr = lockdep_nr_slabs;
+ unsigned long flags;
+ unsigned int i;
+
+ if (!nr)
+ return 0;
+
+ /* Invalidate table bounds under graph_lock */
+ raw_local_irq_save(flags);
+ if (!graph_lock()) {
+ raw_local_irq_restore(flags);
+ return 0;
+ }
+ lockdep_nr_slabs = 0;
+ lockdep_slabs_used = 0;
+ graph_unlock();
+ raw_local_irq_restore(flags);
+
+ for (i = 0; i < nr; i++) {
+ struct page *page;
+ unsigned long p;
+
+ if (!lockdep_slabs[i])
+ continue;
+
+ page = virt_to_page(lockdep_slabs[i]);
+ for (p = 0; p < (LOCKDEP_SLAB_SIZE >> PAGE_SHIFT); p++)
+ free_reserved_page(page + p);
+
+ lockdep_slabs[i] = NULL;
+ freed_slabs++;
+ }
+
+ if (freed_slabs)
+ pr_info("lockdep: emergency sacrifice — released %u slabs (%u kB) to buddy allocator\n",
+ freed_slabs, (freed_slabs * LOCKDEP_SLAB_SIZE) / 1024);
+
+ return freed_slabs;
+}
+
+static void lockdep_sacrifice_work_fn(struct work_struct *work)
+{
+ lockdep_release_slabs_to_buddy();
+}
+static DECLARE_WORK(lockdep_sacrifice_work, lockdep_sacrifice_work_fn);
+
+static int lockdep_oom_notify(struct notifier_block *self,
+ unsigned long dummy, void *parm)
+{
+ unsigned long *freed = parm;
+ unsigned int freed_slabs;
+
+ if (!lockdep_nr_slabs)
+ return NOTIFY_OK;
+
+ /* Turn off lockdep before sacrificing tables */
+ debug_locks_off();
+ freed_slabs = lockdep_release_slabs_to_buddy();
+ if (freed && freed_slabs)
+ *freed += (freed_slabs * (LOCKDEP_SLAB_SIZE >> PAGE_SHIFT));
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block lockdep_oom_nb = {
+ .notifier_call = lockdep_oom_notify,
+};
+
static void print_lockdep_off(const char *bug_msg)
{
printk(KERN_DEBUG "%s\n", bug_msg);
@@ -645,6 +723,8 @@ static void print_lockdep_off(const char *bug_msg)
#ifdef CONFIG_LOCK_STAT
printk(KERN_DEBUG "Please attach the output of /proc/lock_stat to the bug report\n");
#endif
+ if (system_state >= SYSTEM_RUNNING)
+ schedule_work(&lockdep_sacrifice_work);
}
unsigned long nr_stack_trace_entries;
@@ -7105,6 +7185,7 @@ static int __init lockdep_post_init_trim(void)
lockdep_nr_slabs > used ? ((lockdep_nr_slabs - used) * 100) / used : 0,
freed_slabs, (freed_slabs * LOCKDEP_SLAB_SIZE) / 1024);
+ register_oom_notifier(&lockdep_oom_nb);
register_reboot_notifier(&lockdep_reboot_nb);
return 0;
}
--
2.55.0
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
` (7 preceding siblings ...)
2026-08-27 3:58 ` [PATCH 8/8] lockdep: on debug_locks_off or OOM, recycle all slabs to buddy Jim Cromie
@ 2026-08-27 6:46 ` Peter Zijlstra
2026-08-27 8:54 ` jim.cromie
8 siblings, 1 reply; 13+ messages in thread
From: Peter Zijlstra @ 2026-08-27 6:46 UTC (permalink / raw)
To: Jim Cromie
Cc: Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long, linux-kernel
On Wed, Aug 26, 2026 at 09:58:32PM -0600, Jim Cromie wrote:
> Lockdep cannot rely upon any other subsystem that uses locks, so since
> inception, its graph-db has been stored in static arrays, pinning ~10
> MB in .bss. This is a hardcoded compromise between embedded and
> enterprise hardware.
>
> However, if it acts early, lockdep can pre-allocate a pool of slabs
> from memblock_alloc(), enough for its lifetime of anticipated workloads.
> Then it can allocate them as needed to provide new segments/slabs to
> the graph-db.
>
Why? I really don't understand why. Who cares about this bss stuff.
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool
2026-08-27 6:46 ` [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Peter Zijlstra
@ 2026-08-27 8:54 ` jim.cromie
2026-08-27 9:03 ` Peter Zijlstra
0 siblings, 1 reply; 13+ messages in thread
From: jim.cromie @ 2026-08-27 8:54 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long, linux-kernel
On Thu, Aug 27, 2026 at 12:46 AM Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Wed, Aug 26, 2026 at 09:58:32PM -0600, Jim Cromie wrote:
> > Lockdep cannot rely upon any other subsystem that uses locks, so since
> > inception, its graph-db has been stored in static arrays, pinning ~10
> > MB in .bss. This is a hardcoded compromise between embedded and
> > enterprise hardware.
> >
> > However, if it acts early, lockdep can pre-allocate a pool of slabs
> > from memblock_alloc(), enough for its lifetime of anticipated workloads.
> > Then it can allocate them as needed to provide new segments/slabs to
> > the graph-db.
> >
>
> Why? I really don't understand why. Who cares about this bss stuff.
I thought embedded folk might value 10mb less bss ?
Or have they stopped using lockdep already, for size or other reasons.
How about OOM, when kernel needs mem,
or lockdep debug-off, when the slabs tied up in the graph-db could be returned
do these not tip the scales ?
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool
2026-08-27 8:54 ` jim.cromie
@ 2026-08-27 9:03 ` Peter Zijlstra
2026-08-27 18:40 ` jim.cromie
0 siblings, 1 reply; 13+ messages in thread
From: Peter Zijlstra @ 2026-08-27 9:03 UTC (permalink / raw)
To: jim.cromie
Cc: Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long, linux-kernel
On Thu, Aug 27, 2026 at 02:54:12AM -0600, jim.cromie@gmail.com wrote:
> On Thu, Aug 27, 2026 at 12:46 AM Peter Zijlstra <peterz@infradead.org> wrote:
> >
> > On Wed, Aug 26, 2026 at 09:58:32PM -0600, Jim Cromie wrote:
> > > Lockdep cannot rely upon any other subsystem that uses locks, so since
> > > inception, its graph-db has been stored in static arrays, pinning ~10
> > > MB in .bss. This is a hardcoded compromise between embedded and
> > > enterprise hardware.
> > >
> > > However, if it acts early, lockdep can pre-allocate a pool of slabs
> > > from memblock_alloc(), enough for its lifetime of anticipated workloads.
> > > Then it can allocate them as needed to provide new segments/slabs to
> > > the graph-db.
> > >
> >
> > Why? I really don't understand why. Who cares about this bss stuff.
>
> I thought embedded folk might value 10mb less bss ?
> Or have they stopped using lockdep already, for size or other reasons.
I've never heard complaints from embedded people that this is a problem.
Very few Linux capable machines can't spare 10mb.
This is about kernel development, if you need to develop a driver (only
case you might be tied to specific hardware) just get your developer a
board that has a spare 10mb of memory? Your developer is probably
served by having the most beefy board available anyway.
There was a case on sparc where the bss was a problem because the kernel
image had definite size constraints, but I don't think any 'modern'
systems suffer that particular problem.
> How about OOM, when kernel needs mem,
> or lockdep debug-off, when the slabs tied up in the graph-db could be returned
If you're running into OOM while doing kernel dev you're doing it wrong?
^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool
2026-08-27 9:03 ` Peter Zijlstra
@ 2026-08-27 18:40 ` jim.cromie
0 siblings, 0 replies; 13+ messages in thread
From: jim.cromie @ 2026-08-27 18:40 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Ingo Molnar, Will Deacon, Boqun Feng, Waiman Long, linux-kernel
On Thu, Aug 27, 2026 at 3:03 AM Peter Zijlstra <peterz@infradead.org> wrote:
>
> On Thu, Aug 27, 2026 at 02:54:12AM -0600, jim.cromie@gmail.com wrote:
> > On Thu, Aug 27, 2026 at 12:46 AM Peter Zijlstra <peterz@infradead.org> wrote:
> > >
> > > On Wed, Aug 26, 2026 at 09:58:32PM -0600, Jim Cromie wrote:
> > > > Lockdep cannot rely upon any other subsystem that uses locks, so since
> > > > inception, its graph-db has been stored in static arrays, pinning ~10
> > > > MB in .bss. This is a hardcoded compromise between embedded and
> > > > enterprise hardware.
> > > >
> > > > However, if it acts early, lockdep can pre-allocate a pool of slabs
> > > > from memblock_alloc(), enough for its lifetime of anticipated workloads.
> > > > Then it can allocate them as needed to provide new segments/slabs to
> > > > the graph-db.
> > > >
> > >
> > > Why? I really don't understand why. Who cares about this bss stuff.
> >
> > I thought embedded folk might value 10mb less bss ?
> > Or have they stopped using lockdep already, for size or other reasons.
>
> I've never heard complaints from embedded people that this is a problem.
> Very few Linux capable machines can't spare 10mb.
>
> This is about kernel development, if you need to develop a driver (only
> case you might be tied to specific hardware) just get your developer a
> board that has a spare 10mb of memory? Your developer is probably
> served by having the most beefy board available anyway.
>
> There was a case on sparc where the bss was a problem because the kernel
> image had definite size constraints, but I don't think any 'modern'
> systems suffer that particular problem.
>
Fair points on embedded.
So the value proposition is narrow:
folks hitting "BUG: MAX_LOCKDEP_* too low!"
who cannot build, deploy a kernel with tweaked MAX_LOCKDEP constants.
They're running a distro-debug kernel.
this group might include:
Distro QA, enterprise testers, Syzbot/CI runners.
For these users, lockdep sometimes turns off permanently,
silently invalidating the rest of the test run.
if they had the lockdep_slabs=N knob, they might use it,
and throw more workload on the box without a possible hard-fail looming.
> > How about OOM, when kernel needs mem,
> > or lockdep debug-off, when the slabs tied up in the graph-db could be returned
>
> If you're running into OOM while doing kernel dev you're doing it wrong?
heh - not me, that was the other guy.
it was a "feature", I thought it might help the sale. :-)
thanks
^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-08-27 18:41 UTC | newest]
Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-27 3:58 [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Jim Cromie
2026-08-27 3:58 ` [PATCH 1/8] lockdep: Traverse adjacency lists directly in zap_class() Jim Cromie
2026-08-27 3:58 ` [PATCH 2/8] lockdep: Add chunked array infrastructure and embedded indices Jim Cromie
2026-08-27 3:58 ` [PATCH 3/8] lockdep: Pre-reserve early memblock slab pool for dynamic tables Jim Cromie
2026-08-27 3:58 ` [PATCH 4/8] lockdep: Convert 5 graph arrays to chunked tables backed by slab pool Jim Cromie
2026-08-27 3:58 ` [PATCH 5/8] lockdep: Fast-path power-of-2 tables with shift/mask indexing Jim Cromie
2026-08-27 3:58 ` [PATCH 6/8] lockdep: Free unused reservation slabs to buddy allocator at late boot Jim Cromie
2026-08-27 3:58 ` [PATCH 7/8] lockdep: Expose slab pool telemetry in /proc/lockdep_stats and initcalls Jim Cromie
2026-08-27 3:58 ` [PATCH 8/8] lockdep: on debug_locks_off or OOM, recycle all slabs to buddy Jim Cromie
2026-08-27 6:46 ` [PATCH 0/8] lockdep: change 5 graph-db arrays to AofAs, fill from memblock pool Peter Zijlstra
2026-08-27 8:54 ` jim.cromie
2026-08-27 9:03 ` Peter Zijlstra
2026-08-27 18:40 ` jim.cromie
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®