[PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages

From: Muchun Song

Date: Thu Jul 02 2026 - 06:30:33 EST


memmap_init_range() initializes every struct page in the target range.
For compound pages with vmemmap optimization, the tail struct pages are
backed by a shared vmemmap page.

Initializing those tail struct pages would overwrite the shared
vmemmap page contents, so users such as HugeTLB have to open-code
follow-up handling to restore the metadata afterwards.

Use the section's compound page order to detect struct pages that fall
into the shared tail vmemmap range and skip their initialization in
memmap_init_range(). Still initialize the pageblock migratetypes for
the skipped range so the surrounding setup remains intact.

This is a preparatory change for consolidating handling across users of
vmemmap optimization, and it also avoids redundant initialization of
shared tail vmemmap pages during early boot.

Signed-off-by: Muchun Song <songmuchun@xxxxxxxxxxxxx>
---
include/linux/mmzone.h | 4 ++++
mm/internal.h | 16 ++++++++++++++++
mm/mm_init.c | 25 +++++++++++++++++++------
3 files changed, 39 insertions(+), 6 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index ea884245f499..6fa6e7f0abf9 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2375,6 +2375,10 @@ struct mem_section;

#define sparse_vmemmap_init_nid_early(_nid) do {} while (0)
#define pfn_in_present_section pfn_valid
+static inline struct mem_section *__pfn_to_section(unsigned long pfn)
+{
+ return NULL;
+}
#endif /* CONFIG_SPARSEMEM */

#ifdef CONFIG_SPARSEMEM_VMEMMAP
diff --git a/mm/internal.h b/mm/internal.h
index 430aa72a4575..ebbab7421633 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1002,10 +1002,26 @@ static inline void sparse_init(void) {}
*/
#ifdef CONFIG_SPARSEMEM_VMEMMAP
void sparse_init_subsection_map(void);
+
+static inline bool page_vmemmap_optimizable(const struct page *page, unsigned int order)
+{
+ const unsigned long pfn = page_to_pfn(page);
+ const unsigned long nr_pages = 1UL << order;
+
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return (pfn & (nr_pages - 1)) >= OPTIMIZED_FOLIO_VMEMMAP_NR_STRUCT_PAGES;
+}
#else
static inline void sparse_init_subsection_map(void)
{
}
+
+static inline bool page_vmemmap_optimizable(const struct page *page, unsigned int order)
+{
+ return false;
+}
#endif /* CONFIG_SPARSEMEM_VMEMMAP */

#if defined CONFIG_COMPACTION || defined CONFIG_CMA
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 6b89e2e77431..477d39ad1e9e 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -673,19 +673,21 @@ static inline void fixup_hashdist(void)
static inline void fixup_hashdist(void) {}
#endif /* CONFIG_NUMA */

-#if defined(CONFIG_ZONE_DEVICE) || defined(CONFIG_DEFERRED_STRUCT_PAGE_INIT)
static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
- unsigned long nr_pages, int migratetype, bool atomic)
+ unsigned long nr_pages, int migratetype, bool isolate, bool atomic)
{
const unsigned long end = pfn + nr_pages;

for (pfn = pageblock_align(pfn); pfn < end; pfn += pageblock_nr_pages) {
- init_pageblock_migratetype(pfn_to_page(pfn), migratetype, false);
+ init_pageblock_migratetype(pfn_to_page(pfn), migratetype, isolate);
+#ifdef CONFIG_SPARSEMEM
if (!atomic && IS_ALIGNED(pfn, PAGES_PER_SECTION))
+#else
+ if (!atomic && IS_ALIGNED(pfn, MAX_FOLIO_NR_PAGES))
+#endif
cond_resched();
}
}
-#endif

#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
static inline void pgdat_set_deferred_range(pg_data_t *pgdat)
@@ -891,6 +893,8 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
#endif

for (pfn = start_pfn; pfn < end_pfn; ) {
+ unsigned int order = section_order(__pfn_to_section(pfn));
+
/*
* There can be holes in boot-time mem_map[]s handed to this
* function. They do not exist on hotplugged memory.
@@ -905,6 +909,15 @@ void __meminit memmap_init_range(unsigned long size, int nid, unsigned long zone
}

page = pfn_to_page(pfn);
+ if (page_vmemmap_optimizable(page, order)) {
+ const unsigned long start = pfn;
+
+ pfn = min(ALIGN(start, 1UL << order), end_pfn);
+ pageblock_migratetype_init_range(start, pfn - start, migratetype,
+ isolate_pageblock, false);
+ continue;
+ }
+
__init_single_page(page, pfn, zone, nid);
if (context == MEMINIT_HOTPLUG) {
#ifdef CONFIG_ZONE_DEVICE
@@ -1131,7 +1144,7 @@ void __ref memmap_init_zone_device(struct zone *zone,
compound_nr_pages(pfn, altmap, pgmap));
}

- pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE, false);
+ pageblock_migratetype_init_range(start_pfn, nr_pages, MIGRATE_MOVABLE, false, false);

pr_debug("%s initialised %lu pages in %ums\n", __func__,
nr_pages, jiffies_to_msecs(jiffies - start));
@@ -1965,7 +1978,7 @@ static void __init deferred_free_pages(unsigned long pfn,
if (!nr_pages)
return;

- pageblock_migratetype_init_range(pfn, nr_pages, mt, true);
+ pageblock_migratetype_init_range(pfn, nr_pages, mt, false, true);

page = pfn_to_page(pfn);

--
2.54.0