From: "Harry Yoo (Oracle)" <harry@kernel.org>
To: Vlastimil Babka <vbabka@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
Hao Li <hao.li@linux.dev>, Christoph Lameter <cl@gentwo.org>,
David Rientjes <rientjes@google.com>,
Roman Gushchin <roman.gushchin@linux.dev>,
Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Puranjay Mohan <puranjay@kernel.org>,
Amery Hung <ameryhung@gmail.com>,
Sebastian Andrzej Siewior <bigeasy@linutronix.de>,
Clark Williams <clrkwllms@kernel.org>,
Steven Rostedt <rostedt@goodmis.org>,
"Paul E. McKenney" <paulmck@kernel.org>,
Frederic Weisbecker <frederic@kernel.org>,
Neeraj Upadhyay <neeraj.upadhyay@kernel.org>,
Joel Fernandes <joelagnelf@nvidia.com>,
Josh Triplett <josh@joshtriplett.org>,
Boqun Feng <boqun@kernel.org>,
Uladzislau Rezki <urezki@gmail.com>,
Mathieu Desnoyers <mathieu.desnoyers@efficios.com>,
Lai Jiangshan <jiangshanlai@gmail.com>,
Zqiang <qiang.zhang@linux.dev>, Pedro Falcato <pfalcato@suse.de>,
Suren Baghdasaryan <surenb@google.com>,
Shengming Hu <hu.shengming@zte.com.cn>
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
linux-rt-devel@lists.linux.dev, rcu@vger.kernel.org,
bpf@vger.kernel.org, "Harry Yoo (Oracle)" <harry@kernel.org>
Subject: [PATCH slab/for-next v4 6/8] mm/slab: introduce struct kvfree_rcu_head for kvfree_rcu batching
Date: Mon, 20 Jul 2026 21:44:35 +0900 [thread overview]
Message-ID: <20260720-kfree_rcu_nolock-v4-6-964e03c41a4e@kernel.org> (raw)
In-Reply-To: <20260720-kfree_rcu_nolock-v4-0-964e03c41a4e@kernel.org>
rcu_head is overkill for kvfree_rcu() because the callback
function is always either kfree(), vfree(), or free_large_kmalloc(),
and thus there is no need for a function pointer.
kvfree_rcu batching reuses the field to store the start address
of an object, however, this is not strictly needed because we can
calculate the start address in the slowpath. For the purpose of
kvfree_rcu batching, it is sufficient to implement a linked list using
a single pointer.
Introduce a new struct called kvfree_rcu_head (the name was suggested
by Vlastimil Babka), which is similar to rcu_head but is only a single
pointer to build a linked list, without a function pointer, when
CONFIG_KVFREE_RCU_BATCHED=y.
When kvfree_rcu is not batched, kvfree_rcu_head is the same size
as rcu_head. Note that shrinking struct kvfree_rcu_head on
CONFIG_KVFREE_RCU_BATCHED=n kernels would inevitably require additional
complexity and also some sort of batching (which defeats the purpose of
the config option) because it cannot fall back to call_rcu().
For now there are no user-visible changes to the API. k[v]free_rcu()
simply casts rcu_head to kvfree_rcu_head. While this does not affect
the API, it allows kfree_rcu_nolock() to reuse kvfree_rcu batching
as a fallback when trylock or sheaf allocation fails.
Stop storing the object pointer in rcu_head.func and instead calculate
the object's start address in kvfree_rcu_list(). Factor out the existing
logic to calculate the start address from kvfree_rcu_cb() to
kvmalloc_obj_start_addr().
Signed-off-by: Harry Yoo (Oracle) <harry@kernel.org>
---
include/linux/rcupdate.h | 10 ++++++----
include/linux/types.h | 8 ++++++++
include/trace/events/rcu.h | 2 +-
mm/slab.h | 28 ++++++++++++++++++++++++++++
mm/slab_common.c | 17 ++++++++---------
mm/slub.c | 40 +++++++++-------------------------------
6 files changed, 60 insertions(+), 45 deletions(-)
diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index 5e95acc33989..ef5bb6981133 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -1098,19 +1098,21 @@ static inline void rcu_read_unlock_migrate(void)
/*
* In mm/slab_common.c, no suitable header to include here.
*/
-void kvfree_call_rcu(struct rcu_head *head, void *ptr);
+void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr);
/*
* The BUILD_BUG_ON() makes sure the rcu_head offset can be handled. See the
* comment of kfree_rcu() for details.
*/
-#define kvfree_rcu_arg_2(ptr, rhf) \
+#define kvfree_rcu_arg_2(ptr, kvrhf) \
do { \
typeof (ptr) ___p = (ptr); \
+ struct kvfree_rcu_head *___head; \
\
if (___p) { \
- BUILD_BUG_ON(offsetof(typeof(*(ptr)), rhf) >= 4096); \
- kvfree_call_rcu(&((___p)->rhf), (void *) (___p)); \
+ BUILD_BUG_ON(offsetof(typeof(*(ptr)), kvrhf) >= 4096); \
+ ___head = (struct kvfree_rcu_head *) &(___p)->kvrhf; \
+ kvfree_call_rcu(___head, (void *) (___p)); \
} \
} while (0)
diff --git a/include/linux/types.h b/include/linux/types.h
index 93166b0b0617..8b0976669d66 100644
--- a/include/linux/types.h
+++ b/include/linux/types.h
@@ -255,6 +255,14 @@ struct callback_head {
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head
+#ifdef CONFIG_KVFREE_RCU_BATCHED
+struct kvfree_rcu_head {
+ struct kvfree_rcu_head *next;
+};
+#else
+#define kvfree_rcu_head rcu_head
+#endif
+
typedef void (*rcu_callback_t)(struct rcu_head *head);
typedef void (*call_rcu_func_t)(struct rcu_head *head, rcu_callback_t func);
diff --git a/include/trace/events/rcu.h b/include/trace/events/rcu.h
index 5fbdabe3faea..a74027126a2f 100644
--- a/include/trace/events/rcu.h
+++ b/include/trace/events/rcu.h
@@ -625,7 +625,7 @@ TRACE_EVENT_RCU(rcu_invoke_callback,
*/
TRACE_EVENT_RCU(rcu_invoke_kvfree_callback,
- TP_PROTO(const char *rcuname, struct rcu_head *rhp, unsigned long offset),
+ TP_PROTO(const char *rcuname, struct kvfree_rcu_head *rhp, unsigned long offset),
TP_ARGS(rcuname, rhp, offset),
diff --git a/mm/slab.h b/mm/slab.h
index fdd293b3efa5..1efeaceb7db3 100644
--- a/mm/slab.h
+++ b/mm/slab.h
@@ -351,6 +351,33 @@ static inline int objs_per_slab(const struct kmem_cache *cache,
return slab->objects;
}
+/* kvfree_rcu_head offset can be only less than page size */
+static inline void *kvmalloc_obj_start_addr(void *head)
+{
+ void *obj = head;
+
+ if (unlikely(is_vmalloc_addr(obj))) {
+ obj = (void *) PAGE_ALIGN_DOWN((unsigned long)obj);
+ } else {
+ struct page *page = virt_to_page(obj);
+ struct slab *slab = page_slab(page);
+
+ if (!slab) {
+ obj = (void *) PAGE_ALIGN_DOWN((unsigned long)obj);
+ } else if (is_kfence_address(obj)) {
+ obj = kfence_object_start(obj);
+ } else {
+ struct kmem_cache *s = slab->slab_cache;
+ unsigned int idx = __obj_to_index(s, slab_address(slab), obj);
+
+ obj = slab_address(slab) + s->size * idx;
+ obj = fixup_red_left(s, obj);
+ }
+ }
+
+ return obj;
+}
+
/*
* State of the slab allocator.
*
@@ -761,6 +788,7 @@ void __check_heap_object(const void *ptr, unsigned long n,
const struct slab *slab, bool to_user);
void deferred_work_barrier(void);
+void defer_kfree_rcu(struct kvfree_rcu_head *head);
static inline bool slub_debug_orig_size(struct kmem_cache *s)
{
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 8c631bf97cd5..c1c32909fa52 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -1346,7 +1346,7 @@ struct kvfree_rcu_bulk_data {
struct kfree_rcu_cpu_work {
struct rcu_work rcu_work;
- struct rcu_head *head_free;
+ struct kvfree_rcu_head *head_free;
struct rcu_gp_oldstate head_free_gp_snap;
struct list_head bulk_head_free[FREE_N_CHANNELS];
struct kfree_rcu_cpu *krcp;
@@ -1382,7 +1382,7 @@ struct kfree_rcu_cpu_work {
struct kfree_rcu_cpu {
// Objects queued on a linked list
// through their rcu_head structures.
- struct rcu_head *head;
+ struct kvfree_rcu_head *head;
unsigned long head_gp_snap;
atomic_t head_count;
@@ -1523,12 +1523,12 @@ kvfree_rcu_bulk(struct kfree_rcu_cpu *krcp,
}
static void
-kvfree_rcu_list(struct rcu_head *head)
+kvfree_rcu_list(struct kvfree_rcu_head *head)
{
- struct rcu_head *next;
+ struct kvfree_rcu_head *next;
for (; head; head = next) {
- void *ptr = (void *) head->func;
+ void *ptr = kvmalloc_obj_start_addr(head);
unsigned long offset = (void *) head - ptr;
next = head->next;
@@ -1552,7 +1552,7 @@ static void kfree_rcu_work(struct work_struct *work)
unsigned long flags;
struct kvfree_rcu_bulk_data *bnode, *n;
struct list_head bulk_head[FREE_N_CHANNELS];
- struct rcu_head *head;
+ struct kvfree_rcu_head *head;
struct kfree_rcu_cpu *krcp;
struct kfree_rcu_cpu_work *krwp;
struct rcu_gp_oldstate head_gp_snap;
@@ -1683,7 +1683,7 @@ kvfree_rcu_drain_ready(struct kfree_rcu_cpu *krcp)
{
struct list_head bulk_ready[FREE_N_CHANNELS];
struct kvfree_rcu_bulk_data *bnode, *n;
- struct rcu_head *head_ready = NULL;
+ struct kvfree_rcu_head *head_ready = NULL;
unsigned long flags;
int i;
@@ -1946,7 +1946,7 @@ void __init kfree_rcu_scheduler_running(void)
* be free'd in workqueue context. This allows us to: batch requests together to
* reduce the number of grace periods during heavy kfree_rcu()/kvfree_rcu() load.
*/
-void kvfree_call_rcu(struct rcu_head *head, void *ptr)
+void kvfree_call_rcu(struct kvfree_rcu_head *head, void *ptr)
{
unsigned long flags;
struct kfree_rcu_cpu *krcp;
@@ -1984,7 +1984,6 @@ void kvfree_call_rcu(struct rcu_head *head, void *ptr)
// Inline if kvfree_rcu(one_arg) call.
goto unlock_return;
- head->func = ptr;
head->next = krcp->head;
WRITE_ONCE(krcp->head, head);
atomic_inc(&krcp->head_count);
diff --git a/mm/slub.c b/mm/slub.c
index deac315d0f23..d0976396d703 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -6699,43 +6699,21 @@ static void free_large_kmalloc(struct page *page, void *object)
*/
void kvfree_rcu_cb(struct rcu_head *head)
{
- void *obj = head;
- struct page *page;
- struct slab *slab;
- struct kmem_cache *s;
- void *slab_addr;
+ void *obj;
+
+ obj = kvmalloc_obj_start_addr(head);
if (is_vmalloc_addr(obj)) {
- obj = (void *) PAGE_ALIGN_DOWN((unsigned long)obj);
vfree(obj);
- return;
- }
-
- page = virt_to_page(obj);
- slab = page_slab(page);
- if (!slab) {
- /*
- * rcu_head offset can be only less than page size so no need to
- * consider allocation order
- */
- obj = (void *) PAGE_ALIGN_DOWN((unsigned long)obj);
- free_large_kmalloc(page, obj);
- return;
- }
-
- s = slab->slab_cache;
- slab_addr = slab_address(slab);
-
- if (is_kfence_address(obj)) {
- obj = kfence_object_start(obj);
} else {
- unsigned int idx = __obj_to_index(s, slab_addr, obj);
+ struct page *page = virt_to_page(obj);
+ struct slab *slab = page_slab(page);
- obj = slab_addr + s->size * idx;
- obj = fixup_red_left(s, obj);
+ if (slab)
+ slab_free(slab->slab_cache, slab, obj, _RET_IP_);
+ else
+ free_large_kmalloc(page, obj);
}
-
- slab_free(s, slab, obj, _RET_IP_);
}
/**
--
2.53.0
next prev parent reply other threads:[~2026-07-20 12:46 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-20 12:44 [PATCH slab/for-next v4 0/8] mm/slab: introduce kfree_rcu_nolock() and improve slub_kunit coverage Harry Yoo (Oracle)
2026-07-20 12:44 ` [PATCH slab/for-next v4 1/8] mm/slab, slub_kunit: register kprobe to trigger _nolock APIs Harry Yoo (Oracle)
2026-07-20 12:56 ` sashiko-bot
2026-07-22 3:27 ` hu.shengming
2026-07-22 7:16 ` Harry Yoo
2026-07-22 8:32 ` hu.shengming
2026-07-20 12:44 ` [PATCH slab/for-next v4 2/8] mm/slab: handle the !allow_spin case in kfree_rcu_sheaf() Harry Yoo (Oracle)
2026-07-20 13:11 ` sashiko-bot
2026-07-21 10:07 ` Vlastimil Babka (SUSE)
2026-07-22 7:28 ` Harry Yoo
2026-07-22 7:37 ` Vlastimil Babka (SUSE)
2026-07-22 7:39 ` Harry Yoo
2026-07-20 12:44 ` [PATCH slab/for-next v4 3/8] mm/slab: use call_rcu() in unknown context if irqs are enabled Harry Yoo (Oracle)
2026-07-20 13:01 ` sashiko-bot
2026-07-20 12:44 ` [PATCH slab/for-next v4 4/8] mm/slab: extend deferred free mechanism to handle rcu sheaves Harry Yoo (Oracle)
2026-07-20 13:03 ` sashiko-bot
2026-07-23 10:27 ` hu.shengming
2026-07-24 11:42 ` Harry Yoo
2026-07-24 14:10 ` hu.shengming
2026-07-20 12:44 ` [PATCH slab/for-next v4 5/8] mm/slab: allow kfree_rcu_sheaf() on PREEMPT_RT Harry Yoo (Oracle)
2026-07-20 12:56 ` sashiko-bot
2026-07-23 5:39 ` Harry Yoo
2026-07-23 9:46 ` Vlastimil Babka (SUSE)
2026-07-23 10:02 ` Harry Yoo
2026-07-21 10:46 ` Vlastimil Babka (SUSE)
2026-07-22 7:41 ` Harry Yoo
2026-07-20 12:44 ` Harry Yoo (Oracle) [this message]
2026-07-20 13:09 ` [PATCH slab/for-next v4 6/8] mm/slab: introduce struct kvfree_rcu_head for kvfree_rcu batching sashiko-bot
2026-07-21 11:06 ` Vlastimil Babka (SUSE)
2026-07-22 7:42 ` Harry Yoo
2026-07-20 12:44 ` [PATCH slab/for-next v4 7/8] mm/slab: introduce kfree_rcu_nolock() Harry Yoo (Oracle)
2026-07-20 13:07 ` sashiko-bot
2026-07-21 13:09 ` Vlastimil Babka (SUSE)
2026-07-22 8:15 ` Harry Yoo
2026-07-22 10:05 ` Vlastimil Babka (SUSE)
2026-07-22 13:03 ` Harry Yoo
2026-07-20 12:44 ` [PATCH slab/for-next v4 8/8] slub_kunit: extend the test for kfree_rcu_nolock() Harry Yoo (Oracle)
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=20260720-kfree_rcu_nolock-v4-6-964e03c41a4e@kernel.org \
--to=harry@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=ameryhung@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bigeasy@linutronix.de \
--cc=boqun@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=cl@gentwo.org \
--cc=clrkwllms@kernel.org \
--cc=frederic@kernel.org \
--cc=hao.li@linux.dev \
--cc=hu.shengming@zte.com.cn \
--cc=jiangshanlai@gmail.com \
--cc=joelagnelf@nvidia.com \
--cc=josh@joshtriplett.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-rt-devel@lists.linux.dev \
--cc=mathieu.desnoyers@efficios.com \
--cc=neeraj.upadhyay@kernel.org \
--cc=paulmck@kernel.org \
--cc=pfalcato@suse.de \
--cc=puranjay@kernel.org \
--cc=qiang.zhang@linux.dev \
--cc=rcu@vger.kernel.org \
--cc=rientjes@google.com \
--cc=roman.gushchin@linux.dev \
--cc=rostedt@goodmis.org \
--cc=surenb@google.com \
--cc=urezki@gmail.com \
--cc=vbabka@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®