* [PATCH 01/17] mm/sparse: drop power-of-2 size requirement for struct mem_section
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 02/17] mm/sparse-vmemmap: track compound page order in " Muchun Song
` (15 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
struct mem_section is currently forced to a power-of-2 size so the
section-to-root lookup can use a mask instead of a modulo.
That requirement adds configuration-dependent padding, especially with
CONFIG_PAGE_EXTENSION, just to preserve the lookup scheme.
Drop the constraint and use a plain modulo for the lookup instead. The
divisor is constant, so the generated code remains cheap while avoiding
the extra padding. It also removes an unnecessary layout constraint
from the type.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 8 +-------
mm/sparse.c | 2 --
scripts/gdb/linux/mm.py | 6 ++----
3 files changed, 3 insertions(+), 13 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 242ec3cb8b52..1353bcf7b712 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2014,12 +2014,7 @@ struct mem_section {
* section. (see page_ext.h about this.)
*/
struct page_ext *page_ext;
- unsigned long pad;
#endif
- /*
- * WARNING: mem_section must be a power-of-2 in size for the
- * calculation and use of SECTION_ROOT_MASK to make sense.
- */
};
#ifdef CONFIG_SPARSEMEM_EXTREME
@@ -2030,7 +2025,6 @@ struct mem_section {
#define SECTION_NR_TO_ROOT(sec) ((sec) / SECTIONS_PER_ROOT)
#define NR_SECTION_ROOTS DIV_ROUND_UP(NR_MEM_SECTIONS, SECTIONS_PER_ROOT)
-#define SECTION_ROOT_MASK (SECTIONS_PER_ROOT - 1)
#ifdef CONFIG_SPARSEMEM_EXTREME
extern struct mem_section **mem_section;
@@ -2054,7 +2048,7 @@ static inline struct mem_section *__nr_to_section(unsigned long nr)
if (!mem_section || !mem_section[root])
return NULL;
#endif
- return &mem_section[root][nr & SECTION_ROOT_MASK];
+ return &mem_section[root][nr % SECTIONS_PER_ROOT];
}
extern size_t mem_section_usage_size(void);
diff --git a/mm/sparse.c b/mm/sparse.c
index 324213d8bdcb..9457a4d6a6fc 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -331,8 +331,6 @@ void __init sparse_init(void)
unsigned long pnum_end, pnum_begin, map_count = 1;
int nid_begin;
- /* see include/linux/mmzone.h 'struct mem_section' definition */
- BUILD_BUG_ON(!is_power_of_2(sizeof(struct mem_section)));
memblocks_present();
if (compound_info_has_mask()) {
diff --git a/scripts/gdb/linux/mm.py b/scripts/gdb/linux/mm.py
index dffadccbb01d..da4e8e9655a6 100644
--- a/scripts/gdb/linux/mm.py
+++ b/scripts/gdb/linux/mm.py
@@ -70,7 +70,6 @@ class x86_page_ops():
self.SECTIONS_PER_ROOT = 1
self.NR_SECTION_ROOTS = DIV_ROUND_UP(self.NR_MEM_SECTIONS, self.SECTIONS_PER_ROOT)
- self.SECTION_ROOT_MASK = self.SECTIONS_PER_ROOT - 1
try:
self.SECTION_HAS_MEM_MAP = 1 << int(gdb.parse_and_eval('SECTION_HAS_MEM_MAP_BIT'))
@@ -100,7 +99,7 @@ class x86_page_ops():
def __nr_to_section(self, nr):
root = self.SECTION_NR_TO_ROOT(nr)
mem_section = gdb.parse_and_eval("mem_section")
- return mem_section[root][nr & self.SECTION_ROOT_MASK]
+ return mem_section[root][nr % self.SECTIONS_PER_ROOT]
def pfn_to_section_nr(self, pfn):
return pfn >> self.PFN_SECTION_SHIFT
@@ -249,7 +248,6 @@ class aarch64_page_ops():
self.SECTIONS_PER_ROOT = 1
self.NR_SECTION_ROOTS = DIV_ROUND_UP(self.NR_MEM_SECTIONS, self.SECTIONS_PER_ROOT)
- self.SECTION_ROOT_MASK = self.SECTIONS_PER_ROOT - 1
self.SUBSECTION_SHIFT = 21
self.SEBSECTION_SIZE = 1 << self.SUBSECTION_SHIFT
self.PFN_SUBSECTION_SHIFT = self.SUBSECTION_SHIFT - self.PAGE_SHIFT
@@ -304,7 +302,7 @@ class aarch64_page_ops():
def __nr_to_section(self, nr):
root = self.SECTION_NR_TO_ROOT(nr)
mem_section = gdb.parse_and_eval("mem_section")
- return mem_section[root][nr & self.SECTION_ROOT_MASK]
+ return mem_section[root][nr % self.SECTIONS_PER_ROOT]
def pfn_to_section_nr(self, pfn):
return pfn >> self.PFN_SECTION_SHIFT
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 02/17] mm/sparse-vmemmap: track compound page order in struct mem_section
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
2026-07-02 9:38 ` [PATCH 01/17] mm/sparse: drop power-of-2 size requirement for struct mem_section Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 03/17] mm/sparse-vmemmap: introduce folio-oriented vmemmap optimization macros Muchun Song
` (14 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
HugeTLB and DAX both rely on vmemmap optimization, but sparsemem does
not record what compound page order a section is populated with.
As a result, code that needs this information has to open-code
separate handling across users of vmemmap optimization. It also
prevents other memory management code, such as struct page
initialization, from skipping initialization of shared vmemmap pages
when needed.
Track the compound page order in struct mem_section and provide small
helpers to access it. A compound page larger than a section naturally
carries the same order across all covered sections.
This is a preparatory change for consolidating vmemmap optimization
handling and for letting later code make initialization decisions
based on the section's compound page order.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 1353bcf7b712..bacd89572c5c 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2015,6 +2015,14 @@ struct mem_section {
*/
struct page_ext *page_ext;
#endif
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+ /*
+ * The order of compound pages in this section. Typically, the section
+ * holds compound pages of this order; a larger compound page will span
+ * multiple sections.
+ */
+ unsigned int order;
+#endif
};
#ifdef CONFIG_SPARSEMEM_EXTREME
@@ -2361,10 +2369,34 @@ static inline unsigned long next_present_section_nr(unsigned long section_nr)
#endif
#else
+struct mem_section;
+
#define sparse_vmemmap_init_nid_early(_nid) do {} while (0)
#define pfn_in_present_section pfn_valid
#endif /* CONFIG_SPARSEMEM */
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+ VM_WARN_ON(section->order && order && section->order != order);
+ section->order = order;
+}
+
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return section->order;
+}
+#else
+static inline void section_set_order(struct mem_section *section, unsigned int order)
+{
+}
+
+static inline unsigned int section_order(const struct mem_section *section)
+{
+ return 0;
+}
+#endif
+
/*
* Fallback case for when the architecture provides its own pfn_valid() but
* not a corresponding for_each_valid_pfn().
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 03/17] mm/sparse-vmemmap: introduce folio-oriented vmemmap optimization macros
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
2026-07-02 9:38 ` [PATCH 01/17] mm/sparse: drop power-of-2 size requirement for struct mem_section Muchun Song
2026-07-02 9:38 ` [PATCH 02/17] mm/sparse-vmemmap: track compound page order in " Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages Muchun Song
` (13 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
Introduce macros for those folio-oriented properties and use them to
replace the tail-based names in the HVO paths. This makes the code read
in terms of folio semantics instead of an implementation detail and
better matches other folio-based users such as DAX.
No functional change intended.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 18 ++++++++++--------
mm/hugetlb.c | 4 ++--
mm/hugetlb_vmemmap.c | 2 +-
mm/sparse-vmemmap.c | 4 ++--
4 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index bacd89572c5c..ea884245f499 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -107,13 +107,15 @@
is_power_of_2(sizeof(struct page)) ? \
MAX_FOLIO_NR_PAGES * sizeof(struct page) : 0)
-/*
- * vmemmap optimization (like HVO) is only possible for page orders that fill
- * two or more pages with struct pages.
- */
-#define VMEMMAP_TAIL_MIN_ORDER (ilog2(2 * PAGE_SIZE / sizeof(struct page)))
-#define __NR_VMEMMAP_TAILS (MAX_FOLIO_ORDER - VMEMMAP_TAIL_MIN_ORDER + 1)
-#define NR_VMEMMAP_TAILS (__NR_VMEMMAP_TAILS > 0 ? __NR_VMEMMAP_TAILS : 0)
+/* The number of vmemmap pages required by a vmemmap-optimized folio. */
+#define OPTIMIZED_FOLIO_VMEMMAP_PAGES 1
+#define OPTIMIZED_FOLIO_VMEMMAP_SIZE (OPTIMIZED_FOLIO_VMEMMAP_PAGES * PAGE_SIZE)
+#define OPTIMIZED_FOLIO_VMEMMAP_NR_STRUCT_PAGES (OPTIMIZED_FOLIO_VMEMMAP_SIZE / sizeof(struct page))
+#define OPTIMIZABLE_FOLIO_MIN_ORDER (ilog2(OPTIMIZED_FOLIO_VMEMMAP_NR_STRUCT_PAGES) + 1)
+
+#define __NR_OPTIMIZABLE_FOLIO_ORDERS (MAX_FOLIO_ORDER - OPTIMIZABLE_FOLIO_MIN_ORDER + 1)
+#define NR_OPTIMIZABLE_FOLIO_ORDERS \
+ (__NR_OPTIMIZABLE_FOLIO_ORDERS > 0 ? __NR_OPTIMIZABLE_FOLIO_ORDERS : 0)
enum migratetype {
MIGRATE_UNMOVABLE,
@@ -1147,7 +1149,7 @@ struct zone {
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
- struct page *vmemmap_tails[NR_VMEMMAP_TAILS];
+ struct page *vmemmap_tails[NR_OPTIMIZABLE_FOLIO_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index e1bb81631d5e..1ef6edaa3993 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3301,7 +3301,7 @@ void __init hugetlb_bootmem_struct_page_init(void)
struct zone *zone;
for_each_zone(zone) {
- for (int i = 0; i < NR_VMEMMAP_TAILS; i++) {
+ for (int i = 0; i < NR_OPTIMIZABLE_FOLIO_ORDERS; i++) {
struct page *tail, *p;
unsigned int order;
@@ -3309,7 +3309,7 @@ void __init hugetlb_bootmem_struct_page_init(void)
if (!tail)
continue;
- order = i + VMEMMAP_TAIL_MIN_ORDER;
+ order = i + OPTIMIZABLE_FOLIO_MIN_ORDER;
p = page_to_virt(tail);
/*
* prep_and_add_bootmem_folios() can access pageblock
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index eefd6b5f9706..196312a6fafb 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -495,7 +495,7 @@ static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *
static struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
{
- const unsigned int idx = order - VMEMMAP_TAIL_MIN_ORDER;
+ const unsigned int idx = order - OPTIMIZABLE_FOLIO_MIN_ORDER;
struct page *tail, *p;
int node = zone_to_nid(zone);
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 785d2b042fe3..1d72283e179b 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -327,12 +327,12 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *
unsigned int idx;
int node = zone_to_nid(zone);
- if (WARN_ON_ONCE(order < VMEMMAP_TAIL_MIN_ORDER))
+ if (WARN_ON_ONCE(order < OPTIMIZABLE_FOLIO_MIN_ORDER))
return NULL;
if (WARN_ON_ONCE(order > MAX_FOLIO_ORDER))
return NULL;
- idx = order - VMEMMAP_TAIL_MIN_ORDER;
+ idx = order - OPTIMIZABLE_FOLIO_MIN_ORDER;
tail = zone->vmemmap_tails[idx];
if (tail)
return tail;
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (2 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 03/17] mm/sparse-vmemmap: introduce folio-oriented vmemmap optimization macros Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-09 10:45 ` Mike Rapoport
2026-07-02 9:38 ` [PATCH 05/17] mm/sparse-vmemmap: initialize shared tail vmemmap pages on allocation Muchun Song
` (12 subsequent siblings)
16 siblings, 1 reply; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
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@bytedance.com>
---
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
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages
2026-07-02 9:38 ` [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages Muchun Song
@ 2026-07-09 10:45 ` Mike Rapoport
2026-07-09 12:31 ` Muchun Song
0 siblings, 1 reply; 24+ messages in thread
From: Mike Rapoport @ 2026-07-09 10:45 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm,
Vlastimil Babka, Lorenzo Stoakes, Michal Hocko, linux-kernel,
muchun.song
Hi Muchun,
Below are some preliminary comments, I'm planning to spend more time on
review next week.
On Thu, Jul 02, 2026 at 05:38:08PM +0800, Muchun Song wrote:
> 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@bytedance.com>
> ---
> include/linux/mmzone.h | 4 ++++
> mm/internal.h | 16 ++++++++++++++++
> mm/mm_init.c | 25 +++++++++++++++++++------
> 3 files changed, 39 insertions(+), 6 deletions(-)
>
> @@ -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)
What is isolate parameter for?
> {
> 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
Let's trigger cond_resched() on some defined number of iterations or some
memory size chunk, e.g PAGES_PER_128M or even PAGES_PER_1G.
> cond_resched();
> }
> }
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages
2026-07-09 10:45 ` Mike Rapoport
@ 2026-07-09 12:31 ` Muchun Song
2026-07-09 13:05 ` Mike Rapoport
0 siblings, 1 reply; 24+ messages in thread
From: Muchun Song @ 2026-07-09 12:31 UTC (permalink / raw)
To: Mike Rapoport
Cc: Muchun Song, Andrew Morton, Oscar Salvador, David Hildenbrand,
linux-mm, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel
> On Jul 9, 2026, at 18:45, Mike Rapoport <rppt@kernel.org> wrote:
>
> Hi Muchun,
Hi,
>
> Below are some preliminary comments, I'm planning to spend more time on
> review next week.
Thanks for the early feedback! Looking forward to more review next week.
>
> On Thu, Jul 02, 2026 at 05:38:08PM +0800, Muchun Song wrote:
>> 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@bytedance.com>
>> ---
>> include/linux/mmzone.h | 4 ++++
>> mm/internal.h | 16 ++++++++++++++++
>> mm/mm_init.c | 25 +++++++++++++++++++------
>> 3 files changed, 39 insertions(+), 6 deletions(-)
>>
>> @@ -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)
>
> What is isolate parameter for?
I've re-examined the code, and you're right that the isolate parameter is technically
redundant for our current use case, as memmap_init_zone_range() passes false.
The rationale behind keeping it is future-proofing. The ultimate goal of a generic
HVO is to support arbitrary huge pages, not just HugeTLB. I decoupled this as a
parameter to prevent potential regressions down the road; if a developer leverages
this for other huge page types in the future, they won't inadvertently break
things by forgetting to update a hardcoded false in init_pageblock_migratetype(),
especially since memmap_init_range() natively accepts an isolate parameter.
Of course, we could also just delete this parameter for now and add it back later if
needed. I think both approaches work.
Which way are you leaning?
>
>> {
>> 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
>
> Let's trigger cond_resched() on some defined number of iterations or some
> memory size chunk, e.g PAGES_PER_128M or even PAGES_PER_1G.
Yes, that's for the best. I was really struggling to choose a suitable macro
for this earlier, but I realized it's a difficult thing to get right. I'm leaning
toward selecting PAGES_PER_1G instead.
Muchun,
Thanks.
>
>> cond_resched();
>> }
>> }
>
> --
> Sincerely yours,
> Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages
2026-07-09 12:31 ` Muchun Song
@ 2026-07-09 13:05 ` Mike Rapoport
2026-07-09 13:23 ` Muchun Song
0 siblings, 1 reply; 24+ messages in thread
From: Mike Rapoport @ 2026-07-09 13:05 UTC (permalink / raw)
To: Muchun Song
Cc: Muchun Song, Andrew Morton, Oscar Salvador, David Hildenbrand,
linux-mm, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel
On Thu, Jul 09, 2026 at 08:31:31PM +0800, Muchun Song wrote:
> > On Jul 9, 2026, at 18:45, Mike Rapoport <rppt@kernel.org> wrote:
> > On Thu, Jul 02, 2026 at 05:38:08PM +0800, Muchun Song wrote:
> >> 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@bytedance.com>
> >> ---
> >> include/linux/mmzone.h | 4 ++++
> >> mm/internal.h | 16 ++++++++++++++++
> >> mm/mm_init.c | 25 +++++++++++++++++++------
> >> 3 files changed, 39 insertions(+), 6 deletions(-)
> >>
> >> @@ -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)
> >
> > What is isolate parameter for?
>
> I've re-examined the code, and you're right that the isolate parameter is technically
> redundant for our current use case, as memmap_init_zone_range() passes false.
>
> The rationale behind keeping it is future-proofing. The ultimate goal of a generic
> HVO is to support arbitrary huge pages, not just HugeTLB. I decoupled this as a
> parameter to prevent potential regressions down the road; if a developer leverages
> this for other huge page types in the future, they won't inadvertently break
> things by forgetting to update a hardcoded false in init_pageblock_migratetype(),
> especially since memmap_init_range() natively accepts an isolate parameter.
>
> Of course, we could also just delete this parameter for now and add it back later if
> needed. I think both approaches work.
>
> Which way are you leaning?
I'd drop it for now and would revisit when there would be a new HVO user.
Even one boolean means it's hard to tell from a call site what is the
intention of the flag, two make it completely confusing :)
> >> {
> >> 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
> >
> > Let's trigger cond_resched() on some defined number of iterations or some
> > memory size chunk, e.g PAGES_PER_128M or even PAGES_PER_1G.
>
> Yes, that's for the best. I was really struggling to choose a suitable macro
> for this earlier, but I realized it's a difficult thing to get right. I'm leaning
> toward selecting PAGES_PER_1G instead.
Yeah, PAGES_PER_1G makes sense to me too.
> Muchun,
> Thanks.
>
> >
> >> cond_resched();
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread* Re: [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages
2026-07-09 13:05 ` Mike Rapoport
@ 2026-07-09 13:23 ` Muchun Song
0 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-09 13:23 UTC (permalink / raw)
To: Mike Rapoport
Cc: Muchun Song, Andrew Morton, Oscar Salvador, David Hildenbrand,
linux-mm, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel
> On Jul 9, 2026, at 21:05, Mike Rapoport <rppt@kernel.org> wrote:
>
> On Thu, Jul 09, 2026 at 08:31:31PM +0800, Muchun Song wrote:
>>> On Jul 9, 2026, at 18:45, Mike Rapoport <rppt@kernel.org> wrote:
>>> On Thu, Jul 02, 2026 at 05:38:08PM +0800, Muchun Song wrote:
>>>> 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@bytedance.com>
>>>> ---
>>>> include/linux/mmzone.h | 4 ++++
>>>> mm/internal.h | 16 ++++++++++++++++
>>>> mm/mm_init.c | 25 +++++++++++++++++++------
>>>> 3 files changed, 39 insertions(+), 6 deletions(-)
>>>>
>>>> @@ -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)
>>>
>>> What is isolate parameter for?
>>
>> I've re-examined the code, and you're right that the isolate parameter is technically
>> redundant for our current use case, as memmap_init_zone_range() passes false.
>>
>> The rationale behind keeping it is future-proofing. The ultimate goal of a generic
>> HVO is to support arbitrary huge pages, not just HugeTLB. I decoupled this as a
>> parameter to prevent potential regressions down the road; if a developer leverages
>> this for other huge page types in the future, they won't inadvertently break
>> things by forgetting to update a hardcoded false in init_pageblock_migratetype(),
>> especially since memmap_init_range() natively accepts an isolate parameter.
>>
>> Of course, we could also just delete this parameter for now and add it back later if
>> needed. I think both approaches work.
>>
>> Which way are you leaning?
>
> I'd drop it for now and would revisit when there would be a new HVO user.
> Even one boolean means it's hard to tell from a call site what is the
> intention of the flag, two make it completely confusing :)
No problem. I'll drop it next version.
Muchun,
Thanks.
>
>>>> {
>>>> 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
>>>
>>> Let's trigger cond_resched() on some defined number of iterations or some
>>> memory size chunk, e.g PAGES_PER_128M or even PAGES_PER_1G.
>>
>> Yes, that's for the best. I was really struggling to choose a suitable macro
>> for this earlier, but I realized it's a difficult thing to get right. I'm leaning
>> toward selecting PAGES_PER_1G instead.
>
> Yeah, PAGES_PER_1G makes sense to me too.
>
>> Muchun,
>> Thanks.
>>
>>>
>>>> cond_resched();
>
> --
> Sincerely yours,
> Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 05/17] mm/sparse-vmemmap: initialize shared tail vmemmap pages on allocation
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (3 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 04/17] mm/mm_init: skip initializing shared vmemmap tail pages Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 06/17] mm/sparse-vmemmap: support section-based vmemmap accounting Muchun Song
` (11 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
The shared tail vmemmap page allocated in vmemmap_get_tail() used to be
left uninitialized, because memmap_init_range() would later overwrite
it. That forced users such as HugeTLB to defer the initialization to
their own setup paths.
Now that memmap_init_range() skips shared tail vmemmap pages, initialize
them immediately in vmemmap_get_tail() with init_compound_tail()
instead.
This moves the initialization to the point where the shared tail page is
allocated and avoids relying on deferred handling in individual users.
The remaining deferred initialization in HugeTLB will be removed once it
switches to the section-based vmemmap optimization mechanism.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/sparse-vmemmap.c | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 1d72283e179b..8931510c99c4 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -336,19 +336,11 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *
tail = zone->vmemmap_tails[idx];
if (tail)
return tail;
-
- /*
- * Only allocate the page, but do not initialize it.
- *
- * Any initialization done here will be overwritten by memmap_init().
- *
- * hugetlb_bootmem_struct_page_init() will take care of initialization
- * after memmap_init().
- */
-
p = vmemmap_alloc_block_zero(PAGE_SIZE, node);
if (!p)
return NULL;
+ for (int i = 0; i < PAGE_SIZE / sizeof(struct page); i++)
+ init_compound_tail(p + i, NULL, order, zone);
tail = virt_to_page(p);
zone->vmemmap_tails[idx] = tail;
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 06/17] mm/sparse-vmemmap: support section-based vmemmap accounting
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (4 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 05/17] mm/sparse-vmemmap: initialize shared tail vmemmap pages on allocation Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 07/17] mm/sparse-vmemmap: support section-based vmemmap optimization Muchun Song
` (10 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
section_nr_vmemmap_pages() is used to account the vmemmap pages consumed by a
memory section, but it currently only understands the ordinary case and the
pgmap-provided optimization case. That is not enough for section-based vmemmap
optimization, where the compound page order is carried by the memory section
itself and tail vmemmap pages may be shared.
Make the helper report the actual vmemmap footprint of a section, so it can be
used as the common accounting path for both ordinary and optimized sections.
Teach section_nr_vmemmap_pages() to use the section order when there is no
pgmap, and to account only the vmemmap pages that are actually needed for an
optimizable section. This keeps the accounting consistent with section-based
vmemmap optimization.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 8 ++++++++
mm/sparse-vmemmap.c | 13 +++++++++----
2 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 6fa6e7f0abf9..41a1ebbe5e85 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2403,6 +2403,14 @@ static inline unsigned int section_order(const struct mem_section *section)
}
#endif
+static inline bool section_vmemmap_optimizable(const struct mem_section *section)
+{
+ if (!is_power_of_2(sizeof(struct page)))
+ return false;
+
+ return section_order(section) >= OPTIMIZABLE_FOLIO_MIN_ORDER;
+}
+
/*
* Fallback case for when the architecture provides its own pfn_valid() but
* not a corresponding for_each_valid_pfn().
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 8931510c99c4..d3e73272b0e3 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -653,24 +653,29 @@ void offline_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
static int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
{
- const unsigned int order = pgmap ? pgmap->vmemmap_shift : 0;
+ const struct mem_section *ms = __pfn_to_section(pfn);
+ const unsigned int order = pgmap ? pgmap->vmemmap_shift : section_order(ms);
const unsigned long pages_per_compound = 1UL << order;
+ unsigned int vmemmap_pages = OPTIMIZED_FOLIO_VMEMMAP_PAGES;
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
- if (!vmemmap_can_optimize(altmap, pgmap))
+ if (vmemmap_can_optimize(altmap, pgmap))
+ vmemmap_pages = VMEMMAP_RESERVE_NR;
+
+ if (!vmemmap_can_optimize(altmap, pgmap) && !section_vmemmap_optimizable(ms))
return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
if (order < PFN_SECTION_SHIFT) {
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
- return VMEMMAP_RESERVE_NR * nr_pages / pages_per_compound;
+ return vmemmap_pages * nr_pages / pages_per_compound;
}
VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
if (IS_ALIGNED(pfn, pages_per_compound))
- return VMEMMAP_RESERVE_NR;
+ return vmemmap_pages;
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 07/17] mm/sparse-vmemmap: support section-based vmemmap optimization
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (5 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 06/17] mm/sparse-vmemmap: support section-based vmemmap accounting Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 08/17] mm/sparse: mark memory sections present earlier Muchun Song
` (9 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
Teach sparse-vmemmap population code to use the compound page order
when deciding whether a vmemmap page can be optimized.
With this information, the common sparse-vmemmap population path can
allocate or reuse shared tail vmemmap pages directly instead of relying
on HugeTLB/DAX-specific handling.
This centralizes vmemmap optimization logic in the sparse-vmemmap code,
based on section metadata, and prepares for sharing the same mechanism
across different users of vmemmap optimization, including HugeTLB and
DAX.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 2 +-
mm/internal.h | 3 ++
mm/sparse-vmemmap.c | 89 +++++++++++++++++++++++++-----------------
mm/sparse.c | 34 +++++++++++++++-
4 files changed, 89 insertions(+), 39 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 41a1ebbe5e85..41f6a584a52a 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -1148,7 +1148,7 @@ struct zone {
/* Zone statistics */
atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
atomic_long_t vm_numa_event[NR_VM_NUMA_EVENT_ITEMS];
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
+#ifdef CONFIG_SPARSEMEM_VMEMMAP
struct page *vmemmap_tails[NR_OPTIMIZABLE_FOLIO_ORDERS];
#endif
} ____cacheline_internodealigned_in_smp;
diff --git a/mm/internal.h b/mm/internal.h
index ebbab7421633..8ec8b447d75e 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -993,6 +993,9 @@ static inline void __section_mark_present(struct mem_section *ms,
ms->section_mem_map |= SECTION_MARKED_PRESENT;
}
+
+int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
+ struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
#else
static inline void sparse_init(void) {}
#endif /* CONFIG_SPARSEMEM */
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index d3e73272b0e3..38ed7eaf13ea 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -146,17 +146,49 @@ void __meminit vmemmap_verify(pte_t *pte, int node,
start, end - 1);
}
+static struct zone __meminit *pfn_to_zone(unsigned long pfn, int nid)
+{
+ pg_data_t *pgdat = NODE_DATA(nid);
+
+ for (enum zone_type zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
+ struct zone *zone = &pgdat->node_zones[zone_type];
+
+ if (zone_spans_pfn(zone, pfn))
+ return zone;
+ }
+
+ return NULL;
+}
+
+static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone);
+
static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, int node,
struct vmem_altmap *altmap,
unsigned long ptpfn, unsigned long flags)
{
pte_t *pte = pte_offset_kernel(pmd, addr);
+ struct page *page = (struct page *)addr;
+ unsigned long pfn = page_to_pfn(page);
+ unsigned int order = section_order(__pfn_to_section(pfn));
+
if (pte_none(ptep_get(pte))) {
pte_t entry;
- void *p;
+
+ if (page_vmemmap_optimizable(page, order) &&
+ ptpfn == (unsigned long)-1) {
+ struct zone *zone = pfn_to_zone(pfn, node);
+
+ if (WARN_ON_ONCE(!zone))
+ return NULL;
+ page = vmemmap_get_tail(order, zone);
+ if (!page)
+ return NULL;
+ ptpfn = page_to_pfn(page);
+ }
if (ptpfn == (unsigned long)-1) {
- p = vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
+ void *p = vmemmap_alloc_block_buf(PAGE_SIZE, node, altmap);
+
if (!p)
return NULL;
ptpfn = PHYS_PFN(__pa(p));
@@ -175,7 +207,8 @@ static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, in
}
entry = pfn_pte(ptpfn, PAGE_KERNEL);
set_pte_at(&init_mm, addr, pte, entry);
- }
+ } else if (WARN_ON_ONCE(page_vmemmap_optimizable(page, order)))
+ return NULL;
return pte;
}
@@ -320,7 +353,6 @@ void vmemmap_wrprotect_hvo(unsigned long addr, unsigned long end,
}
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
{
struct page *p, *tail;
@@ -348,6 +380,7 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *
return tail;
}
+#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
int __meminit vmemmap_populate_hvo(unsigned long addr, unsigned long end,
unsigned int order, struct zone *zone,
unsigned long headsize)
@@ -402,6 +435,9 @@ int __meminit vmemmap_populate_hugepages(unsigned long start, unsigned long end,
pmd_t *pmd;
for (addr = start; addr < end; addr = next) {
+ unsigned long pfn = page_to_pfn((struct page *)addr);
+ struct mem_section *ms = __pfn_to_section(pfn);
+
next = pmd_addr_end(addr, end);
pgd = vmemmap_pgd_populate(addr, node);
@@ -417,7 +453,7 @@ int __meminit vmemmap_populate_hugepages(unsigned long start, unsigned long end,
return -ENOMEM;
pmd = pmd_offset(pud, addr);
- if (pmd_none(pmdp_get(pmd))) {
+ if (pmd_none(pmdp_get(pmd)) && !section_vmemmap_optimizable(ms)) {
void *p;
p = vmemmap_alloc_block_buf(PMD_SIZE, node, altmap);
@@ -435,8 +471,19 @@ int __meminit vmemmap_populate_hugepages(unsigned long start, unsigned long end,
*/
return -ENOMEM;
}
- } else if (vmemmap_check_pmd(pmd, node, addr, next))
+ } else if (vmemmap_check_pmd(pmd, node, addr, next)) {
+ const struct mem_section *start_ms;
+ unsigned long align = max(1UL << section_order(ms), PAGES_PER_SECTION);
+
+ /* HVO-covered sections must not use PMD mappings. */
+ start_ms = __pfn_to_section(ALIGN_DOWN(pfn, align));
+ if (!IS_ALIGNED(pfn, align) && section_vmemmap_optimizable(start_ms))
+ return -ENOTSUPP;
+
+ /* PMD mappings end HVO coverage for this section. */
+ section_set_order(ms, 0);
continue;
+ }
if (vmemmap_populate_basepages(addr, next, node, altmap))
return -ENOMEM;
}
@@ -650,36 +697,6 @@ void offline_mem_sections(unsigned long start_pfn, unsigned long end_pfn)
}
}
-static int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
- struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
-{
- const struct mem_section *ms = __pfn_to_section(pfn);
- const unsigned int order = pgmap ? pgmap->vmemmap_shift : section_order(ms);
- const unsigned long pages_per_compound = 1UL << order;
- unsigned int vmemmap_pages = OPTIMIZED_FOLIO_VMEMMAP_PAGES;
-
- VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
- VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
-
- if (vmemmap_can_optimize(altmap, pgmap))
- vmemmap_pages = VMEMMAP_RESERVE_NR;
-
- if (!vmemmap_can_optimize(altmap, pgmap) && !section_vmemmap_optimizable(ms))
- return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
-
- if (order < PFN_SECTION_SHIFT) {
- VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
- return vmemmap_pages * nr_pages / pages_per_compound;
- }
-
- VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
-
- if (IS_ALIGNED(pfn, pages_per_compound))
- return vmemmap_pages;
-
- return 0;
-}
-
static struct page * __meminit populate_section_memmap(unsigned long pfn,
unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
struct dev_pagemap *pgmap)
diff --git a/mm/sparse.c b/mm/sparse.c
index 9457a4d6a6fc..3e96478a63e0 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -284,6 +284,36 @@ static void __init sparse_usage_fini(void)
sparse_usagebuf = sparse_usagebuf_end = NULL;
}
+int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
+ struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
+{
+ const struct mem_section *ms = __pfn_to_section(pfn);
+ const unsigned int order = pgmap ? pgmap->vmemmap_shift : section_order(ms);
+ const unsigned long pages_per_compound = 1UL << order;
+ unsigned int vmemmap_pages = OPTIMIZED_FOLIO_VMEMMAP_PAGES;
+
+ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SUBSECTION));
+ VM_WARN_ON_ONCE(nr_pages > PAGES_PER_SECTION);
+
+ if (vmemmap_can_optimize(altmap, pgmap))
+ vmemmap_pages = VMEMMAP_RESERVE_NR;
+
+ if (!vmemmap_can_optimize(altmap, pgmap) && !section_vmemmap_optimizable(ms))
+ return DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE);
+
+ if (order < PFN_SECTION_SHIFT) {
+ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, pages_per_compound));
+ return vmemmap_pages * nr_pages / pages_per_compound;
+ }
+
+ VM_WARN_ON_ONCE(!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION));
+
+ if (IS_ALIGNED(pfn, pages_per_compound))
+ return vmemmap_pages;
+
+ return 0;
+}
+
/*
* Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
* And number of present sections in this node is map_count.
@@ -314,8 +344,8 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
nid, NULL, NULL);
if (!map)
panic("Failed to allocate memmap for section %lu\n", pnum);
- memmap_boot_pages_add(DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
- PAGE_SIZE));
+ memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
+ NULL, NULL));
sparse_init_early_section(nid, map, pnum, 0);
}
}
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 08/17] mm/sparse: mark memory sections present earlier
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (6 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 07/17] mm/sparse-vmemmap: support section-based vmemmap optimization Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-09 10:54 ` Mike Rapoport
2026-07-02 9:38 ` [PATCH 09/17] mm/hugetlb: switch HugeTLB to section-based vmemmap optimization Muchun Song
` (8 subsequent siblings)
16 siblings, 1 reply; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
Upcoming HugeTLB bootmem changes need sparsemem section metadata before
the HugeTLB bootmem allocation path runs. The memblock ranges are marked
present from sparse_init(), which is called too late for that setup.
Move the code that marks memblock ranges present into
mm_core_init_early(), before free_area_init() and the HugeTLB bootmem
setup. Rename the helper to sparse_memblock_present() to make the new
caller describe the sparsemem-specific initialization step.
This is a preparatory change.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/internal.h | 2 ++
mm/mm_init.c | 1 +
mm/sparse.c | 4 +---
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index 8ec8b447d75e..11844b40f47a 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -960,6 +960,7 @@ void memmap_init_range(unsigned long, int, unsigned long, unsigned long,
* mm/sparse.c
*/
#ifdef CONFIG_SPARSEMEM
+void sparse_memblock_present(void);
void sparse_init(void);
int sparse_index_init(unsigned long section_nr, int nid);
@@ -997,6 +998,7 @@ static inline void __section_mark_present(struct mem_section *ms,
int section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap);
#else
+static inline void sparse_memblocks_present(void) {}
static inline void sparse_init(void) {}
#endif /* CONFIG_SPARSEMEM */
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 477d39ad1e9e..0256ab4ba839 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -2682,6 +2682,7 @@ void __init __weak mem_init(void)
void __init mm_core_init_early(void)
{
+ sparse_memblock_present();
free_area_init();
hugetlb_cma_reserve();
diff --git a/mm/sparse.c b/mm/sparse.c
index 3e96478a63e0..2a1a1f5e97c4 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -195,7 +195,7 @@ static void __init memory_present(int nid, unsigned long start, unsigned long en
* This is a convenience function that is useful to mark all of the systems
* memory as present during initialization.
*/
-static void __init memblocks_present(void)
+void __init sparse_memblock_present(void)
{
unsigned long start, end;
int i, nid;
@@ -361,8 +361,6 @@ void __init sparse_init(void)
unsigned long pnum_end, pnum_begin, map_count = 1;
int nid_begin;
- memblocks_present();
-
if (compound_info_has_mask()) {
VM_WARN_ON_ONCE(!IS_ALIGNED((unsigned long) pfn_to_page(0),
MAX_FOLIO_VMEMMAP_ALIGN));
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* Re: [PATCH 08/17] mm/sparse: mark memory sections present earlier
2026-07-02 9:38 ` [PATCH 08/17] mm/sparse: mark memory sections present earlier Muchun Song
@ 2026-07-09 10:54 ` Mike Rapoport
2026-07-09 12:35 ` Muchun Song
0 siblings, 1 reply; 24+ messages in thread
From: Mike Rapoport @ 2026-07-09 10:54 UTC (permalink / raw)
To: Muchun Song
Cc: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm,
Vlastimil Babka, Lorenzo Stoakes, Michal Hocko, linux-kernel,
muchun.song
On Thu, Jul 02, 2026 at 05:38:12PM +0800, Muchun Song wrote:
> Upcoming HugeTLB bootmem changes need sparsemem section metadata before
> the HugeTLB bootmem allocation path runs. The memblock ranges are marked
> present from sparse_init(), which is called too late for that setup.
It's not only that memblock regions are marked present, but it actually
initializes mem_section's for the present memory ...
> Move the code that marks memblock ranges present into
> mm_core_init_early(), before free_area_init() and the HugeTLB bootmem
> setup. Rename the helper to sparse_memblock_present() to make the new
... so let's name this function to reflect that.
How about sparse_sections_init()?
> caller describe the sparsemem-specific initialization step.
>
> This is a preparatory change.
>
> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
> ---
> mm/internal.h | 2 ++
> mm/mm_init.c | 1 +
> mm/sparse.c | 4 +---
> 3 files changed, 4 insertions(+), 3 deletions(-)
--
Sincerely yours,
Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread
* Re: [PATCH 08/17] mm/sparse: mark memory sections present earlier
2026-07-09 10:54 ` Mike Rapoport
@ 2026-07-09 12:35 ` Muchun Song
0 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-09 12:35 UTC (permalink / raw)
To: Mike Rapoport
Cc: Muchun Song, Andrew Morton, Oscar Salvador, David Hildenbrand,
linux-mm, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel
> On Jul 9, 2026, at 18:54, Mike Rapoport <rppt@kernel.org> wrote:
>
> On Thu, Jul 02, 2026 at 05:38:12PM +0800, Muchun Song wrote:
>> Upcoming HugeTLB bootmem changes need sparsemem section metadata before
>> the HugeTLB bootmem allocation path runs. The memblock ranges are marked
>> present from sparse_init(), which is called too late for that setup.
>
> It's not only that memblock regions are marked present, but it actually
> initializes mem_section's for the present memory ...
Right.
>
>> Move the code that marks memblock ranges present into
>> mm_core_init_early(), before free_area_init() and the HugeTLB bootmem
>> setup. Rename the helper to sparse_memblock_present() to make the new
>
> ... so let's name this function to reflect that.
>
> How about sparse_sections_init()?
Make sense. This name really captures the function's purpose well.
I'll go ahead and use it.
Muchun,
Thanks.
>
>> caller describe the sparsemem-specific initialization step.
>>
>> This is a preparatory change.
>>
>> Signed-off-by: Muchun Song <songmuchun@bytedance.com>
>> ---
>> mm/internal.h | 2 ++
>> mm/mm_init.c | 1 +
>> mm/sparse.c | 4 +---
>> 3 files changed, 4 insertions(+), 3 deletions(-)
>
> --
> Sincerely yours,
> Mike.
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH 09/17] mm/hugetlb: switch HugeTLB to section-based vmemmap optimization
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (7 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 08/17] mm/sparse: mark memory sections present earlier Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 10/17] mm/mm_init: factor out pfn_to_zone() Muchun Song
` (7 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
HugeTLB bootmem vmemmap optimization still carries its own early setup
path, including pre-populating optimized mappings before the generic
sparse-vmemmap code runs.
Now that the section-based vmemmap optimization can derive HugeTLB
vmemmap deduplication from section metadata, HugeTLB only needs to mark
the bootmem huge page range with the appropriate order. The generic
sparse-vmemmap population path can then allocate and map the shared tail
vmemmap pages without any HugeTLB-specific early population code.
Do that by setting the section order when a bootmem huge page is
allocated and dropping the dedicated pre-HVO helpers and related
special-casing.
This removes duplicate early setup logic and switches HugeTLB to the
section-based vmemmap optimization path.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/hugetlb.h | 1 -
include/linux/mm.h | 3 -
include/linux/mmzone.h | 17 ++++++
mm/bootmem_info.c | 5 +-
mm/hugetlb.c | 29 +---------
mm/hugetlb_vmemmap.c | 123 ++++++----------------------------------
mm/hugetlb_vmemmap.h | 14 ++---
mm/sparse-vmemmap.c | 31 ----------
8 files changed, 44 insertions(+), 179 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 7bc3cafa7f61..b7c86dc7aacc 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -170,7 +170,6 @@ struct address_space *hugetlb_folio_mapping_lock_write(struct folio *folio);
extern int movable_gigantic_pages __read_mostly;
extern int sysctl_hugetlb_shm_group __read_mostly;
-extern struct list_head huge_boot_pages[MAX_NUMNODES];
void hugetlb_bootmem_struct_page_init(void);
void hugetlb_bootmem_alloc(void);
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 2101e5205fc0..113f5e2c973b 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4908,9 +4908,6 @@ int vmemmap_populate_hugepages(unsigned long start, unsigned long end,
int node, struct vmem_altmap *altmap);
int vmemmap_populate(unsigned long start, unsigned long end, int node,
struct vmem_altmap *altmap);
-int vmemmap_populate_hvo(unsigned long start, unsigned long end,
- unsigned int order, struct zone *zone,
- unsigned long headsize);
void vmemmap_wrprotect_hvo(unsigned long start, unsigned long end, int node,
unsigned long headsize);
void vmemmap_populate_print_last(void);
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 41f6a584a52a..fcbd8d26c5c2 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2392,6 +2392,18 @@ static inline unsigned int section_order(const struct mem_section *section)
{
return section->order;
}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+ unsigned long section_nr = pfn_to_section_nr(pfn);
+
+ if (!IS_ALIGNED(pfn | nr_pages, PAGES_PER_SECTION))
+ return;
+
+ for (unsigned long i = 0; i < nr_pages / PAGES_PER_SECTION; i++)
+ section_set_order(__nr_to_section(section_nr + i), order);
+}
#else
static inline void section_set_order(struct mem_section *section, unsigned int order)
{
@@ -2401,6 +2413,11 @@ static inline unsigned int section_order(const struct mem_section *section)
{
return 0;
}
+
+static inline void section_set_order_range(unsigned long pfn, unsigned long nr_pages,
+ unsigned int order)
+{
+}
#endif
static inline bool section_vmemmap_optimizable(const struct mem_section *section)
diff --git a/mm/bootmem_info.c b/mm/bootmem_info.c
index 0fa78db7fbc0..1b9d34584f85 100644
--- a/mm/bootmem_info.c
+++ b/mm/bootmem_info.c
@@ -45,9 +45,8 @@ static void __init register_page_bootmem_info_section(unsigned long start_pfn)
section_nr = pfn_to_section_nr(start_pfn);
ms = __nr_to_section(section_nr);
- if (!preinited_vmemmap_section(ms))
- register_page_bootmem_memmap(section_nr, pfn_to_page(start_pfn),
- PAGES_PER_SECTION);
+ register_page_bootmem_memmap(section_nr, pfn_to_page(start_pfn),
+ PAGES_PER_SECTION);
}
void __init register_page_bootmem_info_node(struct pglist_data *pgdat)
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 1ef6edaa3993..5a6fb301d9d0 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -57,7 +57,7 @@ unsigned int default_hstate_idx;
struct hstate hstates[HUGE_MAX_HSTATE];
__initdata nodemask_t hugetlb_bootmem_nodes;
-__initdata struct list_head huge_boot_pages[MAX_NUMNODES];
+static struct list_head huge_boot_pages[MAX_NUMNODES] __initdata;
/*
* Due to ordering constraints across the init code for various
@@ -3094,6 +3094,7 @@ static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid)
} else {
list_add_tail(&m->list, &huge_boot_pages[nid]);
m->flags |= HUGE_BOOTMEM_ZONES_VALID;
+ hugetlb_vmemmap_optimize_bootmem_page(m);
/*
* Only initialize the head struct page in memmap_init_reserved_pages,
* rest of the struct pages will be initialized by the HugeTLB
@@ -3254,6 +3255,7 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid)
* this folio.
*/
folio_set_hugetlb_vmemmap_optimized(folio);
+ section_set_order_range(folio_pfn(folio), folio_nr_pages(folio), 0);
if (hugetlb_bootmem_page_earlycma(m))
folio_set_hugetlb_cma(folio);
@@ -3297,31 +3299,6 @@ void __init hugetlb_bootmem_struct_page_init(void)
.max_threads = num_node_state(N_MEMORY),
.numa_aware = true,
};
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
- struct zone *zone;
-
- for_each_zone(zone) {
- for (int i = 0; i < NR_OPTIMIZABLE_FOLIO_ORDERS; i++) {
- struct page *tail, *p;
- unsigned int order;
-
- tail = zone->vmemmap_tails[i];
- if (!tail)
- continue;
-
- order = i + OPTIMIZABLE_FOLIO_MIN_ORDER;
- p = page_to_virt(tail);
- /*
- * prep_and_add_bootmem_folios() can access pageblock
- * flags on bootmem HugeTLB pages, so initialize the
- * shared tail struct pages here before bootmem folios
- * start using them.
- */
- for (int j = 0; j < PAGE_SIZE / sizeof(struct page); j++)
- init_compound_tail(p + j, NULL, order, zone);
- }
- }
-#endif
padata_do_multithreaded(&job);
}
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index 196312a6fafb..b518c2c907f7 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -16,10 +16,10 @@
#include <linux/mmdebug.h>
#include <linux/pagewalk.h>
#include <linux/pgalloc.h>
+#include <linux/io.h>
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
-#include "internal.h"
/**
* struct vmemmap_remap_walk - walk vmemmap page table
@@ -478,12 +478,8 @@ long hugetlb_vmemmap_restore_folios(const struct hstate *h,
return ret;
}
-/* Return true iff a HugeTLB whose vmemmap should and can be optimized. */
-static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *folio)
+static inline bool vmemmap_should_optimize(const struct hstate *h)
{
- if (folio_test_hugetlb_vmemmap_optimized(folio))
- return false;
-
if (!READ_ONCE(vmemmap_optimize_enabled))
return false;
@@ -493,6 +489,15 @@ static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *
return true;
}
+/* Return true iff a HugeTLB whose vmemmap should and can be optimized. */
+static bool vmemmap_should_optimize_folio(const struct hstate *h, struct folio *folio)
+{
+ if (folio_test_hugetlb_vmemmap_optimized(folio))
+ return false;
+
+ return vmemmap_should_optimize(h);
+}
+
static struct page *vmemmap_get_tail(unsigned int order, struct zone *zone)
{
const unsigned int idx = order - OPTIMIZABLE_FOLIO_MIN_ORDER;
@@ -638,9 +643,6 @@ static void __hugetlb_vmemmap_optimize_folios(struct hstate *h,
epfn = spfn + hugetlb_vmemmap_size(h);
vmemmap_wrprotect_hvo(spfn, epfn, folio_nid(folio),
HUGETLB_VMEMMAP_RESERVE_SIZE);
- register_page_bootmem_memmap(pfn_to_section_nr(folio_pfn(folio)),
- &folio->page,
- HUGETLB_VMEMMAP_RESERVE_PAGES);
continue;
}
@@ -706,109 +708,18 @@ void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h, struct list_head
__hugetlb_vmemmap_optimize_folios(h, folio_list, true);
}
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
-
-/* Return true of a bootmem allocated HugeTLB page should be pre-HVO-ed */
-static bool vmemmap_should_optimize_bootmem_page(struct huge_bootmem_page *m)
+void __init hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m)
{
- unsigned long section_size, psize, pmd_vmemmap_size;
- phys_addr_t paddr;
-
- if (!READ_ONCE(vmemmap_optimize_enabled))
- return false;
-
- if (!hugetlb_vmemmap_optimizable(m->hstate))
- return false;
+ struct hstate *h = m->hstate;
+ unsigned long pfn = PHYS_PFN(__pa(m));
- psize = huge_page_size(m->hstate);
- paddr = virt_to_phys(m);
-
- /*
- * Pre-HVO only works if the bootmem huge page
- * is aligned to the section size.
- */
- section_size = (1UL << PA_SECTION_SHIFT);
- if (!IS_ALIGNED(paddr, section_size) ||
- !IS_ALIGNED(psize, section_size))
- return false;
-
- /*
- * The pre-HVO code does not deal with splitting PMDS,
- * so the bootmem page must be aligned to the number
- * of base pages that can be mapped with one vmemmap PMD.
- */
- pmd_vmemmap_size = (PMD_SIZE / (sizeof(struct page))) << PAGE_SHIFT;
- if (!IS_ALIGNED(paddr, pmd_vmemmap_size) ||
- !IS_ALIGNED(psize, pmd_vmemmap_size))
- return false;
-
- return true;
-}
-
-static struct zone *pfn_to_zone(unsigned nid, unsigned long pfn)
-{
- struct zone *zone;
- enum zone_type zone_type;
-
- for (zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
- zone = &NODE_DATA(nid)->node_zones[zone_type];
- if (zone_spans_pfn(zone, pfn))
- return zone;
- }
-
- return NULL;
-}
-
-/*
- * Initialize memmap section for a gigantic page, HVO-style.
- */
-void __init hugetlb_vmemmap_init_early(int nid)
-{
- unsigned long psize, paddr, section_size;
- unsigned long ns, i, pnum, pfn, nr_pages;
- unsigned long start, end;
- struct huge_bootmem_page *m = NULL;
- void *map;
-
- if (!READ_ONCE(vmemmap_optimize_enabled))
+ if (!vmemmap_should_optimize(h))
return;
- section_size = (1UL << PA_SECTION_SHIFT);
-
- list_for_each_entry(m, &huge_boot_pages[nid], list) {
- struct zone *zone;
-
- if (!vmemmap_should_optimize_bootmem_page(m))
- continue;
-
- nr_pages = pages_per_huge_page(m->hstate);
- psize = nr_pages << PAGE_SHIFT;
- paddr = virt_to_phys(m);
- pfn = PHYS_PFN(paddr);
- map = pfn_to_page(pfn);
- start = (unsigned long)map;
- end = start + hugetlb_vmemmap_size(m->hstate);
- zone = pfn_to_zone(nid, pfn);
-
- if (vmemmap_populate_hvo(start, end, huge_page_order(m->hstate),
- zone, HUGETLB_VMEMMAP_RESERVE_SIZE))
- panic("Failed to allocate memmap for HugeTLB page\n");
- memmap_boot_pages_add(DIV_ROUND_UP(HUGETLB_VMEMMAP_RESERVE_SIZE, PAGE_SIZE));
-
- pnum = pfn_to_section_nr(pfn);
- ns = psize / section_size;
-
- for (i = 0; i < ns; i++) {
- sparse_init_early_section(nid, map, pnum,
- SECTION_IS_VMEMMAP_PREINIT);
- map += section_map_size();
- pnum++;
- }
-
+ section_set_order_range(pfn, pages_per_huge_page(h), huge_page_order(h));
+ if (section_vmemmap_optimizable(__pfn_to_section(pfn)))
m->flags |= HUGE_BOOTMEM_HVO;
- }
}
-#endif
static const struct ctl_table hugetlb_vmemmap_sysctls[] = {
{
diff --git a/mm/hugetlb_vmemmap.h b/mm/hugetlb_vmemmap.h
index 7ac49c52457d..20eb03df542a 100644
--- a/mm/hugetlb_vmemmap.h
+++ b/mm/hugetlb_vmemmap.h
@@ -9,8 +9,7 @@
#ifndef _LINUX_HUGETLB_VMEMMAP_H
#define _LINUX_HUGETLB_VMEMMAP_H
#include <linux/hugetlb.h>
-#include <linux/io.h>
-#include <linux/memblock.h>
+#include "internal.h"
/*
* Reserve one vmemmap page, all vmemmap addresses are mapped to it. See
@@ -27,10 +26,7 @@ long hugetlb_vmemmap_restore_folios(const struct hstate *h,
void hugetlb_vmemmap_optimize_folio(const struct hstate *h, struct folio *folio);
void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_list);
void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h, struct list_head *folio_list);
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
-void hugetlb_vmemmap_init_early(int nid);
-#endif
-
+void hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m);
static inline unsigned int hugetlb_vmemmap_size(const struct hstate *h)
{
@@ -76,13 +72,13 @@ static inline void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h,
{
}
-static inline void hugetlb_vmemmap_init_early(int nid)
+static inline unsigned int hugetlb_vmemmap_optimizable_size(const struct hstate *h)
{
+ return 0;
}
-static inline unsigned int hugetlb_vmemmap_optimizable_size(const struct hstate *h)
+static inline void hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m)
{
- return 0;
}
#endif /* CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP */
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 38ed7eaf13ea..90aa11c472d6 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -32,8 +32,6 @@
#include <asm/dma.h>
#include <asm/tlbflush.h>
-#include "hugetlb_vmemmap.h"
-
/*
* Flags for vmemmap_populate_range and friends.
*/
@@ -380,34 +378,6 @@ static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *
return tail;
}
-#ifdef CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP
-int __meminit vmemmap_populate_hvo(unsigned long addr, unsigned long end,
- unsigned int order, struct zone *zone,
- unsigned long headsize)
-{
- unsigned long maddr;
- struct page *tail;
- pte_t *pte;
- int node = zone_to_nid(zone);
-
- tail = vmemmap_get_tail(order, zone);
- if (!tail)
- return -ENOMEM;
-
- for (maddr = addr; maddr < addr + headsize; maddr += PAGE_SIZE) {
- pte = vmemmap_populate_address(maddr, node, NULL, -1, 0);
- if (!pte)
- return -ENOMEM;
- }
-
- /*
- * Reuse the last page struct page mapped above for the rest.
- */
- return vmemmap_populate_range(maddr, end, node, NULL,
- page_to_pfn(tail), 0);
-}
-#endif
-
void __weak __meminit vmemmap_set_pmd(pmd_t *pmd, void *p, int node,
unsigned long addr, unsigned long next)
{
@@ -624,7 +594,6 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn,
*/
void __init sparse_vmemmap_init_nid_early(int nid)
{
- hugetlb_vmemmap_init_early(nid);
}
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 10/17] mm/mm_init: factor out pfn_to_zone()
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (8 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 09/17] mm/hugetlb: switch HugeTLB to section-based vmemmap optimization Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 11/17] mm/sparse-vmemmap: remove SPARSEMEM_VMEMMAP_PREINIT support Muchun Song
` (6 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
pfn_to_zone() in sparse-vmemmap.c duplicates the zone lookup logic in
__init_deferred_page().
Move it to mm_init.c, declare it in mm/internal.h, and reuse it from
__init_deferred_page() instead of open-coding the zone walk there.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
mm/internal.h | 1 +
mm/mm_init.c | 28 ++++++++++++++++++----------
mm/sparse-vmemmap.c | 14 --------------
3 files changed, 19 insertions(+), 24 deletions(-)
diff --git a/mm/internal.h b/mm/internal.h
index 11844b40f47a..1348c8bb18e0 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -1360,6 +1360,7 @@ static inline bool deferred_pages_enabled(void)
}
#endif /* CONFIG_DEFERRED_STRUCT_PAGE_INIT */
+struct zone *pfn_to_zone(unsigned long pfn, int nid);
void init_deferred_page(unsigned long pfn, int nid);
enum mminit_level {
diff --git a/mm/mm_init.c b/mm/mm_init.c
index 0256ab4ba839..69ececb591e2 100644
--- a/mm/mm_init.c
+++ b/mm/mm_init.c
@@ -689,6 +689,20 @@ static __meminit void pageblock_migratetype_init_range(unsigned long pfn,
}
}
+struct zone __meminit *pfn_to_zone(unsigned long pfn, int nid)
+{
+ pg_data_t *pgdat = NODE_DATA(nid);
+
+ for (enum zone_type zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
+ struct zone *zone = &pgdat->node_zones[zone_type];
+
+ if (zone_spans_pfn(zone, pfn))
+ return zone;
+ }
+
+ return NULL;
+}
+
#ifdef CONFIG_DEFERRED_STRUCT_PAGE_INIT
static inline void pgdat_set_deferred_range(pg_data_t *pgdat)
{
@@ -747,20 +761,14 @@ defer_init(int nid, unsigned long pfn, unsigned long end_pfn)
static void __meminit __init_deferred_page(unsigned long pfn, int nid)
{
- pg_data_t *pgdat = NODE_DATA(nid);
- int zid;
+ struct zone *zone;
if (early_page_initialised(pfn, nid))
return;
- for (zid = 0; zid < MAX_NR_ZONES; zid++) {
- struct zone *zone = &pgdat->node_zones[zid];
-
- if (zone_spans_pfn(zone, pfn))
- break;
- }
- __init_single_page(pfn_to_page(pfn), pfn, zid, nid);
-
+ zone = pfn_to_zone(pfn, nid);
+ __init_single_page(pfn_to_page(pfn), pfn,
+ zone ? zone_idx(zone) : MAX_NR_ZONES, nid);
if (pageblock_aligned(pfn)) {
enum migratetype mt =
kho_scratch_migratetype(pfn, MIGRATE_MOVABLE);
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 90aa11c472d6..37afd5ccf260 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -144,20 +144,6 @@ void __meminit vmemmap_verify(pte_t *pte, int node,
start, end - 1);
}
-static struct zone __meminit *pfn_to_zone(unsigned long pfn, int nid)
-{
- pg_data_t *pgdat = NODE_DATA(nid);
-
- for (enum zone_type zone_type = 0; zone_type < MAX_NR_ZONES; zone_type++) {
- struct zone *zone = &pgdat->node_zones[zone_type];
-
- if (zone_spans_pfn(zone, pfn))
- return zone;
- }
-
- return NULL;
-}
-
static __meminit struct page *vmemmap_get_tail(unsigned int order, struct zone *zone);
static pte_t * __meminit vmemmap_pte_populate(pmd_t *pmd, unsigned long addr, int node,
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 11/17] mm/sparse-vmemmap: remove SPARSEMEM_VMEMMAP_PREINIT support
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (9 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 10/17] mm/mm_init: factor out pfn_to_zone() Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 12/17] mm/sparse: inline usemap allocation into sparse_init_nid() Muchun Song
` (5 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
SPARSEMEM_VMEMMAP_PREINIT existed only to support HugeTLB's early
vmemmap optimization setup.
Now that HugeTLB bootmem vmemmap optimization uses the common
section-based sparse-vmemmap path, the sparse initialization code no
longer needs a separate pre-initialization mechanism for vmemmap
population.
Remove the related Kconfig symbols, section flag, and empty early hook,
so present sections always go through the normal sparse setup path.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
arch/x86/Kconfig | 1 -
fs/Kconfig | 1 -
include/linux/mmzone.h | 25 -------------------------
mm/Kconfig | 5 -----
mm/sparse-vmemmap.c | 13 -------------
mm/sparse.c | 23 ++++++++---------------
6 files changed, 8 insertions(+), 60 deletions(-)
diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f210e4..3cab495637c4 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -147,7 +147,6 @@ config X86
select ARCH_WANT_LD_ORPHAN_WARN
select ARCH_WANT_OPTIMIZE_DAX_VMEMMAP if X86_64
select ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP if X86_64
- select ARCH_WANT_HUGETLB_VMEMMAP_PREINIT if X86_64
select ARCH_WANTS_THP_SWAP if X86_64
select ARCH_HAS_PARANOID_L1D_FLUSH
select ARCH_WANT_IRQS_OFF_ACTIVATE_MM
diff --git a/fs/Kconfig b/fs/Kconfig
index cf6ae64776e6..ccb9dd480523 100644
--- a/fs/Kconfig
+++ b/fs/Kconfig
@@ -278,7 +278,6 @@ config HUGETLB_PAGE_OPTIMIZE_VMEMMAP
def_bool HUGETLB_PAGE
depends on ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
depends on SPARSEMEM_VMEMMAP
- select SPARSEMEM_VMEMMAP_PREINIT if ARCH_WANT_HUGETLB_VMEMMAP_PREINIT
config HUGETLB_PMD_PAGE_TABLE_SHARING
def_bool HUGETLB_PAGE
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index fcbd8d26c5c2..dbd3efc53d56 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2081,9 +2081,6 @@ enum {
SECTION_IS_EARLY_BIT,
#ifdef CONFIG_ZONE_DEVICE
SECTION_TAINT_ZONE_DEVICE_BIT,
-#endif
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
- SECTION_IS_VMEMMAP_PREINIT_BIT,
#endif
SECTION_MAP_LAST_BIT,
};
@@ -2095,9 +2092,6 @@ enum {
#ifdef CONFIG_ZONE_DEVICE
#define SECTION_TAINT_ZONE_DEVICE BIT(SECTION_TAINT_ZONE_DEVICE_BIT)
#endif
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
-#define SECTION_IS_VMEMMAP_PREINIT BIT(SECTION_IS_VMEMMAP_PREINIT_BIT)
-#endif
#define SECTION_MAP_MASK (~(BIT(SECTION_MAP_LAST_BIT) - 1))
#define SECTION_NID_SHIFT SECTION_MAP_LAST_BIT
@@ -2152,24 +2146,6 @@ static inline int online_device_section(const struct mem_section *section)
}
#endif
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
-static inline int preinited_vmemmap_section(const struct mem_section *section)
-{
- return (section &&
- (section->section_mem_map & SECTION_IS_VMEMMAP_PREINIT));
-}
-
-void sparse_vmemmap_init_nid_early(int nid);
-#else
-static inline int preinited_vmemmap_section(const struct mem_section *section)
-{
- return 0;
-}
-static inline void sparse_vmemmap_init_nid_early(int nid)
-{
-}
-#endif
-
static inline int online_section_nr(unsigned long nr)
{
return online_section(__nr_to_section(nr));
@@ -2373,7 +2349,6 @@ static inline unsigned long next_present_section_nr(unsigned long section_nr)
#else
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)
{
diff --git a/mm/Kconfig b/mm/Kconfig
index 9e0ca4824905..50fa9724f711 100644
--- a/mm/Kconfig
+++ b/mm/Kconfig
@@ -463,8 +463,6 @@ config SPARSEMEM_VMEMMAP
pfn_to_page and page_to_pfn operations. This is the most
efficient option when sufficient kernel resources are available.
-config SPARSEMEM_VMEMMAP_PREINIT
- bool
#
# Select this config option from the architecture Kconfig, if it is preferred
# to enable the feature of HugeTLB/dev_dax vmemmap optimization.
@@ -475,9 +473,6 @@ config ARCH_WANT_OPTIMIZE_DAX_VMEMMAP
config ARCH_WANT_OPTIMIZE_HUGETLB_VMEMMAP
bool
-config ARCH_WANT_HUGETLB_VMEMMAP_PREINIT
- bool
-
config HAVE_MEMBLOCK_PHYS_MAP
bool
diff --git a/mm/sparse-vmemmap.c b/mm/sparse-vmemmap.c
index 37afd5ccf260..166fb95b5e6d 100644
--- a/mm/sparse-vmemmap.c
+++ b/mm/sparse-vmemmap.c
@@ -570,19 +570,6 @@ struct page * __meminit __populate_section_memmap(unsigned long pfn,
return pfn_to_page(pfn);
}
-#ifdef CONFIG_SPARSEMEM_VMEMMAP_PREINIT
-/*
- * This is called just before initializing sections for a NUMA node.
- * Any special initialization that needs to be done before the
- * generic initialization can be done from here. Sections that
- * are initialized in hooks called from here will be skipped by
- * the generic initialization.
- */
-void __init sparse_vmemmap_init_nid_early(int nid)
-{
-}
-#endif
-
static void subsection_mask_set(unsigned long *map, unsigned long pfn,
unsigned long nr_pages)
{
diff --git a/mm/sparse.c b/mm/sparse.c
index 2a1a1f5e97c4..eb841fe61f0a 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -327,27 +327,20 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
if (sparse_usage_init(nid, map_count))
panic("Failed to allocate usemap for node %d\n", nid);
- sparse_vmemmap_init_nid_early(nid);
-
for_each_present_section_nr(pnum_begin, pnum) {
- struct mem_section *ms;
unsigned long pfn = section_nr_to_pfn(pnum);
+ struct page *map;
if (pnum >= pnum_end)
break;
- ms = __nr_to_section(pnum);
- if (!preinited_vmemmap_section(ms)) {
- struct page *map;
-
- map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
- nid, NULL, NULL);
- if (!map)
- panic("Failed to allocate memmap for section %lu\n", pnum);
- memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
- NULL, NULL));
- sparse_init_early_section(nid, map, pnum, 0);
- }
+ map = __populate_section_memmap(pfn, PAGES_PER_SECTION,
+ nid, NULL, NULL);
+ if (!map)
+ panic("Failed to allocate memmap for section %lu\n", pnum);
+ memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
+ NULL, NULL));
+ sparse_init_early_section(nid, map, pnum, 0);
}
sparse_usage_fini();
}
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 12/17] mm/sparse: inline usemap allocation into sparse_init_nid()
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (10 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 11/17] mm/sparse-vmemmap: remove SPARSEMEM_VMEMMAP_PREINIT support Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 13/17] mm/sparse: remove section_map_size() Muchun Song
` (4 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
After removing SPARSEMEM_VMEMMAP_PREINIT, sparse_init_nid() no longer
needs the transient sparse_usagebuf state and its helper wrappers.
Allocate the usemap buffer directly in sparse_init_nid(), pass it to
sparse_init_one_section(), and drop sparse_usage_init(),
sparse_usage_fini(), and sparse_init_early_section().
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mmzone.h | 3 ---
mm/sparse.c | 46 +++++++-----------------------------------
2 files changed, 7 insertions(+), 42 deletions(-)
diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index dbd3efc53d56..2d1ea42f1d7b 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2209,9 +2209,6 @@ static inline bool pfn_section_first_valid(struct mem_section *ms, unsigned long
}
#endif
-void sparse_init_early_section(int nid, struct page *map, unsigned long pnum,
- unsigned long flags);
-
#ifndef CONFIG_HAVE_ARCH_PFN_VALID
/**
* pfn_valid - check if there is a valid memory map entry for a PFN
diff --git a/mm/sparse.c b/mm/sparse.c
index eb841fe61f0a..4e5f6c327043 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -248,42 +248,6 @@ void __weak __meminit vmemmap_populate_print_last(void)
{
}
-static void *sparse_usagebuf __meminitdata;
-static void *sparse_usagebuf_end __meminitdata;
-
-/*
- * Helper function that is used for generic section initialization, and
- * can also be used by any hooks added above.
- */
-void __init sparse_init_early_section(int nid, struct page *map,
- unsigned long pnum, unsigned long flags)
-{
- BUG_ON(!sparse_usagebuf || sparse_usagebuf >= sparse_usagebuf_end);
- sparse_init_one_section(__nr_to_section(pnum), pnum, map,
- sparse_usagebuf, SECTION_IS_EARLY | flags);
- sparse_usagebuf = (void *)sparse_usagebuf + mem_section_usage_size();
-}
-
-static int __init sparse_usage_init(int nid, unsigned long map_count)
-{
- unsigned long size;
-
- size = mem_section_usage_size() * map_count;
- sparse_usagebuf = memblock_alloc_node(size, SMP_CACHE_BYTES, nid);
- if (!sparse_usagebuf) {
- sparse_usagebuf_end = NULL;
- return -ENOMEM;
- }
-
- sparse_usagebuf_end = sparse_usagebuf + size;
- return 0;
-}
-
-static void __init sparse_usage_fini(void)
-{
- sparse_usagebuf = sparse_usagebuf_end = NULL;
-}
-
int __meminit section_nr_vmemmap_pages(unsigned long pfn, unsigned long nr_pages,
struct vmem_altmap *altmap, struct dev_pagemap *pgmap)
{
@@ -323,8 +287,11 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
unsigned long map_count)
{
unsigned long pnum;
+ struct mem_section_usage *usage;
- if (sparse_usage_init(nid, map_count))
+ usage = memblock_alloc_node(map_count * mem_section_usage_size(),
+ SMP_CACHE_BYTES, nid);
+ if (!usage)
panic("Failed to allocate usemap for node %d\n", nid);
for_each_present_section_nr(pnum_begin, pnum) {
@@ -340,9 +307,10 @@ static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
panic("Failed to allocate memmap for section %lu\n", pnum);
memmap_boot_pages_add(section_nr_vmemmap_pages(pfn, PAGES_PER_SECTION,
NULL, NULL));
- sparse_init_early_section(nid, map, pnum, 0);
+ sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
+ SECTION_IS_EARLY);
+ usage = (void *)usage + mem_section_usage_size();
}
- sparse_usage_fini();
}
/*
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 13/17] mm/sparse: remove section_map_size()
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (11 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 12/17] mm/sparse: inline usemap allocation into sparse_init_nid() Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 14/17] mm/hugetlb: remove HUGE_BOOTMEM_HVO Muchun Song
` (3 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
section_map_size() no longer provides any shared logic.
After the sparse-vmemmap changes, its only remaining user is the
!CONFIG_SPARSEMEM_VMEMMAP path in __populate_section_memmap(), which can
compute the size inline with PAGE_ALIGN(sizeof(struct page) *
PAGES_PER_SECTION).
Remove section_map_size() and inline the remaining calculation.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/mm.h | 1 -
mm/sparse.c | 15 ++-------------
2 files changed, 2 insertions(+), 14 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 113f5e2c973b..60cbc106760e 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -4889,7 +4889,6 @@ static inline void print_vma_addr(char *prefix, unsigned long rip)
}
#endif
-unsigned long section_map_size(void);
struct page * __populate_section_memmap(unsigned long pfn,
unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
struct dev_pagemap *pgmap);
diff --git a/mm/sparse.c b/mm/sparse.c
index 4e5f6c327043..6ba6f31672cf 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -222,23 +222,12 @@ size_t mem_section_usage_size(void)
return sizeof(struct mem_section_usage) + usemap_size();
}
-#ifdef CONFIG_SPARSEMEM_VMEMMAP
-unsigned long __init section_map_size(void)
-{
- return ALIGN(sizeof(struct page) * PAGES_PER_SECTION, PMD_SIZE);
-}
-
-#else
-unsigned long __init section_map_size(void)
-{
- return PAGE_ALIGN(sizeof(struct page) * PAGES_PER_SECTION);
-}
-
+#ifndef CONFIG_SPARSEMEM_VMEMMAP
struct page __init *__populate_section_memmap(unsigned long pfn,
unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
struct dev_pagemap *pgmap)
{
- unsigned long size = section_map_size();
+ unsigned long size = PAGE_ALIGN(sizeof(struct page) * PAGES_PER_SECTION);
return memmap_alloc(size, size, __pa(MAX_DMA_ADDRESS), nid, false);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 14/17] mm/hugetlb: remove HUGE_BOOTMEM_HVO
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (12 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 13/17] mm/sparse: remove section_map_size() Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 15/17] mm/hugetlb: remove HUGE_BOOTMEM_CMA Muchun Song
` (2 subsequent siblings)
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
The HUGE_BOOTMEM_HVO flag tracked whether a bootmem huge page had
already gone through the old early vmemmap optimization path.
Now that HugeTLB uses section-based vmemmap optimization, that state is
already reflected in the section order.
Remove HUGE_BOOTMEM_HVO and its helper, and use the section state
directly when deciding whether to mark a folio as vmemmap-optimized.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/hugetlb.h | 5 ++---
mm/hugetlb.c | 12 +-----------
mm/hugetlb_vmemmap.c | 2 --
3 files changed, 3 insertions(+), 16 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index b7c86dc7aacc..a051c5d6549e 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -674,9 +674,8 @@ struct hstate {
char name[HSTATE_NAME_LEN];
};
-#define HUGE_BOOTMEM_HVO 0x0001
-#define HUGE_BOOTMEM_ZONES_VALID 0x0002
-#define HUGE_BOOTMEM_CMA 0x0004
+#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
+#define HUGE_BOOTMEM_CMA BIT(1)
int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list);
int replace_free_hugepage_folios(unsigned long start_pfn, unsigned long end_pfn);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 5a6fb301d9d0..89ec1e991dc5 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3152,11 +3152,6 @@ static void __init hugetlb_folio_init_vmemmap(struct folio *folio,
prep_compound_head(&folio->page, huge_page_order(h));
}
-static bool __init hugetlb_bootmem_page_prehvo(struct huge_bootmem_page *m)
-{
- return m->flags & HUGE_BOOTMEM_HVO;
-}
-
static bool __init hugetlb_bootmem_page_earlycma(struct huge_bootmem_page *m)
{
return m->flags & HUGE_BOOTMEM_CMA;
@@ -3248,12 +3243,7 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid)
HUGETLB_VMEMMAP_RESERVE_PAGES);
init_new_hugetlb_folio(folio);
- if (hugetlb_bootmem_page_prehvo(m))
- /*
- * If pre-HVO was done, just set the
- * flag, the HVO code will then skip
- * this folio.
- */
+ if (section_vmemmap_optimizable(__pfn_to_section(folio_pfn(folio))))
folio_set_hugetlb_vmemmap_optimized(folio);
section_set_order_range(folio_pfn(folio), folio_nr_pages(folio), 0);
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index b518c2c907f7..e3a200d98389 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -717,8 +717,6 @@ void __init hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m)
return;
section_set_order_range(pfn, pages_per_huge_page(h), huge_page_order(h));
- if (section_vmemmap_optimizable(__pfn_to_section(pfn)))
- m->flags |= HUGE_BOOTMEM_HVO;
}
static const struct ctl_table hugetlb_vmemmap_sysctls[] = {
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 15/17] mm/hugetlb: remove HUGE_BOOTMEM_CMA
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (13 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 14/17] mm/hugetlb: remove HUGE_BOOTMEM_HVO Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 16/17] mm/hugetlb: localize struct huge_bootmem_page Muchun Song
2026-07-02 9:38 ` [PATCH 17/17] mm/hugetlb: localize HUGE_BOOTMEM_ZONES_VALID Muchun Song
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
Track early CMA hugetlb pages from the hstate instead of storing a
redundant bootmem flag. This removes the unused helper and keeps the
bootmem metadata minimal.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/hugetlb.h | 1 -
mm/hugetlb.c | 14 ++++----------
2 files changed, 4 insertions(+), 11 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index a051c5d6549e..d07dc99d8448 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -675,7 +675,6 @@ struct hstate {
};
#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
-#define HUGE_BOOTMEM_CMA BIT(1)
int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list);
int replace_free_hugepage_folios(unsigned long start_pfn, unsigned long end_pfn);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 89ec1e991dc5..265d179a8929 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -3076,7 +3076,7 @@ static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid)
*/
INIT_LIST_HEAD(&m->list);
m->hstate = h;
- m->flags = hugetlb_early_cma(h) ? HUGE_BOOTMEM_CMA : 0;
+ m->flags = 0;
/* CMA pages: zone-crossing is validated in hugetlb_cma_reserve(). */
if (!hugetlb_early_cma(h) &&
@@ -3152,11 +3152,6 @@ static void __init hugetlb_folio_init_vmemmap(struct folio *folio,
prep_compound_head(&folio->page, huge_page_order(h));
}
-static bool __init hugetlb_bootmem_page_earlycma(struct huge_bootmem_page *m)
-{
- return m->flags & HUGE_BOOTMEM_CMA;
-}
-
/*
* memblock-allocated pageblocks might not have the migrate type set
* if marked with the 'noinit' flag. Set it to the default (MIGRATE_MOVABLE)
@@ -3247,9 +3242,6 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid)
folio_set_hugetlb_vmemmap_optimized(folio);
section_set_order_range(folio_pfn(folio), folio_nr_pages(folio), 0);
- if (hugetlb_bootmem_page_earlycma(m))
- folio_set_hugetlb_cma(folio);
-
list_add(&folio->lru, &folio_list);
/*
@@ -3260,7 +3252,9 @@ static void __init gather_bootmem_prealloc_node(unsigned long nid)
* For CMA pages, this is done in init_cma_pageblock
* (via hugetlb_bootmem_init_migratetype), so skip it here.
*/
- if (!folio_test_hugetlb_cma(folio))
+ if (hugetlb_early_cma(h))
+ folio_set_hugetlb_cma(folio);
+ else
adjust_managed_page_count(page, pages_per_huge_page(h));
cond_resched();
}
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 16/17] mm/hugetlb: localize struct huge_bootmem_page
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (14 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 15/17] mm/hugetlb: remove HUGE_BOOTMEM_CMA Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
2026-07-02 9:38 ` [PATCH 17/17] mm/hugetlb: localize HUGE_BOOTMEM_ZONES_VALID Muchun Song
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
struct huge_bootmem_page is only used by hugetlb boot-time allocation
code, but its definition currently lives in mm/internal.h because
hugetlb_vmemmap_optimize_bootmem_page() takes it as an argument. This
exposes a hugetlb-specific internal type more broadly than needed.
Change hugetlb_vmemmap_optimize_bootmem_page() to take the information it
actually needs. With that interface, mm/hugetlb_vmemmap.h no longer needs
to include mm/internal.h, and struct huge_bootmem_page can move into
mm/hugetlb.c.
Make huge_page_order() take const struct hstate * as well so the new
helper interface does not need to discard constness.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/hugetlb.h | 4 ++--
mm/hugetlb.c | 8 +++++++-
mm/hugetlb_vmemmap.c | 6 +++---
mm/hugetlb_vmemmap.h | 5 ++---
mm/internal.h | 7 -------
5 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index d07dc99d8448..99fcb25cc201 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -757,7 +757,7 @@ static inline unsigned long huge_page_mask(struct hstate *h)
return h->mask;
}
-static inline unsigned int huge_page_order(struct hstate *h)
+static inline unsigned int huge_page_order(const struct hstate *h)
{
return h->order;
}
@@ -1165,7 +1165,7 @@ static inline unsigned long huge_page_mask(struct hstate *h)
return PAGE_MASK;
}
-static inline unsigned int huge_page_order(struct hstate *h)
+static inline unsigned int huge_page_order(const struct hstate *h)
{
return 0;
}
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 265d179a8929..5eae762c99d5 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -52,6 +52,12 @@
#include "hugetlb_internal.h"
#include <linux/page-isolation.h>
+struct huge_bootmem_page {
+ struct list_head list;
+ struct hstate *hstate;
+ unsigned long flags;
+};
+
int hugetlb_max_hstate __read_mostly;
unsigned int default_hstate_idx;
struct hstate hstates[HUGE_MAX_HSTATE];
@@ -3094,7 +3100,7 @@ static bool __init alloc_bootmem_huge_page(struct hstate *h, int nid)
} else {
list_add_tail(&m->list, &huge_boot_pages[nid]);
m->flags |= HUGE_BOOTMEM_ZONES_VALID;
- hugetlb_vmemmap_optimize_bootmem_page(m);
+ hugetlb_vmemmap_optimize_bootmem_page(h, m);
/*
* Only initialize the head struct page in memmap_init_reserved_pages,
* rest of the struct pages will be initialized by the HugeTLB
diff --git a/mm/hugetlb_vmemmap.c b/mm/hugetlb_vmemmap.c
index e3a200d98389..c3d4284c19fd 100644
--- a/mm/hugetlb_vmemmap.c
+++ b/mm/hugetlb_vmemmap.c
@@ -20,6 +20,7 @@
#include <asm/tlbflush.h>
#include "hugetlb_vmemmap.h"
+#include "internal.h"
/**
* struct vmemmap_remap_walk - walk vmemmap page table
@@ -708,10 +709,9 @@ void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h, struct list_head
__hugetlb_vmemmap_optimize_folios(h, folio_list, true);
}
-void __init hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m)
+void __init hugetlb_vmemmap_optimize_bootmem_page(const struct hstate *h, void *addr)
{
- struct hstate *h = m->hstate;
- unsigned long pfn = PHYS_PFN(__pa(m));
+ unsigned long pfn = PHYS_PFN(__pa(addr));
if (!vmemmap_should_optimize(h))
return;
diff --git a/mm/hugetlb_vmemmap.h b/mm/hugetlb_vmemmap.h
index 20eb03df542a..0845575207fd 100644
--- a/mm/hugetlb_vmemmap.h
+++ b/mm/hugetlb_vmemmap.h
@@ -9,7 +9,6 @@
#ifndef _LINUX_HUGETLB_VMEMMAP_H
#define _LINUX_HUGETLB_VMEMMAP_H
#include <linux/hugetlb.h>
-#include "internal.h"
/*
* Reserve one vmemmap page, all vmemmap addresses are mapped to it. See
@@ -26,7 +25,7 @@ long hugetlb_vmemmap_restore_folios(const struct hstate *h,
void hugetlb_vmemmap_optimize_folio(const struct hstate *h, struct folio *folio);
void hugetlb_vmemmap_optimize_folios(struct hstate *h, struct list_head *folio_list);
void hugetlb_vmemmap_optimize_bootmem_folios(struct hstate *h, struct list_head *folio_list);
-void hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m);
+void hugetlb_vmemmap_optimize_bootmem_page(const struct hstate *h, void *addr);
static inline unsigned int hugetlb_vmemmap_size(const struct hstate *h)
{
@@ -77,7 +76,7 @@ static inline unsigned int hugetlb_vmemmap_optimizable_size(const struct hstate
return 0;
}
-static inline void hugetlb_vmemmap_optimize_bootmem_page(struct huge_bootmem_page *m)
+static inline void hugetlb_vmemmap_optimize_bootmem_page(const struct hstate *h, void *addr)
{
}
#endif /* CONFIG_HUGETLB_PAGE_OPTIMIZE_VMEMMAP */
diff --git a/mm/internal.h b/mm/internal.h
index 1348c8bb18e0..fdf40fecae57 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -23,13 +23,6 @@
#include "vma.h"
struct folio_batch;
-struct hstate;
-
-struct huge_bootmem_page {
- struct list_head list;
- struct hstate *hstate;
- unsigned long flags;
-};
/*
* Maintains state across a page table move. The operation assumes both source
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread* [PATCH 17/17] mm/hugetlb: localize HUGE_BOOTMEM_ZONES_VALID
2026-07-02 9:38 [PATCH 00/17] mm: Introduce section-based vmemmap optimization for HugeTLB Muchun Song
` (15 preceding siblings ...)
2026-07-02 9:38 ` [PATCH 16/17] mm/hugetlb: localize struct huge_bootmem_page Muchun Song
@ 2026-07-02 9:38 ` Muchun Song
16 siblings, 0 replies; 24+ messages in thread
From: Muchun Song @ 2026-07-02 9:38 UTC (permalink / raw)
To: Andrew Morton, Oscar Salvador, David Hildenbrand, linux-mm
Cc: Mike Rapoport, Vlastimil Babka, Lorenzo Stoakes, Michal Hocko,
linux-kernel, Muchun Song, muchun.song
HUGE_BOOTMEM_ZONES_VALID is only used by the huge_bootmem_page flag
handling in mm/hugetlb.c. Keep the definition next to that private data
structure instead of exposing it through the public hugetlb header.
No functional change is intended.
Signed-off-by: Muchun Song <songmuchun@bytedance.com>
---
include/linux/hugetlb.h | 2 --
mm/hugetlb.c | 2 ++
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/linux/hugetlb.h b/include/linux/hugetlb.h
index 99fcb25cc201..b11d964ac634 100644
--- a/include/linux/hugetlb.h
+++ b/include/linux/hugetlb.h
@@ -674,8 +674,6 @@ struct hstate {
char name[HSTATE_NAME_LEN];
};
-#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
-
int isolate_or_dissolve_huge_folio(struct folio *folio, struct list_head *list);
int replace_free_hugepage_folios(unsigned long start_pfn, unsigned long end_pfn);
void wait_for_freed_hugetlb_folios(void);
diff --git a/mm/hugetlb.c b/mm/hugetlb.c
index 5eae762c99d5..9aa2ebd45ca9 100644
--- a/mm/hugetlb.c
+++ b/mm/hugetlb.c
@@ -52,6 +52,8 @@
#include "hugetlb_internal.h"
#include <linux/page-isolation.h>
+#define HUGE_BOOTMEM_ZONES_VALID BIT(0)
+
struct huge_bootmem_page {
struct list_head list;
struct hstate *hstate;
--
2.54.0
^ permalink raw reply related [flat|nested] 24+ messages in thread