The deferred split queue handles cgroups in a suboptimal fashion. The queue is per-NUMA node or per-cgroup, not the intersection. That means on a cgrouped system, a node-restricted allocation entering reclaim can end up splitting large pages on other nodes: alloc/unmap deferred_split_folio() list_add_tail(memcg->split_queue) set_shrinker_bit(memcg, node, deferred_shrinker_id) for_each_zone_zonelist_nodemask(restricted_nodes) mem_cgroup_iter() shrink_slab(node, memcg) shrink_slab_memcg(node, memcg) if test_shrinker_bit(memcg, node, deferred_shrinker_id) deferred_split_scan() walks memcg->split_queue The shrinker bit adds an imperfect guard rail. As soon as the cgroup has a single large page on the node of interest, all large pages owned by that memcg, including those on other nodes, will be split. list_lru properly sets up per-node, per-cgroup lists. As a bonus, it streamlines a lot of the list operations and reclaim walks. It's used widely by other major shrinkers already. Convert the deferred split queue as well. The list_lru per-memcg heads are instantiated on demand when the first object of interest is allocated for a cgroup, by calling folio_memcg_alloc_deferred(). Add calls to where splittable pages are created: anon faults, swapin faults, khugepaged collapse. These calls create all possible node heads for the cgroup at once, so the migration code (between nodes) doesn't need any special care. [akpm@linux-foundation.org: fix build with CONFIG_TRANSPARENT_HUGEPAGE=n] Link: https://lore.kernel.org/202605281620.lc3rtkBm-lkp@intel.com [hannes@cmpxchg.org: fix cgroup.memory=nokmem handling] Link: https://lore.kernel.org/ah9PGv12mqai84ES@cmpxchg.org Link: https://lore.kernel.org/20260527204757.2544958-10-hannes@cmpxchg.org Signed-off-by: Johannes Weiner <hannes@cmpxchg.org> Reported-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Tested-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Acked-by: Shakeel Butt <shakeel.butt@linux.dev> Reviewed-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org> Acked-by: Usama Arif <usama.arif@linux.dev> Reviewed-by: Kairui Song <kasong@tencent.com> Cc: Baolin Wang <baolin.wang@linux.alibaba.com> Cc: Barry Song <baohua@kernel.org> Cc: Dave Chinner <david@fromorbit.com> Cc: David Hildenbrand (Arm) <david@kernel.org> Cc: Dev Jain <dev.jain@arm.com> Cc: Lance Yang <lance.yang@linux.dev> Cc: Liam R. Howlett <liam@infradead.org> Cc: Michal Hocko <mhocko@kernel.org> Cc: Muchun Song <muchun.song@linux.dev> Cc: Nico Pache <npache@redhat.com> Cc: Roman Gushchin <roman.gushchin@linux.dev> Cc: Ryan Roberts <ryan.roberts@arm.com> Cc: Vasily Gorbik <gor@linux.ibm.com> Cc: Vlastimil Babka <vbabka@kernel.org> Cc: Zi Yan <ziy@nvidia.com> Cc: kernel test robot <lkp@intel.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Showing the changes to include (3 of 10 files). Show all
Unified · Split
| @@ -439,10 +439,10 @@ static inline int split_huge_page(struct page *page) · 438 unmodified lines | |||
| 439 | { | 439 | { |
| 440 | return split_huge_page_to_list_to_order(page, NULL, 0); | 440 | return split_huge_page_to_list_to_order(page, NULL, 0); |
| 441 | } | 441 | } |
| 442 | |||
| 443 | int folio_memcg_alloc_deferred(struct folio *folio); | ||
| 444 | |||
| 442 | void deferred_split_folio(struct folio *folio, bool partially_mapped); | 445 | void deferred_split_folio(struct folio *folio, bool partially_mapped); |
| 443 | #ifdef CONFIG_MEMCG | ||
| 444 | void reparent_deferred_split_queue(struct mem_cgroup *memcg); | ||
| 445 | #endif | ||
| 446 | 446 | ||
| 447 | void __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd, | 447 | void __split_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd, |
| 448 | unsigned long address, bool freeze); | 448 | unsigned long address, bool freeze); |
| @@ -679,8 +679,15 @@ static inline int try_folio_split_to_order(struct folio *folio, · 230 unmodified lines | |||
| 679 | return -EINVAL; | 679 | return -EINVAL; |
| 680 | } | 680 | } |
| 681 | 681 | ||
| 682 | static inline void deferred_split_folio(struct folio *folio, bool partially_mapped) {} | 682 | static inline int folio_memcg_alloc_deferred(struct folio *folio) |
| 683 | static inline void reparent_deferred_split_queue(struct mem_cgroup *memcg) {} | 683 | { |
| 684 | return 0; | ||
| 685 | } | ||
| 686 | |||
| 687 | static inline void deferred_split_folio(struct folio *folio, bool partially_mapped) | ||
| 688 | { | ||
| 689 | } | ||
| 690 | |||
| 684 | #define split_huge_pmd(__vma, __pmd, __address) \ | 691 | #define split_huge_pmd(__vma, __pmd, __address) \ |
| 685 | do { } while (0) | 692 | do { } while (0) |
| 686 | 693 | ||
| @@ -278,10 +278,6 @@ struct mem_cgroup { · 277 unmodified lines | |||
| 278 | struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; | 278 | struct memcg_cgwb_frn cgwb_frn[MEMCG_CGWB_FRN_CNT]; |
| 279 | #endif | 279 | #endif |
| 280 | 280 | ||
| 281 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE | ||
| 282 | struct deferred_split deferred_split_queue; | ||
| 283 | #endif | ||
| 284 | |||
| 285 | #ifdef CONFIG_LRU_GEN_WALKS_MMU | 281 | #ifdef CONFIG_LRU_GEN_WALKS_MMU |
| 286 | /* per-memcg mm_struct list */ | 282 | /* per-memcg mm_struct list */ |
| 287 | struct lru_gen_mm_list mm_list; | 283 | struct lru_gen_mm_list mm_list; |
| @@ -1431,14 +1431,6 @@ struct zonelist { · 1430 unmodified lines | |||
| 1431 | */ | 1431 | */ |
| 1432 | extern struct page *mem_map; | 1432 | extern struct page *mem_map; |
| 1433 | 1433 | ||
| 1434 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE | ||
| 1435 | struct deferred_split { | ||
| 1436 | spinlock_t split_queue_lock; | ||
| 1437 | struct list_head split_queue; | ||
| 1438 | unsigned long split_queue_len; | ||
| 1439 | }; | ||
| 1440 | #endif | ||
| 1441 | |||
| 1442 | #ifdef CONFIG_MEMORY_FAILURE | 1434 | #ifdef CONFIG_MEMORY_FAILURE |
| 1443 | /* | 1435 | /* |
| 1444 | * Per NUMA node memory failure handling statistics. | 1436 | * Per NUMA node memory failure handling statistics. |
| @@ -1564,10 +1556,6 @@ typedef struct pglist_data { · 119 unmodified lines | |||
| 1564 | unsigned long first_deferred_pfn; | 1556 | unsigned long first_deferred_pfn; |
| 1565 | #endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */ | 1557 | #endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */ |
| 1566 | 1558 | ||
| 1567 | #ifdef CONFIG_TRANSPARENT_HUGEPAGE | ||
| 1568 | struct deferred_split deferred_split_queue; | ||
| 1569 | #endif | ||
| 1570 | |||
| 1571 | #ifdef CONFIG_NUMA_BALANCING | 1559 | #ifdef CONFIG_NUMA_BALANCING |
| 1572 | /* start time in ms of current promote rate limit period */ | 1560 | /* start time in ms of current promote rate limit period */ |
| 1573 | unsigned int nbp_rl_start; | 1561 | unsigned int nbp_rl_start; |