[PATCH 03/15] mm/hugetlb: preserve reservations when shrinking on HIGHMEM
From: David Hildenbrand (Arm)
Date: Wed Oct 28 2026 - 17:33:17 EST
try_to_free_low() drops the hugetlb_lock while freeing lowmem folios.
set_max_huge_pages() then resumes freeing folios using the
min_count computed before that unlock. Reservations created in between
can consequently lose their backing.
Collect lowmem folios on the caller's existing page_list instead. Keep
the hugetlb_lock held through both selection passes, and free the combined
list at the caller's existing unlock point.
Likely not stable-worthy.
Fixes: 1121828a0c21 ("hugetlb: call update_and_free_page without hugetlb_lock")
Signed-off-by: David Hildenbrand (Arm) <david@xxxxxxxxxx>
---
mm/hugetlb.c | 20 ++++++--------------
1 file changed, 6 insertions(+), 14 deletions(-)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 3a94ea1c9457..a78b2487c20c 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3708,39 +3708,31 @@ static void __init report_hugepages(void)
#ifdef CONFIG_HIGHMEM
static void try_to_free_low(struct hstate *h, unsigned long count,
- nodemask_t *nodes_allowed)
+ nodemask_t *nodes_allowed, struct list_head *page_list)
{
int i;
- LIST_HEAD(page_list);
lockdep_assert_held(&hugetlb_lock);
if (hstate_is_gigantic(h))
return;
- /*
- * Collect pages to be freed on a list, and free after dropping lock
- */
+ /* Collect lowmem pages to free after dropping the lock. */
for_each_node_mask(i, *nodes_allowed) {
struct folio *folio, *next;
struct list_head *freel = &h->hugepage_freelists[i];
list_for_each_entry_safe(folio, next, freel, lru) {
if (count >= persistent_huge_pages(h))
- goto out;
+ return;
if (folio_test_highmem(folio))
continue;
remove_hugetlb_folio(h, folio, false);
- list_add(&folio->lru, &page_list);
+ list_add(&folio->lru, page_list);
}
}
-
-out:
- spin_unlock_irq(&hugetlb_lock);
- update_and_free_pages_bulk(h, &page_list);
- spin_lock_irq(&hugetlb_lock);
}
#else
static inline void try_to_free_low(struct hstate *h, unsigned long count,
- nodemask_t *nodes_allowed)
+ nodemask_t *nodes_allowed, struct list_head *page_list)
{
}
#endif
@@ -3935,7 +3927,7 @@ static int set_max_huge_pages(struct hstate *h, unsigned long count, int nid,
}
min_count = h->resv_huge_pages + persistent_huge_pages(h) - persistent_free_count;
min_count = max(count, min_count);
- try_to_free_low(h, min_count, nodes_allowed);
+ try_to_free_low(h, min_count, nodes_allowed, &page_list);
/*
* Collect pages to be removed on list without dropping lock
--
2.43.0