[PATCH v2 01/17] mm/sparse: relax struct mem_section size constraints

From: Muchun Song

Date: Mon Jul 20 2026 - 05:41:06 EST


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 makes future extensions harder than necessary: adding a
small field can require configuration-dependent padding or layout checks
just to preserve the lookup scheme. Keep the lookup correct for any
struct mem_section size by using a plain modulo instead.

Do not leave the layout entirely unconstrained, though. Keep struct
mem_section double-word aligned so modest size changes, such as adding
another word-sized field on 64-bit systems, still keep a compact and
efficient layout. If future fields grow the structure beyond that sweet
spot, the lookup remains correct; only the exact layout efficiency
changes.

Signed-off-by: Muchun Song <songmuchun@xxxxxxxxxxxxx>
---
v2:
- Align struct mem_section to power-of-2 where possible, but support
non-power-of-2 lookup (suggested by David Laight)
---
include/linux/mmzone.h | 11 +++--------
mm/sparse.c | 2 --
scripts/gdb/linux/mm.py | 6 ++----
3 files changed, 5 insertions(+), 14 deletions(-)

diff --git a/include/linux/mmzone.h b/include/linux/mmzone.h
index 0507193b3ae3..04f3409edacb 100644
--- a/include/linux/mmzone.h
+++ b/include/linux/mmzone.h
@@ -2016,13 +2016,9 @@ 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.
- */
-};
+/* Sacrifice minor padding space for efficient lookup. */
+} __aligned(2 * sizeof(unsigned long));

#ifdef CONFIG_SPARSEMEM_EXTREME
#define SECTIONS_PER_ROOT (PAGE_SIZE / sizeof (struct mem_section))
@@ -2032,7 +2028,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;
@@ -2056,7 +2051,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 704a9dec2b9a..ca9875f568d3 100644
--- a/mm/sparse.c
+++ b/mm/sparse.c
@@ -332,8 +332,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