Re: [PATCH 2/2] mm/memcg: migrate per-node hugetlb lruvec stat together with hugetlb folio

From: Muchun Song

Date: Tue Sep 22 2026 - 02:24:59 EST




> On Sep 21, 2026, at 17:12, Hongfu Li <hongfu.li@xxxxxxxxx> wrote:
>
> From: Hongfu Li <lihongfu@xxxxxxxxxx>
>
> memory.numa_stat exposes per-node hugetlb counters from per-node lruvec
> stats. These stats are accounted against folio_nid(): incremented on
> the folio's node when handed to a user, decremented when the folio is
> returned to the pool.
>
> During hugetlb folio migration, mem_cgroup_migrate() moves the charge
> to the new folio and drops the memcg data of the old one, so the free
> of the old folio right after the migration skips the memcg per-node
> lruvec decrement. The hugetlb count stays attributed to the old node
> for the rest of the life of the charge, while the target folio gets no
> increment on the new node; its later free decrements a counter that
> was never incremented.
>
> Migrate the per-node lruvec accounting alongside migration. Global
> memcg totals remain balanced because they track resource consumption,
> not node placement.
>
> Fixes: 05d4532b60e3 ("memcg/hugetlb: add hugeTLB counters to memcg")
> Cc: stable@xxxxxxxxxxxxxxx
> Signed-off-by: Hongfu Li <lihongfu@xxxxxxxxxx>
> ---
> mm/memcontrol.c | 31 +++++++++++++++++++++++++++++++
> 1 file changed, 31 insertions(+)
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 1460cba53588..9c96ebd5436f 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -5598,6 +5598,34 @@ void mem_cgroup_replace_folio(struct folio *old, struct folio *new)
> rcu_read_unlock();
> }
>
> +#ifdef CONFIG_HUGETLB_PAGE
> +static void move_hugetlb_lruvec_stat(struct obj_cgroup *objcg,

Actually, we don't need this objcg parameter since we could get it from folio.
And I'd like to move this function to hugetlb.c.

> + struct folio *old, struct folio *new)
> +{
> + long nr_pages = folio_nr_pages(old);
> + struct mem_cgroup *memcg;
> + int old_nid = folio_nid(old);
> + int new_nid = folio_nid(new);
> +
> + if (old_nid == new_nid)
> + return;
> +
> + rcu_read_lock();

Please use guard(rcu)() to simplify the code a little.

Thanks.

> + memcg = obj_cgroup_memcg(objcg);
> + mod_memcg_lruvec_state(mem_cgroup_lruvec(memcg, NODE_DATA(old_nid)),
> + NR_HUGETLB, -nr_pages);
> + mod_memcg_lruvec_state(mem_cgroup_lruvec(memcg, NODE_DATA(new_nid)),
> + NR_HUGETLB, nr_pages);
> + rcu_read_unlock();
> +}
> +#else /* CONFIG_HUGETLB_PAGE */
> +static inline void move_hugetlb_lruvec_stat(struct obj_cgroup *objcg,
> + struct folio *old,
> + struct folio *new)
> +{
> +}
> +#endif /* CONFIG_HUGETLB_PAGE */
> +
> /**
> * mem_cgroup_migrate - Transfer the memcg data from the old to the new folio.
> * @old: Currently circulating folio.
> @@ -5635,6 +5663,9 @@ void mem_cgroup_migrate(struct folio *old, struct folio *new)
>
> new_objcg = get_migration_objcg(old, new);
>
> + if (folio_test_hugetlb(old))
> + move_hugetlb_lruvec_stat(new_objcg, old, new);
> +
> /*
> * @old was charged through a non-root objcg, so its charge is in the
> * page counters. If the re-derivation walked up to the root objcg -
>
> --
> 2.54.0
>