From: Jim Cromie via B4 Relay <devnull+jim.cromie.gmail.com@kernel.org>
To: "Andrew Morton" <akpm@linux-foundation.org>,
"Pablo Neira Ayuso" <pablo@netfilter.org>,
"Florian Westphal" <fw@strlen.de>, "Phil Sutter" <phil@nwl.cc>,
"Alexei Starovoitov" <ast@kernel.org>,
"Daniel Borkmann" <daniel@iogearbox.net>,
"Andrii Nakryiko" <andrii@kernel.org>,
"Eduard Zingerman" <eddyz87@gmail.com>,
"Danilo Krummrich" <dakr@kernel.org>,
"Matthew Brost" <matthew.brost@intel.com>,
"Boris Brezillon" <boris.brezillon@collabora.com>,
"Peter Zijlstra" <peterz@infradead.org>,
"Ingo Molnar" <mingo@redhat.com>, "Will Deacon" <will@kernel.org>,
"Waiman Long" <longman@redhat.com>,
"David S. Miller" <davem@davemloft.net>,
"Eric Dumazet" <edumazet@google.com>,
"Jakub Kicinski" <kuba@kernel.org>,
"Paolo Abeni" <pabeni@redhat.com>,
"Simon Horman" <horms@kernel.org>,
"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
"Martin KaFai Lau" <martin.lau@linux.dev>,
"Song Liu" <song@kernel.org>,
"Yonghong Song" <yonghong.song@linux.dev>,
"Jiri Olsa" <jolsa@kernel.org>,
"Emil Tsalapatis" <emil@etsalapatis.com>,
"John Fastabend" <john.fastabend@gmail.com>,
"Thomas Hellström" <thomas.hellstrom@linux.intel.com>,
"Alice Ryhl" <aliceryhl@google.com>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Boqun Feng" <boqun@kernel.org>, "Boqun Feng" <boqun@kernel.org>
Cc: netfilter-devel@vger.kernel.org, bpf@vger.kernel.org,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
linux-mm@kvack.org, coreteam@netfilter.org,
netdev@vger.kernel.org, Jim Cromie <jim.cromie@gmail.com>
Subject: [PATCH 9/9] locking/lockdep: Migrate and compact boot-time dependency graph from __initdata
Date: Mon, 17 Aug 2026 11:22:23 -0600 [thread overview]
Message-ID: <20260817-folio-pool-v1-v1-9-0c1d230aa3af@gmail.com> (raw)
In-Reply-To: <20260817-folio-pool-v1-v1-0-0c1d230aa3af@gmail.com>
From: Jim Cromie <jim.cromie@gmail.com>
Mark early_list_entries[BOOTSTRAP_LOCKDEP_ENTRIES] and its tracking
bitmap as __initdata, dropping permanent static .bss allocation for
lockdep dependency edges from 1.31 MB to 0 KB.
During early boot before buddy page allocator initialization, lockdep
records initial spinlock dependencies into early_list_entries. At
late_initcall, lockdep_compact_boot_graph() completes the handoff:
0. Pre-allocates a 64 KB folio chunk from lockdep_pool outside
graph_lock to prevent memory allocation recursion with core MM locks.
1. Under raw_local_irq_save() and graph_lock(), walks all_lock_classes,
migrates live bootstrap nodes into contiguous folio slots, and
updates locks_after/locks_before lists via list_replace_rcu().
2. Clears bootstrap pointers to NULL so subsequent runtime allocations
and zap_class() operations bypass the bootstrap address range.
3. Adjusts nr_list_entries to preserve exact live edge accounting.
When free_initmem() executes before userspace launch, the entire 160 KB
bootstrap array is released back to the page allocator.
Live telemetry and graph verification:
- Pre-buddy watermark: 714/4096 bootstrap entries recorded.
- Late initcall migration: 2,656 edges compacted into lockdep_pool.
- Post-boot validation: BFS multi-hop traversals and IRQ constraint
checks navigate the compacted folio topology with zero corruption.
Signed-off-by: Jim Cromie <jim.cromie@gmail.com>
---
Notes:
Live proof of post-compaction validator integrity on v7.2-rc7:
Immediately following free_initmem(), a live multi-hop locking
constraint warning (fs_reclaim vs mmu_notifier) triggered lockdep's
full reporting pipeline.
This report acts as concrete validation of four core invariants:
1. BFS Graph Traversal: __bfs() successfully resolved a 3-hop cycle
across the newly migrated direct-map folio nodes.
2. Payload Integrity: Stack traces stored during early boot were
dereferenced from target->trace and printed without corruption.
3. RCU List Updates: list_replace_rcu() preserved list head pointers
with zero data races or poisoned node dereferences.
4. Zero Stale References: No accesses to reclaimed .init.data memory
occurred during the full graph walk and stack formatting.
---
kernel/locking/lockdep.c | 227 ++++++++++++++++++++++++++++++++++-------------
1 file changed, 164 insertions(+), 63 deletions(-)
diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index 5968a976bf8e..d9541207b342 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -231,9 +231,11 @@ static inline int debug_locks_off_graph_unlock(void)
#define BOOTSTRAP_LOCKDEP_ENTRIES 4096UL
+static struct lock_list early_list_entries[BOOTSTRAP_LOCKDEP_ENTRIES] __initdata;
+static unsigned long early_list_entries_in_use[BITS_TO_LONGS(BOOTSTRAP_LOCKDEP_ENTRIES)] __initdata;
+static struct lock_list *bootstrap_entries __read_mostly = early_list_entries;
+static unsigned long *bootstrap_entries_in_use __read_mostly = early_list_entries_in_use;
unsigned long nr_list_entries;
-static struct lock_list list_entries[BOOTSTRAP_LOCKDEP_ENTRIES];
-static DECLARE_BITMAP(list_entries_in_use, BOOTSTRAP_LOCKDEP_ENTRIES);
/*
* All data structures here are protected by the global debug_lock.
@@ -1055,9 +1057,9 @@ static bool class_lock_list_valid(struct lock_class *c, struct list_head *h)
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",
+ printk(KERN_INFO "class %s: mismatch for lock entry %px; 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 :
@@ -1146,35 +1148,37 @@ 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;
+ if (bootstrap_entries) {
+ /*
+ * Check whether all list entries that are in use occur in a class
+ * lock list.
+ */
+ for_each_set_bit(i, bootstrap_entries_in_use, BOOTSTRAP_LOCKDEP_ENTRIES) {
+ e = bootstrap_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 - bootstrap_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;
+ /*
+ * Check whether all list entries that are not in use do not occur in
+ * a class lock list.
+ */
+ for_each_clear_bit(i, bootstrap_entries_in_use, BOOTSTRAP_LOCKDEP_ENTRIES) {
+ e = bootstrap_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 - bootstrap_entries),
+ e->class && e->class->name ? e->class->name :
+ "(?)",
+ e->links_to && e->links_to->name ?
+ e->links_to->name : "(?)");
+ return false;
+ }
}
}
@@ -1427,30 +1431,32 @@ 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 *p;
- if (idx >= ARRAY_SIZE(list_entries)) {
- struct lock_list *p;
+ if (bootstrap_entries) {
+ int idx = find_first_zero_bit(bootstrap_entries_in_use,
+ BOOTSTRAP_LOCKDEP_ENTRIES);
- p = folio_pool_alloc_type(&lockdep_pool, struct lock_list,
- GFP_ATOMIC);
- if (p) {
+ if (idx < BOOTSTRAP_LOCKDEP_ENTRIES) {
+ __set_bit(idx, bootstrap_entries_in_use);
nr_list_entries++;
- return p;
+ return bootstrap_entries + idx;
}
- if (!debug_locks_off_graph_unlock())
- return NULL;
+ }
- nbcon_cpu_emergency_enter();
- print_lockdep_off("BUG: MAX_LOCKDEP_ENTRIES too low and folio_pool exhausted!");
- dump_stack();
- nbcon_cpu_emergency_exit();
- return NULL;
+ p = folio_pool_alloc_type(&lockdep_pool, struct lock_list, GFP_ATOMIC);
+ if (p) {
+ nr_list_entries++;
+ return p;
}
- nr_list_entries++;
- __set_bit(idx, list_entries_in_use);
- return list_entries + idx;
+ if (!debug_locks_off_graph_unlock())
+ return NULL;
+
+ nbcon_cpu_emergency_enter();
+ print_lockdep_off("BUG: MAX_LOCKDEP_ENTRIES too low and folio_pool exhausted!");
+ dump_stack();
+ nbcon_cpu_emergency_exit();
+ return NULL;
}
/*
@@ -6275,8 +6281,15 @@ static void remove_class_from_lock_chains(struct pending_free *pf,
static inline bool is_bootstrap_entry(const struct lock_list *entry)
{
- return entry >= list_entries &&
- entry < list_entries + ARRAY_SIZE(list_entries);
+ return bootstrap_entries &&
+ entry >= bootstrap_entries &&
+ entry < bootstrap_entries + BOOTSTRAP_LOCKDEP_ENTRIES;
+}
+
+static inline void clear_bootstrap_entry_bit(const struct lock_list *entry)
+{
+ if (is_bootstrap_entry(entry))
+ __clear_bit(entry - bootstrap_entries, bootstrap_entries_in_use);
}
/*
@@ -6295,30 +6308,26 @@ 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) {
- if (is_bootstrap_entry(other))
- __clear_bit(other - list_entries, list_entries_in_use);
+ clear_bootstrap_entry_bit(other);
nr_list_entries--;
list_del_rcu(&other->entry);
break;
}
}
- if (is_bootstrap_entry(entry))
- __clear_bit(entry - list_entries, list_entries_in_use);
+ clear_bootstrap_entry_bit(entry);
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) {
- if (is_bootstrap_entry(other))
- __clear_bit(other - list_entries, list_entries_in_use);
+ clear_bootstrap_entry_bit(other);
nr_list_entries--;
list_del_rcu(&other->entry);
break;
}
}
- if (is_bootstrap_entry(entry))
- __clear_bit(entry - list_entries, list_entries_in_use);
+ clear_bootstrap_entry_bit(entry);
nr_list_entries--;
list_del_rcu(&entry->entry);
}
@@ -6693,6 +6702,9 @@ EXPORT_SYMBOL_GPL(lockdep_unregister_key);
void __init lockdep_init(void)
{
+ bootstrap_entries = early_list_entries;
+ bootstrap_entries_in_use = early_list_entries_in_use;
+
pr_info("Lock dependency validator: Copyright (c) 2006 Red Hat, Inc., Ingo Molnar\n");
pr_info("... MAX_LOCKDEP_SUBCLASSES: %lu\n", MAX_LOCKDEP_SUBCLASSES);
@@ -6707,8 +6719,8 @@ void __init lockdep_init(void)
(sizeof(lock_classes) +
sizeof(lock_classes_in_use) +
sizeof(classhash_table) +
- sizeof(list_entries) +
- sizeof(list_entries_in_use) +
+ sizeof(early_list_entries) +
+ sizeof(early_list_entries_in_use) +
sizeof(chainhash_table) +
sizeof(delayed_free)
#ifdef CONFIG_PROVE_LOCKING
@@ -6733,12 +6745,101 @@ void __init lockdep_init(void)
static int __init lockdep_boot_report(void)
{
pr_info("lockdep: %lu/%lu bootstrap entries used before buddy init, folio_pool active\n",
- min_t(unsigned long, nr_list_entries, ARRAY_SIZE(list_entries)),
- ARRAY_SIZE(list_entries));
+ min_t(unsigned long, nr_list_entries, BOOTSTRAP_LOCKDEP_ENTRIES),
+ BOOTSTRAP_LOCKDEP_ENTRIES);
return 0;
}
core_initcall(lockdep_boot_report);
+static int __init lockdep_compact_boot_graph(void)
+{
+ struct lock_class *class;
+ struct lock_list *entry, *tmp, *new_entry;
+ unsigned long flags;
+ unsigned long migrated = 0;
+
+ if (!debug_locks)
+ return 0;
+
+ /* Pre-allocate 64KB folio chunk outside graph_lock to avoid MM recursion */
+ new_entry = folio_pool_alloc_type(&lockdep_pool, struct lock_list, GFP_KERNEL);
+ if (!new_entry) {
+ pr_err("lockdep: failed to pre-allocate folio chunk for boot compaction\n");
+ return -ENOMEM;
+ }
+
+ raw_local_irq_save(flags);
+ if (!graph_lock()) {
+ raw_local_irq_restore(flags);
+ return 0;
+ }
+
+ list_for_each_entry(class, &all_lock_classes, lock_entry) {
+ list_for_each_entry_safe(entry, tmp, &class->locks_after, entry) {
+ if (is_bootstrap_entry(entry)) {
+ if (new_entry) {
+ *new_entry = *entry;
+ list_replace_rcu(&entry->entry, &new_entry->entry);
+ new_entry = NULL;
+ } else {
+ struct lock_list *slot;
+
+ slot = folio_pool_alloc_type(&lockdep_pool,
+ struct lock_list,
+ GFP_ATOMIC);
+ if (!slot) {
+ debug_locks_off_graph_unlock();
+ raw_local_irq_restore(flags);
+ pr_err("lockdep: folio chunk exhausted during boot compaction\n");
+ return -ENOMEM;
+ }
+ *slot = *entry;
+ list_replace_rcu(&entry->entry, &slot->entry);
+ }
+ migrated++;
+ }
+ }
+
+ list_for_each_entry_safe(entry, tmp, &class->locks_before, entry) {
+ if (is_bootstrap_entry(entry)) {
+ if (new_entry) {
+ *new_entry = *entry;
+ list_replace_rcu(&entry->entry, &new_entry->entry);
+ new_entry = NULL;
+ } else {
+ struct lock_list *slot;
+
+ slot = folio_pool_alloc_type(&lockdep_pool,
+ struct lock_list,
+ GFP_ATOMIC);
+ if (!slot) {
+ debug_locks_off_graph_unlock();
+ raw_local_irq_restore(flags);
+ pr_err("lockdep: folio chunk exhausted during boot compaction\n");
+ return -ENOMEM;
+ }
+ *slot = *entry;
+ list_replace_rcu(&entry->entry, &slot->entry);
+ }
+ migrated++;
+ }
+ }
+ }
+
+ /* Adjust counter so compaction does not double-count migrated nodes */
+ nr_list_entries -= migrated;
+
+ bootstrap_entries = NULL;
+ bootstrap_entries_in_use = NULL;
+ graph_unlock();
+ raw_local_irq_restore(flags);
+
+ pr_info("lockdep: compacted %lu boot entries into folio_pool, freeing bootstrap memory\n",
+ migrated);
+ return 0;
+}
+late_initcall(lockdep_compact_boot_graph);
+
static void
print_freed_lock_bug(struct task_struct *curr, const void *mem_from,
const void *mem_to, struct held_lock *hlock)
--
2.55.0
next prev parent reply other threads:[~2026-08-17 17:22 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-17 17:22 [PATCH 0/9] lib/folio_pool: Direct-Map Large Folio Pool & Scratchpad bump allocators Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 1/9] lib/folio_pool: Introduce " Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 2/9] netfilter/nf_tables: Add folio_scratchpad collector to struct nftables_pernet Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 3/9] bpf/verifier: Route verifier stack state node allocations to folio_pool Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 4/9] drm/gpuvm: Route gpuva_op allocations to folio_scratchpad Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 5/9] bpf/syscall: Route generic_map_update_batch key/value " Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 6/9] locking/lockdep: Fallback to folio_pool in alloc_list_entry when static pool is full Jim Cromie via B4 Relay
2026-08-17 21:01 ` Peter Zijlstra
2026-08-17 17:22 ` [PATCH 7/9] locking/lockdep: Traverse adjacency lists directly in zap_class() Jim Cromie via B4 Relay
2026-08-17 17:22 ` [PATCH 8/9] locking/lockdep: Shrink static list_entries array to early bootstrap buffer Jim Cromie via B4 Relay
2026-08-17 17:22 ` Jim Cromie via B4 Relay [this message]
2026-08-17 18:17 ` [PATCH 0/9] lib/folio_pool: Direct-Map Large Folio Pool & Scratchpad bump allocators David Hildenbrand (Arm)
2026-08-17 18:34 ` Matthew Wilcox
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260817-folio-pool-v1-v1-9-0c1d230aa3af@gmail.com \
--to=devnull+jim.cromie.gmail.com@kernel.org \
--cc=airlied@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=aliceryhl@google.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=boqun@kernel.org \
--cc=boris.brezillon@collabora.com \
--cc=bpf@vger.kernel.org \
--cc=coreteam@netfilter.org \
--cc=dakr@kernel.org \
--cc=daniel@iogearbox.net \
--cc=davem@davemloft.net \
--cc=dri-devel@lists.freedesktop.org \
--cc=eddyz87@gmail.com \
--cc=edumazet@google.com \
--cc=emil@etsalapatis.com \
--cc=fw@strlen.de \
--cc=horms@kernel.org \
--cc=jim.cromie@gmail.com \
--cc=john.fastabend@gmail.com \
--cc=jolsa@kernel.org \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=longman@redhat.com \
--cc=maarten.lankhorst@linux.intel.com \
--cc=martin.lau@linux.dev \
--cc=matthew.brost@intel.com \
--cc=memxor@gmail.com \
--cc=mingo@redhat.com \
--cc=mripard@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pablo@netfilter.org \
--cc=peterz@infradead.org \
--cc=phil@nwl.cc \
--cc=simona@ffwll.ch \
--cc=song@kernel.org \
--cc=thomas.hellstrom@linux.intel.com \
--cc=tzimmermann@suse.de \
--cc=will@kernel.org \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox