Skip to contentSkip to conversation

mm: improve large folio readahead and alignment for exec memory

PATCH Superseded Usama Arif 5 participants 21 messages Mar 20 – Mar 30

Subsystems (7)
Memory management VFS MM core arm64 +3 more subsystems

v2 of 7 Superseded Latest is v7 →

lore
Share & tools
lore

Usama Arif

author reviewer
#0 lore raw

v2 cover mm: improve large folio readahead and alignment for exec memory

v2 takes a different approach from v1 trying to move away from exec_folio_order() and comeup with a generic arch independent solution which is what I think David was suggesting in [1].
I thought I would send it as code rather than try and discuss it in v1 as its easier :)

v1 -> v2: https://lore.kernel.org/all/[email protected]/
- disable mmap_miss logic for VM_EXEC (Jan Kara)
- Align in elf only when segment VA and file offset are already aligned (Rui)
- preferred_exec_order() for VM_EXEC sync mmap_readahead which takes into account zone high watermarks (as an approximation of memory pressure) (David, or atleast my approach to what David suggested in [1] :))
- Extend max alignment to mapping_max_folio_size() instead of exec_folio_order()

Motiviation
===========
exec_folio_order() was introduced [2] to request readahead at an arch-preferred folio order for executable memory, enabling hardware PTE coalescing (e.g. arm64 contpte) and PMD mappings on the fault path.

However, several things prevent this from working optimally:

1. The mmap_miss heuristic in do_sync_mmap_readahead() silently disables exec readahead after 100 page faults. The mmap_miss counter tracks whether readahead is useful for mmap'd file access:

- Incremented by 1 in do_sync_mmap_readahead() on every page cache miss (page needed IO).

- Decremented by N in filemap_map_pages() for N pages successfully mapped via fault-around (pages found in cache without faulting, evidence that readahead was useful). Only non-workingset pages count and recently evicted and re-read pages don't count as hits.

- Decremented by 1 in do_async_mmap_readahead() when a PG_readahead marker page is found (indicates sequential consumption of readahead pages).

When mmap_miss exceeds MMAP_LOTSAMISS (100), all readahead is disabled. On arm64 with 64K pages, both decrement paths are inactive:

- filemap_map_pages() is never called because fault_around_pages (65536 >> PAGE_SHIFT = 1) disables should_fault_around(), which requires fault_around_pages > 1. With only 1 page in the fault-around window, there is nothing "around" to map.

- do_async_mmap_readahead() never fires for exec mappings because exec readahead sets async_size = 0, so no PG_readahead markers are placed.

With no decrements, mmap_miss monotonically increases past MMAP_LOTSAMISS after 100 faults, disabling exec readahead for the remainder of the mapping. Patch 1 fixes this by excluding VM_EXEC VMAs from the mmap_miss logic, similar to how VM_SEQ_READ is already excluded.

2. exec_folio_order() is an arch-specific hook that returns a static order (ilog2(SZ_64K >> PAGE_SHIFT)), which is suboptimal for non-4K page sizes and doesn't adapt to runtime conditions. Patch 2 replaces it with a generic preferred_exec_order() that targets min(PMD_ORDER, 2M), which naturally gives the right answer across architectures and different page sizes: contpte on arm64 (2M for all page sizes), PMD mapping on x86 (2M), and smaller PMDs like s390 (1M). The 2M cap also avoids requesting excessively large folios on configurations where PMD_ORDER is much larger (32M on arm64 16K pages, 512M on arm64 64K pages), which would cause unnecessary memory pressure. The function adapts at runtime based on VMA size and memory pressure (zone watermarks), stepping down the order when memory is tight.

3. Even with correct folio order and readahead, hardware PTE coalescing (e.g. contpte) and PMD mapping require the virtual address to be aligned to the folio size. The readahead path aligns file offsets and the buddy allocator aligns physical memory, but the virtual address depends on the VMA start. For PIE binaries, ASLR randomizes the load address at PAGE_SIZE granularity, so on arm64 with 64K pages only 1/32 of load addresses are 2M-aligned. When misaligned, contpte cannot be used for any folio in the VMA.

Patch 3 fixes this for the main binary by extending maximum_alignment() in the ELF loader to consider mapping_max_folio_size(), aligning load_bias to the largest folio the filesystem will allocate.

Patch 4 fixes this for shared libraries by adding a folio-size alignment fallback in thp_get_unmapped_area_vmflags(). The existing PMD_SIZE alignment (512M on arm64 64K pages) is too large for typical shared libraries, so this smaller fallback succeeds where PMD fails.

I created a benchmark that mmaps a large executable file and calls RET-stub functions at PAGE_SIZE offsets across it. "Cold" measures fault + readahead cost. "Random" first faults in all pages with a sequential sweep (not measured), then measures time for calling random offsets, isolating iTLB miss cost for scattered execution.

The benchmark results on Neoverse V2 (Grace), arm64 with 64K base pages, 512MB executable file on ext4, averaged over 3 runs:

  Phase      | Baseline     | Patched      | Improvement
  -----------|--------------|--------------|------------------
  Cold fault | 83.4 ms      | 41.3 ms      | 50% faster
  Random     | 76.0 ms      | 58.3 ms      | 23% faster

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/

Usama Arif (4):
mm: bypass mmap_miss heuristic for VM_EXEC readahead
mm: replace exec_folio_order() with generic preferred_exec_order()
elf: align ET_DYN base to max folio size for PTE coalescing
mm: align file-backed mmap to max folio order in thp_get_unmapped_area

 arch/arm64/include/asm/pgtable.h |  8 -----
 fs/binfmt_elf.c                  | 38 ++++++++++++++++++--
 include/linux/pgtable.h          | 11 ------
 mm/filemap.c                     | 59 ++++++++++++++++++++++++++++----
 mm/huge_memory.c                 | 14 ++++++++
 5 files changed, 102 insertions(+), 28 deletions(-)
Signature · 1 line
2.52.0
author reviewer
#1 lore raw

PATCH v2 1/4 mm: bypass mmap_miss heuristic for VM_EXEC readahead

The mmap_miss counter in do_sync_mmap_readahead() tracks whether readahead is useful for mmap'd file access. It is incremented by 1 on every page cache miss in do_sync_mmap_readahead(), and decremented in two places:

- filemap_map_pages(): decremented by N for each of N pages successfully mapped via fault-around (pages found already in cache, evidence readahead was useful). Only pages not in the workingset count as hits.

- do_async_mmap_readahead(): decremented by 1 when a page with PG_readahead is found in cache.

When the counter exceeds MMAP_LOTSAMISS (100), all readahead is disabled, including the targeted VM_EXEC readahead [1] that requests large folio orders for contpte mapping.

On arm64 with 64K base pages, both decrement paths are inactive:

1. filemap_map_pages() is never called because fault_around_pages (65536 >> PAGE_SHIFT = 1) disables should_fault_around(), which requires fault_around_pages > 1. With only 1 page in the fault-around window, there is nothing "around" to map.

2. do_async_mmap_readahead() never fires for exec mappings because exec readahead sets async_size = 0, so no PG_readahead markers are placed.

With no decrements, mmap_miss monotonically increases past MMAP_LOTSAMISS after 100 page faults, disabling all subsequent exec readahead.

Fix this by excluding VM_EXEC VMAs from the mmap_miss logic, similar to how VM_SEQ_READ is already excluded. The exec readahead path is targeted (one folio at the fault location, async_size=0), not speculative prefetch, so the mmap_miss heuristic designed to throttle wasteful speculative readahead should not apply to it.

[1] https://lore.kernel.org/all/[email protected]/

---
 mm/filemap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/filemap.c +1 -1
index 6cd7974d4adab..7d89c6b384cc4 100644
@@ -3331,7 +3331,7 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
 		}
 	}
 
-	if (!(vm_flags & VM_SEQ_READ)) {
+	if (!(vm_flags & (VM_SEQ_READ | VM_EXEC))) {
 		/* Avoid banging the cache line if not needed */
 		mmap_miss = READ_ONCE(ra->mmap_miss);
 		if (mmap_miss < MMAP_LOTSAMISS * 10)
Signature · 1 line
2.52.0
maintainer Reviewed-by on v2 1/4
#2 lore raw

Re: [PATCH v2 1/4] mm: bypass mmap_miss heuristic for VM_EXEC readahead

Quoted 38 lines · Usama Arif

The mmap_miss counter in do_sync_mmap_readahead() tracks whether readahead is useful for mmap'd file access. It is incremented by 1 on every page cache miss in do_sync_mmap_readahead(), and decremented in two places:

- filemap_map_pages(): decremented by N for each of N pages successfully mapped via fault-around (pages found already in cache, evidence readahead was useful). Only pages not in the workingset count as hits.

- do_async_mmap_readahead(): decremented by 1 when a page with PG_readahead is found in cache.

When the counter exceeds MMAP_LOTSAMISS (100), all readahead is disabled, including the targeted VM_EXEC readahead [1] that requests large folio orders for contpte mapping.

On arm64 with 64K base pages, both decrement paths are inactive:

1. filemap_map_pages() is never called because fault_around_pages (65536 >> PAGE_SHIFT = 1) disables should_fault_around(), which requires fault_around_pages > 1. With only 1 page in the fault-around window, there is nothing "around" to map.

2. do_async_mmap_readahead() never fires for exec mappings because exec readahead sets async_size = 0, so no PG_readahead markers are placed.

With no decrements, mmap_miss monotonically increases past MMAP_LOTSAMISS after 100 page faults, disabling all subsequent exec readahead.

Fix this by excluding VM_EXEC VMAs from the mmap_miss logic, similar to how VM_SEQ_READ is already excluded. The exec readahead path is targeted (one folio at the fault location, async_size=0), not speculative prefetch, so the mmap_miss heuristic designed to throttle wasteful speculative readahead should not apply to it.

Quoted 3 lines

[1] https://lore.kernel.org/all/[email protected]/

Looks good to me. Feel free to add:

Honza

Quoted 20 lines
---
 mm/filemap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
mm/filemap.c +1 -1
index 6cd7974d4adab..7d89c6b384cc4 100644
@@ -3331,7 +3331,7 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
 		}
 	}
 
-	if (!(vm_flags & VM_SEQ_READ)) {
+	if (!(vm_flags & (VM_SEQ_READ | VM_EXEC))) {
 		/* Avoid banging the cache line if not needed */
 		mmap_miss = READ_ONCE(ra->mmap_miss);
 		if (mmap_miss < MMAP_LOTSAMISS * 10)
Signature · 1 line
2.52.0
Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
reviewer Reviewed-by on v2 1/4
#3 lore raw

Re: [PATCH v2 1/4] mm: bypass mmap_miss heuristic for VM_EXEC readahead

In reply to Usama Arif

mm/filemap.c static struct file *do_sync_mmap_readahead(struct vm_fault *vmf) · line 3334

Quoted 10 lines · Usama Arif
mm/filemap.c +1 -1
index 6cd7974d4adab..7d89c6b384cc4 100644
@@ -3331,7 +3331,7 @@ static struct file *do_sync_mmap_readahead(struct vm_fault *vmf)
 		}
 	}
 
-	if (!(vm_flags & VM_SEQ_READ)) {
+	if (!(vm_flags & (VM_SEQ_READ | VM_EXEC))) {

Strictly speaking the fact that the file mapped as executable doesn't mean we are serving instruction fetch page fault.

FAULT_FLAG_INSTRUCTION would be the signal, but it is only provided by handful of architectures.

VM_EXEC is good enough proxy.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
author reviewer
#4 lore raw

PATCH v2 2/4 mm: replace exec_folio_order() with generic preferred_exec_order()

In reply to Usama Arif

Replace the arch-specific exec_folio_order() hook with a generic preferred_exec_order() that dynamically computes the readahead folio order for executable memory. It targets min(PMD_ORDER, 2M) as the maximum, which optimally gives the right answer for contpte (arm64), PMD mapping (x86, arm64 4K), and architectures with smaller PMDs (s390 1M). It adapts at runtime based on:

- VMA size: caps the order so folios fit within the mapping
- Memory pressure: steps down the order when the local node's free memory is below the high watermark for the requested order

This avoids over-allocating on memory-constrained systems while still requesting the optimal order when memory is plentiful.

Since exec_folio_order() is no longer needed, remove the arm64 definition and the generic default from pgtable.h.

---
 arch/arm64/include/asm/pgtable.h |  8 -----
 include/linux/pgtable.h          | 11 ------
 mm/filemap.c                     | 57 ++++++++++++++++++++++++++++----
 3 files changed, 51 insertions(+), 25 deletions(-)
arch/arm64/include/asm/pgtable.h +0 -8
index b3e58735c49bd..b1e74940624d8 100644
@@ -1599,14 +1599,6 @@ static inline void update_mmu_cache_range(struct vm_fault *vmf,
  */
 #define arch_wants_old_prefaulted_pte	cpu_has_hw_af
 
-/*
- * Request exec memory is read into pagecache in at least 64K folios. This size
- * can be contpte-mapped when 4K base pages are in use (16 pages into 1 iTLB
- * entry), and HPA can coalesce it (4 pages into 1 TLB entry) when 16K base
- * pages are in use.
- */
-#define exec_folio_order() ilog2(SZ_64K >> PAGE_SHIFT)
-
 static inline bool pud_sect_supported(void)
 {
 	return PAGE_SIZE == SZ_4K;
include/linux/pgtable.h +0 -11
index a50df42a893fb..874333549eb3c 100644
@@ -577,17 +577,6 @@ static inline bool arch_has_hw_pte_young(void)
 }
 #endif
 
-#ifndef exec_folio_order
-/*
- * Returns preferred minimum folio order for executable file-backed memory. Must
- * be in range [0, PMD_ORDER). Default to order-0.
- */
-static inline unsigned int exec_folio_order(void)
-{
-	return 0;
-}
-#endif
-
 #ifndef arch_check_zapped_pte
 static inline void arch_check_zapped_pte(struct vm_area_struct *vma,
 					 pte_t pte)
mm/filemap.c +51 -6

75 lines not on this page · raw message

Signature · 1 line
2.52.0
#5 lore raw

Re: [PATCH v2 2/4] mm: replace exec_folio_order() with generic preferred_exec_order()

Quoted 18 lines · Usama Arif

+ * allocations when memory is tight.
+ */

mm/filemap.c +16 -0
+static unsigned int preferred_exec_order(struct vm_area_struct *vma)
+{
+	int order;
+	unsigned long vma_len = vma_pages(vma);
+	struct zone *zone;
+	gfp_t gfp;
+
+	if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+		return 0;
+
+	/* Cap at min(PMD_ORDER, 2M) */
+	order = min(HPAGE_PMD_ORDER, ilog2(SZ_2M >> PAGE_SHIFT));
+
+	/* Don't request folios larger than the VMA */
+	order = min(order, ilog2(vma_len));
+

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3334

Quoted 10 lines
mm/filemap.c +10 -0
+	/* Step down under memory pressure */
+	gfp = mapping_gfp_mask(vma->vm_file->f_mapping);
+	zone = first_zones_zonelist(node_zonelist(numa_node_id(), gfp),
+				    gfp_zone(gfp), NULL)->zone;
+	if (zone) {
+		while (order > 0 &&
+		       !zone_watermark_ok(zone, order,
+					  high_wmark_pages(zone), 0, 0))
+			order--;
+	}

Eww. That's overkill and layering violation.

If we need something like this, it has to be do within page allocator:
an allocation interface that takes a range (or mask) of acceptable order.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
maintainer
#6 lore raw

Re: [PATCH v2 2/4] mm: replace exec_folio_order() with generic preferred_exec_order()

In reply to Usama Arif
Quoted 18 lines · Usama Arif

Replace the arch-specific exec_folio_order() hook with a generic preferred_exec_order() that dynamically computes the readahead folio order for executable memory. It targets min(PMD_ORDER, 2M) as the maximum, which optimally gives the right answer for contpte (arm64), PMD mapping (x86, arm64 4K), and architectures with smaller PMDs (s390 1M). It adapts at runtime based on:

- VMA size: caps the order so folios fit within the mapping
- Memory pressure: steps down the order when the local node's free memory is below the high watermark for the requested order

This avoids over-allocating on memory-constrained systems while still requesting the optimal order when memory is plentiful.

Since exec_folio_order() is no longer needed, remove the arm64 definition and the generic default from pgtable.h.

...

Quoted 5 lines
mm/filemap.c +5 -0
+static unsigned int preferred_exec_order(struct vm_area_struct *vma)
+{
+	int order;
+	unsigned long vma_len = vma_pages(vma);
+	struct zone *zone;

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3323

Quoted 10 lines
mm/filemap.c +10 -0
+	gfp_t gfp;
+
+	if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+		return 0;
+
+	/* Cap at min(PMD_ORDER, 2M) */
+	order = min(HPAGE_PMD_ORDER, ilog2(SZ_2M >> PAGE_SHIFT));
+
+	/* Don't request folios larger than the VMA */
+	order = min(order, ilog2(vma_len));

Hum, as far as I'm checking page_cache_ra_order() used in do_sync_mmap_readahead(), ra->order is the preferred order but it will be trimmed down to fit both within the file and within ra->size. And ra->size is set for the readahead to fit within the vma so I don't think any order trimming based on vma length is needed in this place?

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3334

Quoted 10 lines
mm/filemap.c +10 -0
+	/* Step down under memory pressure */
+	gfp = mapping_gfp_mask(vma->vm_file->f_mapping);
+	zone = first_zones_zonelist(node_zonelist(numa_node_id(), gfp),
+				    gfp_zone(gfp), NULL)->zone;
+	if (zone) {
+		while (order > 0 &&
+		       !zone_watermark_ok(zone, order,
+					  high_wmark_pages(zone), 0, 0))
+			order--;
+	}

It looks wrong for this logic to be here. Trimming order based on memory pressure makes sense (and we've already got reports that on memory limited devices large order folios in the page cache have too big memory overhead so we'll likely need to handle that for page cache allocations in general) but IMHO it belongs to page_cache_ra_order() or some other common place like that.

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
author reviewer
#7 lore raw

Re: [PATCH v2 2/4] mm: replace exec_folio_order() with generic preferred_exec_order()

Quoted 20 lines · Jan Kara
Quoted 18 lines · Usama Arif

Replace the arch-specific exec_folio_order() hook with a generic preferred_exec_order() that dynamically computes the readahead folio order for executable memory. It targets min(PMD_ORDER, 2M) as the maximum, which optimally gives the right answer for contpte (arm64), PMD mapping (x86, arm64 4K), and architectures with smaller PMDs (s390 1M). It adapts at runtime based on:

- VMA size: caps the order so folios fit within the mapping
- Memory pressure: steps down the order when the local node's free memory is below the high watermark for the requested order

This avoids over-allocating on memory-constrained systems while still requesting the optimal order when memory is plentiful.

Since exec_folio_order() is no longer needed, remove the arm64 definition and the generic default from pgtable.h.

...

Quoted 16 lines
Quoted 5 lines
mm/filemap.c +5 -0
+static unsigned int preferred_exec_order(struct vm_area_struct *vma)
+{
+	int order;
+	unsigned long vma_len = vma_pages(vma);
+	struct zone *zone;

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3323

Quoted 10 lines
mm/filemap.c +10 -0
+	gfp_t gfp;
+
+	if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE))
+		return 0;
+
+	/* Cap at min(PMD_ORDER, 2M) */
+	order = min(HPAGE_PMD_ORDER, ilog2(SZ_2M >> PAGE_SHIFT));
+
+	/* Don't request folios larger than the VMA */
+	order = min(order, ilog2(vma_len));

Hi Jan,

Thanks for the feedback and sorry for the late reply! I was travelling during the week.

Quoted 5 lines

Hum, as far as I'm checking page_cache_ra_order() used in do_sync_mmap_readahead(), ra->order is the preferred order but it will be trimmed down to fit both within the file and within ra->size. And ra->size is set for the readahead to fit within the vma so I don't think any order trimming based on vma length is needed in this place?

Ack, yes makes sense.

Quoted 19 lines

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3334

Quoted 11 lines
mm/filemap.c +10 -0
+	/* Step down under memory pressure */
+	gfp = mapping_gfp_mask(vma->vm_file->f_mapping);
+	zone = first_zones_zonelist(node_zonelist(numa_node_id(), gfp),
+				    gfp_zone(gfp), NULL)->zone;
+	if (zone) {
+		while (order > 0 &&
+		       !zone_watermark_ok(zone, order,
+					  high_wmark_pages(zone), 0, 0))
+			order--;
+	}

It looks wrong for this logic to be here. Trimming order based on memory pressure makes sense (and we've already got reports that on memory limited devices large order folios in the page cache have too big memory overhead so we'll likely need to handle that for page cache allocations in general) but IMHO it belongs to page_cache_ra_order() or some other common place like that.

Quoted 1 line

Honza

So I have been thinking about this. readahead_gfp_mask() already sets __GFP_NORETRY, so we wont try aggressive reclaim/compaction to satisfy the allocation. page_cache_ra_order() falls through to the fallback path faulting in order 0 page when allocation is not satsified.

So the allocator already naturally steps down under memory pressure, the explicit zone_watermark_ok() loop might be redundant?

What are your thoughts on just setting
ra->order = min(HPAGE_PMD_ORDER, ilog2(SZ_2M >> PAGE_SHIFT))? We can do the higher orlder allocation with gfp &= ~__GFP_RECLAIM for the VM_EXEC case.

maintainer
#8 lore raw

Re: [PATCH v2 2/4] mm: replace exec_folio_order() with generic preferred_exec_order()

Quoted 21 lines · Usama Arif
Quoted 18 lines · Jan Kara

mm/filemap.c static int lock_folio_maybe_drop_mmap(struct vm_fault *vmf, struct folio *folio, · line 3334

Quoted 10 lines
mm/filemap.c +10 -0
+	/* Step down under memory pressure */
+	gfp = mapping_gfp_mask(vma->vm_file->f_mapping);
+	zone = first_zones_zonelist(node_zonelist(numa_node_id(), gfp),
+				    gfp_zone(gfp), NULL)->zone;
+	if (zone) {
+		while (order > 0 &&
+		       !zone_watermark_ok(zone, order,
+					  high_wmark_pages(zone), 0, 0))
+			order--;
+	}

It looks wrong for this logic to be here. Trimming order based on memory pressure makes sense (and we've already got reports that on memory limited devices large order folios in the page cache have too big memory overhead so we'll likely need to handle that for page cache allocations in general) but IMHO it belongs to page_cache_ra_order() or some other common place like that.

Quoted 1 line

Honza

Quoted 7 lines

So I have been thinking about this. readahead_gfp_mask() already sets __GFP_NORETRY, so we wont try aggressive reclaim/compaction to satisfy the allocation. page_cache_ra_order() falls through to the fallback path faulting in order 0 page when allocation is not satsified.

So the allocator already naturally steps down under memory pressure, the explicit zone_watermark_ok() loop might be redundant?

Probably yes. I still think we'll have to somehow better tweak the used order based on expected size of the page cache (2M folios seem unreasonably large for a machine that has e.g. 1G of memory in total, even if it has enough free memory at this point in time - we'll benefit from smaller folios and thus finer grained folio activity tracking for such cases). But that's not for this patch set.

Quoted 4 lines

What are your thoughts on just setting
ra->order = min(HPAGE_PMD_ORDER, ilog2(SZ_2M >> PAGE_SHIFT))? We can do the higher orlder allocation with gfp &= ~__GFP_RECLAIM for the VM_EXEC case.

Yes, it's simple and it makes sense to me so if others are fine with it...

Honza

Signature · 2 lines
Jan Kara <[email protected]>
SUSE Labs, CR
author reviewer
#9 lore raw

PATCH v2 3/4 elf: align ET_DYN base to max folio size for PTE coalescing

In reply to Usama Arif

For PIE binaries (ET_DYN), the load address is randomized at PAGE_SIZE granularity via arch_mmap_rnd(). On arm64 with 64K base pages, this means the binary is 64K-aligned, but contpte mapping requires 2M (CONT_PTE_SIZE) alignment.

Without proper virtual address alignment, readahead patches that allocate 2M folios with 2M-aligned file offsets and physical addresses cannot benefit from contpte mapping, as the contpte fold check in contpte_set_ptes() requires the virtual address to be CONT_PTE_SIZE- aligned.

Fix this by extending maximum_alignment() to consider the maximum folio size supported by the page cache (via mapping_max_folio_size()). For each PT_LOAD segment, the alignment is bumped to the largest power-of-2 that fits within the segment size, capped by the max folio size the filesystem will allocate, if:

- Both p_vaddr and p_offset are aligned to that size
- The segment is large enough (p_filesz >= size)

This ensures load_bias is folio-aligned so that file-offset-aligned folios map to properly aligned virtual addresses, enabling hardware PTE coalescing (e.g. arm64 contpte) and PMD mappings for large folios.

The segment size check avoids reducing ASLR entropy for small binaries that cannot benefit from large folio alignment.

---
 fs/binfmt_elf.c | 38 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)
fs/binfmt_elf.c +36 -2
index 8e89cc5b28200..042af81766fcd 100644
@@ -49,6 +49,7 @@
 #include <uapi/linux/rseq.h>
 #include <asm/param.h>
 #include <asm/page.h>
+#include <linux/pagemap.h>
 
 #ifndef ELF_COMPAT
 #define ELF_COMPAT 0
@@ -488,19 +489,51 @@ static int elf_read(struct file *file, void *buf, size_t len, loff_t pos)
 	return 0;
 }
 
-static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr)
+static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr,
+				       struct file *filp)
 {
 	unsigned long alignment = 0;
+	unsigned long max_folio_size = PAGE_SIZE;
 	int i;
 
+	if (filp && filp->f_mapping)
+		max_folio_size = mapping_max_folio_size(filp->f_mapping);
+
 	for (i = 0; i < nr; i++) {
 		if (cmds[i].p_type == PT_LOAD) {
 			unsigned long p_align = cmds[i].p_align;
+			unsigned long size;
 
 			/* skip non-power of two alignments as invalid */
 			if (!is_power_of_2(p_align))
 				continue;
 			alignment = max(alignment, p_align);
+
+			/*
+			 * Try to align the binary to the largest folio
+			 * size that the page cache supports, so the
+			 * hardware can coalesce PTEs (e.g. arm64
+			 * contpte) or use PMD mappings for large folios.
+			 *
+			 * Use the largest power-of-2 that fits within
+			 * the segment size, capped by what the page
+			 * cache will allocate. Only align when the
+			 * segment's virtual address and file offset are
+			 * already aligned to the folio size, as
+			 * misalignment would prevent coalescing anyway.
+			 *
+			 * The segment size check avoids reducing ASLR
+			 * entropy for small binaries that cannot
+			 * benefit.
+			 */
+			if (!cmds[i].p_filesz)
+				continue;
+			size = rounddown_pow_of_two(cmds[i].p_filesz);
+			size = min(size, max_folio_size);
+			if (size > PAGE_SIZE &&
+			    IS_ALIGNED(cmds[i].p_vaddr, size) &&
+			    IS_ALIGNED(cmds[i].p_offset, size))
+				alignment = max(alignment, size);
 		}
 	}
 
@@ -1104,7 +1137,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
 			}
 
 			/* Calculate any requested alignment. */
-			alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
+			alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum,
+						      bprm->file);
 
 			/**
 			 * DOC: PIE handling
Signature · 1 line
2.52.0
reviewer Reviewed-by on v2 3/4
#10 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 20 lines · Usama Arif

For PIE binaries (ET_DYN), the load address is randomized at PAGE_SIZE granularity via arch_mmap_rnd(). On arm64 with 64K base pages, this means the binary is 64K-aligned, but contpte mapping requires 2M (CONT_PTE_SIZE) alignment.

Without proper virtual address alignment, readahead patches that allocate 2M folios with 2M-aligned file offsets and physical addresses cannot benefit from contpte mapping, as the contpte fold check in contpte_set_ptes() requires the virtual address to be CONT_PTE_SIZE- aligned.

Fix this by extending maximum_alignment() to consider the maximum folio size supported by the page cache (via mapping_max_folio_size()). For each PT_LOAD segment, the alignment is bumped to the largest power-of-2 that fits within the segment size, capped by the max folio size the filesystem will allocate, if:

- Both p_vaddr and p_offset are aligned to that size
- The segment is large enough (p_filesz >= size)

Quoted 8 lines

This ensures load_bias is folio-aligned so that file-offset-aligned folios map to properly aligned virtual addresses, enabling hardware PTE coalescing (e.g. arm64 contpte) and PMD mappings for large folios.

The segment size check avoids reducing ASLR entropy for small binaries that cannot benefit from large folio alignment.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
maintainer
#11 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

In reply to Usama Arif

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 500

Quoted 10 lines · Usama Arif
fs/binfmt_elf.c +5 -1
-static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr)
+static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr,
+				       struct file *filp)
 {
 	unsigned long alignment = 0;
+	unsigned long max_folio_size = PAGE_SIZE;
 	int i;
 
+	if (filp && filp->f_mapping)
+		max_folio_size = mapping_max_folio_size(filp->f_mapping);

Under what circumstances can bprm->file be NULL?

Also we tend to prefer the name "file" rather than "filp" for new code (yes, there's a lot of old code out there).

Quoted 16 lines
fs/binfmt_elf.c +16 -0
+
+			/*
+			 * Try to align the binary to the largest folio
+			 * size that the page cache supports, so the
+			 * hardware can coalesce PTEs (e.g. arm64
+			 * contpte) or use PMD mappings for large folios.
+			 *
+			 * Use the largest power-of-2 that fits within
+			 * the segment size, capped by what the page
+			 * cache will allocate. Only align when the
+			 * segment's virtual address and file offset are
+			 * already aligned to the folio size, as
+			 * misalignment would prevent coalescing anyway.
+			 *
+			 * The segment size check avoids reducing ASLR
+			 * entropy for small binaries that cannot

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 536

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+			 * benefit.
+			 */
+			if (!cmds[i].p_filesz)
+				continue;
+			size = rounddown_pow_of_two(cmds[i].p_filesz);
+			size = min(size, max_folio_size);
+			if (size > PAGE_SIZE &&
+			    IS_ALIGNED(cmds[i].p_vaddr, size) &&
+			    IS_ALIGNED(cmds[i].p_offset, size))
+				alignment = max(alignment, size);

Can this not all be factored out into a different function? Also, I think it was done a bit better here:
https://lore.kernel.org/linux-fsdevel/[email protected]/

+ if (!IS_ALIGNED(cmd->p_vaddr | cmd->p_offset, PMD_SIZE))
+ return false;

author reviewer
#12 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 9 lines · Matthew Wilcox
Quoted 8 lines · Usama Arif
fs/binfmt_elf.c +3 -1
-static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr)
+static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr,
+				       struct file *filp)
 {
 	unsigned long alignment = 0;
+	unsigned long max_folio_size = PAGE_SIZE;
 	int i;
Quoted 4 lines

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 500

Quoted 2 lines
fs/binfmt_elf.c +2 -0
+	if (filp && filp->f_mapping)
+		max_folio_size = mapping_max_folio_size(filp->f_mapping);

Under what circumstances can bprm->file be NULL?

Yeah its unnecessary here. Its used in other places and this is never checked, so we can remove it.

Quoted 4 lines

Also we tend to prefer the name "file" rather than "filp" for new code (yes, there's a lot of old code out there).

ack. will change in next revision.

Quoted 32 lines
Quoted 16 lines
fs/binfmt_elf.c +16 -0
+
+			/*
+			 * Try to align the binary to the largest folio
+			 * size that the page cache supports, so the
+			 * hardware can coalesce PTEs (e.g. arm64
+			 * contpte) or use PMD mappings for large folios.
+			 *
+			 * Use the largest power-of-2 that fits within
+			 * the segment size, capped by what the page
+			 * cache will allocate. Only align when the
+			 * segment's virtual address and file offset are
+			 * already aligned to the folio size, as
+			 * misalignment would prevent coalescing anyway.
+			 *
+			 * The segment size check avoids reducing ASLR
+			 * entropy for small binaries that cannot

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 536

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+			 * benefit.
+			 */
+			if (!cmds[i].p_filesz)
+				continue;
+			size = rounddown_pow_of_two(cmds[i].p_filesz);
+			size = min(size, max_folio_size);
+			if (size > PAGE_SIZE &&
+			    IS_ALIGNED(cmds[i].p_vaddr, size) &&
+			    IS_ALIGNED(cmds[i].p_offset, size))
+				alignment = max(alignment, size);

Can this not all be factored out into a different function? Also, I think it was done a bit better here:
https://lore.kernel.org/linux-fsdevel/[email protected]/

Review

Quoted 2 lines
(quoted diff) +2 -0
+	if (!IS_ALIGNED(cmd->p_vaddr | cmd->p_offset, PMD_SIZE))
+		return false;

ack, will try and address this accordingly.

Thanks for the reviews!!

#13 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

In reply to Usama Arif

Hi Usama,

Quoted 16 lines · Usama Arif
fs/binfmt_elf.c +1 -0
index 8e89cc5b28200..042af81766fcd 100644
@@ -49,6 +49,7 @@
 #include <uapi/linux/rseq.h>
 #include <asm/param.h>
 #include <asm/page.h>
+#include <linux/pagemap.h>
 
 #ifndef ELF_COMPAT
 #define ELF_COMPAT 0
@@ -488,19 +489,51 @@ static int elf_read(struct file *file, void *buf, size_t len, loff_t pos)
        return 0;
 }
 

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 500

Quoted 10 lines
fs/binfmt_elf.c +5 -1
-static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr)
+static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr,
+                                      struct file *filp)
 {
        unsigned long alignment = 0;
+       unsigned long max_folio_size = PAGE_SIZE;
        int i;
 
+       if (filp && filp->f_mapping)
+               max_folio_size = mapping_max_folio_size(filp->f_mapping);

From experiments (with 16K base pages), mapping_max_folio_size() appears to depend on the filesystem. It returns 8M on ext4, while on btrfs it always falls back to PAGE_SIZE (it seems CONFIG_BTRFS_EXPERIMENTAL=y may change this). This looks overly conservative and ends up missing practical optimization opportunities.

Quoted 26 lines
fs/binfmt_elf.c +18 -0
+
        for (i = 0; i < nr; i++) {
                if (cmds[i].p_type == PT_LOAD) {
                        unsigned long p_align = cmds[i].p_align;
+                       unsigned long size;
 
                        /* skip non-power of two alignments as invalid */
                        if (!is_power_of_2(p_align))
                                continue;
                        alignment = max(alignment, p_align);
+
+                       /*
+                        * Try to align the binary to the largest folio
+                        * size that the page cache supports, so the
+                        * hardware can coalesce PTEs (e.g. arm64
+                        * contpte) or use PMD mappings for large folios.
+                        *
+                        * Use the largest power-of-2 that fits within
+                        * the segment size, capped by what the page
+                        * cache will allocate. Only align when the
+                        * segment's virtual address and file offset are
+                        * already aligned to the folio size, as
+                        * misalignment would prevent coalescing anyway.
+                        *
+                        * The segment size check avoids reducing ASLR
+                        * entropy for small binaries that cannot

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 536

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+                        * benefit.
+                        */
+                       if (!cmds[i].p_filesz)
+                               continue;
+                       size = rounddown_pow_of_two(cmds[i].p_filesz);
+                       size = min(size, max_folio_size);
+                       if (size > PAGE_SIZE &&
+                           IS_ALIGNED(cmds[i].p_vaddr, size) &&
+                           IS_ALIGNED(cmds[i].p_offset, size))
+                               alignment = max(alignment, size);

In my patch [1], by aligning eligible segments to PMD_SIZE, THP can quickly collapse them into large mappings with minimal warmup. That doesn’t happen with the current behavior. I think allowing a reasonably sized PMD (say <= 32M) is worth considering. All we really need here is to ensure virtual address alignment. The rest can be left to THP under always, which can decide whether to collapse or not based on memory pressure and other factors.

[1] https://lore.kernel.org/linux-fsdevel/[email protected]

Quoted 14 lines
fs/binfmt_elf.c +2 -2
                }
        }
 
@@ -1104,7 +1137,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
                        }
 
                        /* Calculate any requested alignment. */
-                       alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
+                       alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum,
+                                                     bprm->file);
 
                        /**
                         * DOC: PIE handling
--
Quoted 2 lines

2.52.0

Thanks,
Rui

maintainer
#14 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 5 lines · WANG Rui
Quoted 1 line

From experiments (with 16K base pages), mapping_max_folio_size() appears to

depend on the filesystem. It returns 8M on ext4, while on btrfs it always falls back to PAGE_SIZE (it seems CONFIG_BTRFS_EXPERIMENTAL=y may change this). This looks overly conservative and ends up missing practical optimization opportunities.

btrfs only supports large folios with CONFIG_BTRFS_EXPERIMENTAL. I mean, it's only been five years since it was added to XFS, can't rush these things.

author reviewer
#15 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

In reply to WANG Rui
Quoted 27 lines · WANG Rui

Hi Usama,

Quoted 24 lines · Usama Arif
fs/binfmt_elf.c +4 -1
index 8e89cc5b28200..042af81766fcd 100644
@@ -49,6 +49,7 @@
 #include <uapi/linux/rseq.h>
 #include <asm/param.h>
 #include <asm/page.h>
+#include <linux/pagemap.h>
 
 #ifndef ELF_COMPAT
 #define ELF_COMPAT 0
@@ -488,19 +489,51 @@ static int elf_read(struct file *file, void *buf, size_t len, loff_t pos)
        return 0;
 }
 
-static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr)
+static unsigned long maximum_alignment(struct elf_phdr *cmds, int nr,
+                                      struct file *filp)
 {
        unsigned long alignment = 0;
+       unsigned long max_folio_size = PAGE_SIZE;
        int i;
 
Quoted 8 lines

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 500

Quoted 2 lines
fs/binfmt_elf.c +2 -0
+       if (filp && filp->f_mapping)
+               max_folio_size = mapping_max_folio_size(filp->f_mapping);

From experiments (with 16K base pages), mapping_max_folio_size() appears to depend on the filesystem. It returns 8M on ext4, while on btrfs it always falls back to PAGE_SIZE (it seems CONFIG_BTRFS_EXPERIMENTAL=y may change this). This looks overly conservative and ends up missing practical optimization opportunities.

mapping_max_folio_size() reflects what the page cache will actually allocate for a given filesystem, since readahead caps folio allocation at mapping_max_folio_order() (in page_cache_ra_order()). If btrfs reports PAGE_SIZE, readahead won't allocate large folios for it, so there are no large folios to coalesce PTEs for, aligning the binary beyond that would only reduce ASLR entropy for no benefit.

I don't think we should over-align binaries on filesystems that can't take advantage of it.

Quoted 66 lines
Quoted 27 lines
fs/binfmt_elf.c +18 -0
+
        for (i = 0; i < nr; i++) {
                if (cmds[i].p_type == PT_LOAD) {
                        unsigned long p_align = cmds[i].p_align;
+                       unsigned long size;
 
                        /* skip non-power of two alignments as invalid */
                        if (!is_power_of_2(p_align))
                                continue;
                        alignment = max(alignment, p_align);
+
+                       /*
+                        * Try to align the binary to the largest folio
+                        * size that the page cache supports, so the
+                        * hardware can coalesce PTEs (e.g. arm64
+                        * contpte) or use PMD mappings for large folios.
+                        *
+                        * Use the largest power-of-2 that fits within
+                        * the segment size, capped by what the page
+                        * cache will allocate. Only align when the
+                        * segment's virtual address and file offset are
+                        * already aligned to the folio size, as
+                        * misalignment would prevent coalescing anyway.
+                        *
+                        * The segment size check avoids reducing ASLR
+                        * entropy for small binaries that cannot

fs/binfmt_elf.c static int elf_read(struct file *file, void *buf, size_t len, loff_t pos) · line 536

Quoted 10 lines
fs/binfmt_elf.c +10 -0
+                        * benefit.
+                        */
+                       if (!cmds[i].p_filesz)
+                               continue;
+                       size = rounddown_pow_of_two(cmds[i].p_filesz);
+                       size = min(size, max_folio_size);
+                       if (size > PAGE_SIZE &&
+                           IS_ALIGNED(cmds[i].p_vaddr, size) &&
+                           IS_ALIGNED(cmds[i].p_offset, size))
+                               alignment = max(alignment, size);

In my patch [1], by aligning eligible segments to PMD_SIZE, THP can quickly collapse them into large mappings with minimal warmup. That doesn’t happen with the current behavior. I think allowing a reasonably sized PMD (say <= 32M) is worth considering. All we really need here is to ensure virtual address alignment. The rest can be left to THP under always, which can decide whether to collapse or not based on memory pressure and other factors.

[1] https://lore.kernel.org/linux-fsdevel/[email protected]

Quoted 14 lines
fs/binfmt_elf.c +2 -2
                }
        }
 
@@ -1104,7 +1137,8 @@ static int load_elf_binary(struct linux_binprm *bprm)
                        }
 
                        /* Calculate any requested alignment. */
-                       alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum);
+                       alignment = maximum_alignment(elf_phdata, elf_ex->e_phnum,
+                                                     bprm->file);
 
                        /**
                         * DOC: PIE handling
--
Quoted 2 lines

2.52.0

Thanks,
Rui

#16 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 9 lines

mapping_max_folio_size() reflects what the page cache will actually allocate for a given filesystem, since readahead caps folio allocation at mapping_max_folio_order() (in page_cache_ra_order()). If btrfs reports PAGE_SIZE, readahead won't allocate large folios for it, so there are no large folios to coalesce PTEs for, aligning the binary beyond that would only reduce ASLR entropy for no benefit.

I don't think we should over-align binaries on filesystems that can't take advantage of it.

Ah, it looks like this might be overlooking another path that can create huge page mappings for read-only code segments: even when the filesystem (e.g. btrfs without experimental) didn't support large folios, READ_ONLY_THP_FOR_FS still allowed read-only file-backed code segments to be collapsed into huge page mappings via khugepaged.

As Wilcox pointed out, it may take quite some time for many filesystems to gain full large folio support? So what I'm trying to clarify is that using mapping_max_folio_size() on this path is not favorable for khugepaged-based optimizations.

Thanks,
Rui

maintainer
#17 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 16 lines · WANG Rui
Quoted 9 lines

mapping_max_folio_size() reflects what the page cache will actually allocate for a given filesystem, since readahead caps folio allocation at mapping_max_folio_order() (in page_cache_ra_order()). If btrfs reports PAGE_SIZE, readahead won't allocate large folios for it, so there are no large folios to coalesce PTEs for, aligning the binary beyond that would only reduce ASLR entropy for no benefit.

I don't think we should over-align binaries on filesystems that can't take advantage of it.

Ah, it looks like this might be overlooking another path that can create huge page mappings for read-only code segments: even when the filesystem (e.g. btrfs without experimental) didn't support large folios, READ_ONLY_THP_FOR_FS still allowed read-only file-backed code segments to be collapsed into huge page mappings via khugepaged.

Quoted 4 lines

As Wilcox pointed out, it may take quite some time for many filesystems to gain full large folio support? So what I'm trying to clarify is that using mapping_max_folio_size() on this path is not favorable for khugepaged-based optimizations.

Nono, that's not what I'm pointing out! btrfs is simply not putting in the effort to support large folios, and that needs to change. READ_ONLY_THP_FOR_FS unnecessaily burdens the rest of the kernel. It was a great hack for its time and paved the path for a lot of what we have today, but it's time to remove it.

author reviewer
#18 lore raw

Re: [PATCH v2 3/4] elf: align ET_DYN base to max folio size for PTE coalescing

Quoted 8 lines · Matthew Wilcox
Quoted 7 lines · WANG Rui
Quoted 7 lines

mapping_max_folio_size() reflects what the page cache will actually allocate for a given filesystem, since readahead caps folio allocation at mapping_max_folio_order() (in page_cache_ra_order()). If btrfs reports PAGE_SIZE, readahead won't allocate large folios for it, so there are no large folios to coalesce PTEs for, aligning the binary beyond that would only reduce ASLR entropy for no benefit.

Quoted 8 lines
Quoted 8 lines
Quoted 2 lines

I don't think we should over-align binaries on filesystems that can't take advantage of it.

Ah, it looks like this might be overlooking another path that can create huge page mappings for read-only code segments: even when the filesystem (e.g. btrfs without experimental) didn't support large folios, READ_ONLY_THP_FOR_FS still allowed read-only file-backed code segments to be collapsed into huge page mappings via khugepaged.

ah yes, Thank you for pointing this out!

Maybe we should rename mapping_max_folio_size() to mapping_fault_max_folio_size().

Quoted 5 lines
Quoted 5 lines

As Wilcox pointed out, it may take quite some time for many filesystems to gain full large folio support? So what I'm trying to clarify is that using mapping_max_folio_size() on this path is not favorable for khugepaged-based optimizations.

ack

I am worried that 32M is too large and we lose out on a lot of ASLR bits. Instead of PMD_ORDER, should we do max(SZ_2M, PMD_ORDER)?

Quoted 5 lines

Nono, that's not what I'm pointing out! btrfs is simply not putting in the effort to support large folios, and that needs to change. READ_ONLY_THP_FOR_FS unnecessaily burdens the rest of the kernel. It was a great hack for its time and paved the path for a lot of what we have today, but it's time to remove it.

author reviewer
#19 lore raw

PATCH v2 4/4 mm: align file-backed mmap to max folio order in thp_get_unmapped_area

In reply to Usama Arif

thp_get_unmapped_area() is the get_unmapped_area callback for filesystems like ext4, xfs, and btrfs. It attempts to align the virtual address for PMD_SIZE THP mappings, but on arm64 with 64K base pages PMD_SIZE is 512M, which is too large for typical shared library mappings, so the alignment always fails and falls back to PAGE_SIZE.

This means shared libraries loaded by ld.so via mmap() get 64K-aligned virtual addresses, preventing contpte mapping even when 2M folios are allocated with properly aligned file offsets and physical addresses.

Add a fallback in thp_get_unmapped_area_vmflags() that uses the filesystem's mapping_max_folio_size() to determine alignment, capped to the mapping length via rounddown_pow_of_two(len). This aligns mappings to the largest folio the page cache will actually allocate, without over-aligning small mappings.

The fallback is naturally a no-op for filesystems that don't support large folios and skips the retry when the alignment would equal PMD_SIZE (already attempted above).

---
 mm/huge_memory.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
mm/huge_memory.c +14 -0
index 8e2746ea74adf..4005084c9c65b 100644
@@ -1242,6 +1242,20 @@ unsigned long thp_get_unmapped_area_vmflags(struct file *filp, unsigned long add
 	if (ret)
 		return ret;
 
+	if (filp && filp->f_mapping) {
+		unsigned long max_folio_size =
+			mapping_max_folio_size(filp->f_mapping);
+		unsigned long size = rounddown_pow_of_two(len);
+
+		size = min(size, max_folio_size);
+		if (size > PAGE_SIZE && size != PMD_SIZE) {
+			ret = __thp_get_unmapped_area(filp, addr, len, off,
+						      flags, size, vm_flags);
+			if (ret)
+				return ret;
+		}
+	}
+
 	return mm_get_unmapped_area_vmflags(filp, addr, len, pgoff, flags,
 					    vm_flags);
 }
Signature · 1 line
2.52.0
#20 lore raw

Re: [PATCH v2 4/4] mm: align file-backed mmap to max folio order in thp_get_unmapped_area

Quoted 32 lines · Usama Arif

thp_get_unmapped_area() is the get_unmapped_area callback for filesystems like ext4, xfs, and btrfs. It attempts to align the virtual address for PMD_SIZE THP mappings, but on arm64 with 64K base pages PMD_SIZE is 512M, which is too large for typical shared library mappings, so the alignment always fails and falls back to PAGE_SIZE.

This means shared libraries loaded by ld.so via mmap() get 64K-aligned virtual addresses, preventing contpte mapping even when 2M folios are allocated with properly aligned file offsets and physical addresses.

Add a fallback in thp_get_unmapped_area_vmflags() that uses the filesystem's mapping_max_folio_size() to determine alignment, capped to the mapping length via rounddown_pow_of_two(len). This aligns mappings to the largest folio the page cache will actually allocate, without over-aligning small mappings.

The fallback is naturally a no-op for filesystems that don't support large folios and skips the retry when the alignment would equal PMD_SIZE (already attempted above).

---
 mm/huge_memory.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
mm/huge_memory.c +0 -0
index 8e2746ea74adf..4005084c9c65b 100644
@@ -1242,6 +1242,20 @@ unsigned long thp_get_unmapped_area_vmflags(struct file *filp, unsigned long add
 	if (ret)
 		return ret;

mm/huge_memory.c unsigned long thp_get_unmapped_area_vmflags(struct file *filp, unsigned long add · line 1253

Quoted 10 lines
mm/huge_memory.c +9 -0
 
+	if (filp && filp->f_mapping) {
+		unsigned long max_folio_size =
+			mapping_max_folio_size(filp->f_mapping);
+		unsigned long size = rounddown_pow_of_two(len);
+
+		size = min(size, max_folio_size);
+		if (size > PAGE_SIZE && size != PMD_SIZE) {
+			ret = __thp_get_unmapped_area(filp, addr, len, off,
+						      flags, size, vm_flags);

Have you considered to integrate inside __thp_get_unmapped_area?

Like, start with PMD_SIZE alignment, then lower to mapping_max_folio_size if needed and then lower further based on mapping size.

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov