[RFC PATCH v3 02/10] static kmem_cache instances for core caches: setup primitives
From: Al Viro
Date: Sat Jun 13 2026 - 01:14:20 EST
Teach the real constructor of kmem_cache needs to deal with preallocated
instances and provide wrappers parallel to kmem_cache_create() and friends.
That turns out to be easy - we already pass an obscene amount of optional
arguments via struct kmem_cache_args, so we can stash the pointer to
preallocated instance in there.
* add struct kmem_cache_args.preallocated - a pointer to preallocated
struct kmem_cache instance into kmem_cache_args. If it's non-NULL,
__kmem_cache_create_args() will set the supplied instance up instead
of allocating a new one.
* new struct kmem_cache.flags bit - SLAB_PREALLOCATED. Set by
__kmem_cache_create_args() when it's asked to use a preallocated instance.
* create_cache(): do allocation (and, in case of failure, freeing) of
struct kmem_cache instance only if no preallocated one has been supplied.
* __kmem_cache_alias(): don't bother with aliases when setting a preallocated
instance up - we want this one and no other, TYVM... Note that such instance
may very well be mergeable - later kmem_cache_create() might decide to return
an alias for it.
* sysfs_slab_add() should treat all preallocated instances as self-named -
no "unique" (== size-and-flags-derived) names for those, any symlinks
from possible future aliases will use the cache's name as target. Think
what happens if two preallocated kmem_cache instances are set up to have
identical sizes and flags - "unique" names will be anything but. Since
the preallocated instance won't go away before possible future aliases,
there's no problem with using its proper name.
* add new wrappers for __kmem_cache_create_args(): kmem_cache_setup(),
kmem_cache_setup_usercopy(), KMEM_CACHE_SETUP(), KMEM_CACHE_SETUP_USERCOPY(),
corresponding to kmem_cache_create(), kmem_cache_create_usercopy(),
KMEM_CACHE() and KMEM_CACHE_USERCOPY() resp. A pointer to preallocated
instance is passed as the first argument, followed by the arguments
one would pass to corresponding kmem_cache constructor.
That covers the instances that never get destroyed. Quite a few
fall into that category, but there's a major exception - anything in
modules must be destroyed before the module gets removed. For example,
filesystems that have their inodes allocated from a private kmem_cache
can't make use of that technics for their inode allocations, etc.
It's not that hard to deal with, but for now let's just ban
including slab-static.h from modules.
Signed-off-by: Al Viro <viro@xxxxxxxxxxxxxxxxxx>
---
include/linux/slab-static.h | 53 +++++++++++++++++++++++++++++++++++++
include/linux/slab.h | 4 +++
mm/slab_common.c | 33 ++++++++++++-----------
mm/slub.c | 20 +++++++-------
4 files changed, 84 insertions(+), 26 deletions(-)
diff --git a/include/linux/slab-static.h b/include/linux/slab-static.h
index 07aca67facee..007fc0bd4e8c 100644
--- a/include/linux/slab-static.h
+++ b/include/linux/slab-static.h
@@ -5,9 +5,62 @@
#include <linux/init.h>
#include <generated/kmem_cache_size.h>
+#ifdef MODULE
+#error "can't use that in modules"
+#endif
+
/* same size and alignment as struct kmem_cache: */
struct kmem_cache_opaque {
unsigned char opaque[KMEM_CACHE_SIZE];
} __aligned(KMEM_CACHE_ALIGN);
+#define __KMEM_CACHE_SETUP(cache, name, size, flags, ...) \
+ __kmem_cache_create_args((name), (size), \
+ &(struct kmem_cache_args) { \
+ .preallocated = (cache), \
+ __VA_ARGS__}, (flags))
+
+static inline int
+kmem_cache_setup_usercopy(struct kmem_cache *s,
+ const char *name, unsigned int size,
+ unsigned int align, slab_flags_t flags,
+ unsigned int useroffset, unsigned int usersize,
+ void (*ctor)(void *))
+{
+ struct kmem_cache *res;
+ res = __KMEM_CACHE_SETUP(s, name, size, flags,
+ .align = align,
+ .ctor = ctor,
+ .useroffset = useroffset,
+ .usersize = usersize);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+ return 0;
+}
+
+static inline int
+kmem_cache_setup(struct kmem_cache *s,
+ const char *name, unsigned int size,
+ unsigned int align, slab_flags_t flags,
+ void (*ctor)(void *))
+{
+ struct kmem_cache *res;
+ res = __KMEM_CACHE_SETUP(s, name, size, flags,
+ .align = align,
+ .ctor = ctor);
+ if (IS_ERR(res))
+ return PTR_ERR(res);
+ return 0;
+}
+
+#define KMEM_CACHE_SETUP(s, __struct, __flags) \
+ __KMEM_CACHE_SETUP((s), #__struct, sizeof(struct __struct), (__flags), \
+ .align = __alignof__(struct __struct))
+
+#define KMEM_CACHE_SETUP_USERCOPY(s, __struct, __flags, __field) \
+ __KMEM_CACHE_SETUP((s), #__struct, sizeof(struct __struct), (__flags), \
+ .align = __alignof__(struct __struct), \
+ .useroffset = offsetof(struct __struct, __field), \
+ .usersize = sizeof_field(struct __struct, __field))
+
#endif
diff --git a/include/linux/slab.h b/include/linux/slab.h
index a43d31eec06c..ec68aabf98df 100644
--- a/include/linux/slab.h
+++ b/include/linux/slab.h
@@ -62,6 +62,7 @@ enum _slab_flag_bits {
#if defined(CONFIG_SLAB_OBJ_EXT) && defined(CONFIG_64BIT)
_SLAB_OBJ_EXT_IN_OBJ,
#endif
+ _SLAB_PREALLOCATED,
_SLAB_FLAGS_LAST_BIT
};
@@ -248,6 +249,8 @@ enum _slab_flag_bits {
#define SLAB_OBJ_EXT_IN_OBJ __SLAB_FLAG_UNUSED
#endif
+#define SLAB_PREALLOCATED __SLAB_FLAG_BIT(_SLAB_PREALLOCATED)
+
/*
* ZERO_SIZE_PTR will be returned for zero sized kmalloc requests.
*
@@ -378,6 +381,7 @@ struct kmem_cache_args {
* %0 means no sheaves will be created.
*/
unsigned int sheaf_capacity;
+ struct kmem_cache *preallocated;
};
struct kmem_cache *__kmem_cache_create_args(const char *name,
diff --git a/mm/slab_common.c b/mm/slab_common.c
index 8b661fff5eed..5b6aaa96d68d 100644
--- a/mm/slab_common.c
+++ b/mm/slab_common.c
@@ -234,33 +234,30 @@ static struct kmem_cache *create_cache(const char *name,
struct kmem_cache_args *args,
slab_flags_t flags)
{
- struct kmem_cache *s;
+ struct kmem_cache *s = args->preallocated;
int err;
/* If a custom freelist pointer is requested make sure it's sane. */
- err = -EINVAL;
if (args->use_freeptr_offset &&
(args->freeptr_offset >= object_size ||
(!(flags & SLAB_TYPESAFE_BY_RCU) && !args->ctor) ||
!IS_ALIGNED(args->freeptr_offset, __alignof__(freeptr_t))))
- goto out;
+ return ERR_PTR(-EINVAL);
- err = -ENOMEM;
- s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
- if (!s)
- goto out;
+ if (!s) {
+ s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
+ if (!s)
+ return ERR_PTR(-ENOMEM);
+ }
err = do_kmem_cache_create(s, name, object_size, args, flags);
- if (err)
- goto out_free_cache;
-
+ if (unlikely(err)) {
+ if (!args->preallocated)
+ kmem_cache_free(kmem_cache, s);
+ return ERR_PTR(err);
+ }
s->refcount = 1;
list_add(&s->list, &slab_caches);
return s;
-
-out_free_cache:
- kmem_cache_free(kmem_cache, s);
-out:
- return ERR_PTR(err);
}
static struct kmem_cache *
@@ -269,6 +266,9 @@ __kmem_cache_alias(const char *name, unsigned int size, slab_flags_t flags,
{
struct kmem_cache *s;
+ if (flags & SLAB_PREALLOCATED) // no aliases - we are using this one
+ return NULL;
+
s = find_mergeable(size, flags, name, args);
if (s) {
if (sysfs_slab_alias(s, name))
@@ -366,6 +366,9 @@ struct kmem_cache *__kmem_cache_create_args(const char *name,
object_size - args->usersize < args->useroffset))
args->usersize = args->useroffset = 0;
+ if (args->preallocated)
+ flags |= SLAB_PREALLOCATED;
+
s = __kmem_cache_alias(name, object_size, flags, args);
if (s)
goto out_unlock;
diff --git a/mm/slub.c b/mm/slub.c
index c0765173911d..eee68e0ad7ed 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -9593,18 +9593,16 @@ static int sysfs_slab_add(struct kmem_cache *s)
int err;
const char *name;
struct kset *kset = cache_kset(s);
- int unmergeable = slab_unmergeable(s);
+ bool no_symlink = slab_unmergeable(s);
- if (!unmergeable && disable_higher_order_debug &&
+ if (s->flags & SLAB_PREALLOCATED)
+ no_symlink = true;
+
+ if (!no_symlink && disable_higher_order_debug &&
(slub_debug & DEBUG_METADATA_FLAGS))
- unmergeable = 1;
+ no_symlink = true;
- if (unmergeable) {
- /*
- * Slabcache can never be merged so we can use the name proper.
- * This is typically the case for debug situations. In that
- * case we can catch duplicate names easily.
- */
+ if (no_symlink) {
sysfs_remove_link(&slab_kset->kobj, s->name);
name = s->name;
} else {
@@ -9622,12 +9620,12 @@ static int sysfs_slab_add(struct kmem_cache *s)
if (err)
goto out;
- if (!unmergeable) {
+ if (!no_symlink) {
/* Setup first alias */
sysfs_slab_alias(s, s->name);
}
out:
- if (!unmergeable)
+ if (!no_symlink)
kfree(name);
return err;
}
--
2.47.3