[PATCH v2 4/5] mm: hugetlb: avoid recursive CMA allocation during migration

From: Qi Zheng

Date: Thu Oct 22 2026 - 03:33:03 EST


From: Qi Zheng <zhengqi.arch@xxxxxxxxxxxxx>

Contiguous allocation can migrate allocated HugeTLB folios or replace free
HugeTLB folios while holding a CMA allocation mutex. Allocating a
replacement through CMA would recurse into that allocation path once
non-gigantic HugeTLB pages can use CMA.

Pass allow_cma through the migration and fresh allocation helpers, and
disable it for MR_CONTIG_RANGE and free folio replacement. Move the CMA
attempt into the common allocation path, retaining the gigantic size
restriction. Honor hugetlb_cma_only even when allow_cma is false.

The existing gigantic migration restriction is unchanged.

Assisted-by: LLM
Signed-off-by: Qi Zheng <zhengqi.arch@xxxxxxxxxxxxx>
---
include/linux/hugetlb.h | 4 +--
mm/hugetlb.c | 69 +++++++++++++++++++++--------------------
mm/mempolicy.c | 2 +-
mm/migrate.c | 3 +-
4 files changed, 40 insertions(+), 38 deletions(-)

diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 5029c72418631..38612f7b3478f 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -736,7 +736,7 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
unsigned long addr, bool cow_from_owner);
struct folio *alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid,
nodemask_t *nmask, gfp_t gfp_mask,
- bool allow_alloc_fallback);
+ bool allow_alloc_fallback, bool allow_cma);
struct folio *alloc_hugetlb_folio_reserve(struct hstate *h, int preferred_nid,
nodemask_t *nmask, gfp_t gfp_mask);

@@ -1172,7 +1172,7 @@ alloc_hugetlb_folio_reserve(struct hstate *h, int preferred_nid,
static inline struct folio *
alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid,
nodemask_t *nmask, gfp_t gfp_mask,
- bool allow_alloc_fallback)
+ bool allow_alloc_fallback, bool allow_cma)
{
return NULL;
}
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 6b08074d11f52..c1a8d00be11ac 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1362,18 +1362,8 @@ static struct folio *dequeue_hugetlb_folio(struct hstate *h, gfp_t gfp_mask,
static struct folio *alloc_gigantic_frozen_folio(int order, gfp_t gfp_mask,
int nid, nodemask_t *nodemask)
{
- struct folio *folio;
-
- folio = hugetlb_cma_alloc_frozen_folio(order, gfp_mask, nid, nodemask);
- if (folio)
- return folio;
-
- if (hugetlb_cma_exclusive_alloc(order))
- return NULL;
-
- folio = (struct folio *)alloc_contig_frozen_pages(1 << order, gfp_mask,
- nid, nodemask);
- return folio;
+ return (struct folio *)alloc_contig_frozen_pages(1 << order, gfp_mask,
+ nid, nodemask);
}
#else /* !CONFIG_ARCH_HAS_GIGANTIC_PAGE || !CONFIG_CONTIG_ALLOC */
static struct folio *alloc_gigantic_frozen_folio(int order, gfp_t gfp_mask, int nid,
@@ -1841,37 +1831,47 @@ static struct folio *alloc_buddy_frozen_folio(int order, gfp_t gfp_mask,

static struct folio *only_alloc_fresh_hugetlb_folio(struct hstate *h,
gfp_t gfp_mask, int nid, nodemask_t *nmask,
- nodemask_t *node_alloc_noretry)
+ nodemask_t *node_alloc_noretry, bool allow_cma)
{
- struct folio *folio;
+ struct folio *folio = NULL;
int order = huge_page_order(h);

if (nid == NUMA_NO_NODE)
nid = numa_mem_id();

- if (order_is_gigantic(order))
- folio = alloc_gigantic_frozen_folio(order, gfp_mask, nid, nmask);
- else
- folio = alloc_buddy_frozen_folio(order, gfp_mask, nid, nmask,
- node_alloc_noretry);
+ if (allow_cma && order_is_gigantic(order))
+ folio = hugetlb_cma_alloc_frozen_folio(order, gfp_mask, nid, nmask);
+ if (!folio) {
+ if (hugetlb_cma_exclusive_alloc(order))
+ return NULL;
+
+ if (order_is_gigantic(order))
+ folio = alloc_gigantic_frozen_folio(order, gfp_mask, nid, nmask);
+ else
+ folio = alloc_buddy_frozen_folio(order, gfp_mask, nid, nmask,
+ node_alloc_noretry);
+ }
+
if (folio)
init_new_hugetlb_folio(folio);
+
return folio;
}

/*
- * Common helper to allocate a fresh hugetlb folio. All specific allocators
- * should use this function to get new hugetlb folio
+ * Common helper to allocate a fresh hugetlb folio. All special allocators
+ * should use this function to get new hugetlb folio.
*
* Note that returned folio is 'frozen': ref count of head page and all tail
* pages is zero, and the accounting must be done in the caller.
*/
static struct folio *alloc_fresh_hugetlb_folio(struct hstate *h,
- gfp_t gfp_mask, int nid, nodemask_t *nmask)
+ gfp_t gfp_mask, int nid, nodemask_t *nmask, bool allow_cma)
{
struct folio *folio;

- folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask, NULL);
+ folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask, NULL,
+ allow_cma);
if (folio)
hugetlb_vmemmap_optimize_folio(h, folio);
return folio;
@@ -1911,7 +1911,7 @@ static struct folio *alloc_pool_huge_folio(struct hstate *h,
struct folio *folio;

folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, node,
- nodes_allowed, node_alloc_noretry);
+ nodes_allowed, node_alloc_noretry, true);
if (folio)
return folio;
}
@@ -2103,7 +2103,7 @@ static struct folio *alloc_surplus_hugetlb_folio(struct hstate *h,
goto out_unlock;
spin_unlock_irq(&hugetlb_lock);

- folio = alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask);
+ folio = alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask, true);
if (!folio)
return NULL;

@@ -2139,14 +2139,14 @@ static struct folio *alloc_surplus_hugetlb_folio(struct hstate *h,
}

static struct folio *alloc_migrate_hugetlb_folio(struct hstate *h, gfp_t gfp_mask,
- int nid, nodemask_t *nmask)
+ int nid, nodemask_t *nmask, bool allow_cma)
{
struct folio *folio;

if (hstate_is_gigantic(h))
return NULL;

- folio = alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask);
+ folio = alloc_fresh_hugetlb_folio(h, gfp_mask, nid, nmask, allow_cma);
if (!folio)
return NULL;

@@ -2219,7 +2219,8 @@ struct folio *alloc_hugetlb_folio_reserve(struct hstate *h, int preferred_nid,

/* folio migration callback function */
struct folio *alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid,
- nodemask_t *nmask, gfp_t gfp_mask, bool allow_alloc_fallback)
+ nodemask_t *nmask, gfp_t gfp_mask, bool allow_alloc_fallback,
+ bool allow_cma)
{
struct folio *folio = NULL;

@@ -2238,7 +2239,7 @@ struct folio *alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid,
gfp_mask |= __GFP_THISNODE;

folio = alloc_migrate_hugetlb_folio(h, gfp_mask, preferred_nid,
- nmask);
+ nmask, allow_cma);
}

if (folio)
@@ -2722,8 +2723,8 @@ static int alloc_and_dissolve_hugetlb_folio(struct folio *old_folio,
if (!new_folio) {
spin_unlock_irq(&hugetlb_lock);
gfp_mask = htlb_alloc_mask(h) | __GFP_THISNODE;
- new_folio = alloc_fresh_hugetlb_folio(h, gfp_mask,
- nid, NULL);
+ new_folio = alloc_fresh_hugetlb_folio(h, gfp_mask, nid,
+ NULL, false);
if (!new_folio)
return -ENOMEM;
goto retry;
@@ -3443,13 +3444,13 @@ static void __init hugetlb_hstate_alloc_pages_onenode(struct hstate *h, int nid)
gfp_t gfp_mask = htlb_alloc_mask(h) | __GFP_THISNODE;

folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, nid,
- &node_states[N_MEMORY], NULL);
+ &node_states[N_MEMORY], NULL, true);
if (!folio && !list_empty(&folio_list) &&
hugetlb_vmemmap_optimizable(h)) {
prep_and_add_allocated_folios(h, &folio_list);
INIT_LIST_HEAD(&folio_list);
folio = only_alloc_fresh_hugetlb_folio(h, gfp_mask, nid,
- &node_states[N_MEMORY], NULL);
+ &node_states[N_MEMORY], NULL, true);
}
if (!folio)
break;
@@ -6357,7 +6358,7 @@ static struct folio *alloc_hugetlb_folio_vma(struct hstate *h,
* consuming a reservation. Set the alloc_fallback to false to indicate
* that breaking the per-node hugetlb pool is not allowed in this case.
*/
- folio = alloc_hugetlb_folio_nodemask(h, node, nodemask, gfp_mask, false);
+ folio = alloc_hugetlb_folio_nodemask(h, node, nodemask, gfp_mask, false, true);
mpol_cond_put(mpol);

return folio;
diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 0884c629670dd..a99ab927cd81a 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -1471,7 +1471,7 @@ static struct folio *alloc_migration_target_by_mpol(struct folio *src,
gfp = htlb_alloc_mask(h);
nodemask = policy_nodemask(gfp, pol, ilx, &nid);
return alloc_hugetlb_folio_nodemask(h, nid, nodemask, gfp,
- htlb_allow_alloc_fallback(MR_MEMPOLICY_MBIND));
+ htlb_allow_alloc_fallback(MR_MEMPOLICY_MBIND), true);
}

if (folio_test_large(src))
diff --git a/mm/migrate.c b/mm/migrate.c
index 7bdcdb57652f8..670556bf5de2c 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -2213,7 +2213,8 @@ struct folio *alloc_migration_target(struct folio *src, unsigned long private)
gfp_mask = htlb_modify_alloc_mask(h, gfp_mask);
return alloc_hugetlb_folio_nodemask(h, nid,
mtc->nmask, gfp_mask,
- htlb_allow_alloc_fallback(mtc->reason));
+ htlb_allow_alloc_fallback(mtc->reason),
+ mtc->reason != MR_CONTIG_RANGE);
}

if (folio_test_large(src)) {
--
2.54.0