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@kernel.org, Josef Bacik <josef@toxicpanda.com>
Subject: [PATCH 04/15] mm/hugetlb: account temporary folios separately
Date: Wed, 28 Oct 2026 21:13:32 +0100 [thread overview]
Message-ID: <20261028-fixes_hugetlb-v1-4-a0a033db180f@kernel.org> (raw)
In-Reply-To: <20261028-fixes_hugetlb-v1-0-a0a033db180f@kernel.org>
Hugetlb migration might have to allocate a temporary folio through
alloc_migrate_hugetlb_folio(). Such folios are currently accounted in
"nr_huge_pages" but not in "surplus_huge_pages". persistent_huge_pages()
can mistake them as persistent huge pages, as it merely calculates the
difference.
If set_max_huge_pages() shrinks the pool at the wrong time, it will turn
hugetlb folios into surplus folios until persistent_huge_pages() returns
the desired number.
After the migration (successful or unsuccessful) either the new (temporary)
folio or the old folio gets freed: this will reduce "nr_huge_pages" but
not "surplus_huge_pages", to undo what was done when allocating a
temporary folio.
This can result in having more surplus folios than actually allocated
folios:
HugePages_Total: 0
HugePages_Surp: 1
From then on persistent_huge_pages() returns garbage.
To fix it, add internal temporary counters and subtract them when
calculating persistent pool sizes. Update persistent_huge_pages() and add
persistent_huge_pages_node() to make the code easier to read.
Count migration targets and rejected surplus allocations as temporary.
Transfer the per-node temporary count along with the temporary flag on
migration, such that folio freeing will always do the right thing.
Take care of calling account_new_hugetlb_folio() only after setting the
folios to be temporary.
For now, keep temporary folios in nr_huge_pages, so HugePages_Total and
Hugetlb continue to include them and overcommit accounting remains
unchanged. The new temporary counters are internal for now and are not
exported to userspace.
Fixes: ab5ac90aecf5 ("mm, hugetlb: do not rely on overcommit limit during migration")
Cc: stable@kernel.org
Reported-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: David Hildenbrand (Arm) <david@kernel.org>
---
include/linux/hugetlb.h | 3 +++
mm/hugetlb.c | 65 +++++++++++++++++++++++++++++--------------------
2 files changed, 42 insertions(+), 26 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 5029c7241863..d49cb15d13e6 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -701,6 +701,8 @@ struct hstate {
unsigned long free_huge_pages;
unsigned long resv_huge_pages;
unsigned long surplus_huge_pages;
+ /* Included in nr_huge_pages; not exported separately to userspace. */
+ unsigned long temporary_huge_pages;
unsigned long nr_overcommit_huge_pages;
struct list_head hugepage_activelist;
struct list_head hugepage_freelists[MAX_NUMNODES];
@@ -708,6 +710,7 @@ struct hstate {
unsigned int nr_huge_pages_node[MAX_NUMNODES];
unsigned int free_huge_pages_node[MAX_NUMNODES];
unsigned int surplus_huge_pages_node[MAX_NUMNODES];
+ unsigned int temporary_huge_pages_node[MAX_NUMNODES];
char name[HSTATE_NAME_LEN];
};
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index a78b2487c20c..110a5c2d9e50 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -115,7 +115,7 @@ early_param(str, func##args)
/*
* Protects updates to hugepage_freelists, hugepage_activelist, nr_huge_pages,
- * free_huge_pages, and surplus_huge_pages.
+ * free_huge_pages, surplus_huge_pages, and temporary_huge_pages.
*/
__cacheline_aligned_in_smp DEFINE_SPINLOCK(hugetlb_lock);
@@ -1337,7 +1337,13 @@ static unsigned long available_huge_pages(struct hstate *h)
static unsigned long persistent_huge_pages(struct hstate *h)
{
- return h->nr_huge_pages - h->surplus_huge_pages;
+ return h->nr_huge_pages - h->surplus_huge_pages - h->temporary_huge_pages;
+}
+
+static unsigned long persistent_huge_pages_node(struct hstate *h, int nid)
+{
+ return h->nr_huge_pages_node[nid] - h->surplus_huge_pages_node[nid] -
+ h->temporary_huge_pages_node[nid];
}
static struct folio *dequeue_hugetlb_folio(struct hstate *h, gfp_t gfp_mask,
@@ -1422,6 +1428,10 @@ void remove_hugetlb_folio(struct hstate *h, struct folio *folio,
h->surplus_huge_pages--;
h->surplus_huge_pages_node[nid]--;
}
+ if (folio_test_hugetlb_temporary(folio)) {
+ h->temporary_huge_pages--;
+ h->temporary_huge_pages_node[nid]--;
+ }
/*
* We can only clear the hugetlb flag after allocating vmemmap
@@ -1764,9 +1774,15 @@ void free_huge_folio(struct folio *folio)
*/
static void account_new_hugetlb_folio(struct hstate *h, struct folio *folio)
{
+ const int nid = folio_nid(folio);
+
lockdep_assert_held(&hugetlb_lock);
h->nr_huge_pages++;
- h->nr_huge_pages_node[folio_nid(folio)]++;
+ h->nr_huge_pages_node[nid]++;
+ if (folio_test_hugetlb_temporary(folio)) {
+ h->temporary_huge_pages++;
+ h->temporary_huge_pages_node[nid]++;
+ }
}
void init_new_hugetlb_folio(struct folio *folio)
@@ -2113,13 +2129,6 @@ static struct folio *alloc_surplus_hugetlb_folio(struct hstate *h,
return NULL;
spin_lock_irq(&hugetlb_lock);
- /*
- * nr_huge_pages needs to be adjusted within the same lock cycle
- * as surplus_pages, otherwise it might confuse
- * persistent_huge_pages() momentarily.
- */
- account_new_hugetlb_folio(h, folio);
-
/*
* We could have raced with the pool size change.
* Double check that and simply deallocate the new page
@@ -2129,11 +2138,18 @@ static struct folio *alloc_surplus_hugetlb_folio(struct hstate *h,
*/
if (h->surplus_huge_pages >= h->nr_overcommit_huge_pages) {
folio_set_hugetlb_temporary(folio);
+ account_new_hugetlb_folio(h, folio);
spin_unlock_irq(&hugetlb_lock);
free_huge_folio(folio);
return NULL;
}
+ /*
+ * nr_huge_pages needs to be adjusted within the same lock cycle
+ * as surplus_pages, otherwise it might confuse
+ * persistent_huge_pages() momentarily.
+ */
+ account_new_hugetlb_folio(h, folio);
h->surplus_huge_pages++;
h->surplus_huge_pages_node[folio_nid(folio)]++;
@@ -2155,17 +2171,17 @@ static struct folio *alloc_migrate_hugetlb_folio(struct hstate *h, gfp_t gfp_mas
if (!folio)
return NULL;
+ /*
+ * Count temporary folios separately so that pool resizing does not
+ * mistake them for persistent pages.
+ */
spin_lock_irq(&hugetlb_lock);
+ folio_set_hugetlb_temporary(folio);
account_new_hugetlb_folio(h, folio);
spin_unlock_irq(&hugetlb_lock);
/* fresh huge pages are frozen */
folio_ref_unfreeze(folio, 1);
- /*
- * We do not account these pages as surplus because they are only
- * temporary and will be released properly on the last reference
- */
- folio_set_hugetlb_temporary(folio);
return folio;
}
@@ -3757,8 +3773,7 @@ static int adjust_pool_surplus(struct hstate *h, nodemask_t *nodes_allowed,
}
} else {
for_each_node_mask_to_free(h, nr_nodes, node, nodes_allowed) {
- if (h->surplus_huge_pages_node[node] <
- h->nr_huge_pages_node[node])
+ if (persistent_huge_pages_node(h, node))
goto found;
}
}
@@ -3807,9 +3822,7 @@ static int set_max_huge_pages(struct hstate *h, unsigned long count, int nid,
if (nid != NUMA_NO_NODE) {
unsigned long old_count = count;
- count += persistent_huge_pages(h) -
- (h->nr_huge_pages_node[nid] -
- h->surplus_huge_pages_node[nid]);
+ count += persistent_huge_pages(h) - persistent_huge_pages_node(h, nid);
/*
* User may have specified a large count value which caused the
* above calculation to overflow. In this case, they wanted
@@ -7440,9 +7453,8 @@ void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio,
* be final while the old one will be freed so it takes over
* the temporary status.
*
- * Also note that we have to transfer the per-node surplus state
- * here as well otherwise the global surplus count will not match
- * the per-node's.
+ * Transfer the per-node temporary count to the old folio's node,
+ * and any surplus count in the opposite direction.
*/
if (folio_test_hugetlb_temporary(new_folio)) {
int old_nid = folio_nid(old_folio);
@@ -7451,13 +7463,14 @@ void move_hugetlb_state(struct folio *old_folio, struct folio *new_folio,
folio_set_hugetlb_temporary(old_folio);
folio_clear_hugetlb_temporary(new_folio);
-
/*
- * There is no need to transfer the per-node surplus state
- * when we do not cross the node.
+ * There is no need to transfer the per-node state when we do
+ * not cross the node.
*/
if (new_nid != old_nid) {
spin_lock_irq(&hugetlb_lock);
+ h->temporary_huge_pages_node[new_nid]--;
+ h->temporary_huge_pages_node[old_nid]++;
if (h->surplus_huge_pages_node[old_nid]) {
h->surplus_huge_pages_node[old_nid]--;
h->surplus_huge_pages_node[new_nid]++;
--
2.43.0
next prev parent reply other threads:[~2026-10-28 20:14 UTC|newest]
Thread overview: 20+ 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-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 ` David Hildenbrand (Arm) [this message]
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 ` [PATCH 14/15] RFC: mm/hugetlb: account restored reservations before freeing folios David Hildenbrand (Arm)
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-4-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@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®