From 397e087605526e31c067afd5fba85bd999e5738f Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Wed, 14 Oct 2026 17:08:59 +0300 Subject: [PATCH] mm: stop recalculating min_free_kbytes once set by userspace min_free_kbytes is recalculated on memory hotplug and on THP sysfs writes. The new value comes from calculate_min_free_kbytes() or, while THP is enabled, from set_recommended_min_free_kbytes(). A value written to the vm.min_free_kbytes sysctl is replaced whenever the new value is higher, so a lower value does not stick. This matters most when THP is enabled on configurations with large pageblocks, where the new value can be excessive [1]. For example, with default arm64 pageblock sizes, the THP recommendation can reach 5.5 GiB per eligible zone with 64 KiB pages vs 22 MiB with 4 KiB pages. This keeps a significant amount of memory free, reducing what applications can use and triggering reclaim much earlier. Users can lower vm.min_free_kbytes to avoid this, but the lower value only sticks until the next recalculation. Stop recalculating min_free_kbytes once a value has been written to the sysctl. Systems where the sysctl is never written keep the current behavior. Link: https://lore.kernel.org/20261014140859.1006967-1-noren@nvidia.com Link: https://lore.kernel.org/r/20260716173504.760369-1-noren@nvidia.com/ [1] Signed-off-by: Nimrod Oren Signed-off-by: Andrew Morton Suggested-by: Vlastimil Babka (SUSE) Link: https://lore.kernel.org/r/d202dcc3-d7d7-4e98-a9a5-3137cfbfd041@kernel.org/ Reviewed-by: Vlastimil Babka (SUSE) Reviewed-by: Johannes Weiner Acked-by: Zi Yan Reviewed-by: Lorenzo Stoakes (ARM) Cc: David Hildenbrand Cc: Liam R. Howlett Cc: Mike Rapoport Cc: Suren Baghdasaryan Cc: Michal Hocko Cc: Jonathan Corbet Cc: Shuah Khan Cc: Randy Dunlap Cc: Baolin Wang Cc: Nico Pache Cc: Ryan Roberts Cc: Dev Jain Cc: Barry Song Cc: Lance Yang Cc: Usama Arif Cc: Kiryl Shutsemau Cc: Brendan Jackman Cc: David Laight --- Documentation/admin-guide/sysctl/vm.rst | 7 +++++++ mm/khugepaged.c | 7 +------ mm/page_alloc.c | 6 +----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/Documentation/admin-guide/sysctl/vm.rst b/Documentation/admin-guide/sysctl/vm.rst index 5b318d17aa4b..189fc57dac5f 100644 --- a/Documentation/admin-guide/sysctl/vm.rst +++ b/Documentation/admin-guide/sysctl/vm.rst @@ -539,6 +539,13 @@ watermark[WMARK_MIN] value for each lowmem zone in the system. Each lowmem zone gets a number of reserved free pages based proportionally on its size. +By default, the kernel sets this value based on the amount of lowmem +and recalculates it on memory hotplug. When Transparent Hugepage (THP) +support is enabled through global or per-size controls, the kernel may +also raise it to help keep pageblocks free and reduce fragmentation for +THP allocations. Once a value is written to this file, the kernel no +longer adjusts it automatically. + Some minimal amount of memory is needed to satisfy PF_MEMALLOC allocations; if you set this to lower than 1024KB, your system will become subtly broken, and prone to deadlock under high loads. diff --git a/mm/khugepaged.c b/mm/khugepaged.c index 17e0e0162e90..4b71d010b2af 100644 --- a/mm/khugepaged.c +++ b/mm/khugepaged.c @@ -3144,13 +3144,8 @@ void set_recommended_min_free_kbytes(void) (unsigned long) nr_free_buffer_pages() / 20); recommended_min <<= (PAGE_SHIFT-10); - if (recommended_min > min_free_kbytes) { - if (user_min_free_kbytes >= 0) - pr_info_ratelimited("raising min_free_kbytes from %d to %lu to help transparent hugepage allocations\n", - min_free_kbytes, recommended_min); - + if (user_min_free_kbytes < 0 && recommended_min > min_free_kbytes) min_free_kbytes = recommended_min; - } update_wmarks: setup_per_zone_wmarks(); diff --git a/mm/page_alloc.c b/mm/page_alloc.c index 914e3c9375f2..f14e2ac21787 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -6722,12 +6722,8 @@ void calculate_min_free_kbytes(void) lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10); new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16); - if (new_min_free_kbytes > user_min_free_kbytes) + if (user_min_free_kbytes < 0) min_free_kbytes = clamp(new_min_free_kbytes, 128, 262144); - else - pr_warn_ratelimited("min_free_kbytes is not updated to %d because user defined value %d is preferred\n", - new_min_free_kbytes, user_min_free_kbytes); - } int __meminit init_per_zone_wmark_min(void)