[PATCH 1/2] mm/hugetlb_cgroup: move per-node usage on cross node migration

From: Hongfu Li

Date: Wed Sep 16 2026 - 04:38:31 EST


From: Hongfu Li <lihongfu@xxxxxxxxxx>

hugetlb.<size>.numa_stat uses folio_nid() to account usage in
__hugetlb_cgroup_commit_charge() and __hugetlb_cgroup_uncharge_folio().
hugetlb_cgroup_migrate() only moves hugetlb_cgroup pointers, leaving
per-node usage behind on the source node during cross-node migration.

When the migrated folio gets uncharged, we subtract usage from the
destination node counter. This creates stale usage on the source node
and unsigned long counter underflow on the destination node.
The hugetlb.<size>.numa_stat interface exposes these incorrect per-node
usage values to userspace.

Add a hugetlb_cgroup_move_usage() helper which moves the usage from the
old node to the new node, and call it from hugetlb_cgroup_migrate().

Fixes: f47761999052 ("hugetlb: add hugetlb.*.numa_stat file")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Hongfu Li <lihongfu@xxxxxxxxxx>
---
mm/hugetlb_cgroup.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)

diff --git a/mm/hugetlb_cgroup.c b/mm/hugetlb_cgroup.c
index ecb6e0b7819a..1040406e7e1d 100644
--- a/mm/hugetlb_cgroup.c
+++ b/mm/hugetlb_cgroup.c
@@ -179,6 +179,34 @@ static void hugetlb_cgroup_css_free(struct cgroup_subsys_state *css)
hugetlb_cgroup_free(hugetlb_cgroup_from_css(css));
}

+static void hugetlb_cgroup_move_usage(struct hugetlb_cgroup *from,
+ struct hugetlb_cgroup *to,
+ struct folio *from_folio,
+ struct folio *to_folio)
+{
+ int idx = hstate_index(folio_hstate(from_folio));
+ unsigned long nr_pages = folio_nr_pages(from_folio);
+ int from_nid = folio_nid(from_folio);
+ int to_nid = folio_nid(to_folio);
+ unsigned long usage;
+
+ lockdep_assert_held(&hugetlb_lock);
+
+ if (!from || !to)
+ return;
+
+ if (from == to && from_nid == to_nid)
+ return;
+
+ usage = READ_ONCE(from->nodeinfo[from_nid]->usage[idx]);
+ if (WARN_ON_ONCE(usage < nr_pages))
+ return;
+ WRITE_ONCE(from->nodeinfo[from_nid]->usage[idx], usage - nr_pages);
+
+ usage = READ_ONCE(to->nodeinfo[to_nid]->usage[idx]);
+ WRITE_ONCE(to->nodeinfo[to_nid]->usage[idx], usage + nr_pages);
+}
+
/*
* Should be called with hugetlb_lock held.
* Since we are holding hugetlb_lock, pages cannot get moved from
@@ -906,6 +934,9 @@ void hugetlb_cgroup_migrate(struct folio *old_folio, struct folio *new_folio)
/* move the h_cg details to new cgroup */
set_hugetlb_cgroup(new_folio, h_cg);
set_hugetlb_cgroup_rsvd(new_folio, h_cg_rsvd);
+
+ hugetlb_cgroup_move_usage(h_cg, h_cg, old_folio, new_folio);
+
list_move(&new_folio->lru, &h->hugepage_activelist);
spin_unlock_irq(&hugetlb_lock);
}

--
2.54.0