* [PATCH 00/15] mm/hugetlb: various fixes
@ 2026-10-28 20:13 David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts David Hildenbrand (Arm)
` (2 more replies)
0 siblings, 3 replies; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-28 20:13 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Naoya Horiguchi, Andrew Morton,
Mel Gorman, Andy Whitcroft, Miaohe Lin, Michal Hocko,
Rik van Riel, Hugh Dickins, Baolin Wang, Vivek Kasireddy,
Dave Airlie, Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann
Cc: linux-mm, linux-kernel, Gerd Hoffmann, linux-doc, linux-arch,
linux-s390, David Hildenbrand (Arm), luojiajun, Josef Bacik,
stable, Chris Mason, Daehyeon Ko, stable
These fixes almost finished me.
Oh. My. God, hugetlb, what have you become.
And I am not talking about good old hugetlb. I am talking about all of the
things we slapped on top of it over the years, including MAP_PRIVATE,
reservation, UFFD, accounting and much more. I mean, I kept complaining
about all that in the past, but I just had an 2 day-long reminder from
hell.
And don't get me started on some of the proposed AI fixes. Dear lord.
There is a reason why I am the author on most of these fixes. And I
try to not swear on the mailing list.
Hey, at least huge PMD sharing was not involved this time, great
success!
Anyhow, nothing here is super urgent IMHO. Just a lot of unpleasant
things and corner cases. Most of the issues reported by Anthropic, some
found by me while poking at things.
Two bigger changes as RFC. One of them documents some remaining
problems, one of both being a hard nut to crack.
I have some more complicated lower priority, hard to solve things
leftover on the hugetlb front (besides some fixes that are already on the
list), maybe I'll report them to hugetlb maintainers to share some of
the "fun". ;)
I threw some tests at it, but there are just too many combinations and
corner cases.
Next, likely we want a kernel config to disable MAP_PRIVATE and start
deprecating it. It's simply unbearable.
I won't pretend that I understood all the details of reservation and
accounting, even rewriting some of the comments took me ages. If any
fixes here go into the wrong direction, I'm happy if someone wants to
take over this series, I tried to get the heavy lifting done.
I guess, have fun reviewing? :)
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
Chris Mason (3):
hugetlbfs: do not grow a sealed file in mmap()
mm/hugetlb: keep surplus folios that are reserved
mm/hugetlb: fix reservation leak on min_size= mounts
David Hildenbrand (Arm) (10):
hugetlbfs: fix integer overflow in fallocate()
mm/hugetlb: compare against persistent count when freeing lowmem folios
mm/hugetlb: preserve reservations when shrinking on HIGHMEM
mm/hugetlb: account temporary folios separately
mm/hugetlb: fix mremap() racing with page migration
mm/hugetlb: serialize page-cache allocations with truncation
mm/memfd: recheck the page cache before allocating a hugetlb folio
mm/hugetlb: count again if the reserve map shrank
RFC: mm/hugetlb: account restored reservations before freeing folios
RFC: mm/hugetlb: delay anon folio rmap removal until after the TLB flush
Josef Bacik (2):
mm/hugetlb: keep surplus folios when the pool shrinks
hugetlbfs: fix unmapping folios from private VMAs
Documentation/mm/hugetlbfs_reserv.rst | 30 +--
arch/s390/include/asm/tlb.h | 5 +-
fs/hugetlbfs/inode.c | 36 +++-
include/asm-generic/tlb.h | 5 +-
include/linux/hugetlb.h | 24 +++
mm/hugetlb.c | 388 ++++++++++++++++++++++------------
mm/hugetlb_internal.h | 11 +
mm/hugetlb_sysfs.c | 2 +-
mm/memfd.c | 17 ++
mm/mmu_gather.c | 14 +-
10 files changed, 361 insertions(+), 171 deletions(-)
---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261027-fixes_hugetlb-2c4f02ff6304
--
Cheers,
David
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
@ 2026-10-28 20:13 ` David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
2026-11-13 20:22 ` [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2 siblings, 0 replies; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-28 20:13 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Naoya Horiguchi, Andrew Morton,
Mel Gorman, Andy Whitcroft, Miaohe Lin, Michal Hocko,
Rik van Riel, Hugh Dickins, Baolin Wang, Vivek Kasireddy,
Dave Airlie, Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann
Cc: linux-mm, linux-kernel, Gerd Hoffmann, linux-doc, linux-arch,
linux-s390, David Hildenbrand (Arm), Chris Mason, stable
From: Chris Mason <mason@kernel.org>
Failed allocations for CoW mappings can leak reservations on hugetlbfs
mounts with min_size= and no size=. The leaked reservations survive
unmount.
HPG_restore_reserve makes free_huge_folio() restore the global
reservation without returning it to the subpool. This assumes the
reserve map holds the reservation. However, a CoW mapping may have no
reserve map, or the map may already account for another folio at the
same index.
Commit 58db7c5fbe7d ("mm/hugetlb: fix avoid_reserve to allow taking
folio from subpool") allowed owner CoW allocations to consume subpool
reservations. Commit 51e1de00acdb ("mm/hugetlb: simplify
vma_has_reserves()") later extended this to CoW mappings without a
reserve map.
For example, with a subpool minimum of one page and a
MAP_PRIVATE|MAP_NORESERVE mapping:
subpool global
rsv_hpages resv_huge_pages
1. Mount 1 1
2. Allocate using subpool reserve 0 0
3. Free on error with flag set 0 1
4. Allocate without a reservation 0 1
5. Free and return to subpool 1 2
Call folio_clear_hugetlb_restore_reserve() for CoW mappings when
map_chg != MAP_CHG_REUSE, meaning no reserve-map reservation was consumed.
This lets free_huge_folio() return subpool reservations to the subpool
on error.
Do not set the flag again if region-cache allocation fails during
rollback: that would undo the flag clearing and skip returning the
reservation to the subpool. This also fixes the older case where setting
the flag invents a reservation for an unreserved allocation for a CoW
mapping.
The doc still mentions PagePrivate at various places: future work should
clean that up.
Fix generated by an LLM and sent by Chris Mason in private.
Fixes: 846be08578ed ("mm/hugetlb: expand restore_reserve_on_error functionality")
Fixes: 58db7c5fbe7d ("mm/hugetlb: fix avoid_reserve to allow taking folio from subpool")
Cc: stable@vger.kernel.org
Signed-off-by: Chris Mason <mason@kernel.org>
[ david: simplify comments and improve description; updated Fixes ]
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
Documentation/mm/hugetlbfs_reserv.rst | 25 ++++++++++++++-----------
include/linux/hugetlb.h | 2 ++
mm/hugetlb.c | 34 +++++++++++++++-------------------
3 files changed, 31 insertions(+), 30 deletions(-)
diff --git a/Documentation/mm/hugetlbfs_reserv.rst b/Documentation/mm/hugetlbfs_reserv.rst
index a49115db18c7..fbca6625ca50 100644
--- a/Documentation/mm/hugetlbfs_reserv.rst
+++ b/Documentation/mm/hugetlbfs_reserv.rst
@@ -201,10 +201,10 @@ The value returned from vma_needs_reservation() is generally
If a reservation does not exist, and there is a subpool associated with the
mapping the subpool is consulted to determine if it contains reservations.
If the subpool contains reservations, one can be used for this allocation.
-However, in every case the avoid_reserve argument overrides the use of
-a reservation for the allocation. After determining whether a reservation
-exists and can be used for the allocation, the routine dequeue_huge_page_vma()
-is called. This routine takes two arguments related to reservations:
+However, avoid_reserve only applies to reservations from the reservation map.
+After determining whether a reservation exists and can be used for the
+allocation, the routine dequeue_huge_page_vma() is called. This routine takes
+two arguments related to reservations:
- avoid_reserve, this is the same value/argument passed to
alloc_hugetlb_folio().
@@ -256,6 +256,10 @@ compensate. See the section
:ref:`Reservation Map Helper Routines <resv_map_helpers>` for more
information on these routines.
+For CoW mappings, alloc_hugetlb_folio() calls
+folio_clear_hugetlb_restore_reserve() if the reservation came from the subpool.
+This makes free_huge_folio() return the reservation to the subpool on error.
+
Instantiate Huge Pages
======================
@@ -284,14 +288,13 @@ be adjusted (see the section
:ref:`Consuming Reservations/Allocating a Huge Page <consume_resv>`
for information on how these are set).
-The routine first calls hugepage_subpool_put_pages() for the page. If this
-routine returns a value of 0 (which does not equal the value passed 1) it
-indicates reserves are associated with the subpool, and this newly free page
-must be used to keep the number of subpool reserves above the minimum size.
-Therefore, the global resv_huge_pages counter is incremented in this case.
+If folio_test_hugetlb_restore_reserve() is false, hugepage_subpool_put_pages()
+returns the page to the subpool. A return value of 0 means the page must restore
+a subpool reservation, so the global resv_huge_pages counter is incremented.
-If the PagePrivate flag was set in the page, the global resv_huge_pages counter
-will always be incremented.
+Otherwise, resv_huge_pages is incremented without calling
+hugepage_subpool_put_pages(): the restored reservation still counts as used in
+the subpool.
.. _sub_pool_resv:
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 2a3fde411d74..badb8790653e 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -593,6 +593,8 @@ hugetlb_get_unmapped_area(struct file *file, unsigned long addr,
* HPG_restore_reserve - Set when a hugetlb page consumes a reservation at
* allocation time. Cleared when page is fully instantiated. Free
* routine checks flag to restore a reservation on error paths.
+ * Cleared when allocating for a CoW mapping using a subpool
+ * reservation, which the free routine returns to the subpool.
* Synchronization: Examined or modified by code that knows it has
* the only reference to page. i.e. After allocation but before use
* or when the page is being freed.
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 32ecac4e0d3e..69a5d2c73ee1 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -2631,7 +2631,7 @@ static long vma_del_reservation(struct hstate *h,
* 1) A reservation was in place and the folio consumed the reservation.
* hugetlb_restore_reserve is set in the folio.
* 2) No reservation was in place for the page, so hugetlb_restore_reserve is
- * not set. However, alloc_hugetlb_folio always updates the reserve map.
+ * not set. However, alloc_hugetlb_folio may have updated the reserve map.
*
* In case 1, free_huge_folio later in the error path will increment the
* global reserve count. But, free_huge_folio does not have enough context
@@ -2641,6 +2641,10 @@ static long vma_del_reservation(struct hstate *h,
* reserve map indicates there is a reservation present.
*
* In case 2, simply undo reserve map modifications done by alloc_hugetlb_folio.
+ *
+ * For CoW mappings, allocations using a subpool reservation leave the
+ * flag clear and the reserve map unchanged. free_huge_folio returns the
+ * reservation to the subpool.
*/
void restore_reserve_on_error(struct hstate *h, struct vm_area_struct *vma,
unsigned long address, struct folio *folio)
@@ -2688,25 +2692,9 @@ void restore_reserve_on_error(struct hstate *h, struct vm_area_struct *vma,
folio_set_hugetlb_restore_reserve(folio);
} else if (rc < 0) {
/*
- * Rare out of memory condition from
- * vma_needs_reservation call. Memory allocation is
- * only attempted if a new entry is needed. Therefore,
- * this implies there is not an entry in the
- * reserve map.
- *
- * For shared mappings, no entry in the map indicates
- * no reservation. We are done.
+ * Region cache allocation can fail even if an entry exists.
+ * Leave the flag and reserve map unchanged.
*/
- if (!(vma->vm_flags & VM_MAYSHARE))
- /*
- * For private mappings, no entry indicates
- * a reservation is present. Since we can
- * not add an entry, set hugetlb_restore_reserve
- * on the folio so reserve count will be
- * incremented when freed. This reserve will
- * be consumed on a subsequent allocation.
- */
- folio_set_hugetlb_restore_reserve(folio);
} else {
/*
* No reservation present, do nothing
@@ -3161,6 +3149,14 @@ struct folio *alloc_hugetlb_folio(struct vm_area_struct *vma,
}
}
+ /*
+ * Return subpool reservations to the subpool on error. A CoW mapping
+ * might not have a reserve map, or it might already account for
+ * another folio at this index.
+ */
+ if (map_chg != MAP_CHG_REUSE && vma_is_cow_mapping(vma))
+ folio_clear_hugetlb_restore_reserve(folio);
+
return folio;
out_subpool_put:
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts David Hildenbrand (Arm)
@ 2026-10-28 20:13 ` David Hildenbrand (Arm)
2026-11-05 10:09 ` David Hildenbrand (Arm)
2026-11-13 20:22 ` [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2 siblings, 1 reply; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-10-28 20:13 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Naoya Horiguchi, Andrew Morton,
Mel Gorman, Andy Whitcroft, Miaohe Lin, Michal Hocko,
Rik van Riel, Hugh Dickins, Baolin Wang, Vivek Kasireddy,
Dave Airlie, Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann
Cc: linux-mm, linux-kernel, Gerd Hoffmann, linux-doc, linux-arch,
linux-s390, David Hildenbrand (Arm), stable, Josef Bacik
Unmapping an anonymous folio restores its private VMA reservation
immediately, but the global reservation count is only restored when the
folio is freed.
If any folio reference delays freeing, closing the VMA or refaulting can
decrement resv_huge_pages before it was incremented. This can underflow
the counter or hide another mapping's reservation, allowing an unreserved
allocation to consume its backing.
Increment resv_huge_pages when restoring the reserve map instead. Detach
the folio from its subpool, since the restored reservation now owns that
charge. Leave the restore-reserve flag clear so freeing the folio does not
repeat the accounting. The folio only becomes free after its last reference
is dropped.
Reservations can now temporarily outnumber free folios. Clamp available
pages to zero in that case, including for demotion, and limit surplus
releases to available pages so they cannot remove backing for remaining
reservations.
Restore reservations even when surplus pages exist. free_huge_folio()
already retains pages needed to back them. It is unclear why that was
really added. The comment about i_mmap_lock_write can go as well, that
doesn't really help for anonymous folios.
Two existing problems remain:
1) Anonymous hugetlb migration temporarily drops the mapcount. A concurrent
unmap can observe zero and restore a reservation even though migration
will reinstall a mapping. i_mmap_lock_write() does not protect anonymous
folio mapcounts, so it does not prevent this race. This change fixes the
resulting counter underflow, but leaves premature restoration possible,
leading to (2). Maybe that anon_vma lock can help?
2) A fault can allocate a replacement folio using the restored
reservation before the old folio is freed. Repeated vmsplice(),
MADV_DONTNEED and refaults can retain multiple folios for one
reservation and consume pages backing other reservations. This was
already possible; counter accounting is now fixed, but early reuse
remains. Removing the surplus check also allows it after unmaps that
previously skipped restoration.
Deferring reservation-map restoration until the final put is tricky:
free_huge_folio() has no VMA context to update the private reserve map, and
that VMA may already have been destroyed.
Fixes: df7a6d1f6405 ("mm/hugetlb: restore the reservation if needed")
Cc: stable@vger.kernel.org
Reported-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
Documentation/mm/hugetlbfs_reserv.rst | 5 +-
mm/hugetlb.c | 90 ++++++++++++++++-------------------
mm/hugetlb_internal.h | 11 +++++
mm/hugetlb_sysfs.c | 2 +-
4 files changed, 57 insertions(+), 51 deletions(-)
diff --git a/Documentation/mm/hugetlbfs_reserv.rst b/Documentation/mm/hugetlbfs_reserv.rst
index fbca6625ca50..4711029bd067 100644
--- a/Documentation/mm/hugetlbfs_reserv.rst
+++ b/Documentation/mm/hugetlbfs_reserv.rst
@@ -34,7 +34,8 @@ resv_huge_pages
This is a global (per-hstate) count of reserved huge pages. Reserved
huge pages are only available to the task which reserved them.
Therefore, the number of huge pages generally available is computed
- as (``free_huge_pages - resv_huge_pages``).
+ as (``free_huge_pages - resv_huge_pages``), clamped to zero when
+ reservations outnumber free huge pages.
Reserve Map
A reserve map is described by the structure::
@@ -155,7 +156,7 @@ are enough free huge pages to accommodate the reservation. If there are,
the global reservation count resv_huge_pages is adjusted something like the
following::
- if (resv_needed <= (free_huge_pages - resv_huge_pages)
+ if (resv_needed <= available_huge_pages(h))
resv_huge_pages += resv_needed;
Note that the global lock hugetlb_lock is held when checking and adjusting
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 69a5d2c73ee1..06ef8ba41df7 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1347,11 +1347,6 @@ static struct folio *dequeue_hugetlb_folio_nodemask(struct hstate *h, gfp_t gfp_
return NULL;
}
-static unsigned long available_huge_pages(struct hstate *h)
-{
- return h->free_huge_pages - h->resv_huge_pages;
-}
-
static unsigned long persistent_huge_pages(struct hstate *h)
{
return h->nr_huge_pages - h->surplus_huge_pages - h->temporary_huge_pages;
@@ -2449,7 +2444,8 @@ static void return_unused_surplus_pages(struct hstate *h,
* Part (or even all) of the reservation could have been backed
* by pre-allocated pages. Only free surplus pages.
*/
- nr_pages = min(unused_resv_pages, h->surplus_huge_pages);
+ nr_pages = min3(unused_resv_pages, h->surplus_huge_pages,
+ available_huge_pages(h));
/*
* We want to release as many surplus pages as possible, spread
@@ -5313,6 +5309,45 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma,
return len + old_addr - old_end;
}
+/* Must be called after rmap removal. */
+static void hugetlb_folio_restore_rsv_on_unmap(struct vm_area_struct *vma,
+ struct folio *folio)
+{
+ struct hstate *h = hstate_vma(vma);
+ unsigned long address;
+ long rc;
+
+ /*
+ * Restore only on the last unmap, otherwise the owner could reuse
+ * the reservation while the folio is still mapped by a child.
+ *
+ * TODO: Handle migration temporarily dropping the mapcount to zero.
+ * TODO: Prevent reservation reuse before the folio is freed.
+ */
+ if (!folio_test_anon(folio) || !__vma_private_lock(vma) ||
+ folio_mapped(folio))
+ return;
+
+ address = vma_anon_address(vma, folio->index, 1);
+ rc = vma_needs_reservation(h, vma, address);
+ if (!rc)
+ vma_end_reservation(h, vma, address);
+ if (rc <= 0)
+ return;
+
+ rc = vma_add_reservation(h, vma, address);
+ if (rc < 0)
+ return;
+
+ /* The restored reservation owns the subpool charge. */
+ hugetlb_set_folio_subpool(folio, NULL);
+
+ /* Account before the VMA can close or fault again. */
+ spin_lock_irq(&hugetlb_lock);
+ h->resv_huge_pages++;
+ spin_unlock_irq(&hugetlb_lock);
+}
+
void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
unsigned long start, unsigned long end,
struct folio *folio, zap_flags_t zap_flags)
@@ -5325,7 +5360,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
spinlock_t *ptl;
struct hstate *h = hstate_vma(vma);
unsigned long sz = huge_page_size(h);
- bool adjust_reservation;
unsigned long last_addr_mask;
i_mmap_assert_write_locked(vma->vm_file->f_mapping);
@@ -5418,47 +5452,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
hugetlb_remove_rmap(folio);
spin_unlock(ptl);
- /*
- * Restore the reservation for anonymous page, otherwise the
- * backing page could be stolen by someone. Restore only on the
- * last unmap, otherwise the owner could empty its resv map
- * while the folio is still mapped by a child. Note that holding
- * i_mmap_lock_write is needed to check the number of mappings.
- * If there we are freeing a surplus, do not set the restore
- * reservation bit.
- */
- adjust_reservation = false;
-
- spin_lock_irq(&hugetlb_lock);
- if (!h->surplus_huge_pages && __vma_private_lock(vma) &&
- !folio_mapped(folio) && folio_test_anon(folio)) {
- folio_set_hugetlb_restore_reserve(folio);
- /* Reservation to be adjusted after the spin lock */
- adjust_reservation = true;
- }
- spin_unlock_irq(&hugetlb_lock);
-
- /*
- * Adjust the reservation for the region that will have the
- * reserve restored. Keep in mind that vma_needs_reservation() changes
- * resv->adds_in_progress if it succeeds. If this is not done,
- * do_exit() will not see it, and will keep the reservation
- * forever.
- */
- if (adjust_reservation) {
- int rc = vma_needs_reservation(h, vma, address);
-
- if (rc < 0)
- /* Presumably allocate_file_region_entries failed
- * to allocate a file_region struct. Clear
- * hugetlb_restore_reserve so that global reserve
- * count will not be incremented by free_huge_folio.
- * Act as if we consumed the reservation.
- */
- folio_clear_hugetlb_restore_reserve(folio);
- else if (rc)
- vma_add_reservation(h, vma, address);
- }
+ hugetlb_folio_restore_rsv_on_unmap(vma, folio);
tlb_remove_page_size(tlb, folio_page(folio, 0),
folio_size(folio));
diff --git a/mm/hugetlb_internal.h b/mm/hugetlb_internal.h
index 1d2f870deccf..d307d472137c 100644
--- a/mm/hugetlb_internal.h
+++ b/mm/hugetlb_internal.h
@@ -10,6 +10,17 @@
#include <linux/hugetlb.h>
#include <linux/hugetlb_cgroup.h>
+static inline unsigned long available_huge_pages(struct hstate *h)
+{
+ /*
+ * Restored reservations may outnumber free folios until the folios
+ * return to the pool.
+ */
+ if (h->resv_huge_pages > h->free_huge_pages)
+ return 0;
+ return h->free_huge_pages - h->resv_huge_pages;
+}
+
/*
* Check if the hstate represents gigantic pages but gigantic page
* runtime support is not available. This is a common condition used to
diff --git a/mm/hugetlb_sysfs.c b/mm/hugetlb_sysfs.c
index c89e4c847575..9e0c5d3d0fcb 100644
--- a/mm/hugetlb_sysfs.c
+++ b/mm/hugetlb_sysfs.c
@@ -211,7 +211,7 @@ static ssize_t demote_store(struct kobject *kobj,
* Check for available pages to demote each time thorough the
* loop as demote_pool_huge_page will drop hugetlb_lock.
*/
- nr_available = h->free_huge_pages - h->resv_huge_pages;
+ nr_available = available_huge_pages(h);
if (nid != NUMA_NO_NODE)
nr_available = min(nr_available,
h->free_huge_pages_node[nid]);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios
2026-10-28 20:13 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
@ 2026-11-05 10:09 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 10:09 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Naoya Horiguchi, Andrew Morton,
Mel Gorman, Andy Whitcroft, Miaohe Lin, Michal Hocko,
Rik van Riel, Hugh Dickins, Baolin Wang, Vivek Kasireddy,
Dave Airlie, Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann
Cc: linux-mm, linux-kernel, Gerd Hoffmann, linux-doc, linux-arch,
linux-s390, stable, Josef Bacik
On 10/28/26 21:13, David Hildenbrand (Arm) wrote:
> Unmapping an anonymous folio restores its private VMA reservation
> immediately, but the global reservation count is only restored when the
> folio is freed.
>
> If any folio reference delays freeing, closing the VMA or refaulting can
> decrement resv_huge_pages before it was incremented. This can underflow
> the counter or hide another mapping's reservation, allowing an unreserved
> allocation to consume its backing.
>
> Increment resv_huge_pages when restoring the reserve map instead. Detach
> the folio from its subpool, since the restored reservation now owns that
> charge. Leave the restore-reserve flag clear so freeing the folio does not
> repeat the accounting. The folio only becomes free after its last reference
> is dropped.
>
> Reservations can now temporarily outnumber free folios. Clamp available
> pages to zero in that case, including for demotion, and limit surplus
> releases to available pages so they cannot remove backing for remaining
> reservations.
>
> Restore reservations even when surplus pages exist. free_huge_folio()
> already retains pages needed to back them. It is unclear why that was
> really added. The comment about i_mmap_lock_write can go as well, that
> doesn't really help for anonymous folios.
>
> Two existing problems remain:
>
> 1) Anonymous hugetlb migration temporarily drops the mapcount. A concurrent
> unmap can observe zero and restore a reservation even though migration
> will reinstall a mapping. i_mmap_lock_write() does not protect anonymous
> folio mapcounts, so it does not prevent this race. This change fixes the
> resulting counter underflow, but leaves premature restoration possible,
> leading to (2). Maybe that anon_vma lock can help?
>
> 2) A fault can allocate a replacement folio using the restored
> reservation before the old folio is freed. Repeated vmsplice(),
> MADV_DONTNEED and refaults can retain multiple folios for one
> reservation and consume pages backing other reservations. This was
> already possible; counter accounting is now fixed, but early reuse
> remains. Removing the surplus check also allows it after unmaps that
> previously skipped restoration.
>
> Deferring reservation-map restoration until the final put is tricky:
> free_huge_folio() has no VMA context to update the private reserve map, and
> that VMA may already have been destroyed.
>
> Fixes: df7a6d1f6405 ("mm/hugetlb: restore the reservation if needed")
> Cc: stable@vger.kernel.org
> Reported-by: Josef Bacik <josef@toxicpanda.com>
> Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
> ---
This is the current updated patch I have:
From 8597f47aef9cbd7669ad893908b1218c2769469b Mon Sep 17 00:00:00 2001
From: "David Hildenbrand (Arm)" <david@kernel.org>
Date: Wed, 28 Oct 2026 17:33:54 +0100
Subject: [PATCH] RFC: mm/hugetlb: account restored reservations before freeing
folios
Unmapping an anonymous folio restores its private VMA reservation
immediately, but the global reservation count is only restored when the
folio is freed.
If any folio reference delays freeing, closing the VMA or refaulting can
decrement resv_huge_pages before it was incremented. This can underflow
the counter or hide another mapping's reservation, allowing an unreserved
allocation to consume its backing.
Increment resv_huge_pages when restoring the reserve map instead. Detach
the folio from its subpool, since the restored reservation now owns that
charge. Leave the restore-reserve flag clear so freeing the folio does not
repeat the accounting. The folio only becomes free after its last reference
is dropped.
Reservations can now temporarily outnumber free folios. Clamp available
pages to zero in that case, including for demotion, and limit releases of
pooled surplus pages to that availability so they cannot remove backing
for remaining reservations.
Use the same clamped availability when gathering surplus pages for a new
reservation. An existing deficit can be backed by unmapped folios that
are still referenced, so do not allocate replacement backing for it as
part of the new request. Otherwise, the request can allocate unnecessary
surplus pages or fail even when the overcommit limit has room for the new
reservation.
Free unused or aborted surplus allocations directly instead of passing
them to free_huge_folio(), which could retain them to cover the existing
deficit. These isolated folios have not backed any committed reservation.
Preserve pages promoted to persistent capacity by a concurrent pool resize,
and use the bulk freeing path to retain vmemmap restoration error handling.
Restore reservations even when surplus pages exist. free_huge_folio()
already retains pages needed to back them. It is unclear why that was
really added. The comment about i_mmap_lock_write can go as well, that
doesn't really help for anonymous folios.
Two existing problems remain:
1) Anonymous hugetlb migration temporarily drops the mapcount. A concurrent
unmap can observe zero and restore a reservation even though migration
will reinstall a mapping. i_mmap_lock_write() does not protect anonymous
folio mapcounts, so it does not prevent this race. This change fixes the
resulting counter underflow, but leaves premature restoration possible,
leading to (2). Maybe that anon_vma lock can help?
2) A fault can allocate a replacement folio using the restored
reservation before the old folio is freed. Repeated vmsplice(),
MADV_DONTNEED and refaults can retain multiple folios for one
reservation and consume pages backing other reservations. This was
already possible; counter accounting is now fixed, but early reuse
remains. Removing the surplus check also allows it after unmaps that
previously skipped restoration.
Deferring reservation-map restoration until the final put is tricky:
free_huge_folio() has no VMA context to update the private reserve map, and
that VMA may already have been destroyed.
Fixes: df7a6d1f6405 ("mm/hugetlb: restore the reservation if needed")
Cc: stable@vger.kernel.org
Reported-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
Documentation/mm/hugetlbfs_reserv.rst | 5 +-
mm/hugetlb.c | 125 ++++++++++++++------------
mm/hugetlb_internal.h | 24 +++++
mm/hugetlb_sysfs.c | 2 +-
4 files changed, 95 insertions(+), 61 deletions(-)
diff --git a/Documentation/mm/hugetlbfs_reserv.rst b/Documentation/mm/hugetlbfs_reserv.rst
index fbca6625ca50d..4711029bd067d 100644
--- a/Documentation/mm/hugetlbfs_reserv.rst
+++ b/Documentation/mm/hugetlbfs_reserv.rst
@@ -34,7 +34,8 @@ resv_huge_pages
This is a global (per-hstate) count of reserved huge pages. Reserved
huge pages are only available to the task which reserved them.
Therefore, the number of huge pages generally available is computed
- as (``free_huge_pages - resv_huge_pages``).
+ as (``free_huge_pages - resv_huge_pages``), clamped to zero when
+ reservations outnumber free huge pages.
Reserve Map
A reserve map is described by the structure::
@@ -155,7 +156,7 @@ are enough free huge pages to accommodate the reservation. If there are,
the global reservation count resv_huge_pages is adjusted something like the
following::
- if (resv_needed <= (free_huge_pages - resv_huge_pages)
+ if (resv_needed <= available_huge_pages(h))
resv_huge_pages += resv_needed;
Note that the global lock hugetlb_lock is held when checking and adjusting
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index ee77994e31970..0a10d18c90c23 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -1347,11 +1347,6 @@ static struct folio *dequeue_hugetlb_folio_nodemask(struct hstate *h, gfp_t gfp_
return NULL;
}
-static unsigned long available_huge_pages(struct hstate *h)
-{
- return h->free_huge_pages - h->resv_huge_pages;
-}
-
static unsigned long persistent_huge_pages(const struct hstate *h)
{
return h->nr_huge_pages - h->surplus_huge_pages - h->temporary_huge_pages;
@@ -2317,11 +2312,13 @@ static nodemask_t *policy_mbind_nodemask(gfp_t gfp)
*/
static long surplus_pages_needed(struct hstate *h, long delta, long allocated)
{
- long global_free = (long)h->free_huge_pages + allocated;
- long allowed_free = (long)allowed_mems_nr(h) + allocated;
+ const long available = min(available_huge_pages(h), allowed_mems_nr(h));
- return max((long)h->resv_huge_pages + delta - global_free,
- delta - allowed_free);
+ /*
+ * Restored reservations can still be backed by folios that were not
+ * freed yet.
+ */
+ return delta - available - allocated;
}
/*
@@ -2332,6 +2329,7 @@ static int gather_surplus_pages(struct hstate *h, long delta)
__must_hold(&hugetlb_lock)
{
LIST_HEAD(surplus_list);
+ LIST_HEAD(free_list);
struct folio *folio, *tmp;
int ret;
long i;
@@ -2411,14 +2409,26 @@ static int gather_surplus_pages(struct hstate *h, long delta)
enqueue_hugetlb_folio(h, folio);
}
free:
- spin_unlock_irq(&hugetlb_lock);
-
/*
- * Free unnecessary surplus pages to the buddy allocator.
- * Pages have no ref count, call free_huge_folio directly.
+ * These folios are not required for any reservation. Don't use
+ * free_huge_folio(), otherwise it might decide to keep them for
+ * reservations that were restored before the original folios were
+ * freed.
*/
- list_for_each_entry_safe(folio, tmp, &surplus_list, lru)
- free_huge_folio(folio);
+ list_for_each_entry_safe(folio, tmp, &surplus_list, lru) {
+ /*
+ * If the pool was resized concurrently, these folios might now
+ * count as persistent and we must keep them.
+ */
+ if (!h->surplus_huge_pages_node[folio_nid(folio)]) {
+ enqueue_hugetlb_folio(h, folio);
+ continue;
+ }
+ remove_hugetlb_folio(h, folio, true);
+ list_add(&folio->lru, &free_list);
+ }
+ spin_unlock_irq(&hugetlb_lock);
+ update_and_free_pages_bulk(h, &free_list);
spin_lock_irq(&hugetlb_lock);
return ret;
@@ -2449,7 +2459,8 @@ static void return_unused_surplus_pages(struct hstate *h,
* Part (or even all) of the reservation could have been backed
* by pre-allocated pages. Only free surplus pages.
*/
- nr_pages = min(unused_resv_pages, h->surplus_huge_pages);
+ nr_pages = min3(unused_resv_pages, h->surplus_huge_pages,
+ available_huge_pages(h));
/*
* We want to release as many surplus pages as possible, spread
@@ -5313,6 +5324,45 @@ int move_hugetlb_page_tables(struct vm_area_struct *vma,
return len + old_addr - old_end;
}
+/* Must be called after rmap removal. */
+static void hugetlb_folio_restore_rsv_on_unmap(struct vm_area_struct *vma,
+ struct folio *folio)
+{
+ struct hstate *h = hstate_vma(vma);
+ unsigned long address;
+ long rc;
+
+ /*
+ * Restore only on the last unmap, otherwise the owner could reuse
+ * the reservation while the folio is still mapped by a child.
+ *
+ * TODO: Handle migration temporarily dropping the mapcount to zero.
+ * TODO: Prevent reservation reuse before the folio is freed.
+ */
+ if (!folio_test_anon(folio) || !__vma_private_lock(vma) ||
+ folio_mapped(folio))
+ return;
+
+ address = vma_anon_address(vma, folio->index, 1);
+ rc = vma_needs_reservation(h, vma, address);
+ if (!rc)
+ vma_end_reservation(h, vma, address);
+ if (rc <= 0)
+ return;
+
+ rc = vma_add_reservation(h, vma, address);
+ if (rc < 0)
+ return;
+
+ /* The restored reservation owns the subpool charge. */
+ hugetlb_set_folio_subpool(folio, NULL);
+
+ /* Account before the VMA can close or fault again. */
+ spin_lock_irq(&hugetlb_lock);
+ h->resv_huge_pages++;
+ spin_unlock_irq(&hugetlb_lock);
+}
+
void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
unsigned long start, unsigned long end,
struct folio *folio, zap_flags_t zap_flags)
@@ -5325,7 +5375,6 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
spinlock_t *ptl;
struct hstate *h = hstate_vma(vma);
unsigned long sz = huge_page_size(h);
- bool adjust_reservation;
unsigned long last_addr_mask;
i_mmap_assert_write_locked(vma->vm_file->f_mapping);
@@ -5418,47 +5467,7 @@ void __unmap_hugepage_range(struct mmu_gather *tlb, struct vm_area_struct *vma,
hugetlb_remove_rmap(folio);
spin_unlock(ptl);
- /*
- * Restore the reservation for anonymous page, otherwise the
- * backing page could be stolen by someone. Restore only on the
- * last unmap, otherwise the owner could empty its resv map
- * while the folio is still mapped by a child. Note that holding
- * i_mmap_lock_write is needed to check the number of mappings.
- * If there we are freeing a surplus, do not set the restore
- * reservation bit.
- */
- adjust_reservation = false;
-
- spin_lock_irq(&hugetlb_lock);
- if (!h->surplus_huge_pages && __vma_private_lock(vma) &&
- !folio_mapped(folio) && folio_test_anon(folio)) {
- folio_set_hugetlb_restore_reserve(folio);
- /* Reservation to be adjusted after the spin lock */
- adjust_reservation = true;
- }
- spin_unlock_irq(&hugetlb_lock);
-
- /*
- * Adjust the reservation for the region that will have the
- * reserve restored. Keep in mind that vma_needs_reservation() changes
- * resv->adds_in_progress if it succeeds. If this is not done,
- * do_exit() will not see it, and will keep the reservation
- * forever.
- */
- if (adjust_reservation) {
- int rc = vma_needs_reservation(h, vma, address);
-
- if (rc < 0)
- /* Presumably allocate_file_region_entries failed
- * to allocate a file_region struct. Clear
- * hugetlb_restore_reserve so that global reserve
- * count will not be incremented by free_huge_folio.
- * Act as if we consumed the reservation.
- */
- folio_clear_hugetlb_restore_reserve(folio);
- else if (rc)
- vma_add_reservation(h, vma, address);
- }
+ hugetlb_folio_restore_rsv_on_unmap(vma, folio);
tlb_remove_page_size(tlb, folio_page(folio, 0),
folio_size(folio));
diff --git a/mm/hugetlb_internal.h b/mm/hugetlb_internal.h
index 1d2f870deccfe..201028e08a46a 100644
--- a/mm/hugetlb_internal.h
+++ b/mm/hugetlb_internal.h
@@ -10,6 +10,30 @@
#include <linux/hugetlb.h>
#include <linux/hugetlb_cgroup.h>
+/**
+ * available_huge_pages - number of free huge pages not needed for reservations
+ * @h: huge page size state
+ *
+ * Global number of free huge pages that are not needed for existing
+ * reservations, available for allocations without reservations. Things like
+ * NUMA placement, cpusets, mempolicy and the usability of individual folios can
+ * further restrict allocation.
+ *
+ * Reservations of anon folios may be restored before their backing folios are
+ * actually freed, so the number of reserved pages can be bigger than the
+ * number of free pages. In such a scenario, the result is clamped to 0.
+ *
+ * Return: The number of free huge pages not needed for reservations.
+ */
+static inline unsigned long available_huge_pages(const struct hstate *h)
+{
+ lockdep_assert_held(&hugetlb_lock);
+
+ if (h->resv_huge_pages > h->free_huge_pages)
+ return 0;
+ return h->free_huge_pages - h->resv_huge_pages;
+}
+
/*
* Check if the hstate represents gigantic pages but gigantic page
* runtime support is not available. This is a common condition used to
diff --git a/mm/hugetlb_sysfs.c b/mm/hugetlb_sysfs.c
index c89e4c8475756..9e0c5d3d0fcb6 100644
--- a/mm/hugetlb_sysfs.c
+++ b/mm/hugetlb_sysfs.c
@@ -211,7 +211,7 @@ static ssize_t demote_store(struct kobject *kobj,
* Check for available pages to demote each time thorough the
* loop as demote_pool_huge_page will drop hugetlb_lock.
*/
- nr_available = h->free_huge_pages - h->resv_huge_pages;
+ nr_available = available_huge_pages(h);
if (nid != NUMA_NO_NODE)
nr_available = min(nr_available,
h->free_huge_pages_node[nid]);
--
2.43.0
--
Cheers,
David
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH 00/15] mm/hugetlb: various fixes
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
@ 2026-11-13 20:22 ` David Hildenbrand (Arm)
2026-11-14 0:43 ` Muchun Song
2 siblings, 1 reply; 6+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-13 20:22 UTC (permalink / raw)
To: Muchun Song, Oscar Salvador, Naoya Horiguchi, Andrew Morton,
Mel Gorman, Andy Whitcroft, Miaohe Lin, Michal Hocko,
Rik van Riel, Hugh Dickins, Baolin Wang, Vivek Kasireddy,
Dave Airlie, Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann
Cc: linux-mm, linux-kernel, Gerd Hoffmann, linux-doc, linux-arch,
linux-s390, luojiajun, Josef Bacik, stable, Chris Mason,
Daehyeon Ko, stable
On 10/28/26 21:13, David Hildenbrand (Arm) wrote:
> These fixes almost finished me.
Muchun, Oscar, should I respin with an un-rfc of patch #14?
--
Cheers,
David
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH 00/15] mm/hugetlb: various fixes
2026-11-13 20:22 ` [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
@ 2026-11-14 0:43 ` Muchun Song
0 siblings, 0 replies; 6+ messages in thread
From: Muchun Song @ 2026-11-14 0:43 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Oscar Salvador, Naoya Horiguchi, Andrew Morton, Mel Gorman,
Andy Whitcroft, Miaohe Lin, Michal Hocko, Rik van Riel,
Hugh Dickins, Baolin Wang, Vivek Kasireddy, Dave Airlie,
Jason Gunthorpe, Lorenzo Stoakes, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan,
Jonathan Corbet, Shuah Khan, Randy Dunlap, Peter Xu, Breno Leitao,
Will Deacon, Aneesh Kumar K.V, Nick Piggin, Peter Zijlstra,
Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Arnd Bergmann, linux-mm,
linux-kernel, Gerd Hoffmann, linux-doc, linux-arch, linux-s390,
luojiajun, Josef Bacik, stable, Chris Mason, Daehyeon Ko, stable
> On Nov 14, 2026, at 04:22, David Hildenbrand (Arm) <david@kernel.org> wrote:
>
> On 10/28/26 21:13, David Hildenbrand (Arm) wrote:
>> These fixes almost finished me.
>
> Muchun, Oscar, should I respin with an un-rfc of patch #14?
Sure, please.
Thanks,
Muchun
>
> --
> Cheers,
>
> David
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-11-14 0:44 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 13/15] mm/hugetlb: fix reservation leak on min_size= mounts David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
2026-11-05 10:09 ` David Hildenbrand (Arm)
2026-11-13 20:22 ` [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-11-14 0:43 ` Muchun Song
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox