From: "David Hildenbrand (Arm)" <david@kernel.org>
To: Muchun Song <muchun.song@linux.dev>,
Oscar Salvador <osalvador@suse.de>,
Naoya Horiguchi <nao.horiguchi@gmail.com>,
Andrew Morton <akpm@linux-foundation.org>,
Mel Gorman <mgorman@suse.de>, Andy Whitcroft <apw@canonical.com>,
Miaohe Lin <linmiaohe@huawei.com>,
Michal Hocko <mhocko@suse.com>, Rik van Riel <riel@surriel.com>,
Hugh Dickins <hughd@google.com>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
Vivek Kasireddy <vivek.kasireddy@intel.com>,
Dave Airlie <airlied@redhat.com>, Jason Gunthorpe <jgg@ziepe.ca>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Randy Dunlap <rdunlap@infradead.org>,
Peter Xu <peterx@redhat.com>, Breno Leitao <leitao@debian.org>,
Will Deacon <will@kernel.org>,
"Aneesh Kumar K.V" <aneesh.kumar@kernel.org>,
Nick Piggin <npiggin@gmail.com>,
Peter Zijlstra <peterz@infradead.org>,
Heiko Carstens <hca@linux.ibm.com>,
Vasily Gorbik <gor@linux.ibm.com>,
Alexander Gordeev <agordeev@linux.ibm.com>,
Christian Borntraeger <borntraeger@linux.ibm.com>,
Sven Schnelle <svens@linux.ibm.com>,
Arnd Bergmann <arnd@arndb.de>
Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org,
Gerd Hoffmann <kraxel@redhat.com>,
linux-doc@vger.kernel.org, linux-arch@vger.kernel.org,
linux-s390@vger.kernel.org,
"David Hildenbrand (Arm)" <david@kernel.org>,
stable@vger.kernel.org, Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios
Date: Wed, 28 Oct 2026 21:13:42 +0100 [thread overview]
Message-ID: <20261028-fixes_hugetlb-v1-14-a0a033db180f@kernel.org> (raw)
In-Reply-To: <20261028-fixes_hugetlb-v1-0-a0a033db180f@kernel.org>
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
next prev parent reply other threads:[~2026-10-28 20:16 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-28 20:13 [PATCH 00/15] mm/hugetlb: various fixes David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 01/15] hugetlbfs: fix integer overflow in fallocate() David Hildenbrand (Arm)
2026-10-29 3:37 ` Muchun Song
2026-10-29 9:05 ` Oscar Salvador (SUSE)
2026-10-28 20:13 ` [PATCH 02/15] mm/hugetlb: compare against persistent count when freeing lowmem folios David Hildenbrand (Arm)
2026-10-29 3:39 ` Muchun Song
2026-10-28 20:13 ` [PATCH 03/15] mm/hugetlb: preserve reservations when shrinking on HIGHMEM David Hildenbrand (Arm)
2026-10-29 3:41 ` Muchun Song
2026-10-28 20:13 ` [PATCH 04/15] mm/hugetlb: account temporary folios separately David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 05/15] mm/hugetlb: keep surplus folios when the pool shrinks David Hildenbrand (Arm)
2026-10-29 3:51 ` Muchun Song
2026-10-28 20:13 ` [PATCH 06/15] mm/hugetlb: fix mremap() racing with page migration David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 07/15] hugetlbfs: fix unmapping folios from private VMAs David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 08/15] mm/hugetlb: serialize page-cache allocations with truncation David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 09/15] mm/memfd: recheck the page cache before allocating a hugetlb folio David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 10/15] mm/hugetlb: count again if the reserve map shrank David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 11/15] hugetlbfs: do not grow a sealed file in mmap() David Hildenbrand (Arm)
2026-10-28 20:13 ` [PATCH 12/15] mm/hugetlb: keep surplus folios that are reserved 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) [this message]
2026-10-28 20:13 ` [PATCH 15/15] RFC: mm/hugetlb: delay anon folio rmap removal until after the TLB flush David Hildenbrand (Arm)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261028-fixes_hugetlb-v1-14-a0a033db180f@kernel.org \
--to=david@kernel.org \
--cc=agordeev@linux.ibm.com \
--cc=airlied@redhat.com \
--cc=akpm@linux-foundation.org \
--cc=aneesh.kumar@kernel.org \
--cc=apw@canonical.com \
--cc=arnd@arndb.de \
--cc=baolin.wang@linux.alibaba.com \
--cc=borntraeger@linux.ibm.com \
--cc=corbet@lwn.net \
--cc=gor@linux.ibm.com \
--cc=hca@linux.ibm.com \
--cc=hughd@google.com \
--cc=jgg@ziepe.ca \
--cc=josef@toxicpanda.com \
--cc=kraxel@redhat.com \
--cc=leitao@debian.org \
--cc=liam@infradead.org \
--cc=linmiaohe@huawei.com \
--cc=linux-arch@vger.kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-s390@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mgorman@suse.de \
--cc=mhocko@suse.com \
--cc=muchun.song@linux.dev \
--cc=nao.horiguchi@gmail.com \
--cc=npiggin@gmail.com \
--cc=osalvador@suse.de \
--cc=peterx@redhat.com \
--cc=peterz@infradead.org \
--cc=rdunlap@infradead.org \
--cc=riel@surriel.com \
--cc=rppt@kernel.org \
--cc=skhan@linuxfoundation.org \
--cc=stable@vger.kernel.org \
--cc=surenb@google.com \
--cc=svens@linux.ibm.com \
--cc=vbabka@kernel.org \
--cc=vivek.kasireddy@intel.com \
--cc=will@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®