Optimize anonymous swapbacked large folio unmapping
Subsystems (6)
v2 cover Optimize anonymous swapbacked large folio unmapping
Speed up unmapping of anonymous swapbacked large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry) is used to measure the time taken to swapout 256M worth of memory backed by 64K large folios:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#define SIZE_MB 256
#define SIZE_BYTES (SIZE_MB * 1024 * 1024)
int main() {
void *addr = mmap(NULL, SIZE_BYTES, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
perror("mmap failed");
return 1;
}
memset(addr, 0, SIZE_BYTES);
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
if (madvise(addr, SIZE_BYTES, MADV_PAGEOUT) != 0) {
perror("madvise(MADV_PAGEOUT) failed");
munmap(addr, SIZE_BYTES);
return 1;
}
clock_gettime(CLOCK_MONOTONIC, &end);
long duration_ns = (end.tv_sec - start.tv_sec) * 1e9 +
(end.tv_nsec - start.tv_nsec);
printf("madvise(MADV_PAGEOUT) took %ld ns (%.3f ms)\n",
duration_ns, duration_ns / 1e6);
munmap(addr, SIZE_BYTES);
return 0;}
Performance as measured on a Linux VM on Apple M3 (arm64):
Vanilla - Mean: 37401913 ns, std dev: 12%
Patched - Mean: 17420282 ns, std dev: 11%
resulting in more than 2x speedup.
No regression observed on 4K folios.
Performance as measured on bare metal x86:
Vanilla - mean: 54986286 ns, std dev: 1.5%
Patched - mean: 51930795 ns, std dev: 3%
I tried magnifying the difference on x86 by using 1M large folios, but can't spot an obvious improvement (looks like my system is too fast to benefit from batched atomic operations!), hinting that the benefit lies mainly in the reduction of ptep_get() calls and the reduction of TLB flushes during contpte-unfolding, on arm64.
No regression is observed on 4K folios on x86 too.
---
Applies on Linux 7.3-rc1 plus below:
https://lore.kernel.org/all/[email protected]/
mm-selftests pass.
This breakout patchset is the final completion of
https://lore.kernel.org/all/[email protected]/
The extra addition is the generic set_softleaf_ptes() helper.
v1->v2:
- Add WARNs for folio span in folio_*_swap_pages
- Trivial changes to page_anon_exclusive_batch
- Add two sanity checks in patch 4
Dev Jain (8):
mm/swapfile: add batched version of folio_dup_swap mm/swapfile: add batched version of folio_put_swap
mm: move anon-exclusive batch helper to mm.h mm/rmap: Add batched version of folio_try_share_anon_rmap_pte mm/internal: rename swap offset helpers to softleaf offset mm/internal: add set_softleaf_ptes
mm/memory: use set_softleaf_ptes for uffd-wp markers mm/rmap: batch unmap anonymous swap-backed large folios
include/linux/mm.h | 19 ++++++++ include/linux/rmap.h | 56 +++++++++++++++------- mm/internal.h | 62 ++++++++++++++++++------ mm/memory.c | 20 +++----- mm/mprotect.c | 17 ------- mm/rmap.c | 110 +++++++++++++++++++++++++++++++------------ mm/shmem.c | 8 ++-- mm/swap.h | 35 ++++++++++++-- mm/swapfile.c | 35 +++++++------- 9 files changed, 247 insertions(+), 115 deletions(-)
Signature · 1 line
2.43.0
PATCH v2 1/8 mm/swapfile: add batched version of folio_dup_swap
Add folio_dup_swap_pages to handle a batch of consecutive pages. Note that folio_dup_swap already can handle a subset of this: nr_pages == 1 and nr_pages == folio_nr_pages(folio). Generalize this to any nr_pages.
Currently we have a not-so-nice logic of passing in subpage == NULL if we mean to exercise the logic on the entire folio, and subpage != NULL if we want to exercise the logic on only that subpage. Remove this
indirection: the caller invokes folio_dup_swap_pages() if it wants to operate on a range of pages in the folio (i.e nr_pages may be anything between 1 and folio_nr_pages()), and invokes folio_dup_swap() if it wants to operate on the entire folio.
--- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 18 ++++++++++++++++-- mm/swapfile.c | 16 ++++++++-------- 4 files changed, 26 insertions(+), 12 deletions(-)
mm/rmap.c +1 -1
@@ -2148,7 +2148,7 @@ static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,swp_entry_t entry =page_swap_entry (page);struct mm_struct *mm = vma->vm_mm; -if (folio_dup_swap (folio, page) <0 ) +if (folio_dup_swap_pages (folio, page,1 ) <0 )return false;/*
mm/shmem.c +1 -1
@@ -1712,7 +1712,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,spin_unlock (&shmem_swaplist_lock); } -folio_dup_swap (folio,NULL ); +folio_dup_swap (folio);shmem_delete_from_page_cache (folio,swp_to_radix_entry (folio->swap));BUG_ON (folio_mapped (folio));
mm/swap.h +16 -2
@@ -244,7 +244,8 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);* folio_put_swap(): does the opposite thing of folio_dup_swap(). */ int folio_alloc_swap (struct folio *folio); -int folio_dup_swap (struct folio *folio,struct page *page); +int folio_dup_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages);void folio_put_swap (struct folio *folio,struct page *page);/* For internal use */
@@ -368,7 +369,8 @@ static inline int folio_alloc_swap(struct folio *folio)return -EINVAL ; } -static inline int folio_dup_swap (struct folio *folio,struct page *page) +static inline int folio_dup_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages) {return -EINVAL ; }
@@ -463,6 +465,18 @@ static inline void __swap_cache_replace_folio(struct swap_cluster_info *ci, }#endif /* CONFIG_SWAP */ +/** +* folio_dup_swap() - Increase swap count of all swap entries of a folio. +* @folio: folio with swap entries bound. +* +* See folio_dup_swap_pages() for more information. +*/ +static inline int folio_dup_swap (struct folio *folio) +{ +return folio_dup_swap_pages (folio,folio_page (folio,0 ), +folio_nr_pages (folio)); +} +extern const struct swap_ops swap_bdev_ops; int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
mm/swapfile.c +8 -8
@@ -1785,9 +1785,10 @@ int folio_alloc_swap(struct folio *folio) }/** -* folio_dup_swap() - Increase swap count of swap entries of a folio. +* folio_dup_swap_pages() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @page: if not NULL, only increase the swap count of this page. +* @page: the first page in the folio to increase the swap count for. +* @nr_pages: the number of pages in the folio to increase the swap count for. * * Typically called when the folio is unmapped and have its swap entry to * take its place: Swap entries allocated to a folio has count == 0 and pinned
@@ -1801,18 +1802,17 @@ int folio_alloc_swap(struct folio *folio)* swap_put_entries_direct on its swap entry before this helper returns, or * the swap count may underflow. */ -int folio_dup_swap (struct folio *folio,struct page *page) +int folio_dup_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio); +unsigned long idx =folio_page_idx (folio, page);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); +VM_WARN_ON_FOLIO (idx + nr_pages >folio_nr_pages (folio), folio); -if (page) { - entry.val +=folio_page_idx (folio, page); - nr_pages =1 ; - } + entry.val += idx;return swap_dup_entries_cluster (swap_entry_to_info (entry),swp_offset (entry), nr_pages);
Signature · 1 line
2.43.0
PATCH v2 2/8 mm/swapfile: add batched version of folio_put_swap
In reply to Dev JainAdd folio_put_swap_pages to handle a batch of consecutive pages. Note that folio_put_swap already can handle a subset of this: nr_pages == 1 and nr_pages == folio_nr_pages(folio). Generalize this to any nr_pages.
Currently we have a not-so-nice logic of passing in subpage == NULL if we mean to exercise the logic on the entire folio, and subpage != NULL if we want to exercise the logic on only that subpage. Remove this
indirection: the caller invokes folio_put_swap_pages() if it wants to operate on a range of pages in the folio (i.e nr_pages may be anything between 1 and folio_nr_pages()), and invokes folio_put_swap() if it wants to operate on the entire folio.
--- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 17 +++++++++++++++-- mm/swapfile.c | 19 ++++++++++--------- 5 files changed, 33 insertions(+), 19 deletions(-)
mm/memory.c +3 -3
@@ -5202,7 +5202,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)if (unlikely (folio != swapcache)) {folio_add_new_anon_rmap (folio, vma, address,RMAP_EXCLUSIVE );folio_add_lru_vma (folio, vma); -folio_put_swap (swapcache,NULL ); +folio_put_swap (swapcache); }else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully
@@ -5211,12 +5211,12 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)VM_WARN_ON_ONCE_FOLIO (folio_nr_pages (folio) != nr_pages, folio);VM_WARN_ON_ONCE_FOLIO (folio_mapped (folio), folio);folio_add_new_anon_rmap (folio, vma, address, rmap_flags); -folio_put_swap (folio,NULL ); +folio_put_swap (folio); }else {VM_WARN_ON_ONCE (nr_pages !=1 && nr_pages !=folio_nr_pages (folio));folio_add_anon_rmap_ptes (folio, page, nr_pages, vma, address, rmap_flags); -folio_put_swap (folio, nr_pages ==1 ? page :NULL ); +folio_put_swap_pages (folio, page, nr_pages); } VM_BUG_ON(!folio_test_anon(folio) ||
mm/rmap.c +2 -2
@@ -2157,13 +2157,13 @@ static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,* so we'll not check/care. */ if (arch_unmap_one (mm, vma, address, pteval) <0 ) { -folio_put_swap (folio, page); +folio_put_swap_pages (folio, page,1 );return false; }/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, page)) { -folio_put_swap (folio, page); +folio_put_swap_pages (folio, page,1 );return false; }
mm/shmem.c +3 -3
@@ -1733,7 +1733,7 @@ int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,/* Swap entry might be erased by racing shmem_free_swap() */ if (!error) {shmem_recalc_inode (inode,0 , -nr_pages); -folio_put_swap (folio,NULL ); +folio_put_swap (folio); }/*
@@ -2163,7 +2163,7 @@ static void shmem_set_folio_swapin_error(struct inode *inode, pgoff_t index, nr_pages =folio_nr_pages (folio);folio_wait_writeback (folio); -folio_put_swap (folio,NULL ); +folio_put_swap (folio);swap_cache_del_folio (folio);/* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks
@@ -2393,7 +2393,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,if (sgp ==SGP_WRITE )folio_mark_accessed (folio); -folio_put_swap (folio,NULL ); +folio_put_swap (folio);swap_cache_del_folio (folio);folio_mark_dirty (folio);put_swap_device (si);
mm/swap.h +15 -2
@@ -246,7 +246,8 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);int folio_alloc_swap (struct folio *folio);int folio_dup_swap_pages (struct folio *folio,struct page *page,unsigned long nr_pages); -void folio_put_swap (struct folio *folio,struct page *page); +void folio_put_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages);/* For internal use */ extern void __swap_cluster_free_entries(struct swap_info_struct *si,
@@ -375,7 +376,8 @@ static inline int folio_dup_swap_pages(struct folio *folio, struct page *page,return -EINVAL ; } -static inline void folio_put_swap (struct folio *folio,struct page *page) +static inline void folio_put_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages) { }
@@ -477,6 +479,17 @@ static inline int folio_dup_swap(struct folio *folio) folio_nr_pages(folio)); } +/** +* folio_put_swap() - Decrease swap count of all swap entries of a folio. +* @folio: folio with swap entries bound. +* +* See folio_put_swap_pages() for more information. +*/ +static inline void folio_put_swap (struct folio *folio) +{ +folio_put_swap_pages (folio,folio_page (folio,0 ),folio_nr_pages (folio)); +} +extern const struct swap_ops swap_bdev_ops; int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
mm/swapfile.c +10 -9
@@ -1819,27 +1819,27 @@ int folio_dup_swap_pages(struct folio *folio, struct page *page, }/** -* folio_put_swap() - Decrease swap count of swap entries of a folio. +* folio_put_swap_pages() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. -* @page: if not NULL, only decrease the swap count of this page. +* @page: the first page in the folio to decrease the swap count for. +* @nr_pages: the number of pages in the folio to decrease the swap count for. * * This won't free the swap slots even if swap count drops to zero, they are * still pinned by the swap cache. User may call folio_free_swap to free them. * Context: Caller must ensure the folio is locked and in the swap cache. */ -void folio_put_swap (struct folio *folio,struct page *page) +void folio_put_swap_pages (struct folio *folio,struct page *page, +unsigned long nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio);struct swap_info_struct *si =__swap_entry_to_info (entry); +unsigned long idx =folio_page_idx (folio, page);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); +VM_WARN_ON_FOLIO (idx + nr_pages >folio_nr_pages (folio), folio); -if (page) { - entry.val +=folio_page_idx (folio, page); - nr_pages =1 ; - } + entry.val += idx;swap_put_entries_cluster (si,swp_offset (entry), nr_pages, false); }
@@ -2518,7 +2518,8 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd, setpte:set_pte_at (vma->vm_mm, addr, pte, new_pte); -folio_put_swap (swapcache,folio_file_page (swapcache,swp_offset (entry))); +folio_put_swap_pages (swapcache, +folio_file_page (swapcache,swp_offset (entry)),1 ); out:if (pte)pte_unmap_unlock (pte, ptl);
Signature · 1 line
2.43.0
PATCH v2 3/8 mm: move anon-exclusive batch helper to mm.h
In reply to Dev JainIn preparation for optimizing large folio unmapping, we need to reuse the page_anon_exclusive_batch helper in rmap.c and rmap.h and obey the existing use in mprotect.c .
Therefore, move it from mprotect.c to mm.h. Gate with CONFIG_MMU since both rmap and mprotect users are built only for CONFIG_MMU.
While at it, change start_idx and max_len to unsigned long type for future proofing against THP support at >= PUD level. Also shorten expected_anon_exclusive -> anon_exclusive.
--- include/linux/mm.h | 19 +++++++++++++++++++ mm/mprotect.c | 17 ----------------- 2 files changed, 19 insertions(+), 17 deletions(-)
include/linux/mm.h +19 -0
@@ -244,6 +244,25 @@ static inline unsigned long folio_page_idx(const struct folio *folio,return page - &folio->page; } +#ifdef CONFIG_MMU +/* +* Get max length of consecutive PTEs pointing to PageAnonExclusive() pages or +* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce +* that the PTEs point to consecutive pages of the same anon large folio. +*/ +static __always_inline int page_anon_exclusive_batch(unsigned long start_idx, +unsigned long max_len,struct page *first_page,bool anon_exclusive) +{ +int idx; + +for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { +if (anon_exclusive !=PageAnonExclusive (first_page + idx)) +break ; + } +return idx - start_idx; +} +#endif +static inline struct folio *lru_to_folio (struct list_head *head) { return list_entry((head)->prev, struct folio, lru);
mm/mprotect.c +0 -17
@@ -138,23 +138,6 @@ static __always_inline void prot_commit_flush_ptes(struct vm_area_struct *vma,tlb_flush_pte_range (tlb, addr, nr_ptes *PAGE_SIZE ); } -/* -* Get max length of consecutive ptes pointing to PageAnonExclusive() pages or -* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce -* that the ptes point to consecutive pages of the same anon large folio. -*/ -static __always_inline int page_anon_exclusive_batch(int start_idx, int max_len, -struct page *first_page,bool expected_anon_exclusive) -{ -int idx; - -for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { -if (expected_anon_exclusive !=PageAnonExclusive (first_page + idx)) -break ; - } -return idx - start_idx; -} -/* * This function is a result of trying our very best to retain the * "avoid the write-fault handler" optimization. In can_change_pte_writable(),
Signature · 1 line
2.43.0
Barry Song
reviewerRe: [PATCH v2 3/8] mm: move anon-exclusive batch helper to mm.h
Quoted 35 lines · Dev Jain
In preparation for optimizing large folio unmapping, we need to reuse the
page_anon_exclusive_batchhelper inrmap.candrmap.hand obey the existing use inmprotect.c.Therefore, move it from
mprotect.ctomm.h. Gate withCONFIG_MMUsince both rmap and mprotect users are built only forCONFIG_MMU.While at it, change
start_idxandmax_lento unsigned long type for future proofing against THP support at >= PUD level. Also shortenexpected_anon_exclusive->anon_exclusive.Signed-off-by Dev Jain <[email protected]>--- include/linux/mm.h | 19 +++++++++++++++++++ mm/mprotect.c | 17 ----------------- 2 files changed, 19 insertions(+), 17 deletions(-)include/linux/mm.h +9
-0@@ -244,6 +244,25 @@ static inline unsigned long folio_page_idx(const struct folio *folio,return page - &folio->page; } +#ifdef CONFIG_MMU +/* +* Get max length of consecutive PTEs pointing to PageAnonExclusive() pages or +* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce +* that the PTEs point to consecutive pages of the same anon large folio. +*/ +static __always_inline int page_anon_exclusive_batch(unsigned long start_idx, +unsigned long max_len,struct page *first_page,bool anon_exclusive) +{
include/linux/mm.h static inline unsigned long folio_page_idx(const struct folio *folio, · line 265
Quoted 10 lines
include/linux/mm.h +10
-0+int idx; + +for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { +if (anon_exclusive !=PageAnonExclusive (first_page + idx)) +break ; + } + return idx - start_idx; +} +#endif +
Hi Dev,
I really don't want everything to become a top-level header file. If these are only mm-internal things, could we move them to mm/internal.h or another more appropriate header?
Best Regards
Barry
Re: [PATCH v2 3/8] mm: move anon-exclusive batch helper to mm.h
Quoted 49 lines · Barry Song
Quoted 35 lines · Dev Jain
In preparation for optimizing large folio unmapping, we need to reuse the
page_anon_exclusive_batchhelper inrmap.candrmap.hand obey the existing use inmprotect.c.Therefore, move it from
mprotect.ctomm.h. Gate withCONFIG_MMUsince both rmap and mprotect users are built only forCONFIG_MMU.While at it, change
start_idxandmax_lento unsigned long type for future proofing against THP support at >= PUD level. Also shortenexpected_anon_exclusive->anon_exclusive.Signed-off-by Dev Jain <[email protected]>--- include/linux/mm.h | 19 +++++++++++++++++++ mm/mprotect.c | 17 ----------------- 2 files changed, 19 insertions(+), 17 deletions(-)include/linux/mm.h +9
-0@@ -244,6 +244,25 @@ static inline unsigned long folio_page_idx(const struct folio *folio,return page - &folio->page; } +#ifdef CONFIG_MMU +/* +* Get max length of consecutive PTEs pointing to PageAnonExclusive() pages or +* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce +* that the PTEs point to consecutive pages of the same anon large folio. +*/ +static __always_inline int page_anon_exclusive_batch(unsigned long start_idx, +unsigned long max_len,struct page *first_page,bool anon_exclusive) +{include/linux/mm.h static inline unsigned long folio_page_idx(const struct folio *folio, · line 265
Quoted 10 lines
include/linux/mm.h +10
-0+int idx; + +for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { +if (anon_exclusive !=PageAnonExclusive (first_page + idx)) +break ; + } + return idx - start_idx; +} +#endif +Hi Dev,
Quoted 3 lines
I really don't want everything to become a top-level header file. If these are only mm-internal things, could we move them to
mm/internal.hor another more appropriate header?
What do you suggest? I had it in mm/internal.h initially, but because I had to use the helper this time in patch 4, I couldn't do that. The helper will now be used by mprotect.c, rmap.c and rmap.h.
I can keep it in rmap.h and make mprotect.c include rmap.h too.
Quoted 3 lines
Best Regards
Barry
Barry Song
reviewerRe: [PATCH v2 3/8] mm: move anon-exclusive batch helper to mm.h
On Tue, Sep 1, 2026 at 2:24 PM Dev Jain <[email protected]> wrote:
[...]
Quoted 7 lines
Quoted 6 lines
Hi Dev,
I really don't want everything to become a top-level header file. If these are only mm-internal things, could we move them to
mm/internal.hor another more appropriate header?
Quoted 6 lines
What do you suggest? I had it in
mm/internal.hinitially, but because I had to use the helper this time in patch 4, I couldn't do that. The helper will now be used bymprotect.c,rmap.candrmap.h.I can keep it in
rmap.hand makemprotect.cincludermap.htoo.
I’d vote for this approach until we find a better one :-)
include/linux/mm.h is already way, way too long.
Best Regards
Barry
Re: [PATCH v2 3/8] mm: move anon-exclusive batch helper to mm.h
In reply to Dev JainQuoted 22 lines · Dev Jain
In preparation for optimizing large folio unmapping, we need to reuse the
page_anon_exclusive_batchhelper inrmap.candrmap.hand obey the existing use inmprotect.c.Therefore, move it from
mprotect.ctomm.h. Gate withCONFIG_MMUsince both rmap and mprotect users are built only forCONFIG_MMU.While at it, change
start_idxandmax_lento unsigned long type for future proofing against THP support at >= PUD level. Also shortenexpected_anon_exclusive->anon_exclusive.Signed-off-by Dev Jain <[email protected]>--- include/linux/mm.h | 19 +++++++++++++++++++ mm/mprotect.c | 17 ----------------- 2 files changed, 19 insertions(+), 17 deletions(-)include/linux/mm.h +0
-0
include/linux/mm.h static inline unsigned long folio_page_idx(const struct folio *folio, · line 253
Quoted 10 lines
include/linux/mm.h +7
-0@@ -244,6 +244,25 @@ static inline unsigned long folio_page_idx(const struct folio *folio,return page - &folio->page; } +#ifdef CONFIG_MMU +/* +* Get max length of consecutive PTEs pointing to PageAnonExclusive() pages or +* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce +* that the PTEs point to consecutive pages of the same anon large folio. +*/ +static __always_inline int page_anon_exclusive_batch(unsigned long start_idx,
Sashiko says to make the return value unsigned long here, I'll do that. Now we have an unfortunate mix of int's in existing callers, but keeping nr_pages unsigned long everywhere eventually seems like the right thing to do.
Quoted 43 lines
include/linux/mm.h +12
-0+ unsigned long max_len, struct page *first_page, bool anon_exclusive) +{ + int idx; + +for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { +if (anon_exclusive !=PageAnonExclusive (first_page + idx)) +break ; + } +return idx - start_idx; +} +#endif +static inline struct folio *lru_to_folio (struct list_head *head) { return list_entry((head)->prev, struct folio, lru);mm/mprotect.c +0
-17@@ -138,23 +138,6 @@ static __always_inline void prot_commit_flush_ptes(struct vm_area_struct *vma,tlb_flush_pte_range (tlb, addr, nr_ptes *PAGE_SIZE ); } -/* -* Get max length of consecutive ptes pointing to PageAnonExclusive() pages or -* !PageAnonExclusive() pages, starting from start_idx. Caller must enforce -* that the ptes point to consecutive pages of the same anon large folio. -*/ -static __always_inline int page_anon_exclusive_batch(int start_idx, int max_len, -struct page *first_page,bool expected_anon_exclusive) -{ -int idx; - -for (idx = start_idx +1 ; idx < start_idx + max_len; ++idx) { -if (expected_anon_exclusive !=PageAnonExclusive (first_page + idx)) -break ; - } -return idx - start_idx; -} -/* * This function is a result of trying our very best to retain the * "avoid the write-fault handler" optimization. In can_change_pte_writable(),
PATCH v2 4/8 mm/rmap: Add batched version of folio_try_share_anon_rmap_pte
In reply to Dev JainTo enable batched unmapping of anonymous folios, we need to handle the sharing of exclusive pages. Hence, a batched version of folio_try_share_anon_rmap_pte is required.
Currently, the sole purpose of nr_pages in __folio_try_share_anon_rmap is to do some rmap sanity checks. Now, clear the PageAnonExclusive bit on a batch of nr_pages. Refactor the function such that the clearing of the bit can be done at one place without duplication.
Note that __folio_try_share_anon_rmap can receive nr_pages == HPAGE_PMD_NR from the PMD path, but currently we only clear the bit on the head page. Retain this behaviour by setting nr_pages = 1 in case the caller is folio_try_share_anon_rmap_pmd.
While at it, convert nr_pages to unsigned long to future-proof from overflow in case P4D-huge mappings etc get supported down the road. I haven't made such a change in each function receiving nr_pages in try_to_unmap_one - perhaps this can be done incrementally.
Add two WARN's: check that the batch is entirely exclusive (for PMD callers, need to check only head page), and that there are only PTE/PMD paths converging into __folio_try_share_anon_rmap.
--- include/linux/rmap.h | 56 ++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-)
include/linux/rmap.h +39 -17
@@ -706,17 +706,23 @@ static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, } static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, -struct page *page,int nr_pages,enum pgtable_level level) +struct page *page,unsigned long nr_pages,enum pgtable_level level) { +/* device private folios cannot get pinned via GUP. */ +const bool pinnable = !folio_is_device_private (folio); +VM_WARN_ON_FOLIO (!folio_test_anon (folio), folio);VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio); +__folio_rmap_sanity_checks (folio, page, nr_pages, level); -/* device private folios cannot get pinned via GUP. */ -if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); -return 0 ; - } +VM_WARN_ON_ONCE (level >PGTABLE_LEVEL_PMD ); + +/* We only clear anon-exclusive from head page of PMD folio. */ +if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; + +VM_WARN_ON_FOLIO (page_anon_exclusive_batch (0 , nr_pages, page, true) != nr_pages, folio);/* * We have to make sure that when we clear PageAnonExclusive, that
@@ -760,29 +766,38 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,* so we use explicit ones here. */ -/* Paired with the memory barrier in try_grab_folio(). */ -if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) -smp_mb (); +if (likely (pinnable)) { +/* Paired with the memory barrier in try_grab_folio(). */ +if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) +smp_mb (); -if (unlikely (folio_maybe_dma_pinned (folio))) -return -EBUSY ; -ClearPageAnonExclusive (page); +if (unlikely (folio_maybe_dma_pinned (folio))) +return -EBUSY ; + } + +for (;;) { +ClearPageAnonExclusive (page); +if (--nr_pages ==0 ) +break ; + page++; + }/* * This is conceptually a smp_wmb() paired with the smp_rmb() in * gup_must_unshare(). */ -if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) +if (likely (pinnable) &&IS_ENABLED (CONFIG_HAVE_GUP_FAST ))smp_mb__after_atomic ();return 0 ; }/** -* folio_try_share_anon_rmap_pte - try marking an exclusive anonymous page -* mapped by a PTE possibly shared to prepare +* folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages +* mapped by PTEs possibly shared to prepare * for KSM or temporary unmapping * @folio: The folio to share a mapping of -* @page: The mapped exclusive page +* @page: The first mapped exclusive page of the batch in the folio +* @nr_pages: The number of pages to share in the folio (batch size) * * The caller needs to hold the page table lock and has to have the page table * entries cleared/invalidated.
@@ -798,10 +813,17 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,* Returns 0 if marking the mapped page possibly shared succeeded. Returns * -EBUSY otherwise. */ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned long nr_pages) +{ +return __folio_try_share_anon_rmap (folio, page, nr_pages, +PGTABLE_LEVEL_PTE ); +} +static inline int folio_try_share_anon_rmap_pte (struct folio *folio,struct page *page) { -return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); +return folio_try_share_anon_rmap_ptes (folio, page,1 ); }/**
Signature · 1 line
2.43.0
Barry Song
reviewerRe: [PATCH v2 4/8] mm/rmap: Add batched version of folio_try_share_anon_rmap_pte
Quoted 43 lines · Dev Jain
To enable batched unmapping of anonymous folios, we need to handle the sharing of exclusive pages. Hence, a batched version of
folio_try_share_anon_rmap_pteis required.Currently, the sole purpose of
nr_pagesin__folio_try_share_anon_rmapis to do some rmap sanity checks. Now, clear the PageAnonExclusive bit on a batch ofnr_pages. Refactor the function such that the clearing of the bit can be done at one place without duplication.Note that
__folio_try_share_anon_rmapcan receivenr_pages==HPAGE_PMD_NRfrom the PMD path, but currently we only clear the bit on the head page. Retain this behaviour by settingnr_pages= 1 in case the caller isfolio_try_share_anon_rmap_pmd.While at it, convert
nr_pagesto unsigned long to future-proof from overflow in case P4D-huge mappings etc get supported down the road. I haven't made such a change in each function receivingnr_pagesintry_to_unmap_one- perhaps this can be done incrementally.Add two WARN's: check that the batch is entirely exclusive (for PMD callers, need to check only head page), and that there are only PTE/PMD paths converging into
__folio_try_share_anon_rmap.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 56 ++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-)include/linux/rmap.h +4
-1@@ -706,17 +706,23 @@ static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, } static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, -struct page *page,int nr_pages,enum pgtable_level level) +struct page *page,unsigned long nr_pages,enum pgtable_level level) { +/* device private folios cannot get pinned via GUP. */ +const bool pinnable = !folio_is_device_private (folio); +
include/linux/rmap.h static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, · line 719
Quoted 10 lines
include/linux/rmap.h +1
-5VM_WARN_ON_FOLIO (!folio_test_anon (folio), folio);VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio); +__folio_rmap_sanity_checks (folio, page, nr_pages, level); -/* device private folios cannot get pinned via GUP. */ -if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); -return 0 ; - }
Somehow, I feel the early return for folio_is_device_private(folio) is more readable. Can we keep it? Then we can avoid many if (pinnable) checks later.
include/linux/rmap.h static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, · line 719
Quoted 1 line
include/linux/rmap.h +1
-0+VM_WARN_ON_ONCE (level >PGTABLE_LEVEL_PMD );
Maybe the below would be better, as it avoids depending on the exact value of PGTABLE_LEVEL_PMD and above.
VM_WARN_ON_ONCE(level != PGTABLE_LEVEL_PTE && level != PGTABLE_LEVEL_PMD);
Quoted 10 lines
include/linux/rmap.h +6
-0+ +/* We only clear anon-exclusive from head page of PMD folio. */ +if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; + +VM_WARN_ON_FOLIO (page_anon_exclusive_batch (0 , nr_pages, page, true) != nr_pages, folio);/* * We have to make sure that when we clear PageAnonExclusive, that
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 772
Quoted 10 lines
include/linux/rmap.h +4
-3@@ -760,29 +766,38 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,* so we use explicit ones here. */ -/* Paired with the memory barrier in try_grab_folio(). */ -if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) -smp_mb (); +if (likely (pinnable)) { +/* Paired with the memory barrier in try_grab_folio(). */ +if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) +smp_mb ();
If we return early for !pinnable, shouldn't we be able to avoid this? Is the reason you don't do the early return that you want to batch the folio_is_device_private(folio) case as well? If so, that seems sensible.
Is this a real use case that you're supporting with your patchset?
Quoted 4 lines
include/linux/rmap.h +0
-3-if (unlikely (folio_maybe_dma_pinned (folio))) -return -EBUSY ; -ClearPageAnonExclusive (page);
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 783
Quoted 10 lines
include/linux/rmap.h +10
-0+if (unlikely (folio_maybe_dma_pinned (folio))) +return -EBUSY ; + } + +for (;;) { +ClearPageAnonExclusive (page); +if (--nr_pages ==0 ) +break ; + page++; + }
Maybe ?
while (nr_pages--)
ClearPageAnonExclusive(page++);Best Regards
Barry
Re: [PATCH v2 4/8] mm/rmap: Add batched version of folio_try_share_anon_rmap_pte
Quoted 59 lines · Barry Song
Quoted 43 lines · Dev Jain
To enable batched unmapping of anonymous folios, we need to handle the sharing of exclusive pages. Hence, a batched version of
folio_try_share_anon_rmap_pteis required.Currently, the sole purpose of
nr_pagesin__folio_try_share_anon_rmapis to do some rmap sanity checks. Now, clear the PageAnonExclusive bit on a batch ofnr_pages. Refactor the function such that the clearing of the bit can be done at one place without duplication.Note that
__folio_try_share_anon_rmapcan receivenr_pages==HPAGE_PMD_NRfrom the PMD path, but currently we only clear the bit on the head page. Retain this behaviour by settingnr_pages= 1 in case the caller isfolio_try_share_anon_rmap_pmd.While at it, convert
nr_pagesto unsigned long to future-proof from overflow in case P4D-huge mappings etc get supported down the road. I haven't made such a change in each function receivingnr_pagesintry_to_unmap_one- perhaps this can be done incrementally.Add two WARN's: check that the batch is entirely exclusive (for PMD callers, need to check only head page), and that there are only PTE/PMD paths converging into
__folio_try_share_anon_rmap.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 56 ++++++++++++++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 17 deletions(-)include/linux/rmap.h +4
-1@@ -706,17 +706,23 @@ static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, } static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, -struct page *page,int nr_pages,enum pgtable_level level) +struct page *page,unsigned long nr_pages,enum pgtable_level level) { +/* device private folios cannot get pinned via GUP. */ +const bool pinnable = !folio_is_device_private (folio); +include/linux/rmap.h static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, · line 719
Quoted 10 lines
include/linux/rmap.h +1
-5VM_WARN_ON_FOLIO (!folio_test_anon (folio), folio);VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio); +__folio_rmap_sanity_checks (folio, page, nr_pages, level); -/* device private folios cannot get pinned via GUP. */ -if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); -return 0 ; - }Somehow, I feel the early return for
folio_is_device_private(folio)is more readable. Can we keep it? Then we can avoid manyif (pinnable)checks later.
Quoted 6 lines
include/linux/rmap.h static inline int folio_try_dup_anon_rmap_pmd(struct folio *folio, · line 719
Quoted 1 line
include/linux/rmap.h +1
-0+VM_WARN_ON_ONCE (level >PGTABLE_LEVEL_PMD );Maybe the below would be better, as it avoids depending on the exact value of
PGTABLE_LEVEL_PMDand above.
VM_WARN_ON_ONCE(level != PGTABLE_LEVEL_PTE && level != PGTABLE_LEVEL_PMD);
Can do this.
Quoted 14 lines
Quoted 14 lines
include/linux/rmap.h +6
-0+ +/* We only clear anon-exclusive from head page of PMD folio. */ +if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; + +VM_WARN_ON_FOLIO (page_anon_exclusive_batch (0 , nr_pages, page, true) != nr_pages, folio);/* * We have to make sure that when we clear PageAnonExclusive, that @@ -760,29 +766,38 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,* so we use explicit ones here. */
Quoted 12 lines
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 772
Quoted 7 lines
include/linux/rmap.h +4
-3-/* Paired with the memory barrier in try_grab_folio(). */ -if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) -smp_mb (); +if (likely (pinnable)) { +/* Paired with the memory barrier in try_grab_folio(). */ +if (IS_ENABLED (CONFIG_HAVE_GUP_FAST )) +smp_mb ();If we return early for
!pinnable, shouldn't we be able to avoid this? Is the reason you don't do the early return that you want to batch thefolio_is_device_private(folio)case as well? If so, that seems sensible.
Yes.
Quoted 18 lines
Is this a real use case that you're supporting with your patchset?
Quoted 4 lines
include/linux/rmap.h +0
-3-if (unlikely (folio_maybe_dma_pinned (folio))) -return -EBUSY ; -ClearPageAnonExclusive (page);include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 783
Quoted 10 lines
include/linux/rmap.h +10
-0+if (unlikely (folio_maybe_dma_pinned (folio))) +return -EBUSY ; + } + +for (;;) { +ClearPageAnonExclusive (page); +if (--nr_pages ==0 ) +break ; + page++; + }
Quoted 4 lines
Maybe ?
while (nr_pages--) ClearPageAnonExclusive(page++);
Was following the pattern elsewhere ... I vaguely remember the for (;;) being faster for some reason?
Quoted 3 lines
Best Regards
Barry
PATCH v2 5/8 mm/internal: rename swap offset helpers to softleaf offset
In reply to Dev JainIn preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
For example, for a migration entry, pte_swp_exclusive() will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)
mm/internal.h +15 -14
@@ -468,16 +468,16 @@ unsigned int folio_pte_batch(struct folio *folio, pte_t *ptep, pte_t pte, unsigned int max_nr);/** -* pte_move_swp_offset - Move the swap entry offset field of a swap pte -* forward or backward by delta -* @pte: The initial pte state; must be a swap entry +* pte_move_softleaf_offset - Move the softleaf entry offset field of a +* softleaf pte forward or backward by delta +* @pte: The initial pte state; must be a softleaf entry * @delta: The direction and the offset we are moving; forward if delta * is positive; backward if delta is negative * -* Moves the swap offset, while maintaining all other fields, including -* swap type, and any swp pte bits. The resulting pte is returned. +* Moves the softleaf offset, while maintaining all other fields, including +* softleaf type, and any softleaf pte bits. The resulting pte is returned. */ -static inline pte_t pte_move_swp_offset (pte_t pte,long delta) +static inline pte_t pte_move_softleaf_offset (pte_t pte,long delta) {const softleaf_t entry =softleaf_from_pte (pte); pte_t new = __swp_entry_to_pte(__swp_entry(swp_type(entry),
@@ -495,15 +495,16 @@ static inline pte_t pte_move_swp_offset(pte_t pte, long delta)/** -* pte_next_swp_offset - Increment the swap entry offset field of a swap pte. -* @pte: The initial pte state; must be a swap entry. +* pte_next_softleaf_offset - Increment the softleaf entry offset field of a +* non-present pte. +* @pte: The initial pte state; must be a softleaf entry. * -* Increments the swap offset, while maintaining all other fields, including -* swap type, and any swp pte bits. The resulting pte is returned. +* Increments the softleaf offset, while maintaining all other fields, including +* softleaf type, and any softleaf pte bits. The resulting pte is returned. */ -static inline pte_t pte_next_swp_offset (pte_t pte) +static inline pte_t pte_next_softleaf_offset (pte_t pte) { -return pte_move_swp_offset (pte,1 ); +return pte_move_softleaf_offset (pte,1 ); }/**
@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) { -pte_t expected_pte =pte_next_swp_offset (pte); +pte_t expected_pte =pte_next_softleaf_offset (pte);const pte_t *end_ptep = start_ptep + max_nr;pte_t *ptep = start_ptep +1 ;
@@ -535,7 +536,7 @@ static inline int swap_pte_batch(pte_t *start_ptep, int max_nr, pte_t pte)if (!pte_same (pte, expected_pte))break ; - expected_pte =pte_next_swp_offset (expected_pte); + expected_pte =pte_next_softleaf_offset (expected_pte); ptep++; }
mm/memory.c +2 -2
@@ -4757,7 +4757,7 @@ static bool can_swapin_thp(struct vm_fault *vmf, pte_t *ptep, int nr_pages) idx = (vmf->address - addr) /PAGE_SIZE ; pte =ptep_get (ptep); -if (!pte_same (pte,pte_move_swp_offset (vmf->orig_pte, -idx))) +if (!pte_same (pte,pte_move_softleaf_offset (vmf->orig_pte, -idx)))return false;/* * swap_read_folio() can't handle the case a large folio is hybridly
@@ -5066,7 +5066,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf) folio_ptep = vmf->pte - idx; folio_pte =ptep_get (folio_ptep); -if (!pte_same (folio_pte,pte_move_swp_offset (vmf->orig_pte, -idx)) || +if (!pte_same (folio_pte,pte_move_softleaf_offset (vmf->orig_pte, -idx)) ||swap_pte_batch (folio_ptep, nr, folio_pte) != nr) goto check_folio;
Signature · 1 line
2.43.0
Re: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
Quoted 5 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Quoted 8 lines
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).
I don't quite understand why you mention this. Is anyone calling pte_swp_exclusive() on a migration entry? Shouldn't it only be called when softleaf_is_swap() is true?
Quoted 2 lines
Signed-off-by Dev Jain <[email protected]>
Quoted 5 lines
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)
[...]
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 526
Quoted 6 lines
/**
mm/internal.h +0
-0@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) {
We might find a user for this in the future, in which case we might want to rename swap_pte_batch() to swap_softleaf_batch()?
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 527
Quoted 2 lines
mm/internal.h +1
-1-pte_t expected_pte =pte_next_swp_offset (pte); +pte_t expected_pte =pte_next_softleaf_offset (pte);
Best Regards
Barry
Barry Song
reviewerRe: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
Quoted 12 lines · Barry Song
Quoted 10 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
Quoted 7 lines
Quoted 3 lines
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).I don't quite understand why you mention this. Is anyone calling
pte_swp_exclusive()on a migration entry? Shouldn't it only be called whensoftleaf_is_swap()is true?
Is it the rename from pte_move_swp_offset() to pte_move_softleaf_offset() here that is causing this semantic confusion?
static inline pte_t pte_move_swp_offset(pte_t pte, long delta)
{
const softleaf_t entry = softleaf_from_pte(pte);
pte_t new = __swp_entry_to_pte(__swp_entry(swp_type(entry),
(swp_offset(entry)+ delta)));
if (pte_swp_soft_dirty(pte))
new = pte_swp_mksoft_dirty(new);
if (pte_swp_exclusive(pte))
new = pte_swp_mkexclusive(new);
if (pte_swp_uffd(pte))
new = pte_swp_mkuffd(new);
return new;
}If so, I wonder if it would be better not to rely on the implicit assumption that the softleaf type and offset fields are laid out before the soft-dirty, exclusive, and uffd bits. Could we make this explicit in the code?
Re: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
In reply to Barry SongQuoted 11 lines · Barry Song
Quoted 10 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
Quoted 7 lines
Quoted 3 lines
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).I don't quite understand why you mention this. Is anyone calling
pte_swp_exclusive()on a migration entry? Shouldn't it only be called whensoftleaf_is_swap()is true?
You are right. I just wanted to emphasize the second paragraph - that the pte_move_swp_offset will also work for softleaf entries. But I think the third para confuses more, I'll drop it.
Quoted 5 lines
Quoted 3 lines
Signed-off-by Dev Jain <[email protected]>Reviewed-by Barry Song <[email protected]>
Thanks.
Quoted 8 lines
Quoted 6 lines
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)[...]
Quoted 1 line
Quoted 8 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 526
Quoted 5 lines
/**
mm/internal.h +0
-0@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) {We might find a user for this in the future, in which case we might want to rename
swap_pte_batch()toswap_softleaf_batch()?
That is what is being done here:
https://lore.kernel.org/all/[email protected]/
I don't have a strong opinion, I can also generalize this right now.
Quoted 6 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 527
Quoted 3 lines
mm/internal.h +1
-1-pte_t expected_pte =pte_next_swp_offset (pte); +pte_t expected_pte =pte_next_softleaf_offset (pte);Best Regards
Barry
Barry Song
reviewerRe: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
Quoted 19 lines · Dev Jain
Quoted 15 lines · Barry Song
Quoted 13 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).
Quoted 7 lines
Quoted 3 lines
I don't quite understand why you mention this. Is anyone calling
pte_swp_exclusive()on a migration entry? Shouldn't it only be called whensoftleaf_is_swap()is true?You are right. I just wanted to emphasize the second paragraph - that the
pte_move_swp_offsetwill also work for softleaf entries. But I think the third para confuses more, I'll drop it.
I would rather interpret this as meaning that pte_move_softleaf_offset will also work for swap softleafs, since pte_move_softleaf_offset will call some pure-swap functions?
Would it be possible to use if (softleaf_is_swap()) for these cases to make the intent clearer? or we can keep both pte_move_softleaf_offset and pte_move_swap_offset?
As pte_move_softleaf_offset() is getting a bit weird now, a generic softleaf function has a lot of swap-specific code in it:
static inline pte_t pte_move_softleaf_offset(pte_t pte, long delta)
{
const softleaf_t entry = softleaf_from_pte(pte);
pte_t new = __swp_entry_to_pte(__swp_entry(swp_type(entry),
(swp_offset(entry)+ delta)));
if (pte_swp_soft_dirty(pte))
new = pte_swp_mksoft_dirty(new);
if (pte_swp_exclusive(pte))
new = pte_swp_mkexclusive(new);
if (pte_swp_uffd(pte))
new = pte_swp_mkuffd(new);
return new;
}Am I missing something here?
Quoted 27 lines
Quoted 6 lines
Quoted 4 lines
Signed-off-by Dev Jain <[email protected]>Reviewed-by Barry Song <[email protected]>Thanks.
Quoted 8 lines
Quoted 6 lines
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)[...]
Quoted 1 line
Quoted 8 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 526
Quoted 5 lines
/**
mm/internal.h +0
-0@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) {We might find a user for this in the future, in which case we might want to rename
swap_pte_batch()toswap_softleaf_batch()?
Quoted 4 lines
That is what is being done here:
https://lore.kernel.org/all/[email protected]/I don't have a strong opinion, I can also generalize this right now.
I don't know how you and Shivank are collaborating, since you're both changing the same thing at the same time :-)
Hopefully, we can get one of yours into mm-new first, and the other one can find a way to resolve the conflicts.
Quoted 6 lines
Quoted 6 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 527
Quoted 5 lines
mm/internal.h +1
-1-pte_t expected_pte =pte_next_swp_offset (pte); +pte_t expected_pte =pte_next_softleaf_offset (pte);
Best Regards
Barry
Re: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
Quoted 24 lines · Barry Song
Quoted 23 lines · Dev Jain
Quoted 18 lines · Barry Song
Quoted 5 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Quoted 8 lines
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).I don't quite understand why you mention this. Is anyone calling
pte_swp_exclusive()on a migration entry? Shouldn't it only be called whensoftleaf_is_swap()is true?
Quoted 8 lines
Quoted 3 lines
You are right. I just wanted to emphasize the second paragraph - that the
pte_move_swp_offsetwill also work for softleaf entries. But I think the third para confuses more, I'll drop it.I would rather interpret this as meaning that
pte_move_softleaf_offsetwill also work for swap softleafs, sincepte_move_softleaf_offsetwill call some pure-swap functions?
Yes, so if you see remove_migration_pte, there pte_swp_uffd and pte_swp_soft_dirty are being used. Same with restore_exclusive_pte (for device-exclusive stuff).
Which means that pte_swp_exclusive is the only one being used *only* for swap entries.
Quoted 4 lines
Would it be possible to use
if (softleaf_is_swap())for these cases to make the intent clearer? or we can keep bothpte_move_softleaf_offsetandpte_move_swap_offset?
So in my patch I can do
if (softleaf_is_swap(entry) && pte_swp_exclusive(pte))
That would make the intent clear.
Quoted 50 lines
As
pte_move_softleaf_offset()is getting a bit weird now, a generic softleaf function has a lot of swap-specific code in it:static inline
pte_tpte_move_softleaf_offset(pte_t pte, long delta){ const softleaf_t entry = softleaf_from_pte(pte); pte_t new = __swp_entry_to_pte(__swp_entry(swp_type(entry), (swp_offset(entry)+ delta)));
if (pte_swp_soft_dirty(pte)) new = pte_swp_mksoft_dirty(new); if (pte_swp_exclusive(pte)) new = pte_swp_mkexclusive(new); if (pte_swp_uffd(pte)) new = pte_swp_mkuffd(new); return new; }Am I missing something here?
Quoted 27 lines
Quoted 6 lines
Quoted 4 lines
Signed-off-by Dev Jain <[email protected]>Reviewed-by Barry Song <[email protected]>Thanks.
Quoted 16 lines
Quoted 6 lines
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)[...]
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 526
Quoted 6 lines
/**
mm/internal.h +0
-0@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) {We might find a user for this in the future, in which case we might want to rename
swap_pte_batch()toswap_softleaf_batch()?
Quoted 7 lines
Quoted 4 lines
That is what is being done here:
https://lore.kernel.org/all/[email protected]/I don't have a strong opinion, I can also generalize this right now.
I don't know how you and Shivank are collaborating, since you're both changing the same thing at the same time :-)
We are not collaborating really : ) he should have ideally waited for my patchset to go in first since I was doing big changes in rmap.c.
Quoted 3 lines
Hopefully, we can get one of yours into mm-new first, and the other one can find a way to resolve the conflicts.
Yes.
Quoted 10 lines
Quoted 7 lines
Quoted 7 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 527
Quoted 6 lines
mm/internal.h +1
-1-pte_t expected_pte =pte_next_swp_offset (pte); +pte_t expected_pte =pte_next_softleaf_offset (pte);Best Regards
Barry
Re: [PATCH v2 5/8] mm/internal: rename swap offset helpers to softleaf offset
Quoted 108 lines · Dev Jain
Quoted 24 lines · Barry Song
Quoted 23 lines · Dev Jain
Quoted 18 lines · Barry Song
Quoted 5 lines · Dev Jain
In preparation for adding a helper to set softleaf ptes in one go, generalize the swap entry helpers shifting the swap offset by delta, for softleaves.
Quoted 8 lines
Note that the soft-dirty bit, exclusive bit and uffd bit preservation will still work for non-swap softleaves, since a softleaf entry is constructed out of a type and offset, and those bits are ahead of the soft-dirty, exclusive and uffd bits.
For example, for a migration entry,
pte_swp_exclusive()will return false, as the exclusivity is encoded in the type itself (SOFTLEAF_MIGRATION_READ_EXCLUSIVE).I don't quite understand why you mention this. Is anyone calling
pte_swp_exclusive()on a migration entry? Shouldn't it only be called whensoftleaf_is_swap()is true?Quoted 8 lines
Quoted 3 lines
You are right. I just wanted to emphasize the second paragraph - that the
pte_move_swp_offsetwill also work for softleaf entries. But I think the third para confuses more, I'll drop it.I would rather interpret this as meaning that
pte_move_softleaf_offsetwill also work for swap softleafs, sincepte_move_softleaf_offsetwill call some pure-swap functions?Yes, so if you see
remove_migration_pte, therepte_swp_uffdandpte_swp_soft_dirtyare being used. Same withrestore_exclusive_pte(for device-exclusive stuff).Which means that
pte_swp_exclusiveis the only one being used *only* for swap entries.Quoted 4 lines
Would it be possible to use
if (softleaf_is_swap())for these cases to make the intent clearer? or we can keep bothpte_move_softleaf_offsetandpte_move_swap_offset?So in my patch I can do
if (
softleaf_is_swap(entry)&&pte_swp_exclusive(pte))That would make the intent clear.
Quoted 53 lines
As
pte_move_softleaf_offset()is getting a bit weird now, a generic softleaf function has a lot of swap-specific code in it:static inline
pte_tpte_move_softleaf_offset(pte_t pte, long delta){ const softleaf_t entry = softleaf_from_pte(pte); pte_t new = __swp_entry_to_pte(__swp_entry(swp_type(entry), (swp_offset(entry)+ delta)));
if (pte_swp_soft_dirty(pte)) new = pte_swp_mksoft_dirty(new); if (pte_swp_exclusive(pte)) new = pte_swp_mkexclusive(new); if (pte_swp_uffd(pte)) new = pte_swp_mkuffd(new); return new; }Am I missing something here?
Quoted 30 lines
Quoted 6 lines
Quoted 4 lines
Signed-off-by Dev Jain <[email protected]>Reviewed-by Barry Song <[email protected]>Thanks.
Quoted 8 lines
Quoted 6 lines
--- mm/internal.h | 29 +++++++++++++++-------------- mm/memory.c | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-)[...]
Quoted 1 line
Quoted 8 lines
mm/internal.h static inline pte_t pte_next_swp_offset(pte_t pte) · line 526
Quoted 5 lines
/**
mm/internal.h +0
-0@@ -523,7 +524,7 @@ static inline pte_t pte_next_swp_offset(pte_t pte)*/ static inline int swap_pte_batch (pte_t *start_ptep,int max_nr,pte_t pte) {We might find a user for this in the future, in which case we might want to rename
swap_pte_batch()toswap_softleaf_batch()?That is what is being done here:
https://lore.kernel.org/all/[email protected]/
Quoted 9 lines
Quoted 4 lines
Quoted 1 line
I don't have a strong opinion, I can also generalize this right now.
I don't know how you and Shivank are collaborating, since you're both changing the same thing at the same time :-)
We are not collaborating really : ) he should have ideally waited for my patchset to go in first since I was doing big changes in
rmap.c.
Just to clarify, my series is already based on your V1 series, as noted in its cover letter and b4 prerequisite-message-id.
The intention was always for your series to go in first. I posted mine meanwhile to collect review rather than wait for yours to land.
Quoted 6 lines
Quoted 3 lines
Hopefully, we can get one of yours into mm-new first, and the other one can find a way to resolve the conflicts.
Yes.
I agree there is some overlap in rmap.c, and I will rebase my series on your latest version to avoid conflicts.
Regarding softleaf_pte_batch(), my patch factors the generic logic out of swap_pte_batch() because the migration path (migration_pte_batch) needs it. I suggest keeping it in my series, but I can drop that patch if you prefer to carry this revised patch[1] in yours.
[1] https://lore.kernel.org/all/[email protected]
Thanks,
Shivank
PATCH v2 6/8 mm/internal: add set_softleaf_ptes
In reply to Dev JainCurrently we have a helper called set_ptes() which is used to set consecutive present ptes in the pgtables. To do the same operation but to set "consecutive" nonpresent (softleaf) ptes, add set_softleaf_ptes().
The softleaves which have a notion of consecutivity is swap softleaf, and those grouped by softeaf_has_pfn(). The latter is trivial; future code can convert present ptes pointing to the same large folio to swap softleaves with consecutive offsets using this helper.
The other case is softleaf markers. They do not have a notion of a swap offset or PFN, so future code can use set_softleaf_ptes() to store multiple (same) markers on the ptes.
--- mm/internal.h | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+)
mm/internal.h +33 -0
@@ -507,6 +507,39 @@ static inline pte_t pte_next_softleaf_offset(pte_t pte)return pte_move_softleaf_offset (pte,1 ); } +/** +* set_softleaf_ptes - Set consecutive softleaf PTEs. +* @mm: Address space the PTEs belong to. +* @addr: Address of the first PTE. +* @ptep: Page table pointer for the first PTE. +* @pte: PTE to set for the first entry. +* @nr: Number of PTEs to set. +* +* Install @nr softleaf PTEs, advancing @pte when its softleaf entry +* represents consecutive offsets. Swap entries advance through swap offsets, +* PFN softleaf entries advance through PFNs (encoded by swap offset), and +* marker entries are repeated unchanged. +*/ +static inline void set_softleaf_ptes (struct mm_struct *mm,unsigned long addr, +pte_t *ptep,pte_t pte,unsigned long nr) +{ +softleaf_t entry; +bool advance; + + entry =softleaf_from_pte (pte); + advance =softleaf_is_swap (entry) ||softleaf_has_pfn (entry); + +for (;;) { +set_pte_at (mm, addr, ptep, pte); +if (--nr ==0 ) +break ; +if (advance) + pte =pte_next_softleaf_offset (pte); + ptep++; + addr +=PAGE_SIZE ; + } +} +/** * swap_pte_batch - detect a PTE batch for a set of contiguous swap entries * @start_ptep: Page table pointer for the first entry.
Signature · 1 line
2.43.0
Re: [PATCH v2 6/8] mm/internal: add set_softleaf_ptes
Quoted 10 lines · Dev Jain
Currently we have a helper called
set_ptes()which is used to set consecutive present ptes in the pgtables. To do the same operation but to set "consecutive" nonpresent (softleaf) ptes, addset_softleaf_ptes().The softleaves which have a notion of consecutivity is swap softleaf, and those grouped by
softeaf_has_pfn(). The latter is trivial; future code can convert present ptes pointing to the same large folio to swap softleaves with consecutive offsets using this helper.
Quoted 6 lines
The other case is softleaf markers. They do not have a notion of a swap offset or PFN, so future code can use
set_softleaf_ptes()to store multiple (same) markers on the ptes.Signed-off-by Dev Jain <[email protected]>---
PATCH v2 7/8 mm/memory: use set_softleaf_ptes for uffd-wp markers
In reply to Dev JainUse set_softleaf_ptes() to store multiple uffd-wp marker entries instead of open coding the batched setting.
--- mm/memory.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-)
mm/memory.c +2 -8
@@ -1737,14 +1737,8 @@ bool cond_install_uffd_wp_ptes(struct vm_area_struct *vma,if (likely (!arm_uffd_pte))return false; -for (;;) { -set_pte_at (vma->vm_mm, addr, ptep, -make_pte_marker (PTE_MARKER_UFFD_WP )); -if (--nr_ptes ==0 ) -break ; - ptep++; - addr +=PAGE_SIZE ; - } +set_softleaf_ptes (vma->vm_mm, addr, ptep, +make_pte_marker (PTE_MARKER_UFFD_WP ), nr_ptes);return true; }
Signature · 1 line
2.43.0
Re: [PATCH v2 7/8] mm/memory: use set_softleaf_ptes for uffd-wp markers
Quoted 6 lines · Dev Jain
Use
set_softleaf_ptes()to store multiple uffd-wp marker entries instead of open coding the batched setting.Signed-off-by Dev Jain <[email protected]>---
PATCH v2 8/8 mm/rmap: batch unmap anonymous swap-backed large folios
In reply to Dev JainEnable batch clearing of ptes, and batch swap setting of ptes for anon swap-backed folio unmapping.
Processing all ptes of a large folio in one go helps us batch across atomics (add_mm_counter etc), barriers (in the function __folio_try_share_anon_rmap), repeated calls to page_vma_mapped_walk(), to name a few. In general, batching helps us to execute similar code together, making the execution of the program more memory and CPU friendly.
On arm64-contpte, batching also helps us avoid redundant ptep_get() calls and TLB flushes while breaking the contpte mapping.
The handling of anon-exclusivity is very similar to commit cac1db8c3aad ("mm: optimize mprotect() by PTE batching"). Since folio_unmap_pte_batch() won't look at the bits of the underlying page, we need to process sub-batches of ptes pointing to pages which are same w.r.t exclusivity, and batch set only those ptes to swap ptes in one go.
arch_unmap_one() is only defined for sparc64; I am not comfortable regarding the nuances between retrieving the pfn from pte_pfn() or from (paddr = pte_val(oldpte) & _PAGE_PADDR_4V).
(And, pte_next_pfn() can't even be called from arch_unmap_one() because that file does not include pgtable.h) So just disable the "sparc64-anon-swapbacked" case for now.
We need to take care of rmap accounting (folio_remove_rmap_ptes) and reference accounting (folio_put_refs) when anon folio unmap succeeds. In case we partially batch the large folio and fail, we need to correctly do the accounting for pages which were successfully unmapped. So, put this accounting code (which is finish_folio_unmap()) in __ttu_anon_swapbacked_folio() itself, instead of doing some horrible goto jumping at the callsite of ttu_anon_folio().
Similarly, do the finish_folio_unmap() in ttu_anon_folio itself for the non-swapbacked (lazyfree) case.
If the batch length is less than the number of pages in the folio, then we must skip over this batch.
The page_vma_mapped_walk API ensures this - check_pte() will return true only if any of [pvmw->pfn, pvmw->pfn + nr_pages) is mapped by the pte. There is no pfn underlying a swap pte, so check_pte returns false and we keep skipping until we hit a present pte, which is where we want to start unmapping from next.
Remove the label finish_unmap since no goto callers are left now.
--- mm/rmap.c | 110 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 29 deletions(-)
mm/rmap.c +81 -29
@@ -1964,12 +1964,14 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr =pmd_addr_end (addr, vma->vm_end); max_nr = (end_addr - addr) >>PAGE_SHIFT ; -/* We only support lazyfree or file folios batching for now ... */ -if (folio_test_anon (folio) &&folio_test_swapbacked (folio)) +if (pte_unused (pte))return 1 ; -if (pte_unused (pte)) +#ifdef __HAVE_ARCH_UNMAP_ONE +/* Add batching support to arch_unmap_one() to remove this */ +if (folio_test_anon (folio) &&folio_test_swapbacked (folio))return 1 ; +#endif /* * If unmap fails, we need to restore the ptes. To avoid accidentally
@@ -2139,16 +2141,25 @@ static pte_t swp_pte_prepare(swp_entry_t entry, pte_t old_pte,return swp_pte; } -static bool ttu_anon_swapbacked_folio (struct vm_area_struct *vma, +static void finish_folio_unmap (struct vm_area_struct *vma, +struct folio *folio,struct page *page,unsigned long nr_pages) +{ +folio_remove_rmap_ptes (folio, page, nr_pages, vma); +if (vma->vm_flags &VM_LOCKED ) +mlock_drain_local (); +folio_put_refs (folio, nr_pages); +} + +static bool __ttu_anon_swapbacked_folio (struct vm_area_struct *vma,struct folio *folio,struct page *page,unsigned long address, -pte_t *ptep,pte_t pteval) +pte_t *ptep,pte_t pteval,unsigned long nr_pages, +bool anon_exclusive) { -const bool anon_exclusive =folio_test_anon (folio) && -PageAnonExclusive (page);swp_entry_t entry =page_swap_entry (page);struct mm_struct *mm = vma->vm_mm; +pte_t swp_pte; -if (folio_dup_swap_pages (folio, page,1 ) <0 ) +if (folio_dup_swap_pages (folio, page, nr_pages) <0 )return false;/*
@@ -2157,21 +2168,57 @@ static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,* so we'll not check/care. */ if (arch_unmap_one (mm, vma, address, pteval) <0 ) { -folio_put_swap_pages (folio, page,1 ); +VM_WARN_ON (nr_pages !=1 ); +folio_put_swap_pages (folio, page, nr_pages);return false; }/* See folio_try_share_anon_rmap(): clear PTE first. */ -if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, page)) { -folio_put_swap_pages (folio, page,1 ); +if (anon_exclusive && +folio_try_share_anon_rmap_ptes (folio, page, nr_pages)) { +folio_put_swap_pages (folio, page, nr_pages);return false; }mm_prepare_for_swap_entries (mm); -dec_mm_counter (mm,MM_ANONPAGES ); -inc_mm_counter (mm,MM_SWAPENTS ); -set_pte_at (mm, address, ptep, -swp_pte_prepare (entry, pteval, anon_exclusive)); +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +add_mm_counter (mm,MM_SWAPENTS , nr_pages); + swp_pte =swp_pte_prepare (entry, pteval, anon_exclusive); +set_softleaf_ptes (mm, address, ptep, swp_pte, nr_pages); +finish_folio_unmap (vma, folio, page, nr_pages); +return true; +} + +static bool ttu_anon_swapbacked_folio (struct vm_area_struct *vma, +struct folio *folio,struct page *first_page, +unsigned long address,pte_t *ptep,pte_t pteval, +unsigned long nr_pages) +{ +unsigned long batch_idx =0 ; + +while (nr_pages) { +bool anon_exclusive =PageAnonExclusive (first_page + batch_idx); +unsigned long len =page_anon_exclusive_batch (batch_idx, + nr_pages, first_page, anon_exclusive); + +if (!__ttu_anon_swapbacked_folio (vma, folio, + first_page + batch_idx, address, ptep, pteval, + len, anon_exclusive)) { +/* Restore the remaining PTEs that were cleared. */ +set_ptes (vma->vm_mm, address, ptep, pteval, nr_pages); +return false; + } + + nr_pages -= len; +if (!nr_pages) +break ; + + pteval =pte_advance_pfn (pteval, len); + address += len *PAGE_SIZE ; + batch_idx += len; + ptep += len; + } +return true; }
@@ -2184,15 +2231,22 @@ static bool ttu_anon_folio(struct vm_area_struct *vma, struct folio *folio,* See handle_pte_fault() ... */ if (WARN_ON_ONCE (folio_test_swapbacked (folio) != -folio_test_swapcache (folio))) +folio_test_swapcache (folio))) { +set_ptes (vma->vm_mm, address, ptep, pteval, nr_pages);return false; + } -if (!folio_test_swapbacked (folio)) -return ttu_anon_lazyfree_folio (vma, folio, nr_pages); +if (!folio_test_swapbacked (folio)) { +if (!ttu_anon_lazyfree_folio (vma, folio, nr_pages)) { +set_ptes (vma->vm_mm, address, ptep, pteval, nr_pages); +return false; + } +finish_folio_unmap (vma, folio, page, nr_pages); +return true; + } -/* nr_pages > 1 not supported yet */ return ttu_anon_swapbacked_folio (vma, folio, page, address, ptep, - pteval); + pteval, nr_pages); }/*
@@ -2370,13 +2424,14 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,*/ dec_mm_counter (mm,mm_counter (folio)); }else if (folio_test_anon (folio)) { +/* finish_folio_unmap handled internally */ if (!ttu_anon_folio (vma, folio, page, address, - pvmw.pte, pteval, nr_pages)) { -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); + pvmw.pte, pteval, nr_pages)) goto walk_abort; - } - goto finish_unmap; +if (nr_pages ==folio_nr_pages (folio)) + goto walk_done; +continue ; }else {/* * This is a locked file-backed folio,
@@ -2391,11 +2446,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,*/ add_mm_counter (mm,mm_counter_file (folio), -nr_pages); } -finish_unmap: -folio_remove_rmap_ptes (folio, page, nr_pages, vma); -if (vma->vm_flags &VM_LOCKED ) -mlock_drain_local (); -folio_put_refs (folio, nr_pages); + +finish_folio_unmap (vma, folio, page, nr_pages);/* * If we are sure that we batched the entire folio and cleared
Signature · 1 line
2.43.0
Barry Song
reviewerRe: [PATCH v2 8/8] mm/rmap: batch unmap anonymous swap-backed large folios
Quoted 61 lines · Dev Jain
Enable batch clearing of ptes, and batch swap setting of ptes for anon swap-backed folio unmapping.
Processing all ptes of a large folio in one go helps us batch across atomics (
add_mm_counteretc), barriers (in the function__folio_try_share_anon_rmap), repeated calls topage_vma_mapped_walk(), to name a few. In general, batching helps us to execute similar code together, making the execution of the program more memory and CPU friendly.On arm64-contpte, batching also helps us avoid redundant
ptep_get()calls and TLB flushes while breaking the contpte mapping.The handling of anon-exclusivity is very similar to commit
cac1db8c3aad("mm: optimizemprotect()by PTE batching"). Sincefolio_unmap_pte_batch()won't look at the bits of the underlying page, we need to process sub-batches of ptes pointing to pages which are same w.r.t exclusivity, and batch set only those ptes to swap ptes in one go.
arch_unmap_one()is only defined for sparc64; I am not comfortable regarding the nuances between retrieving the pfn frompte_pfn()or from (paddr =pte_val(oldpte)&_PAGE_PADDR_4V).(And,
pte_next_pfn()can't even be called fromarch_unmap_one()because that file does not includepgtable.h) So just disable the "sparc64-anon-swapbacked" case for now.We need to take care of rmap accounting (
folio_remove_rmap_ptes) and reference accounting (folio_put_refs) when anon folio unmap succeeds. In case we partially batch the large folio and fail, we need to correctly do the accounting for pages which were successfully unmapped. So, put this accounting code (which isfinish_folio_unmap()) in__ttu_anon_swapbacked_folio()itself, instead of doing some horrible goto jumping at the callsite ofttu_anon_folio().Similarly, do the
finish_folio_unmap()inttu_anon_folioitself for the non-swapbacked (lazyfree) case.If the batch length is less than the number of pages in the folio, then we must skip over this batch.
The
page_vma_mapped_walkAPI ensures this -check_pte()will return true only if any of [pvmw->pfn,pvmw->pfn+nr_pages) is mapped by the pte. There is no pfn underlying a swap pte, socheck_ptereturns false and we keep skipping until we hit a present pte, which is where we want to start unmapping from next.Remove the label
finish_unmapsince no goto callers are left now.Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 110 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 29 deletions(-)mm/rmap.c +0
-0@@ -1964,12 +1964,14 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr =pmd_addr_end (addr, vma->vm_end);
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1971
Quoted 10 lines
mm/rmap.c +3
-3max_nr = (end_addr - addr) >>PAGE_SHIFT ; -/* We only support lazyfree or file folios batching for now ... */ -if (folio_test_anon (folio) &&folio_test_swapbacked (folio)) +if (pte_unused (pte))return 1 ; -if (pte_unused (pte)) +#ifdef __HAVE_ARCH_UNMAP_ONE +/* Add batching support to arch_unmap_one() to remove this */
I'd like to make this clearer. For example, could we say that sparc has arch_unmap_one(), which doesn't support batching?
BTW, it shouldn't be too hard to save nr_pages tags, looking at the code:
static inline int arch_unmap_one(struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long addr, pte_t oldpte)
{
if (adi_state.enabled && (pte_val(oldpte) & _PAGE_MCD_4V))
return adi_save_tags(mm, vma, addr, oldpte);
return 0;
}Maybe the sparc folks can handle this.
mm/rmap.c static pte_t swp_pte_prepare(swp_entry_t entry, pte_t old_pte, · line 2145
Quoted 13 lines
mm/rmap.c +4
-1+if (folio_test_anon (folio) &&folio_test_swapbacked (folio))return 1 ; +#endif /* * If unmap fails, we need to restore the ptes. To avoid accidentally @@ -2139,16 +2141,25 @@ static pte_t swp_pte_prepare(swp_entry_t entry, pte_t old_pte,return swp_pte; } -static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, +static void finish_folio_unmap (struct vm_area_struct *vma, +struct folio *folio,struct page *page,unsigned long nr_pages)
We are not necessarily finishing the whole folio here, right? The name is a bit misleading to me, as it sounds like we're finishing the whole folio.
Maybe finish_folio_unmap_batch()?
Quoted 57 lines
mm/rmap.c +26
-11+{ +folio_remove_rmap_ptes (folio, page, nr_pages, vma); +if (vma->vm_flags &VM_LOCKED ) +mlock_drain_local (); +folio_put_refs (folio, nr_pages); +} + +static bool __ttu_anon_swapbacked_folio (struct vm_area_struct *vma,struct folio *folio,struct page *page,unsigned long address, - pte_t *ptep, pte_t pteval) +pte_t *ptep,pte_t pteval,unsigned long nr_pages, +bool anon_exclusive) { - const bool anon_exclusive = folio_test_anon(folio) && -PageAnonExclusive (page);swp_entry_t entry =page_swap_entry (page);struct mm_struct *mm = vma->vm_mm; +pte_t swp_pte; -if (folio_dup_swap_pages (folio, page,1 ) <0 ) +if (folio_dup_swap_pages (folio, page, nr_pages) <0 )return false;/* @@ -2157,21 +2168,57 @@ static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,* so we'll not check/care. */ if (arch_unmap_one (mm, vma, address, pteval) <0 ) { -folio_put_swap_pages (folio, page,1 ); +VM_WARN_ON (nr_pages !=1 ); +folio_put_swap_pages (folio, page, nr_pages);return false; }/* See folio_try_share_anon_rmap(): clear PTE first. */ -if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, page)) { -folio_put_swap_pages (folio, page,1 ); +if (anon_exclusive && +folio_try_share_anon_rmap_ptes (folio, page, nr_pages)) { +folio_put_swap_pages (folio, page, nr_pages);return false; }mm_prepare_for_swap_entries (mm); -dec_mm_counter (mm,MM_ANONPAGES ); -inc_mm_counter (mm,MM_SWAPENTS ); -set_pte_at (mm, address, ptep, -swp_pte_prepare (entry, pteval, anon_exclusive)); +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +add_mm_counter (mm,MM_SWAPENTS , nr_pages); + swp_pte =swp_pte_prepare (entry, pteval, anon_exclusive); +set_softleaf_ptes (mm, address, ptep, swp_pte, nr_pages); +finish_folio_unmap (vma, folio, page, nr_pages); +return true; +} + +static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,
mm/rmap.c static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, · line 2202
Quoted 10 lines
mm/rmap.c +10
-0+ struct folio *folio, struct page *first_page, + unsigned long address, pte_t *ptep, pte_t pteval, + unsigned long nr_pages) +{ + unsigned long batch_idx = 0; + +while (nr_pages) { +bool anon_exclusive =PageAnonExclusive (first_page + batch_idx); +unsigned long len =page_anon_exclusive_batch (batch_idx, + nr_pages, first_page, anon_exclusive);
len is really a bad name, as len usually describes a size. Maybe batch_pages?
Quoted 11 lines
mm/rmap.c +11
-0+ +if (!__ttu_anon_swapbacked_folio (vma, folio, + first_page + batch_idx, address, ptep, pteval, + len, anon_exclusive)) { +/* Restore the remaining PTEs that were cleared. */ +set_ptes (vma->vm_mm, address, ptep, pteval, nr_pages); +return false; + } + + nr_pages -= len; +if (!nr_pages)
mm/rmap.c static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, · line 2223
Quoted 10 lines
mm/rmap.c +8
-0+break ; + + pteval =pte_advance_pfn (pteval, len); + address += len *PAGE_SIZE ; + batch_idx += len; + ptep += len; + } +return true; }
Best Regards
Barry
Re: [PATCH v2 8/8] mm/rmap: batch unmap anonymous swap-backed large folios
+ sparc guys for batching arch_unmap_one
Quoted 82 lines · Barry Song
Quoted 61 lines · Dev Jain
Enable batch clearing of ptes, and batch swap setting of ptes for anon swap-backed folio unmapping.
Processing all ptes of a large folio in one go helps us batch across atomics (
add_mm_counteretc), barriers (in the function__folio_try_share_anon_rmap), repeated calls topage_vma_mapped_walk(), to name a few. In general, batching helps us to execute similar code together, making the execution of the program more memory and CPU friendly.On arm64-contpte, batching also helps us avoid redundant
ptep_get()calls and TLB flushes while breaking the contpte mapping.The handling of anon-exclusivity is very similar to commit
cac1db8c3aad("mm: optimizemprotect()by PTE batching"). Sincefolio_unmap_pte_batch()won't look at the bits of the underlying page, we need to process sub-batches of ptes pointing to pages which are same w.r.t exclusivity, and batch set only those ptes to swap ptes in one go.
arch_unmap_one()is only defined for sparc64; I am not comfortable regarding the nuances between retrieving the pfn frompte_pfn()or from (paddr =pte_val(oldpte)&_PAGE_PADDR_4V).(And,
pte_next_pfn()can't even be called fromarch_unmap_one()because that file does not includepgtable.h) So just disable the "sparc64-anon-swapbacked" case for now.We need to take care of rmap accounting (
folio_remove_rmap_ptes) and reference accounting (folio_put_refs) when anon folio unmap succeeds. In case we partially batch the large folio and fail, we need to correctly do the accounting for pages which were successfully unmapped. So, put this accounting code (which isfinish_folio_unmap()) in__ttu_anon_swapbacked_folio()itself, instead of doing some horrible goto jumping at the callsite ofttu_anon_folio().Similarly, do the
finish_folio_unmap()inttu_anon_folioitself for the non-swapbacked (lazyfree) case.If the batch length is less than the number of pages in the folio, then we must skip over this batch.
The
page_vma_mapped_walkAPI ensures this -check_pte()will return true only if any of [pvmw->pfn,pvmw->pfn+nr_pages) is mapped by the pte. There is no pfn underlying a swap pte, socheck_ptereturns false and we keep skipping until we hit a present pte, which is where we want to start unmapping from next.Remove the label
finish_unmapsince no goto callers are left now.Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 110 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 81 insertions(+), 29 deletions(-)mm/rmap.c +0
-0@@ -1964,12 +1964,14 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, end_addr =pmd_addr_end (addr, vma->vm_end);mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1971
Quoted 10 lines
mm/rmap.c +3
-3max_nr = (end_addr - addr) >>PAGE_SHIFT ; -/* We only support lazyfree or file folios batching for now ... */ -if (folio_test_anon (folio) &&folio_test_swapbacked (folio)) +if (pte_unused (pte))return 1 ; -if (pte_unused (pte)) +#ifdef __HAVE_ARCH_UNMAP_ONE +/* Add batching support to arch_unmap_one() to remove this */ I'd like to make this clearer. For example, could we say that sparc has
arch_unmap_one(), which doesn't support batching?BTW, it shouldn't be too hard to save
nr_pagestags, looking at the code:static inline int arch_unmap_one(struct
mm_struct*mm,
structvm_area_struct*vma, unsigned long addr,pte_toldpte)
Quoted 6 lines
{ if (adi_state.enabled && (pte_val(oldpte) & _PAGE_MCD_4V)) return adi_save_tags(mm, vma, addr, oldpte); return 0; }Maybe the sparc folks can handle this.
I have mentioned in the patch description why I wasn't comfortable changing this.
Perhaps the sparc guys can help me with the best way. Otherwise I'll try harder in the next iteration to solve it myself : )
Quoted 19 lines
Quoted 8 lines
mm/rmap.c +2
-0+if (folio_test_anon (folio) &&folio_test_swapbacked (folio))return 1 ; +#endif /* * If unmap fails, we need to restore the ptes. To avoid accidentally mm/rmap.c static pte_t swp_pte_prepare(swp_entry_t entry, pte_t old_pte, · line 2145
Quoted 6 lines
mm/rmap.c +2
-1@@ -2139,16 +2141,25 @@ static pte_t swp_pte_prepare(swp_entry_t entry, pte_t old_pte,return swp_pte; } -static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, +static void finish_folio_unmap (struct vm_area_struct *vma, +struct folio *folio,struct page *page,unsigned long nr_pages)We are not necessarily finishing the whole folio here, right? The name is a bit misleading to me, as it sounds like we're finishing the whole folio.
Quoted 1 line
Maybe
finish_folio_unmap_batch()?
Yes makes sense, it finishes the batch rather than finishing the folio.
Quoted 63 lines
Quoted 63 lines
mm/rmap.c +31
-11+{ +folio_remove_rmap_ptes (folio, page, nr_pages, vma); +if (vma->vm_flags &VM_LOCKED ) +mlock_drain_local (); +folio_put_refs (folio, nr_pages); +} + +static bool __ttu_anon_swapbacked_folio (struct vm_area_struct *vma,struct folio *folio,struct page *page,unsigned long address, - pte_t *ptep, pte_t pteval) +pte_t *ptep,pte_t pteval,unsigned long nr_pages, +bool anon_exclusive) { - const bool anon_exclusive = folio_test_anon(folio) && -PageAnonExclusive (page);swp_entry_t entry =page_swap_entry (page);struct mm_struct *mm = vma->vm_mm; +pte_t swp_pte; -if (folio_dup_swap_pages (folio, page,1 ) <0 ) +if (folio_dup_swap_pages (folio, page, nr_pages) <0 )return false;/* @@ -2157,21 +2168,57 @@ static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma,* so we'll not check/care. */ if (arch_unmap_one (mm, vma, address, pteval) <0 ) { -folio_put_swap_pages (folio, page,1 ); +VM_WARN_ON (nr_pages !=1 ); +folio_put_swap_pages (folio, page, nr_pages);return false; }/* See folio_try_share_anon_rmap(): clear PTE first. */ -if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, page)) { -folio_put_swap_pages (folio, page,1 ); +if (anon_exclusive && +folio_try_share_anon_rmap_ptes (folio, page, nr_pages)) { +folio_put_swap_pages (folio, page, nr_pages);return false; }mm_prepare_for_swap_entries (mm); -dec_mm_counter (mm,MM_ANONPAGES ); -inc_mm_counter (mm,MM_SWAPENTS ); -set_pte_at (mm, address, ptep, -swp_pte_prepare (entry, pteval, anon_exclusive)); +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +add_mm_counter (mm,MM_SWAPENTS , nr_pages); + swp_pte =swp_pte_prepare (entry, pteval, anon_exclusive); +set_softleaf_ptes (mm, address, ptep, swp_pte, nr_pages); +finish_folio_unmap (vma, folio, page, nr_pages); +return true; +} + +static bool ttu_anon_swapbacked_folio (struct vm_area_struct *vma, +struct folio *folio,struct page *first_page, +unsigned long address,pte_t *ptep,pte_t pteval, +unsigned long nr_pages) +{ +unsigned long batch_idx =0 ;
Quoted 8 lines
mm/rmap.c static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, · line 2202
Quoted 5 lines
mm/rmap.c +5
-0+ +while (nr_pages) { +bool anon_exclusive =PageAnonExclusive (first_page + batch_idx); +unsigned long len =page_anon_exclusive_batch (batch_idx, + nr_pages, first_page, anon_exclusive);
lenis really a bad name, aslenusually describes a size. Maybebatch_pages?
I disagree here : ) I don't think someone should mistake len with size. len is ... "length". So in this case it is the length of pages in the array, starting from batch_idx, upto nr_pages, which are all exclusive or not. Also I would prefer short variable names.
Quoted 25 lines
Quoted 12 lines
mm/rmap.c +11
-0+ +if (!__ttu_anon_swapbacked_folio (vma, folio, + first_page + batch_idx, address, ptep, pteval, + len, anon_exclusive)) { +/* Restore the remaining PTEs that were cleared. */ +set_ptes (vma->vm_mm, address, ptep, pteval, nr_pages); +return false; + } + + nr_pages -= len; +if (!nr_pages)mm/rmap.c static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, · line 2223
Quoted 10 lines
mm/rmap.c +8
-0+break ; + + pteval =pte_advance_pfn (pteval, len); + address += len *PAGE_SIZE ; + batch_idx += len; + ptep += len; + } +return true; }Best Regards
Barry
Barry Song
reviewerRe: [PATCH v2 8/8] mm/rmap: batch unmap anonymous swap-backed large folios
On Thu, Sep 10, 2026 at 12:39 PM Dev Jain <[email protected]> wrote:
[...]
Quoted 15 lines
Quoted 6 lines
Quoted 6 lines
mm/rmap.c +6
-0+static bool ttu_anon_swapbacked_folio (struct vm_area_struct *vma, +struct folio *folio,struct page *first_page, +unsigned long address,pte_t *ptep,pte_t pteval, +unsigned long nr_pages) +{ +unsigned long batch_idx =0 ;Quoted 8 lines
mm/rmap.c static bool ttu_anon_swapbacked_folio(struct vm_area_struct *vma, · line 2202
Quoted 5 lines
mm/rmap.c +5
-0+ +while (nr_pages) { +bool anon_exclusive =PageAnonExclusive (first_page + batch_idx); +unsigned long len =page_anon_exclusive_batch (batch_idx, + nr_pages, first_page, anon_exclusive);
lenis really a bad name, aslenusually describes a size. Maybebatch_pages?
Quoted 4 lines
I disagree here : ) I don't think someone should mistake len with size. len is ... "length". So in this case it is the length of pages in the array, starting from
batch_idx, uptonr_pages, which are all exclusive or not. Also I would prefer short variable names.
OK, up to you :-)