[RFC PATCH 2/3] mm/slub: count kvmalloc() allocations forced to vmalloc()
From: Daniil Tatianin <hidden>
Date: 2026-08-05 09:50:31
Also in:
linux-mm, lkml
Subsystem:
documentation, memory management, memory management - core, slab allocator, the rest · Maintainers:
Jonathan Corbet, Andrew Morton, David Hildenbrand, Vlastimil Babka, Harry Yoo, Linus Torvalds
vm.kvmalloc_max_contig_order changes allocation behavior silently. There is no way to tell whether a given value is doing anything at all, or how much of the workload it affects, short of tracing kvmalloc() by hand, which makes it hard to pick a value and hard to notice when a workload starts running into it. Add a kvmalloc_forced_vmalloc counter to /proc/vmstat, incremented for every allocation that skipped kmalloc() because of the limit. Requests above KMALLOC_MAX_SIZE are skipped by the limit like any other, since the attempt would fail regardless, but they are not counted, as they would have reached vmalloc() either way. What is left is the number of allocations the limit actually diverted, which is what is needed to tell whether it is set sensibly. Note that the counter stays at zero while the sysctl is left at its default, since no allocation is denied in that case. Determining what to set the limit to in the first place still requires tracing kvmalloc() directly, or setting the limit on a canary first and reading the counter there. Signed-off-by: Daniil Tatianin <redacted> --- Documentation/admin-guide/sysctl/vm.rst | 5 +++++ include/linux/vm_event_item.h | 3 +++ mm/Kconfig | 2 ++ mm/slub.c | 15 ++++++++++++++- mm/vmstat.c | 3 +++ 5 files changed, 27 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst
index bc669052c50b..4581fb68c974 100644
--- a/Documentation/admin-guide/sysctl/vm.rst
+++ b/Documentation/admin-guide/sysctl/vm.rst@@ -394,6 +394,11 @@ the order of KMALLOC_MAX_CACHE_SIZE up to MAX_PAGE_ORDER. Requests above KMALLOC_MAX_SIZE end up in vmalloc() whatever this is set to, since kmalloc() cannot serve them at all. +The kvmalloc_forced_vmalloc counter in /proc/vmstat counts the allocations +that were routed to vmalloc() because of this limit. Requests that +kmalloc() could not have served anyway are not counted, so the counter only +reflects allocations the limit itself diverted. + legacy_va_layout ================
diff --git a/include/linux/vm_event_item.h b/include/linux/vm_event_item.h
index 03fe95f5a020..c060c06ca2d2 100644
--- a/include/linux/vm_event_item.h
+++ b/include/linux/vm_event_item.h@@ -145,6 +145,9 @@ enum vm_event_item { PGPGIN, PGPGOUT, PSWPIN, PSWPOUT, DIRECT_MAP_LEVEL2_COLLAPSE, DIRECT_MAP_LEVEL3_COLLAPSE, #endif +#ifdef CONFIG_KVMALLOC_ORDER_LIMIT + KVMALLOC_FORCED_VMALLOC, +#endif #ifdef CONFIG_PER_VMA_LOCK_STATS VMA_LOCK_SUCCESS, VMA_LOCK_ABORT,
diff --git a/mm/Kconfig b/mm/Kconfig
index 9b1fcabb6d8f..0ca7fc5321cf 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig@@ -262,6 +262,8 @@ config KVMALLOC_ORDER_LIMIT This enables the vm.kvmalloc_max_contig_order sysctl, which caps the allocation order kvmalloc() is willing to ask the page allocator for. Larger requests are routed to vmalloc() directly. + It also adds the kvmalloc_forced_vmalloc counter to /proc/vmstat, + which tells how many allocations were routed this way. The default value of the sysctl preserves the existing behavior, so saying Y here only makes the tunable available.
diff --git a/mm/slub.c b/mm/slub.c
index 0a9910602cec..7287949ea247 100644
--- a/mm/slub.c
+++ b/mm/slub.c@@ -51,6 +51,7 @@ #include <linux/kprobes.h> #include <linux/debugfs.h> #include <linux/sysctl.h> +#include <linux/vmstat.h> #include <trace/events/kmem.h> #include "internal.h"
@@ -6916,7 +6917,19 @@ static bool kvmalloc_order_denied(size_t size) if (likely(sysctl_kvmalloc_max_contig_order >= MAX_PAGE_ORDER)) return false; - return get_order(size) > sysctl_kvmalloc_max_contig_order; + if (get_order(size) <= sysctl_kvmalloc_max_contig_order) + return false; + + /* + * kmalloc() cannot serve anything above KMALLOC_MAX_SIZE, so such a + * request would have reached vmalloc() with or without the limit. + * Skipping the doomed attempt is still worthwhile, but the limit did + * not divert anything, so do not account it. + */ + if (size <= KMALLOC_MAX_SIZE) + count_vm_event(KVMALLOC_FORCED_VMALLOC); + + return true; } static const struct ctl_table kvmalloc_sysctl_table[] = {
diff --git a/mm/vmstat.c b/mm/vmstat.c
index f534972f517d..e7139c0947ce 100644
--- a/mm/vmstat.c
+++ b/mm/vmstat.c@@ -1459,6 +1459,9 @@ const char * const vmstat_text[] = { [I(DIRECT_MAP_LEVEL2_COLLAPSE)] = "direct_map_level2_collapses", [I(DIRECT_MAP_LEVEL3_COLLAPSE)] = "direct_map_level3_collapses", #endif +#ifdef CONFIG_KVMALLOC_ORDER_LIMIT + [I(KVMALLOC_FORCED_VMALLOC)] = "kvmalloc_forced_vmalloc", +#endif #ifdef CONFIG_PER_VMA_LOCK_STATS [I(VMA_LOCK_SUCCESS)] = "vma_lock_success", [I(VMA_LOCK_ABORT)] = "vma_lock_abort",