linux-mm.kvack.org archive mirror
 help / color / mirror / Atom feed
* [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]()
@ 2026-02-10  4:46 Harry Yoo
  2026-02-10  4:46 ` [PATCH V2 1/2] mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]() Harry Yoo
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Harry Yoo @ 2026-02-10  4:46 UTC (permalink / raw)
  To: Andrew Morton, Vlastimil Babka
  Cc: Christoph Lameter, David Rientjes, Roman Gushchin, Hao Li,
	Alexei Starovoitov, Catalin Marinas, Uladzislau Rezki,
	Suren Baghdasaryan, linux-mm, Harry Yoo

This is separated from the RFC version of "k[v]free_rcu() improvements"
series [1], as these changes are relatively small and beneficial for BPF
because it enables the bpf code to use kfree_rcu() instead of
call_rcu() + kfree_nolock().

Patch 1 allows kfree() and kfree_rcu() to be used with objects that are
allocated from kmalloc_nolock().

Patch 2 is a cleanup that frees a bit used to record whether obj_exts
was allocated using kmalloc_nolock() or kmalloc(), since now both cases
can be freed with kfree().

[1] https://lore.kernel.org/linux-mm/20260206093410.160622-1-harry.yoo@oracle.com

v1 -> v2:
  - Added Catalin's Reviewed-by tag on patch 1, and Alexei's Acked-by tag
    on patch 2 that I forgot to add. Thanks!

  - Fix calling kfree() in free_slab_obj_exts() and alloc_slab_obj_exts()
    when !allow_spin. Pass allow_spin parameter down to free_slab_obj_exts().

V1:  https://lore.kernel.org/linux-mm/20260209121013.50475-1-harry.yoo@oracle.com

Harry Yoo (2):
  mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]()
  mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags

 include/linux/memcontrol.h |  3 +--
 include/linux/rcupdate.h   |  4 ++--
 mm/kmemleak.c              | 22 ++++++++++-----------
 mm/slub.c                  | 39 +++++++++++++++++++++++++++-----------
 4 files changed, 41 insertions(+), 27 deletions(-)


base-commit: f6ed7e47c1fc78e78c9bfeb668b1ad9ba5c58120
-- 
2.43.0



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH V2 1/2] mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]()
  2026-02-10  4:46 [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Harry Yoo
@ 2026-02-10  4:46 ` Harry Yoo
  2026-02-10  4:46 ` [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags Harry Yoo
  2026-02-10 14:08 ` [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Vlastimil Babka
  2 siblings, 0 replies; 6+ messages in thread
From: Harry Yoo @ 2026-02-10  4:46 UTC (permalink / raw)
  To: Andrew Morton, Vlastimil Babka
  Cc: Christoph Lameter, David Rientjes, Roman Gushchin, Hao Li,
	Alexei Starovoitov, Catalin Marinas, Uladzislau Rezki,
	Suren Baghdasaryan, linux-mm, Harry Yoo

Slab objects that are allocated with kmalloc_nolock() must be freed
using kfree_nolock() because only a subset of alloc hooks are called,
since kmalloc_nolock() can't spin on a lock during allocation.

This imposes a limitation: such objects cannot be freed with kfree_rcu(),
forcing users to work around this limitation by calling call_rcu()
with a callback that frees the object using kfree_nolock().

Remove this limitation by teaching kmemleak to gracefully ignore cases
when kmemleak_free() or kmemleak_ignore() is called without a prior
kmemleak_alloc().

Unlike kmemleak, kfence already handles this case, because,
due to its design, only a subset of allocations are served from kfence.

With this change, kfree() and kfree_rcu() can be used to free objects
that are allocated using kmalloc_nolock().

Suggested-by: Alexei Starovoitov <ast@kernel.org>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Reviewed-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Harry Yoo <harry.yoo@oracle.com>
---
 include/linux/rcupdate.h |  4 ++--
 mm/kmemleak.c            | 22 ++++++++++------------
 mm/slub.c                | 21 ++++++++++++++++++++-
 3 files changed, 32 insertions(+), 15 deletions(-)

diff --git a/include/linux/rcupdate.h b/include/linux/rcupdate.h
index c5b30054cd01..72ba681360ad 100644
--- a/include/linux/rcupdate.h
+++ b/include/linux/rcupdate.h
@@ -1076,8 +1076,8 @@ static inline void rcu_read_unlock_migrate(void)
  * either fall back to use of call_rcu() or rearrange the structure to
  * position the rcu_head structure into the first 4096 bytes.
  *
- * The object to be freed can be allocated either by kmalloc() or
- * kmem_cache_alloc().
+ * The object to be freed can be allocated either by kmalloc(),
+ * kmalloc_nolock(), or kmem_cache_alloc().
  *
  * Note that the allowable offset might decrease in the future.
  *
diff --git a/mm/kmemleak.c b/mm/kmemleak.c
index 1ac56ceb29b6..95ad827fcd69 100644
--- a/mm/kmemleak.c
+++ b/mm/kmemleak.c
@@ -837,13 +837,12 @@ static void delete_object_full(unsigned long ptr, unsigned int objflags)
 	struct kmemleak_object *object;
 
 	object = find_and_remove_object(ptr, 0, objflags);
-	if (!object) {
-#ifdef DEBUG
-		kmemleak_warn("Freeing unknown object at 0x%08lx\n",
-			      ptr);
-#endif
+	if (!object)
+		/*
+		 * kmalloc_nolock() -> kfree() calls kmemleak_free()
+		 * without kmemleak_alloc().
+		 */
 		return;
-	}
 	__delete_object(object);
 }
 
@@ -926,13 +925,12 @@ static void paint_ptr(unsigned long ptr, int color, unsigned int objflags)
 	struct kmemleak_object *object;
 
 	object = __find_and_get_object(ptr, 0, objflags);
-	if (!object) {
-		kmemleak_warn("Trying to color unknown object at 0x%08lx as %s\n",
-			      ptr,
-			      (color == KMEMLEAK_GREY) ? "Grey" :
-			      (color == KMEMLEAK_BLACK) ? "Black" : "Unknown");
+	if (!object)
+		/*
+		 * kmalloc_nolock() -> kfree_rcu() calls kmemleak_ignore()
+		 * without kmemleak_alloc().
+		 */
 		return;
-	}
 	paint_it(object, color);
 	put_object(object);
 }
diff --git a/mm/slub.c b/mm/slub.c
index 11a99bd06ac7..63b03fd62ca7 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2584,6 +2584,24 @@ struct rcu_delayed_free {
  * Returns true if freeing of the object can proceed, false if its reuse
  * was delayed by CONFIG_SLUB_RCU_DEBUG or KASAN quarantine, or it was returned
  * to KFENCE.
+ *
+ * For objects allocated via kmalloc_nolock(), only a subset of alloc hooks
+ * are invoked, so some free hooks must handle asymmetric hook calls.
+ *
+ * Alloc hooks called for kmalloc_nolock():
+ * - kmsan_slab_alloc()
+ * - kasan_slab_alloc()
+ * - memcg_slab_post_alloc_hook()
+ * - alloc_tagging_slab_alloc_hook()
+ *
+ * Free hooks that must handle missing corresponding alloc hooks:
+ * - kmemleak_free_recursive()
+ * - kfence_free()
+ *
+ * Free hooks that have no alloc hook counterpart, and thus safe to call:
+ * - debug_check_no_locks_freed()
+ * - debug_check_no_obj_freed()
+ * - __kcsan_check_access()
  */
 static __always_inline
 bool slab_free_hook(struct kmem_cache *s, void *x, bool init,
@@ -6368,7 +6386,7 @@ void kvfree_rcu_cb(struct rcu_head *head)
 
 /**
  * kfree - free previously allocated memory
- * @object: pointer returned by kmalloc() or kmem_cache_alloc()
+ * @object: pointer returned by kmalloc(), kmalloc_nolock(), or kmem_cache_alloc()
  *
  * If @object is NULL, no operation is performed.
  */
@@ -6387,6 +6405,7 @@ void kfree(const void *object)
 	page = virt_to_page(object);
 	slab = page_slab(page);
 	if (!slab) {
+		/* kmalloc_nolock() doesn't support large kmalloc */
 		free_large_kmalloc(page, (void *)object);
 		return;
 	}
-- 
2.43.0



^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags
  2026-02-10  4:46 [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Harry Yoo
  2026-02-10  4:46 ` [PATCH V2 1/2] mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]() Harry Yoo
@ 2026-02-10  4:46 ` Harry Yoo
  2026-02-10  8:57   ` Hao Li
  2026-02-10 14:08 ` [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Vlastimil Babka
  2 siblings, 1 reply; 6+ messages in thread
From: Harry Yoo @ 2026-02-10  4:46 UTC (permalink / raw)
  To: Andrew Morton, Vlastimil Babka
  Cc: Christoph Lameter, David Rientjes, Roman Gushchin, Hao Li,
	Alexei Starovoitov, Catalin Marinas, Uladzislau Rezki,
	Suren Baghdasaryan, linux-mm, Harry Yoo

OBJEXTS_NOSPIN_ALLOC was used to remember whether a slabobj_ext vector
was allocated via kmalloc_nolock(), so that free_slab_obj_exts() could
call kfree_nolock() instead of kfree().

Now that kfree() supports freeing kmalloc_nolock() objects, this flag is
no longer needed. Instead, pass the allow_spin parameter down to
free_slab_obj_exts() to determine whether kfree_nolock() or kfree()
should be called in the free path, and free one bit in
enum objext_flags.

Acked-by: Alexei Starovoitov <ast@kernel.org>
Signed-off-by: Harry Yoo <harry.yoo@oracle.com>
---
 include/linux/memcontrol.h |  3 +--
 mm/slub.c                  | 18 ++++++++----------
 2 files changed, 9 insertions(+), 12 deletions(-)

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 0651865a4564..bb789ec4a2a2 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -359,8 +359,7 @@ enum objext_flags {
 	 * MEMCG_DATA_OBJEXTS.
 	 */
 	OBJEXTS_ALLOC_FAIL = __OBJEXTS_ALLOC_FAIL,
-	/* slabobj_ext vector allocated with kmalloc_nolock() */
-	OBJEXTS_NOSPIN_ALLOC = __FIRST_OBJEXT_FLAG,
+	__OBJEXTS_FLAG_UNUSED = __FIRST_OBJEXT_FLAG,
 	/* the next bit after the last actual flag */
 	__NR_OBJEXTS_FLAGS  = (__FIRST_OBJEXT_FLAG << 1),
 };
diff --git a/mm/slub.c b/mm/slub.c
index 63b03fd62ca7..a73a80b33ff9 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -2189,8 +2189,6 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
 			virt_to_slab(vec)->slab_cache == s);
 
 	new_exts = (unsigned long)vec;
-	if (unlikely(!allow_spin))
-		new_exts |= OBJEXTS_NOSPIN_ALLOC;
 #ifdef CONFIG_MEMCG
 	new_exts |= MEMCG_DATA_OBJEXTS;
 #endif
@@ -2228,7 +2226,7 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
 	return 0;
 }
 
-static inline void free_slab_obj_exts(struct slab *slab)
+static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin)
 {
 	struct slabobj_ext *obj_exts;
 
@@ -2256,10 +2254,10 @@ static inline void free_slab_obj_exts(struct slab *slab)
 	 * the extension for obj_exts is expected to be NULL.
 	 */
 	mark_objexts_empty(obj_exts);
-	if (unlikely(READ_ONCE(slab->obj_exts) & OBJEXTS_NOSPIN_ALLOC))
-		kfree_nolock(obj_exts);
-	else
+	if (allow_spin)
 		kfree(obj_exts);
+	else
+		kfree_nolock(obj_exts);
 	slab->obj_exts = 0;
 }
 
@@ -2323,7 +2321,7 @@ static int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
 	return 0;
 }
 
-static inline void free_slab_obj_exts(struct slab *slab)
+static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin)
 {
 }
 
@@ -3387,14 +3385,14 @@ static __always_inline void account_slab(struct slab *slab, int order,
 }
 
 static __always_inline void unaccount_slab(struct slab *slab, int order,
-					   struct kmem_cache *s)
+					   struct kmem_cache *s, bool allow_spin)
 {
 	/*
 	 * The slab object extensions should now be freed regardless of
 	 * whether mem_alloc_profiling_enabled() or not because profiling
 	 * might have been disabled after slab->obj_exts got allocated.
 	 */
-	free_slab_obj_exts(slab);
+	free_slab_obj_exts(slab, allow_spin);
 
 	mod_node_page_state(slab_pgdat(slab), cache_vmstat_idx(s),
 			    -(PAGE_SIZE << order));
@@ -3498,7 +3496,7 @@ static void __free_slab(struct kmem_cache *s, struct slab *slab, bool allow_spin
 	page->mapping = NULL;
 	__ClearPageSlab(page);
 	mm_account_reclaimed_pages(pages);
-	unaccount_slab(slab, order, s);
+	unaccount_slab(slab, order, s, allow_spin);
 	if (allow_spin)
 		free_frozen_pages(page, order);
 	else
-- 
2.43.0



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags
  2026-02-10  4:46 ` [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags Harry Yoo
@ 2026-02-10  8:57   ` Hao Li
  2026-02-10 10:32     ` Harry Yoo
  0 siblings, 1 reply; 6+ messages in thread
From: Hao Li @ 2026-02-10  8:57 UTC (permalink / raw)
  To: Harry Yoo
  Cc: Andrew Morton, Vlastimil Babka, Christoph Lameter,
	David Rientjes, Roman Gushchin, Alexei Starovoitov,
	Catalin Marinas, Uladzislau Rezki, Suren Baghdasaryan, linux-mm

On Tue, Feb 10, 2026 at 01:46:42PM +0900, Harry Yoo wrote:
> OBJEXTS_NOSPIN_ALLOC was used to remember whether a slabobj_ext vector
> was allocated via kmalloc_nolock(), so that free_slab_obj_exts() could
> call kfree_nolock() instead of kfree().
> 
> Now that kfree() supports freeing kmalloc_nolock() objects, this flag is
> no longer needed. Instead, pass the allow_spin parameter down to
> free_slab_obj_exts() to determine whether kfree_nolock() or kfree()
> should be called in the free path, and free one bit in
> enum objext_flags.
> 
> Acked-by: Alexei Starovoitov <ast@kernel.org>
> Signed-off-by: Harry Yoo <harry.yoo@oracle.com>
> ---
>  include/linux/memcontrol.h |  3 +--
>  mm/slub.c                  | 18 ++++++++----------
>  2 files changed, 9 insertions(+), 12 deletions(-)
> 
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 0651865a4564..bb789ec4a2a2 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -359,8 +359,7 @@ enum objext_flags {
>  	 * MEMCG_DATA_OBJEXTS.
>  	 */
>  	OBJEXTS_ALLOC_FAIL = __OBJEXTS_ALLOC_FAIL,
> -	/* slabobj_ext vector allocated with kmalloc_nolock() */
> -	OBJEXTS_NOSPIN_ALLOC = __FIRST_OBJEXT_FLAG,
> +	__OBJEXTS_FLAG_UNUSED = __FIRST_OBJEXT_FLAG,
>  	/* the next bit after the last actual flag */
>  	__NR_OBJEXTS_FLAGS  = (__FIRST_OBJEXT_FLAG << 1),
>  };
> diff --git a/mm/slub.c b/mm/slub.c
> index 63b03fd62ca7..a73a80b33ff9 100644
> --- a/mm/slub.c
> +++ b/mm/slub.c
> @@ -2189,8 +2189,6 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
>  			virt_to_slab(vec)->slab_cache == s);
>  
>  	new_exts = (unsigned long)vec;
> -	if (unlikely(!allow_spin))
> -		new_exts |= OBJEXTS_NOSPIN_ALLOC;
>  #ifdef CONFIG_MEMCG
>  	new_exts |= MEMCG_DATA_OBJEXTS;
>  #endif
> @@ -2228,7 +2226,7 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
>  	return 0;
>  }
>  
> -static inline void free_slab_obj_exts(struct slab *slab)
> +static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin)
>  {
>  	struct slabobj_ext *obj_exts;
>  
> @@ -2256,10 +2254,10 @@ static inline void free_slab_obj_exts(struct slab *slab)
>  	 * the extension for obj_exts is expected to be NULL.
>  	 */
>  	mark_objexts_empty(obj_exts);
> -	if (unlikely(READ_ONCE(slab->obj_exts) & OBJEXTS_NOSPIN_ALLOC))
> -		kfree_nolock(obj_exts);
> -	else
> +	if (allow_spin)
>  		kfree(obj_exts);
> +	else
> +		kfree_nolock(obj_exts);

Looks good to me.

One small observation from my side: at first glance I briefly wondered if we
could ever allocate in an allow_spin=true context but free in an
allow_spin=false context, potentially resulting in a kmalloc() -> kfree_nolock()
mismatch.

After taking a closer look, the only case where "free_new_slab_nolock() -> ...
-> free_slab_obj_exts()" runs with allow_spin=false is along the "___slab_alloc
-> alloc_single_from_new_slab()/alloc_from_new_slab()" path. In that scenario,
alloc_slab_obj_exts() is also called under allow_spin=false and uses
kmalloc_nolock(), so the allocation/free paths remain consistent. So "kmalloc()
-> kfree_nolock()" case can't happen, and the code looks solid to me!

Reviewed-by: Hao Li <hao.li@linux.dev>

>  	slab->obj_exts = 0;
>  }
>  
> @@ -2323,7 +2321,7 @@ static int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
>  	return 0;
>  }
>  
> -static inline void free_slab_obj_exts(struct slab *slab)
> +static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin)
>  {
>  }
>  
> @@ -3387,14 +3385,14 @@ static __always_inline void account_slab(struct slab *slab, int order,
>  }
>  
>  static __always_inline void unaccount_slab(struct slab *slab, int order,
> -					   struct kmem_cache *s)
> +					   struct kmem_cache *s, bool allow_spin)
>  {
>  	/*
>  	 * The slab object extensions should now be freed regardless of
>  	 * whether mem_alloc_profiling_enabled() or not because profiling
>  	 * might have been disabled after slab->obj_exts got allocated.
>  	 */
> -	free_slab_obj_exts(slab);
> +	free_slab_obj_exts(slab, allow_spin);
>  
>  	mod_node_page_state(slab_pgdat(slab), cache_vmstat_idx(s),
>  			    -(PAGE_SIZE << order));
> @@ -3498,7 +3496,7 @@ static void __free_slab(struct kmem_cache *s, struct slab *slab, bool allow_spin
>  	page->mapping = NULL;
>  	__ClearPageSlab(page);
>  	mm_account_reclaimed_pages(pages);
> -	unaccount_slab(slab, order, s);
> +	unaccount_slab(slab, order, s, allow_spin);
>  	if (allow_spin)
>  		free_frozen_pages(page, order);
>  	else
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags
  2026-02-10  8:57   ` Hao Li
@ 2026-02-10 10:32     ` Harry Yoo
  0 siblings, 0 replies; 6+ messages in thread
From: Harry Yoo @ 2026-02-10 10:32 UTC (permalink / raw)
  To: Hao Li
  Cc: Andrew Morton, Vlastimil Babka, Christoph Lameter,
	David Rientjes, Roman Gushchin, Alexei Starovoitov,
	Catalin Marinas, Uladzislau Rezki, Suren Baghdasaryan, linux-mm

On Tue, Feb 10, 2026 at 04:57:42PM +0800, Hao Li wrote:
> On Tue, Feb 10, 2026 at 01:46:42PM +0900, Harry Yoo wrote:
> > OBJEXTS_NOSPIN_ALLOC was used to remember whether a slabobj_ext vector
> > was allocated via kmalloc_nolock(), so that free_slab_obj_exts() could
> > call kfree_nolock() instead of kfree().
> > 
> > Now that kfree() supports freeing kmalloc_nolock() objects, this flag is
> > no longer needed. Instead, pass the allow_spin parameter down to
> > free_slab_obj_exts() to determine whether kfree_nolock() or kfree()
> > should be called in the free path, and free one bit in
> > enum objext_flags.
> > 
> > Acked-by: Alexei Starovoitov <ast@kernel.org>
> > Signed-off-by: Harry Yoo <harry.yoo@oracle.com>
> > ---
> >  include/linux/memcontrol.h |  3 +--
> >  mm/slub.c                  | 18 ++++++++----------
> >  2 files changed, 9 insertions(+), 12 deletions(-)
> > 
> > diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> > index 0651865a4564..bb789ec4a2a2 100644
> > --- a/include/linux/memcontrol.h
> > +++ b/include/linux/memcontrol.h
> > @@ -359,8 +359,7 @@ enum objext_flags {
> >  	 * MEMCG_DATA_OBJEXTS.
> >  	 */
> >  	OBJEXTS_ALLOC_FAIL = __OBJEXTS_ALLOC_FAIL,
> > -	/* slabobj_ext vector allocated with kmalloc_nolock() */
> > -	OBJEXTS_NOSPIN_ALLOC = __FIRST_OBJEXT_FLAG,
> > +	__OBJEXTS_FLAG_UNUSED = __FIRST_OBJEXT_FLAG,
> >  	/* the next bit after the last actual flag */
> >  	__NR_OBJEXTS_FLAGS  = (__FIRST_OBJEXT_FLAG << 1),
> >  };
> > diff --git a/mm/slub.c b/mm/slub.c
> > index 63b03fd62ca7..a73a80b33ff9 100644
> > --- a/mm/slub.c
> > +++ b/mm/slub.c
> > @@ -2189,8 +2189,6 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
> >  			virt_to_slab(vec)->slab_cache == s);
> >  
> >  	new_exts = (unsigned long)vec;
> > -	if (unlikely(!allow_spin))
> > -		new_exts |= OBJEXTS_NOSPIN_ALLOC;
> >  #ifdef CONFIG_MEMCG
> >  	new_exts |= MEMCG_DATA_OBJEXTS;
> >  #endif
> > @@ -2228,7 +2226,7 @@ int alloc_slab_obj_exts(struct slab *slab, struct kmem_cache *s,
> >  	return 0;
> >  }
> >  
> > -static inline void free_slab_obj_exts(struct slab *slab)
> > +static inline void free_slab_obj_exts(struct slab *slab, bool allow_spin)
> >  {
> >  	struct slabobj_ext *obj_exts;
> >  
> > @@ -2256,10 +2254,10 @@ static inline void free_slab_obj_exts(struct slab *slab)
> >  	 * the extension for obj_exts is expected to be NULL.
> >  	 */
> >  	mark_objexts_empty(obj_exts);
> > -	if (unlikely(READ_ONCE(slab->obj_exts) & OBJEXTS_NOSPIN_ALLOC))
> > -		kfree_nolock(obj_exts);
> > -	else
> > +	if (allow_spin)
> >  		kfree(obj_exts);
> > +	else
> > +		kfree_nolock(obj_exts);
> 
> Looks good to me.
> 
> One small observation from my side: at first glance I briefly wondered if we
> could ever allocate in an allow_spin=true context but free in an
> allow_spin=false context, potentially resulting in a kmalloc() -> kfree_nolock()
> mismatch.

Right, kmalloc() -> kfree_nolock() is not supported and must be avoided.

> After taking a closer look, the only case where "free_new_slab_nolock() -> ...
> -> free_slab_obj_exts()" runs with allow_spin=false is along the "___slab_alloc
> -> alloc_single_from_new_slab()/alloc_from_new_slab()" path.

Right, this is when trylock failed after allocating new slab.

> In that scenario, alloc_slab_obj_exts() is also called under allow_spin=false
> and uses kmalloc_nolock(), so the allocation/free paths remain consistent.
> So "kmalloc() -> kfree_nolock()" case can't happen,
> and the code looks solid to me!

Nice analysis, thanks!

Yeah, for this reason, we should not free slabs that are allocated
with allow_spin == true, when allow_spin is false.

kfree_nolock() avoids this by deferring frees in the slowpath.
If someone is tempated to rework kfree_nolock() slowpath, he or she
should be careful :)

> Reviewed-by: Hao Li <hao.li@linux.dev>

Thanks for review, Hao!

-- 
Cheers,
Harry / Hyeonggon


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]()
  2026-02-10  4:46 [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Harry Yoo
  2026-02-10  4:46 ` [PATCH V2 1/2] mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]() Harry Yoo
  2026-02-10  4:46 ` [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags Harry Yoo
@ 2026-02-10 14:08 ` Vlastimil Babka
  2 siblings, 0 replies; 6+ messages in thread
From: Vlastimil Babka @ 2026-02-10 14:08 UTC (permalink / raw)
  To: Harry Yoo, Andrew Morton
  Cc: Christoph Lameter, David Rientjes, Roman Gushchin, Hao Li,
	Alexei Starovoitov, Catalin Marinas, Uladzislau Rezki,
	Suren Baghdasaryan, linux-mm

On 2/10/26 05:46, Harry Yoo wrote:
> This is separated from the RFC version of "k[v]free_rcu() improvements"
> series [1], as these changes are relatively small and beneficial for BPF
> because it enables the bpf code to use kfree_rcu() instead of
> call_rcu() + kfree_nolock().
> 
> Patch 1 allows kfree() and kfree_rcu() to be used with objects that are
> allocated from kmalloc_nolock().
> 
> Patch 2 is a cleanup that frees a bit used to record whether obj_exts
> was allocated using kmalloc_nolock() or kmalloc(), since now both cases
> can be freed with kfree().
> 
> [1] https://lore.kernel.org/linux-mm/20260206093410.160622-1-harry.yoo@oracle.com

Thanks, added to slab/for-next

> v1 -> v2:
>   - Added Catalin's Reviewed-by tag on patch 1, and Alexei's Acked-by tag
>     on patch 2 that I forgot to add. Thanks!
> 
>   - Fix calling kfree() in free_slab_obj_exts() and alloc_slab_obj_exts()
>     when !allow_spin. Pass allow_spin parameter down to free_slab_obj_exts().
> 
> V1:  https://lore.kernel.org/linux-mm/20260209121013.50475-1-harry.yoo@oracle.com
> 
> Harry Yoo (2):
>   mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]()
>   mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags
> 
>  include/linux/memcontrol.h |  3 +--
>  include/linux/rcupdate.h   |  4 ++--
>  mm/kmemleak.c              | 22 ++++++++++-----------
>  mm/slub.c                  | 39 +++++++++++++++++++++++++++-----------
>  4 files changed, 41 insertions(+), 27 deletions(-)
> 
> 
> base-commit: f6ed7e47c1fc78e78c9bfeb668b1ad9ba5c58120



^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-02-10 14:08 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-10  4:46 [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Harry Yoo
2026-02-10  4:46 ` [PATCH V2 1/2] mm/slab: allow freeing kmalloc_nolock()'d objects using kfree[_rcu]() Harry Yoo
2026-02-10  4:46 ` [PATCH V2 2/2] mm/slab: drop the OBJEXTS_NOSPIN_ALLOC flag from enum objext_flags Harry Yoo
2026-02-10  8:57   ` Hao Li
2026-02-10 10:32     ` Harry Yoo
2026-02-10 14:08 ` [PATCH V2 0/2] mm/slab: support kmalloc_nolock() -> kfree[_rcu]() Vlastimil Babka

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox