mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Vlad Poenaru <vlad.wing@gmail.com>
To: Peter Zijlstra <peterz@infradead.org>,
	Ingo Molnar <mingo@redhat.com>, Will Deacon <will@kernel.org>,
	Boqun Feng <boqun@kernel.org>, Waiman Long <longman@redhat.com>
Cc: linux-kernel@vger.kernel.org, Breno Leitao <leitao@debian.org>,
	Eric Dumazet <edumazet@kernel.org>
Subject: [PATCH] lockdep: Walk only in-use lock chains in zap_class()
Date: Mon, 19 Oct 2026 10:06:01 -0700	[thread overview]
Message-ID: <20261019170601.805087-1-vlad.wing@gmail.com> (raw)

remove_class_from_lock_chains() visits every bucket of chainhash_table,
which has MAX_LOCKDEP_CHAINS / 2 buckets however many chains exist. With
CONFIG_LOCKDEP_CHAINS_BITS=20 that is 512K buckets per zapped class,
walked with IRQs disabled under the graph lock, on every
lockdep_unregister_key().

That hurts users that unregister keys often. Almost every qdisc has a
dynamic key, so "tc qdisc replace dev eth0 root mq" on a multi-queue NIC
unregisters one key per TX queue, all under RTNL.

Iterate lock_chains_in_use instead. Scanning the bitmap costs
MAX_LOCKDEP_CHAINS / BITS_PER_LONG words (16K at CHAINS_BITS=20); the
rest of the cost follows the number of chains actually allocated. To
tell hashed chains apart from slots that are allocated but not hashed:

 - unhash with hlist_del_init_rcu(), so a chain waiting for its RCU
   grace period is hlist_unhashed(); RCU readers still follow ->next;
 - initialise ->entry in alloc_lock_chain(), so a slot orphaned when
   alloc_chain_hlocks() fails in add_chain_cache() is unhashed too;
 - have lockdep_reset() release the chain slots along with the hash
   buckets, and forget pending frees of them, instead of leaving them
   marked in use.

On an x86_64 KASAN + PROVE_LOCKING kernel with LOCKDEP_BITS=20 and
LOCKDEP_CHAINS_BITS=20 (64 vCPU KVM guest, mq on a 64-queue netdevsim
device), the chain walk per zap_class() drops from 7.3 ms to 0.09 ms
with 8K chains in use, and from 14.8 ms to 1.2 ms with 214K chains.

Signed-off-by: Vlad Poenaru <vlad.wing@gmail.com>
---
 kernel/locking/lockdep.c | 48 +++++++++++++++++++++++++++++-----------
 1 file changed, 35 insertions(+), 13 deletions(-)

diff --git a/kernel/locking/lockdep.c b/kernel/locking/lockdep.c
index c3dc84a7cef26..3a42811b37d39 100644
--- a/kernel/locking/lockdep.c
+++ b/kernel/locking/lockdep.c
@@ -3761,6 +3761,8 @@ static struct lock_chain *alloc_lock_chain(void)
 	if (unlikely(idx >= ARRAY_SIZE(lock_chains)))
 		return NULL;
 	__set_bit(idx, lock_chains_in_use);
+	/* Unhashed until add_chain_cache() succeeds, see zap_class(). */
+	INIT_HLIST_NODE(&lock_chains[idx].entry);
 	return lock_chains + idx;
 }
 
@@ -6279,15 +6281,22 @@ void lockdep_reset(void)
 	debug_locks = 1;
 	for (i = 0; i < CHAINHASH_SIZE; i++)
 		INIT_HLIST_HEAD(chainhash_table + i);
+#ifdef CONFIG_PROVE_LOCKING
+	/* The chains are gone from the hash, release their slots too. */
+	bitmap_zero(lock_chains_in_use, ARRAY_SIZE(lock_chains));
+	for (i = 0; i < ARRAY_SIZE(delayed_free.pf); i++)
+		bitmap_zero(delayed_free.pf[i].lock_chains_being_freed,
+			    ARRAY_SIZE(lock_chains));
+#endif
 	raw_local_irq_restore(flags);
 }
 
+#ifdef CONFIG_PROVE_LOCKING
 /* Remove a class from a lock chain. Must be called with the graph lock held. */
 static void remove_class_from_lock_chain(struct pending_free *pf,
 					 struct lock_chain *chain,
 					 struct lock_class *class)
 {
-#ifdef CONFIG_PROVE_LOCKING
 	int i;
 
 	for (i = chain->base; i < chain->base + chain->depth; i++) {
@@ -6309,29 +6318,42 @@ static void remove_class_from_lock_chain(struct pending_free *pf,
 	dec_chains(chain->irq_context);
 
 	/*
-	 * Note: calling hlist_del_rcu() from inside a
-	 * hlist_for_each_entry_rcu() loop is safe.
+	 * Use the _init variant so that hlist_unhashed() tells
+	 * remove_class_from_lock_chains() to skip this chain while it waits
+	 * for an RCU grace period. Concurrent RCU readers can still follow
+	 * ->next.
 	 */
-	hlist_del_rcu(&chain->entry);
+	hlist_del_init_rcu(&chain->entry);
 	__set_bit(chain - lock_chains, pf->lock_chains_being_freed);
 	nr_zapped_lock_chains++;
-#endif
 }
+#endif
 
-/* Must be called with the graph lock held. */
+/*
+ * Must be called with the graph lock held.
+ *
+ * Walk the lock_chains_in_use bitmap rather than every bucket of
+ * chainhash_table: the hash table has MAX_LOCKDEP_CHAINS / 2 buckets
+ * whether or not they are in use, so walking it costs the same with a
+ * nearly empty table as with a full one. That walk dominates
+ * lockdep_unregister_key() with large CONFIG_LOCKDEP_CHAINS_BITS values.
+ * Slots that are allocated but not hashed (being freed, or orphaned when
+ * chain_hlocks ran out) are skipped.
+ */
 static void remove_class_from_lock_chains(struct pending_free *pf,
 					  struct lock_class *class)
 {
+#ifdef CONFIG_PROVE_LOCKING
 	struct lock_chain *chain;
-	struct hlist_head *head;
-	int i;
+	unsigned long i;
 
-	for (i = 0; i < ARRAY_SIZE(chainhash_table); i++) {
-		head = chainhash_table + i;
-		hlist_for_each_entry_rcu(chain, head, entry) {
-			remove_class_from_lock_chain(pf, chain, class);
-		}
+	for_each_set_bit(i, lock_chains_in_use, ARRAY_SIZE(lock_chains)) {
+		chain = lock_chains + i;
+		if (hlist_unhashed(&chain->entry))
+			continue;
+		remove_class_from_lock_chain(pf, chain, class);
 	}
+#endif
 }
 
 /*
-- 
2.53.0-Meta


                 reply	other threads:[~2026-10-19 17:06 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

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=20261019170601.805087-1-vlad.wing@gmail.com \
    --to=vlad.wing@gmail.com \
    --cc=boqun@kernel.org \
    --cc=edumazet@kernel.org \
    --cc=leitao@debian.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=will@kernel.org \
    /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

all inboxes | Powered by JetHome®