Re: [PATCH mm-hotfixes 1/2] mm/huge_memory: separate out CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic
From: Kiryl Shutsemau
Date: Tue Jul 28 2026 - 11:29:38 EST
On Tue, Jul 28, 2026 at 01:05:44PM +0100, Lorenzo Stoakes (ARM) wrote:
> Rather than mixing the refcounted and non-refcounted
> CONFIG_PERSISTENT_HUGE_ZERO_FOLIO logic, separate the two out cleanly
> so it is clear what happens when this configuration option is set and what
> happens when it is not.
>
> Introduce HUGE_ZERO_UNSET_PFN to abstract the ~0UL assignment, only
> introduce the refcount and shrinker if !CONFIG_PERSISTENT_HUGE_ZERO_FOLIO,
> abstract initialisation and teardown, abstract the huge zero folio
> allocation from refcounting.
>
> Also change a BUG_ON() to WARN_ON_ONCE() while we're at it.
>
> Without this change, the subsequent fix for a subtle race is harder to
> understand thus this is a dependency of it.
>
> Cc: stable@xxxxxxxxxxxxxxx # 6.18.x: dependency of subsequent fix
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@xxxxxxxxxx>
> ---
> mm/huge_memory.c | 159 ++++++++++++++++++++++++++++++++-----------------------
> 1 file changed, 94 insertions(+), 65 deletions(-)
>
> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
> index 032702a4637b..0f60bc82e87a 100644
> --- a/mm/huge_memory.c
> +++ b/mm/huge_memory.c
> @@ -77,9 +77,14 @@ static unsigned long deferred_split_scan(struct shrinker *shrink,
> struct shrink_control *sc);
> static bool split_underused_thp = true;
>
> -static atomic_t huge_zero_refcount;
> +#define HUGE_ZERO_UNSET_PFN (~0UL)
> struct folio *huge_zero_folio __read_mostly;
> -unsigned long huge_zero_pfn __read_mostly = ~0UL;
> +unsigned long huge_zero_pfn __read_mostly = HUGE_ZERO_UNSET_PFN;
> +#ifndef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
> +static atomic_t huge_zero_refcount;
> +static struct shrinker *huge_zero_folio_shrinker;
> +#endif
> +
> unsigned long huge_anon_orders_always __read_mostly;
> unsigned long huge_anon_orders_madvise __read_mostly;
> unsigned long huge_anon_orders_inherit __read_mostly;
> @@ -221,22 +226,58 @@ unsigned long __thp_vma_allowable_orders(struct vm_area_struct *vma,
> return orders;
> }
>
> -static bool get_huge_zero_folio(void)
> +static struct folio *alloc_huge_zero_folio(void)
> {
> struct folio *zero_folio;
> -retry:
> - if (likely(atomic_inc_not_zero(&huge_zero_refcount)))
> - return true;
>
> zero_folio = folio_alloc((GFP_TRANSHUGE | __GFP_ZERO | __GFP_ZEROTAGS) &
> ~__GFP_MOVABLE,
> HPAGE_PMD_ORDER);
> if (!zero_folio) {
> count_vm_event(THP_ZERO_PAGE_ALLOC_FAILED);
> - return false;
> + return NULL;
> + }
> + folio_clear_large_rmappable(zero_folio); /* Explicitly not rmappable. */
> + return zero_folio;
> +}
> +
> +#ifdef CONFIG_PERSISTENT_HUGE_ZERO_FOLIO
> +static int __init huge_zero_init(void)
> +{
> + huge_zero_folio = alloc_huge_zero_folio();
> + if (!huge_zero_folio) {
> + pr_warn("Allocating persistent huge zero folio failed\n");
I am not sure the warn is enough. mm_get_huge_zero_folio() will produce
NULL pointer now without any attempts to allocate again.
Have you considered moving huge_zero_folio to BSS for
CONFIG_PERSISTENT_HUGE_ZERO_FOLIO=y?
> @@ -308,7 +323,46 @@ static unsigned long shrink_huge_zero_folio_scan(struct shrinker *shrink,
> return 0;
> }
>
> -static struct shrinker *huge_zero_folio_shrinker;
> +static int __init huge_zero_init(void)
> +{
> + huge_zero_folio_shrinker = shrinker_alloc(0, "thp-zero");
> + if (!huge_zero_folio_shrinker) {
> + shrinker_free(deferred_split_shrinker);
> + list_lru_destroy(&deferred_split_lru);
Hm. What? Why does huge_zero_init() touches deferred_*?
That's caller business.
> static void __init thp_shrinker_exit(void)
> {
> - shrinker_free(huge_zero_folio_shrinker);
> shrinker_free(deferred_split_shrinker);
> list_lru_destroy(&deferred_split_lru);
> + huge_zero_shrinker_exit();
Any reason behind the reorder?
> }
--
Kiryl Shutsemau / Kirill A. Shutemov