Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v6 0/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range
@ 2026-07-23  8:49 Yuan Liu
  2026-07-23  8:49 ` [PATCH v6 1/2] " Yuan Liu
  2026-07-23  8:49 ` [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks Yuan Liu
  0 siblings, 2 replies; 4+ messages in thread
From: Yuan Liu @ 2026-07-23  8:49 UTC (permalink / raw)
  To: David Hildenbrand, Oscar Salvador, Mike Rapoport, Wei Yang
  Cc: linux-mm, Nanhai Zou, Yuan Liu, Pan Deng, Tianyou Li, Chen Zhang,
	Jason Zeng, linux-kernel

This series introduces a pages_with_online_memmap member into struct
zone to avoid pageblock-by-pageblock scans across the entire zone and
improve memory hotplug performance.

Reworked how pages_with_online_memmap accounts for PFNs in early
boot memory in response to Sashiko's review comments [2], which
identified potential issues with the previous implementation.

Approach
========
Add a new zone member pages_with_online_memmap that tracks the number of
pages within the zone span that have an online memory map (including
present pages and memory holes smaller than a subsection whose memory
map has been initialized). When spanned_pages == pages_with_online_memmap
the zone is contiguous and pfn_to_page() can be called on any PFN in the
zone span without further pfn_valid() checks.

For early boot memory, pages_with_online_memmap is calculated in
memmap_init_zone_range(). Since every PFN within a memblock region
satisfies pfn_to_online_page(), the calculation aligns both boundaries
of each memblock range to PAGES_PER_SUBSECTION and counts the aligned
pages that fall within the zone span. If two adjacent memblock regions
share a subsection (i.e. the hole between them is smaller than a
subsection), the overlapping subsection pages are counted only once.
For hotplugged memory, pages_with_online_memmap is updated through
adjust_present_page_count(), which is called during memory online and
offline.

Only pages that fall within the current zone span are accounted towards
pages_with_online_memmap. A "too small" value is safe, it merely
prevents detecting a contiguous zone.

The contiguity check using pages_with_online_memmap is stricter than the
old pageblock-by-pageblock scan. The old set_zone_contiguous() iterated
at pageblock granularity via pageblock_pfn_to_page(), so a zone could be
marked contiguous even if a subsection-sized hole existed within a
pageblock. The new check requires spanned_pages == pages_with_online_memmap,
meaning every PFN in the zone span must satisfy pfn_to_online_page().

Performance
===========
1. For VM hotplug performance data, please refer to Patch 1.
2. This series also benefits CXL hotplug. Performance results are
   as follows
   https://lore.kernel.org/all/20260409023552.GA2807@AE/

Tested cases
============
1. Hotplug/unplug correctness with both online_movable and online
   policies, including partial unplug, middle-block offline gaps, and
   edge-block span shrink.
2. Large scale (256G/512G) plug/unplug performance.
3. Boot-time subsection holes (aligned/unaligned, in-zone and
   cross-zone) with correct pages_with_online_memmap accounting.
4. kernelcore=mirror: verified no overcounting.

Patch overview
==============
This series is based on the kernelcore-mirror branch [1].

Patch 1 introduces pages_with_online_memmap to replace
pageblock-by-pageblock scans across the entire zone for zone contiguity
checks.

Patch 2 avoids incorrectly shrinking the zone span during memory unplug
when a memory or hole boundary falls in the middle of a subsection.

v6 changes:
  1. Dropped patches 1-3 from v5. The kernelcore=mirror overlap issue
     that caused pages_with_online_memmap to be overcounted has been
     resolved by the series [1].
  2. Reworked pages_with_online_memmap accounting during early boot.

v5:
    https://lore.kernel.org/linux-mm/20260520093457.3719960-1-yuan1.liu@intel.com/

v4:
    https://lore.kernel.org/linux-mm/20260421125508.2317429-1-yuan1.liu@intel.com/

v3:
    https://lore.kernel.org/linux-mm/20260408031615.1831922-1-yuan1.liu@intel.com/

[1] https://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git/log/?h=kernelcore-mirror
[2] https://sashiko.dev/#/patchset/20260520093457.3719960-3-yuan1.liu@intel.com

Yuan Liu (2):
  mm/memory_hotplug: optimize zone contiguous check when changing pfn
    range
  mm/memory_hotplug: improve shrink_zone_span() subsection boundary
    checks

 Documentation/mm/physical_memory.rst | 13 +++++++
 drivers/base/memory.c                |  6 +++
 include/linux/mmzone.h               | 47 ++++++++++++++++++++++
 mm/internal.h                        |  8 +---
 mm/memory_hotplug.c                  | 54 ++++++++++++++------------
 mm/mm_init.c                         | 58 +++++++++++++++++-----------
 6 files changed, 132 insertions(+), 54 deletions(-)


base-commit: b69c2a1fa9beb4c3db24b4013e07f5cb7e7260aa
-- 
2.47.3



^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH v6 1/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range
  2026-07-23  8:49 [PATCH v6 0/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range Yuan Liu
@ 2026-07-23  8:49 ` Yuan Liu
  2026-07-23  8:49 ` [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks Yuan Liu
  1 sibling, 0 replies; 4+ messages in thread
From: Yuan Liu @ 2026-07-23  8:49 UTC (permalink / raw)
  To: David Hildenbrand, Oscar Salvador, Mike Rapoport, Wei Yang
  Cc: linux-mm, Nanhai Zou, Yuan Liu, Pan Deng, Tianyou Li, Chen Zhang,
	Jason Zeng, linux-kernel

When move_pfn_range_to_zone() or remove_pfn_range_from_zone() updates a
zone, set_zone_contiguous() rescans the entire zone
pageblock-by-pageblock to rebuild zone->contiguous. For large zones this
is a significant cost during memory hotplug and hot-unplug.

Add a new zone member pages_with_online_memmap that tracks the number of
pages within the zone span that have an online memory map (including
present pages and memory holes smaller than a subsection whose memory
map has been initialized). When spanned_pages == pages_with_online_memmap
the zone is contiguous and pfn_to_page() can be called on any PFN in the
zone span without further pfn_valid() checks.

For early boot memory, pages_with_online_memmap is calculated in
memmap_init_zone_range(). Since every PFN within a memblock region
satisfies pfn_to_online_page(), the calculation aligns both boundaries
of each memblock range to PAGES_PER_SUBSECTION and counts the aligned
pages that fall within the zone span. If two adjacent memblock regions
share a subsection (i.e. the hole between them is smaller than a
subsection), the overlapping subsection pages are counted only once.
For hotplugged memory, pages_with_online_memmap is updated through
adjust_present_page_count(), which is called during memory online and
offline.

Only pages that fall within the current zone span are accounted towards
pages_with_online_memmap. A "too small" value is safe, it merely
prevents detecting a contiguous zone.

The contiguity check using pages_with_online_memmap is stricter than the
old pageblock-by-pageblock scan. The old set_zone_contiguous() iterated
at pageblock granularity via pageblock_pfn_to_page(), so a zone could be
marked contiguous even if a subsection-sized hole existed within a
pageblock. The new check requires
spanned_pages == pages_with_online_memmap, meaning every PFN in the zone
span must satisfy pfn_to_online_page().

The following test cases of memory hotplug for a VM [1], tested in the
environment [2], show that this optimization can significantly reduce
the memory hotplug time [3].

+----------------+------+---------------+--------------+----------------+
|                | Size | Time (before) | Time (after) | Time Reduction |
|                +------+---------------+--------------+----------------+
| Plug Memory    | 256G |      10s      |      3s      |       70%      |
|                +------+---------------+--------------+----------------+
|                | 512G |      36s      |      7s      |       81%      |
+----------------+------+---------------+--------------+----------------+

+----------------+------+---------------+--------------+----------------+
|                | Size | Time (before) | Time (after) | Time Reduction |
|                +------+---------------+--------------+----------------+
| Unplug Memory  | 256G |      11s      |      4s      |       64%      |
|                +------+---------------+--------------+----------------+
|                | 512G |      36s      |      9s      |       75%      |
+----------------+------+---------------+--------------+----------------+

[1] Qemu commands to hotplug 256G/512G memory for a VM:
    object_add memory-backend-ram,id=hotmem0,size=256G/512G,share=on
    device_add virtio-mem-pci,id=vmem1,memdev=hotmem0,bus=port1
    qom-set vmem1 requested-size 256G/512G (Plug Memory)
    qom-set vmem1 requested-size 0G (Unplug Memory)

[2] Hardware     : Intel Icelake server
    Guest Kernel : v7.0-rc4
    Qemu         : v9.0.0

    Launch VM    :
    qemu-system-x86_64 -accel kvm -cpu host \
    -drive file=./Centos10_cloud.qcow2,format=qcow2,if=virtio \
    -drive file=./seed.img,format=raw,if=virtio \
    -smp 3,cores=3,threads=1,sockets=1,maxcpus=3 \
    -m 2G,slots=10,maxmem=2052472M \
    -device pcie-root-port,id=port1,bus=pcie.0,slot=1,multifunction=on \
    -device pcie-root-port,id=port2,bus=pcie.0,slot=2 \
    -nographic -machine q35 \
    -nic user,hostfwd=tcp::3000-:22

    Guest kernel auto-onlines newly added memory blocks:
    echo online > /sys/devices/system/memory/auto_online_blocks

[3] The time from typing the QEMU commands in [1] to when the output of
    'grep MemTotal /proc/meminfo' on Guest reflects that all hotplugged
    memory is recognized.

Reported-by: Nanhai Zou <nanhai.zou@intel.com>
Reported-by: Chen Zhang <zhangchen.kidd@jd.com>
Reviewed-by: Pan Deng <pan.deng@intel.com>
Reviewed-by: Jason Zeng <jason.zeng@intel.com>
Co-developed-by: Tianyou Li <tianyou.li@intel.com>
Signed-off-by: Tianyou Li <tianyou.li@intel.com>
Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
---
 Documentation/mm/physical_memory.rst | 13 +++++++
 drivers/base/memory.c                |  6 +++
 include/linux/mmzone.h               | 47 ++++++++++++++++++++++
 mm/internal.h                        |  8 +---
 mm/memory_hotplug.c                  | 12 +-----
 mm/mm_init.c                         | 58 +++++++++++++++++-----------
 6 files changed, 105 insertions(+), 39 deletions(-)

diff --git a/Documentation/mm/physical_memory.rst b/Documentation/mm/physical_memory.rst
index b76183545e5b..0aa65e6b5499 100644
--- a/Documentation/mm/physical_memory.rst
+++ b/Documentation/mm/physical_memory.rst
@@ -483,6 +483,19 @@ General
   ``present_pages`` should use ``get_online_mems()`` to get a stable value. It
   is initialized by ``calculate_node_totalpages()``.
 
+``pages_with_online_memmap``
+  Tracks pages within the zone that have an online memory map (present pages
+  and memory holes whose memory map has been initialized). When
+  ``spanned_pages`` == ``pages_with_online_memmap``, ``pfn_to_page()`` can be
+  performed without further checks on any PFN within the zone span.
+
+  Note: this counter may temporarily undercount when pages with an online
+  memory map exist outside the current zone span. This can only happen during
+  boot, when initializing the memory map of pages that do not fall into any
+  zone span. Growing the zone to cover such pages and later shrinking it back
+  may result in a "too small" value. This is safe: it merely prevents
+  detecting a contiguous zone.
+
 ``present_early_pages``
   The present pages existing within the zone located on memory available since
   early boot, excluding hotplugged memory. Defined only when
diff --git a/drivers/base/memory.c b/drivers/base/memory.c
index bcfe2d9f4adb..237ace435372 100644
--- a/drivers/base/memory.c
+++ b/drivers/base/memory.c
@@ -246,6 +246,7 @@ static int memory_block_online(struct memory_block *mem)
 		nr_vmemmap_pages = mem->altmap->free;
 
 	mem_hotplug_begin();
+	clear_zone_contiguous(zone);
 	if (nr_vmemmap_pages) {
 		ret = mhp_init_memmap_on_memory(start_pfn, nr_vmemmap_pages, zone);
 		if (ret)
@@ -270,6 +271,7 @@ static int memory_block_online(struct memory_block *mem)
 
 	mem->zone = zone;
 out:
+	set_zone_contiguous(zone);
 	mem_hotplug_done();
 	return ret;
 }
@@ -282,6 +284,7 @@ static int memory_block_offline(struct memory_block *mem)
 	unsigned long start_pfn = section_nr_to_pfn(mem->start_section_nr);
 	unsigned long nr_pages = PAGES_PER_SECTION * sections_per_block;
 	unsigned long nr_vmemmap_pages = 0;
+	struct zone *zone;
 	int ret;
 
 	if (!mem->zone)
@@ -294,7 +297,9 @@ static int memory_block_offline(struct memory_block *mem)
 	if (mem->altmap)
 		nr_vmemmap_pages = mem->altmap->free;
 
+	zone = mem->zone;
 	mem_hotplug_begin();
+	clear_zone_contiguous(zone);
 	if (nr_vmemmap_pages)
 		adjust_present_page_count(pfn_to_page(start_pfn), mem->group,
 					  -nr_vmemmap_pages);
@@ -314,6 +319,7 @@ static int memory_block_offline(struct memory_block *mem)
 
 	mem->zone = NULL;
 out:
+	set_zone_contiguous(zone);
 	mem_hotplug_done();
 	return ret;
 }
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ca2712187147..ac1467ac3336 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1032,6 +1032,20 @@ struct zone {
 	 * cma pages is present pages that are assigned for CMA use
 	 * (MIGRATE_CMA).
 	 *
+	 * pages_with_online_memmap tracks pages within the zone that have
+	 * an online memory map (present pages and memory holes whose memory
+	 * map has been initialized). When spanned_pages ==
+	 * pages_with_online_memmap, pfn_to_page() can be performed without
+	 * further checks on any PFN within the zone span.
+	 *
+	 * Note: this counter may temporarily undercount when pages with an
+	 * online memory map exist outside the current zone span. This can
+	 * only happen during boot, when initializing the memory map of
+	 * pages that do not fall into any zone span. Growing the zone to
+	 * cover such pages and later shrinking it back may result in a
+	 * "too small" value. This is safe: it merely prevents detecting a
+	 * contiguous zone.
+	 *
 	 * So present_pages may be used by memory hotplug or memory power
 	 * management logic to figure out unmanaged pages by checking
 	 * (present_pages - managed_pages). And managed_pages should be used
@@ -1056,6 +1070,7 @@ struct zone {
 	atomic_long_t		managed_pages;
 	unsigned long		spanned_pages;
 	unsigned long		present_pages;
+	unsigned long		pages_with_online_memmap;
 #if defined(CONFIG_MEMORY_HOTPLUG)
 	unsigned long		present_early_pages;
 #endif
@@ -1681,6 +1696,38 @@ static inline bool zone_is_zone_device(const struct zone *zone)
 }
 #endif
 
+/**
+ * zone_is_contiguous - test whether a zone is contiguous
+ * @zone: the zone to test.
+ *
+ * In a contiguous zone, it is valid to call pfn_to_page() on any PFN in the
+ * spanned zone without requiring pfn_valid() or pfn_to_online_page() checks.
+ *
+ * Note that missing synchronization with memory offlining makes any PFN
+ * traversal prone to races.
+ *
+ * ZONE_DEVICE zones are always marked non-contiguous.
+ *
+ * Return: true if contiguous, otherwise false.
+ */
+static inline bool zone_is_contiguous(const struct zone *zone)
+{
+	return zone->contiguous;
+}
+
+static inline void set_zone_contiguous(struct zone *zone)
+{
+	if (zone_is_zone_device(zone))
+		return;
+	if (zone->spanned_pages == zone->pages_with_online_memmap)
+		zone->contiguous = true;
+}
+
+static inline void clear_zone_contiguous(struct zone *zone)
+{
+	zone->contiguous = false;
+}
+
 /*
  * Returns true if a zone has pages managed by the buddy allocator.
  * All the reclaim decisions have to use this function rather than
diff --git a/mm/internal.h b/mm/internal.h
index 181e79f1d6a2..f932b7577c92 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -805,21 +805,15 @@ extern struct page *__pageblock_pfn_to_page(unsigned long start_pfn,
 static inline struct page *pageblock_pfn_to_page(unsigned long start_pfn,
 				unsigned long end_pfn, struct zone *zone)
 {
-	if (zone->contiguous)
+	if (zone_is_contiguous(zone))
 		return pfn_to_page(start_pfn);
 
 	return __pageblock_pfn_to_page(start_pfn, end_pfn, zone);
 }
 
-void set_zone_contiguous(struct zone *zone);
 bool pfn_range_intersects_zones(int nid, unsigned long start_pfn,
 			   unsigned long nr_pages);
 
-static inline void clear_zone_contiguous(struct zone *zone)
-{
-	zone->contiguous = false;
-}
-
 extern int __isolate_free_page(struct page *page, unsigned int order);
 extern void __putback_isolated_page(struct page *page, unsigned int order,
 				    int mt);
diff --git a/mm/memory_hotplug.c b/mm/memory_hotplug.c
index 7ac19fab2263..4c699fd9c479 100644
--- a/mm/memory_hotplug.c
+++ b/mm/memory_hotplug.c
@@ -557,18 +557,13 @@ void remove_pfn_range_from_zone(struct zone *zone,
 
 	/*
 	 * Zone shrinking code cannot properly deal with ZONE_DEVICE. So
-	 * we will not try to shrink the zones - which is okay as
-	 * set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
+	 * we will not try to shrink it.
 	 */
 	if (zone_is_zone_device(zone))
 		return;
 
-	clear_zone_contiguous(zone);
-
 	shrink_zone_span(zone, start_pfn, start_pfn + nr_pages);
 	update_pgdat_span(pgdat);
-
-	set_zone_contiguous(zone);
 }
 
 /**
@@ -746,8 +741,6 @@ void move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
 	struct pglist_data *pgdat = zone->zone_pgdat;
 	int nid = pgdat->node_id;
 
-	clear_zone_contiguous(zone);
-
 	if (zone_is_empty(zone))
 		init_currently_empty_zone(zone, start_pfn, nr_pages);
 	resize_zone_range(zone, start_pfn, nr_pages);
@@ -775,8 +768,6 @@ void move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
 	memmap_init_range(nr_pages, nid, zone_idx(zone), start_pfn, 0,
 			 MEMINIT_HOTPLUG, altmap, migratetype,
 			 isolate_pageblock);
-
-	set_zone_contiguous(zone);
 }
 
 struct auto_movable_stats {
@@ -1072,6 +1063,7 @@ void adjust_present_page_count(struct page *page, struct memory_group *group,
 	if (early_section(__pfn_to_section(page_to_pfn(page))))
 		zone->present_early_pages += nr_pages;
 	zone->present_pages += nr_pages;
+	zone->pages_with_online_memmap += nr_pages;
 	zone->zone_pgdat->node_present_pages += nr_pages;
 
 	if (group && movable)
diff --git a/mm/mm_init.c b/mm/mm_init.c
index f1afe023e4e7..73d1fde17040 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -916,6 +916,40 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
 	}
 }
 
+static void __init update_zone_online_memmap_pages(struct zone *zone,
+						   unsigned long start_pfn,
+						   unsigned long end_pfn,
+						   unsigned long *hole_pfn)
+{
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+	unsigned long zone_start_pfn = zone->zone_start_pfn;
+	unsigned long zone_end_pfn = zone_start_pfn + zone->spanned_pages;
+	unsigned long sub_start, sub_end;
+
+	sub_start = max(ALIGN_DOWN(start_pfn, PAGES_PER_SUBSECTION),
+			zone_start_pfn);
+	sub_end = min(ALIGN(end_pfn, PAGES_PER_SUBSECTION), zone_end_pfn);
+
+	/*
+	 * For an in-zone hole smaller than a subsection, the adjacent
+	 * memblock ranges share a subsection. Avoid counting the same
+	 * subsection's pages twice.
+	 */
+	if (*hole_pfn > zone_start_pfn) {
+		unsigned long prev_sub_end;
+
+		prev_sub_end = min(ALIGN(*hole_pfn, PAGES_PER_SUBSECTION),
+				   zone_end_pfn);
+		sub_start = max(sub_start, prev_sub_end);
+	}
+
+	if (sub_start < sub_end)
+		zone->pages_with_online_memmap += sub_end - sub_start;
+#else
+	zone->pages_with_online_memmap += end_pfn - start_pfn;
+#endif
+}
+
 static void __init memmap_init_zone_range(struct zone *zone,
 					  unsigned long start_pfn,
 					  unsigned long end_pfn,
@@ -932,6 +966,8 @@ static void __init memmap_init_zone_range(struct zone *zone,
 	if (start_pfn >= end_pfn)
 		return;
 
+	update_zone_online_memmap_pages(zone, start_pfn, end_pfn, hole_pfn);
+
 	memmap_init_range(end_pfn - start_pfn, nid, zone_id, start_pfn,
 			  zone_end_pfn, MEMINIT_EARLY, NULL, mt, false);
 
@@ -2195,28 +2231,6 @@ void __init init_cma_pageblock(struct page *page)
 }
 #endif
 
-void set_zone_contiguous(struct zone *zone)
-{
-	unsigned long block_start_pfn = zone->zone_start_pfn;
-	unsigned long block_end_pfn;
-
-	block_end_pfn = pageblock_end_pfn(block_start_pfn);
-	for (; block_start_pfn < zone_end_pfn(zone);
-			block_start_pfn = block_end_pfn,
-			 block_end_pfn += pageblock_nr_pages) {
-
-		block_end_pfn = min(block_end_pfn, zone_end_pfn(zone));
-
-		if (!__pageblock_pfn_to_page(block_start_pfn,
-					     block_end_pfn, zone))
-			return;
-		cond_resched();
-	}
-
-	/* We confirm that there is no hole */
-	zone->contiguous = true;
-}
-
 /*
  * Check if a PFN range intersects multiple zones on one or more
  * NUMA nodes. Specify the @nid argument if it is known that this
-- 
2.47.3



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks
  2026-07-23  8:49 [PATCH v6 0/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range Yuan Liu
  2026-07-23  8:49 ` [PATCH v6 1/2] " Yuan Liu
@ 2026-07-23  8:49 ` Yuan Liu
  2026-07-25  2:49   ` Wei Yang
  1 sibling, 1 reply; 4+ messages in thread
From: Yuan Liu @ 2026-07-23  8:49 UTC (permalink / raw)
  To: David Hildenbrand, Oscar Salvador, Mike Rapoport, Wei Yang
  Cc: linux-mm, Nanhai Zou, Yuan Liu, Pan Deng, Tianyou Li, Chen Zhang,
	Jason Zeng, linux-kernel

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: Jason Zeng <jason.zeng@intel.com>
Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
---
 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 4c699fd9c479..3a281d595207 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



^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks
  2026-07-23  8:49 ` [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks Yuan Liu
@ 2026-07-25  2:49   ` Wei Yang
  0 siblings, 0 replies; 4+ messages in thread
From: Wei Yang @ 2026-07-25  2:49 UTC (permalink / raw)
  To: Yuan Liu
  Cc: David Hildenbrand, Oscar Salvador, Mike Rapoport, Wei Yang,
	linux-mm, Nanhai Zou, Pan Deng, Tianyou Li, Chen Zhang,
	Jason Zeng, linux-kernel

On Thu, Jul 23, 2026 at 04:49:46AM -0400, Yuan Liu wrote:
>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: Jason Zeng <jason.zeng@intel.com>
>Signed-off-by: Yuan Liu <yuan1.liu@intel.com>
>---
> 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 4c699fd9c479..3a281d595207 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;

Here we are checking range [start_pfn, tail_pfn]. When we come here, it means
start_pfn's nid or zone doesn't match our expectation. But if tail_pfn does,
why it still 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

-- 
Wei Yang
Help you, Help me


^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-07-25  2:50 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23  8:49 [PATCH v6 0/2] mm/memory_hotplug: optimize zone contiguous check when changing pfn range Yuan Liu
2026-07-23  8:49 ` [PATCH v6 1/2] " Yuan Liu
2026-07-23  8:49 ` [PATCH v6 2/2] mm/memory_hotplug: improve shrink_zone_span() subsection boundary checks Yuan Liu
2026-07-25  2:49   ` Wei Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox