mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
mm/hugetlb: account migration target folio in per-node NR_HUGETLB vmstat
Patch series "mm: fix hugetlb NR_HUGETLB accounting on folio migration", v2. The hugeTLB counters added by05d4532b60("memcg/hugetlb: add hugeTLB counters to memcg") are maintained in two per-node places: - the per-node vmstat counter NR_HUGETLB, exposed as nr_hugetlb in /proc/vmstat; - the per-node memcg lruvec stat, exposed via memory.numa_stat. Both are accounted against the folio's node, and both drift when a hugetlb folio is migrated, though in different ways. A migration target folio is allocated by alloc_hugetlb_folio_nodemask() and inherits the old folio's state without ever being accounted, while the old folio is freed right after and its free is accounted. That alone loses vmstat accounting: the target node has no matching increment for the decrement on the old node, so /proc/vmstat's nr_hugetlb shrinks by nr_pages per migration. Patch 1 accounts the folio where it is obtained, so the increment pairs with the free in free_huge_folio() on the successful as well as the failed migration path. The memfd page cache preallocation helper has the same asymmetry and is fixed in the same patch. The per-node lruvec stat breaks differently. mem_cgroup_migrate() moves the charge to the new folio and drops the old folio's memcg data, so the old folio's free right after migration skips the memcg per-node decrement; the count stays attributed to the old node for the rest of the charge's life, and the target folio never gets an increment on its new node. Patch 2 moves that per-node accounting along with the charge, in move_hugetlb_state(). This patch (of 2): The NR_HUGETLB vmstat counter is maintained per folio's node: incremented when a huge page is handed to a user via hugetlb_alloc_folio() and decremented when it is returned to the pool via free_huge_folio(). A folio obtained by alloc_hugetlb_folio_nodemask() never goes through hugetlb_alloc_folio(), so it is never accounted, while its free always is. For a migration target this means the target node gets no matching increment for the decrement on the old node, so the global nr_hugetlb in /proc/vmstat drops by nr_pages for each migration. The same asymmetry affects the failed migration path, which frees the target again right away, and the temporary folio hugetlb_mfill_atomic_pte() takes from the same helper. alloc_hugetlb_folio_reserve(), used to preallocate the memfd page cache folios, has the same asymmetry: the folio is handed to a user without being accounted, while its free is accounted through free_huge_folio(). Account the folio where it is obtained, in alloc_hugetlb_folio_nodemask() and alloc_hugetlb_folio_reserve(), so that the increment pairs with the decrement in free_huge_folio(): a successful migration hands the folio to a user, a failed one frees it again. Link: https://lore.kernel.org/20260923-for-hugetlb_state3-v2-0-e8a36245bfab@kylinos.cn Link: https://lore.kernel.org/20260923-for-hugetlb_state3-v2-1-e8a36245bfab@kylinos.cn Fixes:05d4532b60("memcg/hugetlb: add hugeTLB counters to memcg") Signed-off-by: Hongfu Li <lihongfu@kylinos.cn> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Tested-by: Joshua Hahn <joshua.hahnjy@gmail.com> Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com> Acked-by: Muchun Song <muchun.song@linux.dev> Acked-by: Oscar Salvador <osalvador@suse.de> Cc: David Hildenbrand <david@kernel.org> Cc: Shakeel Butt <shakeel.butt@linux.dev> Cc: Michal Hocko <mhocko@suse.com> Cc: Johannes Weiner <hannes@cmpxchg.org> Cc: Nhat Pham <nphamcs@gmail.com> Cc: Michal Hocko <mhocko@kernel.org> Cc: Roman Gushchin <roman.gushchin@linux.dev> Cc: Chris Down <chris@chrisdown.name> Cc: <stable@vger.kernel.org>
This commit is contained in:
parent
cc078ca0b5
commit
92635a3ffe
35
mm/hugetlb.c
35
mm/hugetlb.c
|
|
@ -2206,6 +2206,11 @@ struct folio *alloc_hugetlb_folio_reserve(struct hstate *h, int preferred_nid,
|
|||
}
|
||||
|
||||
spin_unlock_irq(&hugetlb_lock);
|
||||
|
||||
if (folio)
|
||||
lruvec_stat_mod_folio(folio, NR_HUGETLB,
|
||||
folio_nr_pages(folio));
|
||||
|
||||
return folio;
|
||||
}
|
||||
|
||||
|
|
@ -2213,24 +2218,30 @@ struct folio *alloc_hugetlb_folio_reserve(struct hstate *h, int preferred_nid,
|
|||
struct folio *alloc_hugetlb_folio_nodemask(struct hstate *h, int preferred_nid,
|
||||
nodemask_t *nmask, gfp_t gfp_mask, bool allow_alloc_fallback)
|
||||
{
|
||||
spin_lock_irq(&hugetlb_lock);
|
||||
if (available_huge_pages(h)) {
|
||||
struct folio *folio;
|
||||
struct folio *folio = NULL;
|
||||
|
||||
spin_lock_irq(&hugetlb_lock);
|
||||
if (available_huge_pages(h))
|
||||
folio = dequeue_hugetlb_folio_nodemask(h, gfp_mask,
|
||||
preferred_nid, nmask);
|
||||
if (folio) {
|
||||
spin_unlock_irq(&hugetlb_lock);
|
||||
return folio;
|
||||
}
|
||||
}
|
||||
spin_unlock_irq(&hugetlb_lock);
|
||||
|
||||
/* We cannot fallback to other nodes, as we could break the per-node pool. */
|
||||
if (!allow_alloc_fallback)
|
||||
gfp_mask |= __GFP_THISNODE;
|
||||
if (!folio) {
|
||||
/*
|
||||
* We cannot fallback to other nodes, as we could break the
|
||||
* per-node pool.
|
||||
*/
|
||||
if (!allow_alloc_fallback)
|
||||
gfp_mask |= __GFP_THISNODE;
|
||||
|
||||
return alloc_migrate_hugetlb_folio(h, gfp_mask, preferred_nid, nmask);
|
||||
folio = alloc_migrate_hugetlb_folio(h, gfp_mask, preferred_nid,
|
||||
nmask);
|
||||
}
|
||||
|
||||
if (folio)
|
||||
lruvec_stat_mod_folio(folio, NR_HUGETLB, folio_nr_pages(folio));
|
||||
|
||||
return folio;
|
||||
}
|
||||
|
||||
static nodemask_t *policy_mbind_nodemask(gfp_t gfp)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user