[PATCH v5 5/5] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks
From: Yuan Liu
Date: Wed May 20 2026 - 05:38:37 EST
When shrinking a zone span after removing a PFN range,
find_smallest_section_pfn() and find_biggest_section_pfn()
only checked one edge PFN in each subsection for nid/zone matching.
If a memory or hole boundary falls in the middle of a subsection,
that edge PFN may belong to a different nid/zone, causing the helpers
to miss a valid PFN within that subsection.
Fix this by checking both subsection edge PFNs for nid/zone matching.
Keep a single pfn_to_online_page() check per subsection, since online
state is the same for all PFNs in a subsection.
Reviewed-by: Wei Yang <richard.weiyang@xxxxxxxxx>
Reviewed-by: Jason Zeng <jason.zeng@xxxxxxxxx>
Signed-off-by: Yuan Liu <yuan1.liu@xxxxxxxxx>
---
mm/memory_hotplug.c | 42 +++++++++++++++++++++++++++---------------
1 file changed, 27 insertions(+), 15 deletions(-)
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index fbe863441761..20b61f70cd81 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -427,17 +427,24 @@ static unsigned long find_smallest_section_pfn(int nid, struct zone *zone,
unsigned long start_pfn,
unsigned long end_pfn)
{
- for (; start_pfn < end_pfn; start_pfn += PAGES_PER_SUBSECTION) {
- if (unlikely(!pfn_to_online_page(start_pfn)))
- continue;
+ unsigned long next_pfn;
- if (unlikely(pfn_to_nid(start_pfn) != nid))
- continue;
+ for (; start_pfn < end_pfn; start_pfn = next_pfn) {
+ unsigned long tail_pfn;
- if (zone != page_zone(pfn_to_page(start_pfn)))
+ next_pfn = start_pfn + PAGES_PER_SUBSECTION;
+ tail_pfn = next_pfn - 1;
+
+ if (unlikely(!pfn_to_online_page(start_pfn)))
continue;
- return start_pfn;
+ if (likely(pfn_to_nid(start_pfn) == nid) &&
+ zone == page_zone(pfn_to_page(start_pfn)))
+ return start_pfn;
+
+ if (likely(pfn_to_nid(tail_pfn) == nid) &&
+ zone == page_zone(pfn_to_page(tail_pfn)))
+ return start_pfn;
}
return 0;
@@ -448,21 +455,26 @@ static unsigned long find_biggest_section_pfn(int nid, struct zone *zone,
unsigned long start_pfn,
unsigned long end_pfn)
{
- unsigned long pfn;
+ unsigned long pfn, prev_pfn;
/* pfn is the end pfn of a memory section. */
pfn = end_pfn - 1;
- for (; pfn >= start_pfn; pfn -= PAGES_PER_SUBSECTION) {
- if (unlikely(!pfn_to_online_page(pfn)))
- continue;
+ for (; pfn >= start_pfn; pfn = prev_pfn) {
+ unsigned long head_pfn;
- if (unlikely(pfn_to_nid(pfn) != nid))
- continue;
+ prev_pfn = pfn - PAGES_PER_SUBSECTION;
+ head_pfn = prev_pfn + 1;
- if (zone != page_zone(pfn_to_page(pfn)))
+ if (unlikely(!pfn_to_online_page(pfn)))
continue;
- return pfn;
+ if (likely(pfn_to_nid(pfn) == nid) &&
+ zone == page_zone(pfn_to_page(pfn)))
+ return pfn;
+
+ if (likely(pfn_to_nid(head_pfn) == nid) &&
+ zone == page_zone(pfn_to_page(head_pfn)))
+ return pfn;
}
return 0;
--
2.47.3