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

From: Muchun Song

Date: Thu Sep 17 2026 - 01:58:43 EST




> On Sep 16, 2026, at 16:30, Hongfu Li <hongfu.li@xxxxxxxxx> wrote:
>
> 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]);

READ_ONCE is unnecessary because there is no concurrent writer.

> + 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]);

Same here.

> + 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
>