mm/rmap: Optimize anonymous large folio unmapping
Subsystems (7)
cover mm/rmap: Optimize anonymous large folio unmapping
Speed up unmapping of anonymous large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry at [1]) 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;}
On arm64, only showing one of the middle values in the distribution:
without patch:madvise(MADV_PAGEOUT) took 52192959 ns (52.193 ms)
with patch:madvise(MADV_PAGEOUT) took 26676625 ns (26.677 ms)
[1] https://lore.kernel.org/all/[email protected]/
---
Based on mm-unstable bb420884e9e0. mm-selftests pass.
Dev Jain (9):
mm/rmap: make nr_pages signed in try_to_unmap_one mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one mm/rmap: refactor lazyfree unmap commit path to
commit_ttu_lazyfree_folio()
mm/memory: Batch set uffd-wp markers during zapping mm/rmap: batch unmap folios belonging to uffd-wp VMAs mm/swapfile: Make folio_dup_swap batchable
mm/swapfile: Make folio_put_swap batchable
mm/rmap: introduce folio_try_share_anon_rmap_ptes mm/rmap: enable batch unmapping of anonymous folios
include/linux/mm_inline.h | 37 +++-- include/linux/page-flags.h | 11 ++ include/linux/rmap.h | 38 ++++- mm/internal.h | 26 ++++ mm/memory.c | 26 +--- mm/mprotect.c | 17 --- mm/rmap.c | 274 ++++++++++++++++++++++++------------- mm/shmem.c | 8 +- mm/swap.h | 10 +- mm/swapfile.c | 25 ++-- 10 files changed, 298 insertions(+), 174 deletions(-)
Signature · 1 line
2.34.1
PATCH 1/9 mm/rmap: make nr_pages signed in try_to_unmap_one
Currently, nr_pages is defined as unsigned long. We use nr_pages to manipulate mm rss counters for lazyfree folios as follows:
add_mm_counter(mm, MM_ANONPAGES, -nr_pages);
Suppose nr_pages = 3. -nr_pages underflows and becomes ULONG_MAX - 2. Then, since add_mm_counter() uses this -nr_pages as a long, ULONG_MAX - 2 does not fit into the positive range of long, and is converted to -3. Eventually all of this works out, but for keeping things simple, declare nr_pages as a signed variable.
--- mm/rmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)
mm/rmap.c +2 -1
@@ -1979,9 +1979,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,struct page *subpage;struct mmu_notifier_range range;enum ttu_flags flags = (enum ttu_flags )(long )arg; -unsigned long nr_pages =1 , end_addr; +unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; +long nr_pages =1 ;int ptes =0 ;/*
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 1/9] mm/rmap: make nr_pages signed in try_to_unmap_one
Quoted 21 lines · Dev Jain
Currently,
nr_pagesis defined as unsigned long. We usenr_pagesto manipulate mm rss counters for lazyfree folios as follows:add_mm_counter(mm, MM_ANONPAGES, -nr_pages);Suppose
nr_pages= 3. -nr_pages underflows and becomesULONG_MAX- 2. Then, sinceadd_mm_counter()uses this -nr_pages as a long,ULONG_MAX- 2 does not fit into the positive range of long, and is converted to -3. Eventually all of this works out, but for keeping things simple, declarenr_pagesas a signed variable.Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)mm/rmap.c +0
-0
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 1985
Quoted 8 lines
mm/rmap.c +2
-1@@ -1979,9 +1979,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,struct page *subpage;struct mmu_notifier_range range;enum ttu_flags flags = (enum ttu_flags )(long )arg; -unsigned long nr_pages =1 , end_addr; +unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; +long nr_pages =1 ;
This is a non-issue that makes the code confusing, so let's not?
The convention throughout the kernel is nr_pages generally is unsigned because you can't have negative nr_pages.
Quoted 6 lines
mm/rmap.c +0
-1int ptes =0 ;/* -- 2.34.1
David Hildenbrand (Arm)
maintainerRe: [PATCH 1/9] mm/rmap: make nr_pages signed in try_to_unmap_one
Quoted 33 lines · Lorenzo Stoakes (Oracle)
Quoted 21 lines · Dev Jain
Currently,
nr_pagesis defined as unsigned long. We usenr_pagesto manipulate mm rss counters for lazyfree folios as follows:add_mm_counter(mm, MM_ANONPAGES, -nr_pages);Suppose
nr_pages= 3. -nr_pages underflows and becomesULONG_MAX- 2. Then, sinceadd_mm_counter()uses this -nr_pages as a long,ULONG_MAX- 2 does not fit into the positive range of long, and is converted to -3. Eventually all of this works out, but for keeping things simple, declarenr_pagesas a signed variable.Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)mm/rmap.c +0
-0mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 1985
Quoted 8 lines
mm/rmap.c +2
-1@@ -1979,9 +1979,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,struct page *subpage;struct mmu_notifier_range range;enum ttu_flags flags = (enum ttu_flags )(long )arg; -unsigned long nr_pages =1 , end_addr; +unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; +long nr_pages =1 ;This is a non-issue that makes the code confusing, so let's not?
Quoted 2 lines
The convention throughout the kernel is
nr_pagesgenerally is unsigned because you can't have negativenr_pages.
Indeed. Documented in:
commit fa17bcd5f65ed702df001579cca8c885fa6bf3e7
Author: Aristeu Rozanski <[email protected]> Date: Tue Aug 26 11:37:21 2025 -0400
mm: make folio page count functions return unsigned
As raised by Andrew [1], a folio/compound page never spans a negative number of pages. Consequently, let's use "unsigned long" instead of "long" consistently for folio_nr_pages(), folio_large_nr_pages() and compound_nr().
Using "unsigned long" as return value is fine, because even "(long)-folio_nr_pages()" will keep on working as expected. Using "unsigned int" instead would actually break these use cases.
This patch takes the first step changing these to return unsigned long (and making drm_gem_get_pages() use the new types instead of replacing min()).
In the future, we might want to make more callers of these functions to consistently use "unsigned long".
Signature · 3 lines
Cheers, David
Re: [PATCH 1/9] mm/rmap: make nr_pages signed in try_to_unmap_one
Quoted 54 lines · David Hildenbrand (Arm)
Quoted 33 lines · Lorenzo Stoakes (Oracle)
Quoted 21 lines · Dev Jain
Currently,
nr_pagesis defined as unsigned long. We usenr_pagesto manipulate mm rss counters for lazyfree folios as follows:add_mm_counter(mm, MM_ANONPAGES, -nr_pages);Suppose
nr_pages= 3. -nr_pages underflows and becomesULONG_MAX- 2. Then, sinceadd_mm_counter()uses this -nr_pages as a long,ULONG_MAX- 2 does not fit into the positive range of long, and is converted to -3. Eventually all of this works out, but for keeping things simple, declarenr_pagesas a signed variable.Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-)mm/rmap.c +0
-0mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 1985
Quoted 8 lines
mm/rmap.c +2
-1@@ -1979,9 +1979,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,struct page *subpage;struct mmu_notifier_range range;enum ttu_flags flags = (enum ttu_flags )(long )arg; -unsigned long nr_pages =1 , end_addr; +unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; +long nr_pages =1 ;This is a non-issue that makes the code confusing, so let's not?
Quoted 2 lines
The convention throughout the kernel is
nr_pagesgenerally is unsigned because you can't have negativenr_pages.Indeed. Documented in:
commit
fa17bcd5f65ed702df001579cca8c885fa6bf3e7Author: Aristeu Rozanski <[email protected]> Date: Tue Aug 26 11:37:21 2025 -0400mm: make folio page count functions return unsigned
As raised by Andrew [1], a folio/compound page never spans a negative number of pages. Consequently, let's use "unsigned long" instead of "long" consistently for
folio_nr_pages(),folio_large_nr_pages()andcompound_nr().Using "unsigned long" as return value is fine, because even "(long)-folio_nr_pages()" will keep on working as expected. Using "unsigned int" instead would actually break these use cases.
Quoted 6 lines
This patch takes the first step changing these to return unsigned long (and making
drm_gem_get_pages()use the new types instead of replacingmin()).In the future, we might want to make more callers of these functions to consistently use "unsigned long".
So when I was playing around with the code, I noticed that passing unsigned int nr_pages to add_mm_counter(-nr_pages) messes up things. Then I noticed we have an unsigned long here, which prevents that. This is quite non-trivial information for me, especially when I searched around the codebase and found this is the only place where we pass a negative unsigned long.
But thanks for pointing out this commit. If it is a well-known fact that (long)-folio_nr_pages() will work correctly, then we can drop this patch.
Quoted 3 lines
Re: [PATCH 1/9] mm/rmap: make nr_pages signed in try_to_unmap_one
Quoted 2 lines · Dev Jain
So when I was playing around with the code, I noticed that passing unsigned int
nr_pagestoadd_mm_counter(-nr_pages)messes up things. Then
Using int (whether signed or unsigned) to store nr_pages is a bad idea. Look how many people are asking about supporting PUD-sized folios. On an ARM 64k PAGE_SIZE machine, that's 2^26 pages which is uncomfortably close to 2^32. It'll only take one more level to exceed that, so, what, five to ten more years?
Just use unsigned long everywhere now and save ourselves the grief later.
Re: [PATCH 1/9] mm/rmap: make nr_pages signed in try_to_unmap_one
Quoted 11 lines · Matthew Wilcox
Quoted 2 lines · Dev Jain
So when I was playing around with the code, I noticed that passing unsigned int
nr_pagestoadd_mm_counter(-nr_pages)messes up things. ThenUsing int (whether signed or unsigned) to store
nr_pagesis a bad idea. Look how many people are asking about supporting PUD-sized folios. On an ARM 64kPAGE_SIZEmachine, that's 2^26 pages which is uncomfortably close to 2^32. It'll only take one more level to exceed that, so, what, five to ten more years?Just use unsigned long everywhere now and save ourselves the grief later.
Thanks, let us do this.
PATCH 2/9 mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one
In reply to Dev JainInitialize nr_pages to 1 at the start of the loop, similar to what is being done in folio_referenced_one(). It may happen that the nr_pages computed from a previous call to folio_unmap_pte_batch gets reused without again going through folio_unmap_pte_batch, messing up things. Although, I don't think there is any bug right now; a bug would have been there, if in the same instance of a call to try_to_unmap_one, we end up in the pte_present(pteval) branch, then in the else branch doing pte_clear() for device-exclusive ptes. This means that a lazyfree folio has some present entries and some device entries mapping it. Since a pte being device-exclusive means that a GUP reference on the underlying folio is held, the lazyfree unmapping path upon witnessing this will abort try_to_unmap_one.
--- mm/rmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)
mm/rmap.c +3 -1
@@ -1982,7 +1982,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; -long nr_pages =1 ; +long nr_pages;int ptes =0 ;/*
@@ -2019,6 +2019,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,mmu_notifier_invalidate_range_start (&range);while (page_vma_mapped_walk (&pvmw)) { + nr_pages =1 ; +/* * If the folio is in an mlock()d vma, we must not swap it out. */
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 2/9] mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one
Quoted 4 lines · Dev Jain
Initialize
nr_pagesto 1 at the start of the loop, similar to what is being done infolio_referenced_one(). It may happen that thenr_pagescomputed from a previous call tofolio_unmap_pte_batchgets reused without again going throughfolio_unmap_pte_batch, messing up things. Although, I don't
Quoted 8 lines
think there is any bug right now; a bug would have been there, if in the same instance of a call to
try_to_unmap_one, we end up in thepte_present(pteval)branch, then in the else branch doingpte_clear()for device-exclusive ptes. This means that a lazyfree folio has some present entries and some device entries mapping it. Since a pte being device-exclusive means that a GUP reference on the underlying folio is held, the lazyfree unmapping path upon witnessing this will aborttry_to_unmap_one.
Dude, paragraphs. PARAGRAPHS :) this is one dense set of words.
It's also a very compressed 'stream of consciousness' hard-to-read block here.
I'm not sure it's really worth having this as a separate commit either, it's pretty trivial.
Quoted 20 lines
Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)mm/rmap.c +1
-1@@ -1982,7 +1982,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; -long nr_pages =1 ; +long nr_pages;int ptes =0 ;/*
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2023
Quoted 5 lines
mm/rmap.c +2
-0@@ -2019,6 +2019,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,mmu_notifier_invalidate_range_start (&range);while (page_vma_mapped_walk (&pvmw)) { + nr_pages =1 ; +
This seems valid but I really hate how we default-set it then in some branch set it to something else.
But I think fixing that would be part of a bigger cleanup...
Quoted 6 lines
mm/rmap.c +0
-1/* * If the folio is in an mlock()d vma, we must not swap it out. */ --2.34.1
Re: [PATCH 2/9] mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one
Quoted 14 lines · Lorenzo Stoakes (Oracle)
Quoted 4 lines · Dev Jain
Initialize
nr_pagesto 1 at the start of the loop, similar to what is being done infolio_referenced_one(). It may happen that thenr_pagescomputed from a previous call tofolio_unmap_pte_batchgets reused without again going throughfolio_unmap_pte_batch, messing up things. Although, I don'tQuoted 8 lines
think there is any bug right now; a bug would have been there, if in the same instance of a call to
try_to_unmap_one, we end up in thepte_present(pteval)branch, then in the else branch doingpte_clear()for device-exclusive ptes. This means that a lazyfree folio has some present entries and some device entries mapping it. Since a pte being device-exclusive means that a GUP reference on the underlying folio is held, the lazyfree unmapping path upon witnessing this will aborttry_to_unmap_one.
Quoted 3 lines
Dude, paragraphs. PARAGRAPHS :) this is one dense set of words.
It's also a very compressed 'stream of consciousness' hard-to-read block here.
Sure :) I'll try to break this down.
Quoted 3 lines
I'm not sure it's really worth having this as a separate commit either, it's pretty trivial.
Hmm...well, as I explain above, it's not trivial for me :) it is difficult for me to reason here whether nr_pages can be reused without a reset in a future iteration.
Quoted 38 lines
Quoted 21 lines
Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-)mm/rmap.c +1
-1@@ -1982,7 +1982,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,unsigned long end_addr;unsigned long pfn;unsigned long hsz =0 ; -long nr_pages =1 ; +long nr_pages;int ptes =0 ;/* mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2023
Quoted 5 lines
mm/rmap.c +2
-0@@ -2019,6 +2019,8 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,mmu_notifier_invalidate_range_start (&range);while (page_vma_mapped_walk (&pvmw)) { + nr_pages =1 ; +This seems valid but I really hate how we default-set it then in some branch set it to something else.
But I think fixing that would be part of a bigger cleanup...
Quoted 6 lines
mm/rmap.c +0
-1/* * If the folio is in an mlock()d vma, we must not swap it out. */ --2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 2/9] mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one
Quoted 24 lines · Dev Jain
Quoted 14 lines · Lorenzo Stoakes (Oracle)
Quoted 4 lines · Dev Jain
Initialize
nr_pagesto 1 at the start of the loop, similar to what is being done infolio_referenced_one(). It may happen that thenr_pagescomputed from a previous call tofolio_unmap_pte_batchgets reused without again going throughfolio_unmap_pte_batch, messing up things. Although, I don'tQuoted 8 lines
think there is any bug right now; a bug would have been there, if in the same instance of a call to
try_to_unmap_one, we end up in thepte_present(pteval)branch, then in the else branch doingpte_clear()for device-exclusive ptes. This means that a lazyfree folio has some present entries and some device entries mapping it. Since a pte being device-exclusive means that a GUP reference on the underlying folio is held, the lazyfree unmapping path upon witnessing this will aborttry_to_unmap_one.Quoted 3 lines
Dude, paragraphs. PARAGRAPHS :) this is one dense set of words.
It's also a very compressed 'stream of consciousness' hard-to-read block here.
Sure :) I'll try to break this down.
Quoted 1 line
Quoted 6 lines
Quoted 2 lines
I'm not sure it's really worth having this as a separate commit either, it's pretty trivial.
Hmm...well, as I explain above, it's not trivial for me :) it is difficult for me to reason here whether
nr_pagescan be reused without a reset in a future iteration.
OK you can have it be separate, let's just really clean up the commit message then please :)
Thanks, Lorenzo
Re: [PATCH 2/9] mm/rmap: initialize nr_pages to 1 at loop start in try_to_unmap_one
Quoted 28 lines · Lorenzo Stoakes (Oracle)
Quoted 27 lines · Dev Jain
Quoted 14 lines · Lorenzo Stoakes (Oracle)
Quoted 4 lines · Dev Jain
Initialize
nr_pagesto 1 at the start of the loop, similar to what is being done infolio_referenced_one(). It may happen that thenr_pagescomputed from a previous call tofolio_unmap_pte_batchgets reused without again going throughfolio_unmap_pte_batch, messing up things. Although, I don'tQuoted 8 lines
think there is any bug right now; a bug would have been there, if in the same instance of a call to
try_to_unmap_one, we end up in thepte_present(pteval)branch, then in the else branch doingpte_clear()for device-exclusive ptes. This means that a lazyfree folio has some present entries and some device entries mapping it. Since a pte being device-exclusive means that a GUP reference on the underlying folio is held, the lazyfree unmapping path upon witnessing this will aborttry_to_unmap_one.Quoted 3 lines
Dude, paragraphs. PARAGRAPHS :) this is one dense set of words.
It's also a very compressed 'stream of consciousness' hard-to-read block here.
Sure :) I'll try to break this down.
Quoted 3 lines
I'm not sure it's really worth having this as a separate commit either, it's pretty trivial.
Quoted 6 lines
Quoted 3 lines
Hmm...well, as I explain above, it's not trivial for me :) it is difficult for me to reason here whether
nr_pagescan be reused without a reset in a future iteration.OK you can have it be separate, let's just really clean up the commit message then please :)
Okay, thanks.
Quoted 2 lines
Thanks, Lorenzo
PATCH 3/9 mm/rmap: refactor lazyfree unmap commit path to commit_ttu_lazyfree_folio()
In reply to Dev JainClean up the code by refactoring the post-pte-clearing path of lazyfree folio unmapping, into commit_ttu_lazyfree_folio().
No functional change is intended.
--- mm/rmap.c | 93 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 39 deletions(-)
mm/rmap.c +54 -39
@@ -1966,6 +1966,57 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); } +static inline int commit_ttu_lazyfree_folio (struct vm_area_struct *vma, +struct folio *folio,unsigned long address,pte_t *ptep, +pte_t pteval,long nr_pages) +{ +struct mm_struct *mm = vma->vm_mm; +int ref_count, map_count; + +/* +* Synchronize with gup_pte_range(): +* - clear PTE; barrier; read refcount +* - inc refcount; barrier; read PTE +*/ +smp_mb (); + + ref_count =folio_ref_count (folio); + map_count =folio_mapcount (folio); + +/* +* Order reads for page refcount and dirty flag +* (see comments in __remove_mapping()). +*/ +smp_rmb (); + +if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { +/* +* redirtied either using the page table or a previously +* obtained GUP reference. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +folio_set_swapbacked (folio); +return 1 ; + } + +if (ref_count !=1 + map_count) { +/* +* Additional reference. Could be a GUP reference or any +* speculative reference. GUP users must mark the folio +* dirty if there was a modification. This folio cannot be +* reclaimed right now either way, so act just like nothing +* happened. +* We'll come back here later and detect if the folio was +* dirtied when the additional reference is gone. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +return 1 ; + } + +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +return 0 ; +} +/* * @arg: enum ttu_flags will be passed to this argument */
@@ -2227,46 +2278,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* MADV_FREE page check */ if (!folio_test_swapbacked (folio)) { -int ref_count, map_count; - -/* -* Synchronize with gup_pte_range(): -* - clear PTE; barrier; read refcount -* - inc refcount; barrier; read PTE -*/ -smp_mb (); - - ref_count =folio_ref_count (folio); - map_count =folio_mapcount (folio); - -/* -* Order reads for page refcount and dirty flag -* (see comments in __remove_mapping()). -*/ -smp_rmb (); - -if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { -/* -* redirtied either using the page table or a previously -* obtained GUP reference. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); -folio_set_swapbacked (folio); +if (commit_ttu_lazyfree_folio (vma, folio, address, + pvmw.pte, pteval, + nr_pages)) goto walk_abort; - }else if (ref_count !=1 + map_count) { -/* -* Additional reference. Could be a GUP reference or any -* speculative reference. GUP users must mark the folio -* dirty if there was a modification. This folio cannot be -* reclaimed right now either way, so act just like nothing -* happened. -* We'll come back here later and detect if the folio was -* dirtied when the additional reference is gone. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); - goto walk_abort; - } -add_mm_counter (mm,MM_ANONPAGES , -nr_pages); goto discard; }
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 3/9] mm/rmap: refactor lazyfree unmap commit path to commit_ttu_lazyfree_folio()
Quoted 6 lines · Dev Jain
Clean up the code by refactoring the post-pte-clearing path of lazyfree folio unmapping, into
commit_ttu_lazyfree_folio().No functional change is intended.
Signed-off-by Dev Jain <[email protected]>
This is a good idea, and we need more refactoring like this in the rmap code, but comments/nits below.
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1969
Quoted 13 lines
--- mm/rmap.c | 93 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 39 deletions(-)mm/rmap.c +1
-0@@ -1966,6 +1966,57 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); } +static inline int commit_ttu_lazyfree_folio(struct vm_area_struct *vma,
Strange name, maybe lazyfree_range()? Not sure what ttu has to do with anything...
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1971
Quoted 2 lines
mm/rmap.c +2
-0+ struct folio *folio, unsigned long address, pte_t *ptep, + pte_t pteval, long nr_pages)
That long nr_pages is really grating now...
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1972
Quoted 1 line
mm/rmap.c +1
-0+{
Come on Dev, it's 2026, why on earth are you returning an integer and not a bool?
Also it would make sense for this to return false if something breaks, otherwise true.
Quoted 72 lines
mm/rmap.c +47
-18+struct mm_struct *mm = vma->vm_mm; +int ref_count, map_count; + +/* +* Synchronize with gup_pte_range(): +* - clear PTE; barrier; read refcount +* - inc refcount; barrier; read PTE +*/ +smp_mb (); + + ref_count =folio_ref_count (folio); + map_count =folio_mapcount (folio); + +/* +* Order reads for page refcount and dirty flag +* (see comments in __remove_mapping()). +*/ +smp_rmb (); + +if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { +/* +* redirtied either using the page table or a previously +* obtained GUP reference. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +folio_set_swapbacked (folio); +return 1 ; + } + +if (ref_count !=1 + map_count) { +/* +* Additional reference. Could be a GUP reference or any +* speculative reference. GUP users must mark the folio +* dirty if there was a modification. This folio cannot be +* reclaimed right now either way, so act just like nothing +* happened. +* We'll come back here later and detect if the folio was +* dirtied when the additional reference is gone. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +return 1 ; + } + +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +return 0 ; +} +/* * @arg: enum ttu_flags will be passed to this argument */ @@ -2227,46 +2278,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* MADV_FREE page check */ if (!folio_test_swapbacked (folio)) { -int ref_count, map_count; - -/* -* Synchronize with gup_pte_range(): -* - clear PTE; barrier; read refcount -* - inc refcount; barrier; read PTE -*/ -smp_mb (); - - ref_count =folio_ref_count (folio); - map_count =folio_mapcount (folio); - -/* -* Order reads for page refcount and dirty flag -* (see comments in __remove_mapping()). -*/ -smp_rmb (); -
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2283
Quoted 10 lines
mm/rmap.c +3
-7-if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { -/* -* redirtied either using the page table or a previously -* obtained GUP reference. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); -folio_set_swapbacked (folio); +if (commit_ttu_lazyfree_folio (vma, folio, address, + pvmw.pte, pteval, + nr_pages))
With above corrections this would be:
if (!lazyfree_range(vma, folio, address, pvme.pte, pteval, nr_pages)) ...
Quoted 19 lines
mm/rmap.c +0
-15goto walk_abort; - }else if (ref_count !=1 + map_count) { -/* -* Additional reference. Could be a GUP reference or any -* speculative reference. GUP users must mark the folio -* dirty if there was a modification. This folio cannot be -* reclaimed right now either way, so act just like nothing -* happened. -* We'll come back here later and detect if the folio was -* dirtied when the additional reference is gone. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); - goto walk_abort; - } -add_mm_counter (mm,MM_ANONPAGES , -nr_pages); goto discard; } --
Quoted 2 lines
2.34.1
Thanks, Lorenzo
Re: [PATCH 3/9] mm/rmap: refactor lazyfree unmap commit path to commit_ttu_lazyfree_folio()
Quoted 23 lines · Lorenzo Stoakes (Oracle)
Quoted 6 lines · Dev Jain
Clean up the code by refactoring the post-pte-clearing path of lazyfree folio unmapping, into
commit_ttu_lazyfree_folio().No functional change is intended.
Signed-off-by Dev Jain <[email protected]>This is a good idea, and we need more refactoring like this in the rmap code, but comments/nits below.
Quoted 12 lines
--- mm/rmap.c | 93 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 39 deletions(-)mm/rmap.c +0
-0@@ -1966,6 +1966,57 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); }
Quoted 3 lines
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1969
Quoted 1 line
mm/rmap.c +1
-0+static inline int commit_ttu_lazyfree_folio(struct vm_area_struct *vma,Strange name, maybe
lazyfree_range()? Not sure what ttu has to do with
ttu means try_to_unmap, just like it is used in TTU_SYNC, TTU_SPLIT_HUGE_PMD, etc. So personally I really like the name, it reads "commit the try-to-unmap of a lazyfree folio". The "commit" comes because the pte clearing has already happened, so now we are deciding if at all to back-off and restore the ptes.
Quoted 6 lines
anything...
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1971
Quoted 2 lines
mm/rmap.c +2
-0+ struct folio *folio, unsigned long address, pte_t *ptep, + pte_t pteval, long nr_pages)That long
nr_pagesis really grating now...
Reading the discussion on patch 1, I'll convert this to unsigned long.
Quoted 8 lines
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1972
Quoted 2 lines
mm/rmap.c +1
-0+{Come on Dev, it's 2026, why on earth are you returning an integer and not a bool?
Also it would make sense for this to return false if something breaks, otherwise true.
Yes I was confused on which one of the options to choose :). Since the function does a lot more than just test some functionality (which is what boolean functions usually do) I was feeling weird when returning bool. But yeah alright, I'll convert this to bool.
Quoted 113 lines
Quoted 73 lines
mm/rmap.c +47
-18+struct mm_struct *mm = vma->vm_mm; +int ref_count, map_count; + +/* +* Synchronize with gup_pte_range(): +* - clear PTE; barrier; read refcount +* - inc refcount; barrier; read PTE +*/ +smp_mb (); + + ref_count =folio_ref_count (folio); + map_count =folio_mapcount (folio); + +/* +* Order reads for page refcount and dirty flag +* (see comments in __remove_mapping()). +*/ +smp_rmb (); + +if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { +/* +* redirtied either using the page table or a previously +* obtained GUP reference. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +folio_set_swapbacked (folio); +return 1 ; + } + +if (ref_count !=1 + map_count) { +/* +* Additional reference. Could be a GUP reference or any +* speculative reference. GUP users must mark the folio +* dirty if there was a modification. This folio cannot be +* reclaimed right now either way, so act just like nothing +* happened. +* We'll come back here later and detect if the folio was +* dirtied when the additional reference is gone. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +return 1 ; + } + +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +return 0 ; +} +/* * @arg: enum ttu_flags will be passed to this argument */ @@ -2227,46 +2278,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* MADV_FREE page check */ if (!folio_test_swapbacked (folio)) { -int ref_count, map_count; - -/* -* Synchronize with gup_pte_range(): -* - clear PTE; barrier; read refcount -* - inc refcount; barrier; read PTE -*/ -smp_mb (); - - ref_count =folio_ref_count (folio); - map_count =folio_mapcount (folio); - -/* -* Order reads for page refcount and dirty flag -* (see comments in __remove_mapping()). -*/ -smp_rmb (); -mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2283
Quoted 10 lines
mm/rmap.c +3
-7-if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { -/* -* redirtied either using the page table or a previously -* obtained GUP reference. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); -folio_set_swapbacked (folio); +if (commit_ttu_lazyfree_folio (vma, folio, address, + pvmw.pte, pteval, + nr_pages))With above corrections this would be:
if (!lazyfree_range(vma, folio, address, pvme.pte, pteval, nr_pages)) ...Quoted 19 lines
mm/rmap.c +0
-15goto walk_abort; - }else if (ref_count !=1 + map_count) { -/* -* Additional reference. Could be a GUP reference or any -* speculative reference. GUP users must mark the folio -* dirty if there was a modification. This folio cannot be -* reclaimed right now either way, so act just like nothing -* happened. -* We'll come back here later and detect if the folio was -* dirtied when the additional reference is gone. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); - goto walk_abort; - } -add_mm_counter (mm,MM_ANONPAGES , -nr_pages); goto discard; } --Quoted 2 lines
2.34.1
Thanks, Lorenzo
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 3/9] mm/rmap: refactor lazyfree unmap commit path to commit_ttu_lazyfree_folio()
Quoted 30 lines · Dev Jain
Quoted 23 lines · Lorenzo Stoakes (Oracle)
Quoted 6 lines · Dev Jain
Clean up the code by refactoring the post-pte-clearing path of lazyfree folio unmapping, into
commit_ttu_lazyfree_folio().No functional change is intended.
Signed-off-by Dev Jain <[email protected]>This is a good idea, and we need more refactoring like this in the rmap code, but comments/nits below.
Quoted 12 lines
--- mm/rmap.c | 93 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 54 insertions(+), 39 deletions(-)mm/rmap.c +0
-0@@ -1966,6 +1966,57 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio, FPB_RESPECT_WRITE | FPB_RESPECT_SOFT_DIRTY); }Quoted 3 lines
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1969
Quoted 1 line
mm/rmap.c +1
-0+static inline int commit_ttu_lazyfree_folio(struct vm_area_struct *vma,Strange name, maybe
lazyfree_range()? Not sure what ttu has to do with
Quoted 5 lines
ttu means
try_to_unmap, just like it is used inTTU_SYNC,TTU_SPLIT_HUGE_PMD, etc. So personally I really like the name, it reads "commit the try-to-unmap of a lazyfree folio". The "commit" comes because the pte clearing has already happened, so now we are deciding if at all to back-off and restore the ptes.
I absolutely hate the name, and nobody sane is going to read it like that sorry :) I think this is a case of being too close to the work.
I also hate the overloading of 'lazyfree' which is actually MADV_FREE, users don't know what lazyfree is and it's a bit of an overloaded term (we've had discussions of a new 'lazy free' implementation at conferences before).
Commit is overloaded everywhere I'm not sure it's even particularly pertinent here.
Also it's not necessarily a folio is it? It could be a contpte range within a folio so that's just misleading...
lazyfree_pte_range() or something like that seems better to me.
Quoted 9 lines
Quoted 7 lines
anything...
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1971
Quoted 2 lines
mm/rmap.c +2
-0+ struct folio *folio, unsigned long address, pte_t *ptep, + pte_t pteval, long nr_pages)That long
nr_pagesis really grating now...Reading the discussion on patch 1, I'll convert this to unsigned long.
Thanks!
Quoted 7 lines
Quoted 7 lines
mm/rmap.c static inline unsigned int folio_unmap_pte_batch(struct folio *folio, · line 1972
Quoted 3 lines
mm/rmap.c +1
-0+{Come on Dev, it's 2026, why on earth are you returning an integer and not a bool?
Quoted 7 lines
Quoted 2 lines
Also it would make sense for this to return false if something breaks, otherwise true.
Yes I was confused on which one of the options to choose :). Since the function does a lot more than just test some functionality (which is what boolean functions usually do) I was feeling weird when returning bool. But yeah alright, I'll convert this to bool.
Thanks!
Cheers, Lorenzo
Quoted 115 lines
Quoted 114 lines
Quoted 74 lines
mm/rmap.c +47
-18+struct mm_struct *mm = vma->vm_mm; +int ref_count, map_count; + +/* +* Synchronize with gup_pte_range(): +* - clear PTE; barrier; read refcount +* - inc refcount; barrier; read PTE +*/ +smp_mb (); + + ref_count =folio_ref_count (folio); + map_count =folio_mapcount (folio); + +/* +* Order reads for page refcount and dirty flag +* (see comments in __remove_mapping()). +*/ +smp_rmb (); + +if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { +/* +* redirtied either using the page table or a previously +* obtained GUP reference. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +folio_set_swapbacked (folio); +return 1 ; + } + +if (ref_count !=1 + map_count) { +/* +* Additional reference. Could be a GUP reference or any +* speculative reference. GUP users must mark the folio +* dirty if there was a modification. This folio cannot be +* reclaimed right now either way, so act just like nothing +* happened. +* We'll come back here later and detect if the folio was +* dirtied when the additional reference is gone. +*/ +set_ptes (mm, address, ptep, pteval, nr_pages); +return 1 ; + } + +add_mm_counter (mm,MM_ANONPAGES , -nr_pages); +return 0 ; +} +/* * @arg: enum ttu_flags will be passed to this argument */ @@ -2227,46 +2278,10 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* MADV_FREE page check */ if (!folio_test_swapbacked (folio)) { -int ref_count, map_count; - -/* -* Synchronize with gup_pte_range(): -* - clear PTE; barrier; read refcount -* - inc refcount; barrier; read PTE -*/ -smp_mb (); - - ref_count =folio_ref_count (folio); - map_count =folio_mapcount (folio); - -/* -* Order reads for page refcount and dirty flag -* (see comments in __remove_mapping()). -*/ -smp_rmb (); -mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2283
Quoted 10 lines
mm/rmap.c +3
-7-if (folio_test_dirty (folio) && !(vma->vm_flags &VM_DROPPABLE )) { -/* -* redirtied either using the page table or a previously -* obtained GUP reference. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); -folio_set_swapbacked (folio); +if (commit_ttu_lazyfree_folio (vma, folio, address, + pvmw.pte, pteval, + nr_pages))With above corrections this would be:
if (!lazyfree_range(vma, folio, address, pvme.pte, pteval, nr_pages)) ...Quoted 19 lines
mm/rmap.c +0
-15goto walk_abort; - }else if (ref_count !=1 + map_count) { -/* -* Additional reference. Could be a GUP reference or any -* speculative reference. GUP users must mark the folio -* dirty if there was a modification. This folio cannot be -* reclaimed right now either way, so act just like nothing -* happened. -* We'll come back here later and detect if the folio was -* dirtied when the additional reference is gone. -*/ -set_ptes (mm, address, pvmw.pte, pteval, nr_pages); - goto walk_abort; - } -add_mm_counter (mm,MM_ANONPAGES , -nr_pages); goto discard; } --Quoted 2 lines
2.34.1
Thanks, Lorenzo
PATCH 4/9 mm/memory: Batch set uffd-wp markers during zapping
In reply to Dev JainIn preparation for the next patch, enable batch setting of uffd-wp ptes.
The code paths passing nr > 1 to zap_install_uffd_wp_if_needed() produce that nr through either folio_pte_batch or swap_pte_batch, guaranteeing that all ptes are the same w.r.t belonging to the same type of VMA (anonymous or non-anonymous, wp-armed or non-wp-armed), and all being marked with uffd-wp or all being not marked.
Note that we will have to use set_pte_at() in a loop instead of set_ptes() since the latter cannot handle present->non-present conversion for nr_pages > 1.
Convert documentation of install_uffd_wp_ptes_if_needed to kerneldoc format.
No functional change is intended.
--- include/linux/mm_inline.h | 37 +++++++++++++++++++++++-------------- mm/memory.c | 20 +------------------- mm/rmap.c | 2 +- 3 files changed, 25 insertions(+), 34 deletions(-)
include/linux/mm_inline.h +23 -14
@@ -560,21 +560,30 @@ static inline pte_marker copy_pte_marker(return dstm; } -/* -* If this pte is wr-protected by uffd-wp in any form, arm the special pte to -* replace a none pte. NOTE! This should only be called when *pte is already +/** +* install_uffd_wp_ptes_if_needed - install uffd-wp marker on PTEs that map +* consecutive pages of the same large folio. +* @vma: The VMA the pages are mapped into. +* @addr: Address the first page of this batch is mapped at. +* @ptep: Page table pointer for the first entry of this batch. +* @pteval: old value of the entry pointed to by ptep. +* @nr: Number of entries to clear (batch size). +* +* If the ptes were wr-protected by uffd-wp in any form, arm special ptes to +* replace none ptes. NOTE! This should only be called when *pte is already * cleared so we will never accidentally replace something valuable. Meanwhile * none pte also means we are not demoting the pte so tlb flushed is not needed. * E.g., when pte cleared the caller should have taken care of the tlb flush. * -* Must be called with pgtable lock held so that no thread will see the none -* pte, and if they see it, they'll fault and serialize at the pgtable lock. +* Context: The caller holds the page table lock. The PTEs map consecutive +* pages that belong to the same folio. The PTEs are all in the same PMD +* and the same VMA. * -* Returns true if an uffd-wp pte was installed, false otherwise. +* Returns true if uffd-wp ptes were installed, false otherwise. */ static inline bool -pte_install_uffd_wp_if_needed (struct vm_area_struct *vma,unsigned long addr, -pte_t *pte,pte_t pteval) +install_uffd_wp_ptes_if_needed (struct vm_area_struct *vma,unsigned long addr, +pte_t *pte,pte_t pteval,unsigned int nr) {bool arm_uffd_pte = false;
@@ -604,13 +613,13 @@ pte_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr,if (unlikely (pte_swp_uffd_wp_any (pteval))) arm_uffd_pte = true; -if (unlikely (arm_uffd_pte)) { -set_pte_at (vma->vm_mm, addr, pte, -make_pte_marker (PTE_MARKER_UFFD_WP )); -return true; - } +if (likely (!arm_uffd_pte)) +return false; -return false; +for (int i =0 ; i < nr; ++i, ++pte, addr +=PAGE_SIZE ) +set_pte_at (vma->vm_mm, addr, pte,make_pte_marker (PTE_MARKER_UFFD_WP )); + +return true; }static inline bool vma_has_recency (const struct vm_area_struct *vma)
mm/memory.c +1 -19
@@ -1594,29 +1594,11 @@ zap_install_uffd_wp_if_needed(struct vm_area_struct *vma, unsigned long addr, pte_t *pte, int nr, struct zap_details *details, pte_t pteval) { - bool was_installed = false; - -if (!uffd_supports_wp_marker ()) -return false; - -/* Zap on anonymous always means dropping everything */ -if (vma_is_anonymous (vma)) -return false; - if (zap_drop_markers(details)) return false; -for (;;) { -/* the PFN in the PTE is irrelevant. */ -if (pte_install_uffd_wp_if_needed (vma, addr, pte, pteval)) - was_installed = true; -if (--nr ==0 ) -break ; - pte++; - addr +=PAGE_SIZE ; - } +return install_uffd_wp_ptes_if_needed (vma, addr, pte, pteval, nr); -return was_installed; } static __always_inline void zap_present_folio_ptes(struct mmu_gather *tlb,
mm/rmap.c +1 -1
@@ -2235,7 +2235,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ -pte_install_uffd_wp_if_needed (vma, address, pvmw.pte, pteval); +install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval,1 );/* Update high watermark before we lower rss */ update_hiwater_rss (mm);
Signature · 1 line
2.34.1
PATCH 5/9 mm/rmap: batch unmap folios belonging to uffd-wp VMAs
In reply to Dev JainThe ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition from folio_unmap_pte_batch.
It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that - page_vma_mapped_walk_jump() will increment the relevant fields of pvmw by nr pages.
I think that we can get away with just incrementing pvmw->pte and pvmw->address, since looking at the code in page_vma_mapped.c, pvmw->pfn and pvmw->nr_pages are used in conjunction, and pvmw->pgoff and pvmw->nr_pages (in vma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.
Export this function to rmap.h to enable future reuse.
--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)
include/linux/rmap.h +10 -0
@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr) +{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +} +/** * page_vma_mapped_walk_restart - Restart the page table walk. * @pvmw: Pointer to struct page_vma_mapped_walk.
mm/rmap.c +3 -5
@@ -1953,9 +1953,6 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio,if (pte_unused (pte))return 1 ; -if (userfaultfd_wp (vma)) -return 1 ; -/* * If unmap fails, we need to restore the ptes. To avoid accidentally * upgrading write permissions for ptes that were not originally
@@ -2235,7 +2232,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ -install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval,1 ); +install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval, nr_pages);/* Update high watermark before we lower rss */ update_hiwater_rss (mm);
@@ -2359,8 +2356,9 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* If we are sure that we batched the entire folio and cleared * all PTEs, we can just optimize and stop right here. */ -if (nr_pages ==folio_nr_pages (folio)) +if (likely (nr_pages ==folio_nr_pages (folio))) goto walk_done; +page_vma_mapped_walk_jump (&pvmw, nr_pages -1 );continue ; walk_abort: ret = false;
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 5/9] mm/rmap: batch unmap folios belonging to uffd-wp VMAs
Quoted 11 lines · Dev Jain
The ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through
install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition fromfolio_unmap_pte_batch.It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that -
page_vma_mapped_walk_jump()will increment the relevant fields of pvmw by nr pages.
Quoted 6 lines
I think that we can get away with just incrementing
pvmw->pteandpvmw->address, since looking at the code inpage_vma_mapped.c,pvmw->pfnandpvmw->nr_pagesare used in conjunction, andpvmw->pgoffandpvmw->nr_pages(invma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.
This isn't simple...
Quoted 14 lines
Export this function to
rmap.hto enable future reuse.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)include/linux/rmap.h +0
-0
include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 896
Quoted 5 lines
include/linux/rmap.h +2
-0@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr)
unsigned long nr_pages... 'nr' is meaningless and you're mixing + matching types for no reason.
include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 903
Quoted 7 lines
include/linux/rmap.h +7
-0+{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +}
I absolutely hate this. It's extremely confusing, especially since you're now going from looking at 1 page to nr_pages - 1, jump doesn't really mean anything here, you're losing sight of the batch size and exposing a silly detail to the caller, and I really don't want to 'export' this at this time.
If we must have this, can you please make it static in rmap.c at least for the time being.
Or perhaps instead, have a batched variant of page_vma_mapped_walk(), like page_vma_mapped_walk_batch()?
I think that makes a lot more sense...
I mean I kind of hate the pvmw interface in general, this is a hack to handle batching clamped on to the side of it, let's figure out how to do this sensibly and do what's needed rather than adding yet more hacks-on-hacks please.
Quoted 28 lines
include/linux/rmap.h +1
-0+/** * page_vma_mapped_walk_restart - Restart the page table walk. * @pvmw: Pointer to struct page_vma_mapped_walk. mm/rmap.c +1
-4@@ -1953,9 +1953,6 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio,if (pte_unused (pte))return 1 ; -if (userfaultfd_wp (vma)) -return 1 ; -/* * If unmap fails, we need to restore the ptes. To avoid accidentally * upgrading write permissions for ptes that were not originally @@ -2235,7 +2232,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ -install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval,1 ); +install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval, nr_pages);/* Update high watermark before we lower rss */ update_hiwater_rss (mm);
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2359
Quoted 5 lines
mm/rmap.c +1
-1@@ -2359,8 +2356,9 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* If we are sure that we batched the entire folio and cleared * all PTEs, we can just optimize and stop right here. */ -if (nr_pages ==folio_nr_pages (folio)) +if (likely (nr_pages ==folio_nr_pages (folio)))
Please don't add random likely()'s based on what you think is likely(). This kind of thing should only be done based on profiling.
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2361
Quoted 2 lines
mm/rmap.c +1
-0goto walk_done; +page_vma_mapped_walk_jump (&pvmw, nr_pages -1 );
(You're now passing a signed long to an unsigned int...!)
Quoted 6 lines
mm/rmap.c +0
-1continue ; walk_abort: ret = false; --2.34.1
Thanks, Lorenzo
Re: [PATCH 5/9] mm/rmap: batch unmap folios belonging to uffd-wp VMAs
Quoted 45 lines · Lorenzo Stoakes (Oracle)
Quoted 11 lines · Dev Jain
The ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through
install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition fromfolio_unmap_pte_batch.It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that -
page_vma_mapped_walk_jump()will increment the relevant fields of pvmw by nr pages.Quoted 6 lines
I think that we can get away with just incrementing
pvmw->pteandpvmw->address, since looking at the code inpage_vma_mapped.c,pvmw->pfnandpvmw->nr_pagesare used in conjunction, andpvmw->pgoffandpvmw->nr_pages(invma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.This isn't simple...
Quoted 14 lines
Export this function to
rmap.hto enable future reuse.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)include/linux/rmap.h +0
-0include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 896
Quoted 5 lines
include/linux/rmap.h +2
-0@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr)unsigned long
nr_pages... 'nr' is meaningless and you're mixing + matching types for no reason.
Quoted 12 lines
include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 903
Quoted 7 lines
include/linux/rmap.h +7
-0+{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +}I absolutely hate this. It's extremely confusing, especially since you're now going from looking at 1 page to
nr_pages- 1, jump doesn't really mean anything here, you're losing sight of the batch size and exposing a silly detail to the caller, and I really don't want to 'export' this at this time.
I’m fairly sure I raised the same concern when Dev first suggested this, but somehow it seems my comment was completely overlooked. :-)
Quoted 6 lines
If we must have this, can you please make it static in
rmap.cat least for the time being.Or perhaps instead, have a batched variant of
page_vma_mapped_walk(), likepage_vma_mapped_walk_batch()?
Right now, for non-anon pages we face the same issues, but page_vma_mapped_walk() can skip those PTEs once it finds that nr - 1 PTEs are none.
next_pte:
do {
pvmw->address += PAGE_SIZE;
if (pvmw->address >= end)
return not_found(pvmw);
/* Did we cross page table boundary? */
if ((pvmw->address & (PMD_SIZE - PAGE_SIZE)) == 0) {
if (pvmw->ptl) {
spin_unlock(pvmw->ptl);
pvmw->ptl = NULL;
}
pte_unmap(pvmw->pte);
pvmw->pte = NULL;
pvmw->flags |= PVMW_PGTABLE_CROSSED;
goto restart;
}
pvmw->pte++;
} while (pte_none(ptep_get(pvmw->pte)));The difference now is that swap entries cannot be skipped.
If we're trying to find page_vma_mapped_walk_batch(), I suppose it could be like this?
bool page_vma_mapped_walk_batch(struct page_vma_mapped_walk *pvmw, unsigned long nr)
{
...
}
static inline bool page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw)
{
return page_vma_mapped_walk_batch(pvmw, 1);
}Thanks
barry
Re: [PATCH 5/9] mm/rmap: batch unmap folios belonging to uffd-wp VMAs
Quoted 104 lines · Barry Song
Quoted 45 lines · Lorenzo Stoakes (Oracle)
Quoted 11 lines · Dev Jain
The ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through
install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition fromfolio_unmap_pte_batch.It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that -
page_vma_mapped_walk_jump()will increment the relevant fields of pvmw by nr pages.Quoted 6 lines
I think that we can get away with just incrementing
pvmw->pteandpvmw->address, since looking at the code inpage_vma_mapped.c,pvmw->pfnandpvmw->nr_pagesare used in conjunction, andpvmw->pgoffandpvmw->nr_pages(invma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.This isn't simple...
Quoted 14 lines
Export this function to
rmap.hto enable future reuse.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)include/linux/rmap.h +0
-0include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 896
Quoted 5 lines
include/linux/rmap.h +2
-0@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr)unsigned long
nr_pages... 'nr' is meaningless and you're mixing + matching types for no reason.Quoted 12 lines
include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 903
Quoted 7 lines
include/linux/rmap.h +7
-0+{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +}I absolutely hate this. It's extremely confusing, especially since you're now going from looking at 1 page to
nr_pages- 1, jump doesn't really mean anything here, you're losing sight of the batch size and exposing a silly detail to the caller, and I really don't want to 'export' this at this time.I’m fairly sure I raised the same concern when Dev first suggested this, but somehow it seems my comment was completely overlooked. :-)
Quoted 6 lines
If we must have this, can you please make it static in
rmap.cat least for the time being.Or perhaps instead, have a batched variant of
page_vma_mapped_walk(), likepage_vma_mapped_walk_batch()?Right now, for non-anon pages we face the same issues, but
page_vma_mapped_walk()can skip those PTEs once it finds that nr - 1 PTEs are none.
next_pte:do { pvmw->address += PAGE_SIZE; if (pvmw->address >= end) return not_found(pvmw); /* Did we cross page table boundary? */ if ((pvmw->address & (PMD_SIZE - PAGE_SIZE)) == 0) { if (pvmw->ptl) { spin_unlock(pvmw->ptl); pvmw->ptl = NULL; } pte_unmap(pvmw->pte); pvmw->pte = NULL; pvmw->flags |= PVMW_PGTABLE_CROSSED; goto restart; } pvmw->pte++; } while (pte_none(ptep_get(pvmw->pte)));The difference now is that swap entries cannot be skipped.
If we're trying to find
page_vma_mapped_walk_batch(), I suppose it could be like this?bool
page_vma_mapped_walk_batch(struct page_vma_mapped_walk *pvmw, unsigned long nr)
{
...
}
Quoted 4 lines
static inline bool
page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw){ return page_vma_mapped_walk_batch(pvmw, 1); }
Another approach might be to introduce a flag so that page_vma_mapped_walk() knows we are doing batched unmaps and can skip nr - 1 swap entries.
include/linux/rmap.h +0 -0
@@ -856,6 +856,9 @@ struct page *make_device_exclusive(struct
mm_struct *mm, unsigned long addr,
/* Look for migration entries rather than present PTEs */
#define PVMW_MIGRATION (1 << 1)
+/* Batched unmap: skip swap entries. */
+#define PVMW_BATCH_UNMAP (1 << 2)
+
/* Result flags */
/* The page is mapped across page table boundary */
Thanks
Barry
Re: [PATCH 5/9] mm/rmap: batch unmap folios belonging to uffd-wp VMAs
Quoted 61 lines · Barry Song
Quoted 60 lines · Barry Song
Quoted 57 lines · Lorenzo Stoakes (Oracle)
Quoted 11 lines · Dev Jain
The ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through
install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition fromfolio_unmap_pte_batch.It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that -
page_vma_mapped_walk_jump()will increment the relevant fields of pvmw by nr pages.Quoted 6 lines
I think that we can get away with just incrementing
pvmw->pteandpvmw->address, since looking at the code inpage_vma_mapped.c,pvmw->pfnandpvmw->nr_pagesare used in conjunction, andpvmw->pgoffandpvmw->nr_pages(invma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.This isn't simple...
Quoted 14 lines
Export this function to
rmap.hto enable future reuse.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)include/linux/rmap.h +0
-0include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 896
Quoted 5 lines
include/linux/rmap.h +2
-0@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr)unsigned long
nr_pages... 'nr' is meaningless and you're mixing + matching types for no reason.include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 903
Quoted 7 lines
include/linux/rmap.h +7
-0+{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +}I absolutely hate this. It's extremely confusing, especially since you're now going from looking at 1 page to
nr_pages- 1, jump doesn't really mean anything here, you're losing sight of the batch size and exposing a silly detail to the caller, and I really don't want to 'export' this at this time.
Quoted 2 lines
Quoted 2 lines
I’m fairly sure I raised the same concern when Dev first suggested this, but somehow it seems my comment was completely overlooked. :-)
I do recall, perhaps I was lazy to look at the pvmw code :) But I should have just looked at this earlier, it's fairly simple. See below.
Quoted 56 lines
Quoted 42 lines
Quoted 7 lines
If we must have this, can you please make it static in
rmap.cat least for the time being.Or perhaps instead, have a batched variant of
page_vma_mapped_walk(), likepage_vma_mapped_walk_batch()?Right now, for non-anon pages we face the same issues, but
page_vma_mapped_walk()can skip those PTEs once it finds that nr - 1 PTEs are none.
next_pte:do { pvmw->address += PAGE_SIZE; if (pvmw->address >= end) return not_found(pvmw); /* Did we cross page table boundary? */ if ((pvmw->address & (PMD_SIZE - PAGE_SIZE)) == 0) { if (pvmw->ptl) { spin_unlock(pvmw->ptl); pvmw->ptl = NULL; } pte_unmap(pvmw->pte); pvmw->pte = NULL; pvmw->flags |= PVMW_PGTABLE_CROSSED; goto restart; } pvmw->pte++; } while (pte_none(ptep_get(pvmw->pte)));The difference now is that swap entries cannot be skipped.
If we're trying to find
page_vma_mapped_walk_batch(), I suppose it could be like this?bool
page_vma_mapped_walk_batch(struct page_vma_mapped_walk *pvmw, unsigned long nr)
{
...
}Quoted 4 lines
static inline bool
page_vma_mapped_walk(struct page_vma_mapped_walk *pvmw){ return page_vma_mapped_walk_batch(pvmw, 1); }Another approach might be to introduce a flag so that
page_vma_mapped_walk()knows we are doing batched unmaps and can skip nr - 1 swap entries.include/linux/rmap.h +0
-0
include/linux/rmap.h struct page *make_device_exclusive(struct · line 861
Quoted 6 lines
include/linux/rmap.h +2
-0@@ -856,6 +856,9 @@ struct page *make_device_exclusive(struct mm_struct *mm, unsigned long addr,/* Look for migration entries rather than present PTEs */ #define PVMW_MIGRATION (1 << 1) +/* Batched unmap: skip swap entries. */ +#define PVMW_BATCH_UNMAP (1 << 2)
Exactly, I just also came up with the same solution and saw your reply :) We can just name this PVMW_BATCH_PRESENT, the comment saying "Look for present entries", and fix the comment above PVMW_MIGRATION to drop the "rather than present PTEs" because that is wrong, we are currently also looking for softleaf entries by default.
Quoted 8 lines
include/linux/rmap.h +1
-0+/* Result flags */ /* The page is mapped across page table boundary */
Thanks
Barry
Re: [PATCH 5/9] mm/rmap: batch unmap folios belonging to uffd-wp VMAs
In reply to Lorenzo Stoakes (Oracle)Quoted 94 lines · Lorenzo Stoakes (Oracle)
Quoted 11 lines · Dev Jain
The ptes are all the same w.r.t belonging to the same type of VMA, and being marked with uffd-wp or all being not marked. Therefore we can batch set uffd-wp markers through
install_uffd_wp_ptes_if_needed, and enable batched unmapping of folios belonging to uffd-wp VMAs by dropping that condition fromfolio_unmap_pte_batch.It may happen that we don't batch over the entire folio in one go, in which case, we must skip over the current batch. Add a helper to do that -
page_vma_mapped_walk_jump()will increment the relevant fields of pvmw by nr pages.Quoted 6 lines
I think that we can get away with just incrementing
pvmw->pteandpvmw->address, since looking at the code inpage_vma_mapped.c,pvmw->pfnandpvmw->nr_pagesare used in conjunction, andpvmw->pgoffandpvmw->nr_pages(invma_address_end()) are used in conjunction, cancelling out the increment and decrement in the respective fields. But let us not rely on the pvmw implementation and keep this simple.This isn't simple...
Quoted 14 lines
Export this function to
rmap.hto enable future reuse.Signed-off-by Dev Jain <[email protected]>--- include/linux/rmap.h | 10 ++++++++++ mm/rmap.c | 8 +++----- 2 files changed, 13 insertions(+), 5 deletions(-)include/linux/rmap.h +0
-0include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 896
Quoted 5 lines
include/linux/rmap.h +2
-0@@ -892,6 +892,16 @@ static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw)spin_unlock (pvmw->ptl); } +static inline void page_vma_mapped_walk_jump (struct page_vma_mapped_walk *pvmw, +unsigned int nr)unsigned long
nr_pages... 'nr' is meaningless and you're mixing + matching types for no reason.include/linux/rmap.h static inline void page_vma_mapped_walk_done(struct page_vma_mapped_walk *pvmw) · line 903
Quoted 7 lines
include/linux/rmap.h +7
-0+{ + pvmw->pfn += nr; + pvmw->nr_pages -= nr; + pvmw->pgoff += nr; + pvmw->pte += nr; + pvmw->address += nr *PAGE_SIZE ; +}I absolutely hate this. It's extremely confusing, especially since you're now going from looking at 1 page to
nr_pages- 1, jump doesn't really mean anything here, you're losing sight of the batch size and exposing a silly detail to the caller, and I really don't want to 'export' this at this time.If we must have this, can you please make it static in
rmap.cat least for the time being.Or perhaps instead, have a batched variant of
page_vma_mapped_walk(), likepage_vma_mapped_walk_batch()?I think that makes a lot more sense...
I mean I kind of hate the pvmw interface in general, this is a hack to handle batching clamped on to the side of it, let's figure out how to do this sensibly and do what's needed rather than adding yet more hacks-on-hacks please.
Quoted 25 lines
include/linux/rmap.h +1
-0+/** * page_vma_mapped_walk_restart - Restart the page table walk. * @pvmw: Pointer to struct page_vma_mapped_walk. mm/rmap.c +1
-4@@ -1953,9 +1953,6 @@ static inline unsigned int folio_unmap_pte_batch(struct folio *folio,if (pte_unused (pte))return 1 ; -if (userfaultfd_wp (vma)) -return 1 ; -/* * If unmap fails, we need to restore the ptes. To avoid accidentally * upgrading write permissions for ptes that were not originally @@ -2235,7 +2232,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* we may want to replace a none pte with a marker pte if * it's file-backed, so we don't lose the tracking info. */ -install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval,1 ); +install_uffd_wp_ptes_if_needed (vma, address, pvmw.pte, pteval, nr_pages);
Quoted 11 lines
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2359
Quoted 8 lines
mm/rmap.c +1
-1/* Update high watermark before we lower rss */ update_hiwater_rss (mm);@@ -2359,8 +2356,9 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,* If we are sure that we batched the entire folio and cleared * all PTEs, we can just optimize and stop right here. */ -if (nr_pages ==folio_nr_pages (folio)) +if (likely (nr_pages ==folio_nr_pages (folio)))Please don't add random likely()'s based on what you think is
likely(). This kind of thing should only be done based on profiling.
Okay.
Quoted 5 lines
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2361
Quoted 3 lines
mm/rmap.c +1
-0goto walk_done; +page_vma_mapped_walk_jump (&pvmw, nr_pages -1 );(You're now passing a signed long to an unsigned int...!)
Will fix all instances of nr_pages to unsigned long.
Quoted 10 lines
Quoted 8 lines
mm/rmap.c +0
-1continue ; walk_abort: ret = false; --2.34.1
Thanks, Lorenzo
PATCH 6/9 mm/swapfile: Make folio_dup_swap batchable
In reply to Dev JainTeach folio_dup_swap 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, and explicitly pass subpage != NULL, and the number of pages required.
--- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 5 +++-- mm/swapfile.c | 12 +++++------- 4 files changed, 10 insertions(+), 11 deletions(-)
mm/rmap.c +1 -1
@@ -2282,7 +2282,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto discard; } -if (folio_dup_swap (folio, subpage) <0 ) { +if (folio_dup_swap (folio, subpage,1 ) <0 ) {set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }
mm/shmem.c +1 -1
@@ -1695,7 +1695,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,spin_unlock (&shmem_swaplist_lock); } -folio_dup_swap (folio,NULL ); +folio_dup_swap (folio,folio_page (folio,0 ),folio_nr_pages (folio));shmem_delete_from_page_cache (folio,swp_to_radix_entry (folio->swap));BUG_ON (folio_mapped (folio));
mm/swap.h +3 -2
@@ -206,7 +206,7 @@ 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 *subpage); +int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);void folio_put_swap (struct folio *folio,struct page *subpage);/* For internal use */
@@ -390,7 +390,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 (struct folio *folio,struct page *page, +unsigned int nr_pages) {return -EINVAL ; }
mm/swapfile.c +5 -7
@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward. * * 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
@@ -1752,18 +1753,15 @@ 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 *subpage) +int folio_dup_swap (struct folio *folio,struct page *subpage, +unsigned int nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);return swap_dup_entries_cluster (swap_entry_to_info (entry),swp_offset (entry), nr_pages);
Signature · 1 line
2.34.1
Re: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
Quoted 4 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_pages.
Thanks a lot for doing this. I was thinking it's about time we respin the batch unmapping of anon folios idea. Barry tried that before with an RFC, and now batching from swap side is easier, so some parts can be done cleaner.
Quoted 5 lines
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, and explicitly pass subpage != NULL, and the number of pages required.
I was hoping most callers will just use the whole folio, but after checking your code, yeah, using explicit subpage and nr does fit the other parts better.
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1742
Quoted 11 lines
mm/swapfile.c +2
-1@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward.
The new nr_pages parameter isn't documented in the kernel-doc comment?
Re: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
Quoted 21 lines · Kairui Song
Quoted 4 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_pages.Thanks a lot for doing this. I was thinking it's about time we respin the batch unmapping of anon folios idea. Barry tried that before with an RFC, and now batching from swap side is easier, so some parts can be done cleaner.
Quoted 5 lines
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, and explicitly pass subpage != NULL, and the number of pages required.
I was hoping most callers will just use the whole folio, but after checking your code, yeah, using explicit subpage and nr does fit the other parts better.
Quoted 13 lines
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1742
Quoted 11 lines
mm/swapfile.c +2
-1@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward. The new
nr_pagesparameter isn't documented in the kernel-doc comment?
Oops, will add this in v2, thanks.
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
In reply to Dev JainQuoted 9 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
You've made the interface more confusing? Now we can update multiple subpages but specify only one? :)
Let's try to actually refactor this into something sane... see below.
Quoted 63 lines
Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 5 +++-- mm/swapfile.c | 12 +++++------- 4 files changed, 10 insertions(+), 11 deletions(-)mm/rmap.c +1
-1@@ -2282,7 +2282,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto discard; } -if (folio_dup_swap (folio, subpage) <0 ) { +if (folio_dup_swap (folio, subpage,1 ) <0 ) {set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +1
-1@@ -1695,7 +1695,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,spin_unlock (&shmem_swaplist_lock); } -folio_dup_swap (folio,NULL ); +folio_dup_swap (folio,folio_page (folio,0 ),folio_nr_pages (folio));shmem_delete_from_page_cache (folio,swp_to_radix_entry (folio->swap));BUG_ON (folio_mapped (folio));mm/swap.h +3
-2@@ -206,7 +206,7 @@ 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 *subpage); +int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);void folio_put_swap (struct folio *folio,struct page *subpage);/* For internal use */ @@ -390,7 +390,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 (struct folio *folio,struct page *page, +unsigned int nr_pages) {return -EINVAL ; }mm/swapfile.c +0
-0
mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1742
Quoted 6 lines
mm/swapfile.c +2
-1@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward.
(Obviously also Kairui's point about missing entry in kdoc)
This is REALLY confusing sorry. And this interface is just a horror show.
Before we had subpage == only increase the swap count of the subpage.
Now subpage = the first subpage at which we do that? Please, no.
You just need to rework this interface in general, this is a hack.
Something like:
int __folio_dup_swap(struct folio *folio, unsigned int subpage_start_index,
unsigned int nr_subpages)
{
...
}...
int folio_dup_swap_subpage(struct folio *folio, struct page *subpage)
{
return __folio_dup_swap(folio, folio_page_idx(folio, subpage), 1);
}int folio_dup_swap(struct folio *folio)
{
return __folio_dup_swap(folio, 0, folio_nr_pages(folio));
}Or something like that.
We're definitely _not_ keeping the subpage parameter like that and hacking on batching, PLEASE.
Quoted 26 lines
mm/swapfile.c +3
-7* * 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 @@ -1752,18 +1753,15 @@ 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 *subpage) +int folio_dup_swap (struct folio *folio,struct page *subpage, +unsigned int nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);return swap_dup_entries_cluster (swap_entry_to_info (entry),swp_offset (entry), nr_pages); --
Quoted 2 lines
2.34.1
Thanks, Lorenzo
Re: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
Quoted 111 lines · Lorenzo Stoakes (Oracle)
Quoted 9 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
You've made the interface more confusing? Now we can update multiple subpages but specify only one? :)
Let's try to actually refactor this into something sane... see below.
Quoted 63 lines
Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 5 +++-- mm/swapfile.c | 12 +++++------- 4 files changed, 10 insertions(+), 11 deletions(-)mm/rmap.c +1
-1@@ -2282,7 +2282,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto discard; } -if (folio_dup_swap (folio, subpage) <0 ) { +if (folio_dup_swap (folio, subpage,1 ) <0 ) {set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +1
-1@@ -1695,7 +1695,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,spin_unlock (&shmem_swaplist_lock); } -folio_dup_swap (folio,NULL ); +folio_dup_swap (folio,folio_page (folio,0 ),folio_nr_pages (folio));shmem_delete_from_page_cache (folio,swp_to_radix_entry (folio->swap));BUG_ON (folio_mapped (folio));mm/swap.h +3
-2@@ -206,7 +206,7 @@ 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 *subpage); +int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);void folio_put_swap (struct folio *folio,struct page *subpage);/* For internal use */ @@ -390,7 +390,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 (struct folio *folio,struct page *page, +unsigned int nr_pages) {return -EINVAL ; }mm/swapfile.c +0
-0mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1742
Quoted 6 lines
mm/swapfile.c +2
-1@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward. (Obviously also Kairui's point about missing entry in kdoc)
This is REALLY confusing sorry. And this interface is just a horror show.
Before we had subpage == only increase the swap count of the subpage.
Now subpage = the first subpage at which we do that? Please, no.
You just need to rework this interface in general, this is a hack.
Something like:
int __folio_dup_swap(struct folio *folio, unsigned int
subpage_start_index,
unsigned intnr_subpages){ ... }...
int
folio_dup_swap_subpage(struct folio *folio, struct page *subpage){ return __folio_dup_swap(folio, folio_page_idx(folio, subpage), 1); }
Quoted 6 lines
int
folio_dup_swap(struct folio *folio){ return __folio_dup_swap(folio, 0, folio_nr_pages(folio)); }Or something like that.
I get the essence of the point you are making.
Since most callers of folio_put_swap mean it for entire folio, perhaps we can have folio_put_swap for these callers, and the ones which are not sure can call folio_put_swap_subpages? Same for folio_dup_swap. And since we are calling it folio_put_swap_subpages, we can retain the subpage parameter?
Quoted 34 lines
We're definitely _not_ keeping the subpage parameter like that and hacking on batching, PLEASE.
Quoted 26 lines
mm/swapfile.c +3
-7* * 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 @@ -1752,18 +1753,15 @@ 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 *subpage) +int folio_dup_swap (struct folio *folio,struct page *subpage, +unsigned int nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);return swap_dup_entries_cluster (swap_entry_to_info (entry),swp_offset (entry), nr_pages); --Quoted 2 lines
2.34.1
Thanks, Lorenzo
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
Quoted 122 lines · Dev Jain
Quoted 111 lines · Lorenzo Stoakes (Oracle)
Quoted 9 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
You've made the interface more confusing? Now we can update multiple subpages but specify only one? :)
Let's try to actually refactor this into something sane... see below.
Quoted 63 lines
Signed-off-by Dev Jain <[email protected]>--- mm/rmap.c | 2 +- mm/shmem.c | 2 +- mm/swap.h | 5 +++-- mm/swapfile.c | 12 +++++------- 4 files changed, 10 insertions(+), 11 deletions(-)mm/rmap.c +1
-1@@ -2282,7 +2282,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, goto discard; } -if (folio_dup_swap (folio, subpage) <0 ) { +if (folio_dup_swap (folio, subpage,1 ) <0 ) {set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +1
-1@@ -1695,7 +1695,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,spin_unlock (&shmem_swaplist_lock); } -folio_dup_swap (folio,NULL ); +folio_dup_swap (folio,folio_page (folio,0 ),folio_nr_pages (folio));shmem_delete_from_page_cache (folio,swp_to_radix_entry (folio->swap));BUG_ON (folio_mapped (folio));mm/swap.h +3
-2@@ -206,7 +206,7 @@ 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 *subpage); +int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);void folio_put_swap (struct folio *folio,struct page *subpage);/* For internal use */ @@ -390,7 +390,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 (struct folio *folio,struct page *page, +unsigned int nr_pages) {return -EINVAL ; }mm/swapfile.c +0
-0mm/swapfile.c int folio_alloc_swap(struct folio *folio) · line 1742
Quoted 6 lines
mm/swapfile.c +2
-1@@ -1738,7 +1738,8 @@ int folio_alloc_swap(struct folio *folio)/** * folio_dup_swap() - Increase swap count of swap entries of a folio. * @folio: folio with swap entries bounded. -* @subpage: if not NULL, only increase the swap count of this subpage. +* @subpage: Increase the swap count of this subpage till nr number of +* pages forward. (Obviously also Kairui's point about missing entry in kdoc)
This is REALLY confusing sorry. And this interface is just a horror show.
Before we had subpage == only increase the swap count of the subpage.
Now subpage = the first subpage at which we do that? Please, no.
You just need to rework this interface in general, this is a hack.
Something like:
int __folio_dup_swap(struct folio *folio, unsigned int
subpage_start_index,
unsigned intnr_subpages){ ... }...
int
folio_dup_swap_subpage(struct folio *folio, struct page *subpage){ return __folio_dup_swap(folio, folio_page_idx(folio, subpage), 1); }Quoted 6 lines
int
folio_dup_swap(struct folio *folio){ return __folio_dup_swap(folio, 0, folio_nr_pages(folio)); }Or something like that.
Quoted 7 lines
I get the essence of the point you are making.
Since most callers of
folio_put_swapmean it for entire folio, perhaps we can havefolio_put_swapfor these callers, and the ones which are not sure can callfolio_put_swap_subpages? Same forfolio_dup_swap. And since we are calling itfolio_put_swap_subpages, we can retain the subpage parameter?
Right but I'm talking about dup variants here but the principle is the same.
Quoted 28 lines
Quoted 28 lines
We're definitely _not_ keeping the subpage parameter like that and hacking on batching, PLEASE.
Quoted 23 lines
mm/swapfile.c +3
-6* * 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 @@ -1752,18 +1753,15 @@ 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 *subpage) +int folio_dup_swap (struct folio *folio,struct page *subpage, +unsigned int nr_pages) {swp_entry_t entry = folio->swap; -unsigned long nr_pages =folio_nr_pages (folio);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);
Quoted 8 lines
Quoted 7 lines
Quoted 5 lines
mm/swapfile.c +0
-1return swap_dup_entries_cluster (swap_entry_to_info (entry),swp_offset (entry), nr_pages); --2.34.1
Thanks, Lorenzo
Cheers, Lorenzo
Re: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
In reply to Dev JainQuoted 6 lines · Dev Jain
Quoted 5 lines · Lorenzo Stoakes (Oracle)
Quoted 4 lines · Dev Jain
Teach
folio_dup_swapto handle a batch of consecutive pages. Note thatfolio_dup_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_pages.
Quoted 8 lines
Quoted 8 lines
Quoted 5 lines
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, and explicitly pass subpage != NULL, and the number of pages required.
You've made the interface more confusing? Now we can update multiple subpages but specify only one? :)
For the next version, please lose the "subpage" name. It's just a page. folios have pages, not subpages.
kernel test robot
build botRe: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable
In reply to Dev Jainkernel test robot noticed the following build warnings:
Hi Dev,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
url: https://github.com/intel-lab-lkp/linux/commits/Dev-Jain/mm-rmap-make-nr_pages-signed-in-try_to_unmap_one/20260310-153604 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything patch link: https://lore.kernel.org/r/20260310073013.4069309-7-dev.jain%40arm.com patch subject: [PATCH 6/9] mm/swapfile: Make folio_dup_swap batchable config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20260318/[email protected]/config) compiler: m68k-linux-gcc (GCC) 15.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260318/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
>> Warning: mm/swapfile.c:1757 function parameter 'nr_pages' not described in 'folio_dup_swap' >> Warning: mm/swapfile.c:1757 function parameter 'nr_pages' not described in 'folio_dup_swap'
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
PATCH 7/9 mm/swapfile: Make folio_put_swap batchable
In reply to Dev JainTeach folio_put_swap 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, and explicitly pass subpage != NULL, and the number of pages required.
--- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 5 +++-- mm/swapfile.c | 13 +++++-------- 5 files changed, 16 insertions(+), 18 deletions(-)
mm/memory.c +3 -3
@@ -5002,7 +5002,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,folio_page (swapcache,0 ),folio_nr_pages (swapcache)); }else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully
@@ -5011,12 +5011,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,folio_page (folio,0 ),folio_nr_pages (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 (folio, page, nr_pages); } VM_BUG_ON(!folio_test_anon(folio) ||
mm/rmap.c +2 -2
@@ -2293,7 +2293,7 @@ static bool try_to_unmap_one(struct folio *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, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }
@@ -2301,7 +2301,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, subpage)) { -folio_put_swap (folio, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }
mm/shmem.c +3 -3
@@ -1716,7 +1716,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,/* 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,folio_page (folio,0 ),folio_nr_pages (folio)); }/*
@@ -2196,7 +2196,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,folio_page (folio,0 ),folio_nr_pages (folio));swap_cache_del_folio (folio);/* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks
@@ -2426,7 +2426,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,folio_page (folio,0 ),folio_nr_pages (folio));swap_cache_del_folio (folio);folio_mark_dirty (folio);put_swap_device (si);
mm/swap.h +3 -2
@@ -207,7 +207,7 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);*/ int folio_alloc_swap (struct folio *folio);int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages); -void folio_put_swap (struct folio *folio,struct page *subpage); +void folio_put_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);/* For internal use */ extern void __swap_cluster_free_entries(struct swap_info_struct *si,
@@ -396,7 +396,8 @@ static inline int folio_dup_swap(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 (struct folio *folio,struct page *page, +unsigned int nr_pages) { }
mm/swapfile.c +5 -8
@@ -1770,25 +1770,22 @@ int folio_dup_swap(struct folio *folio, struct page *subpage,/** * folio_put_swap() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. -* @subpage: if not NULL, only decrease the swap count of this subpage. +* @subpage: decrease the swap count of this subpage till nr_pages. * * 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 *subpage) +void folio_put_swap (struct folio *folio,struct page *subpage, +unsigned int 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);VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);swap_put_entries_cluster (si,swp_offset (entry), nr_pages, false); }
@@ -2334,7 +2331,7 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd, new_pte =pte_mkuffd_wp (new_pte); 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 (swapcache,folio_file_page (swapcache,swp_offset (entry)),1 ); out:if (pte)pte_unmap_unlock (pte, ptl);
Signature · 1 line
2.34.1
Re: [PATCH 7/9] mm/swapfile: Make folio_put_swap batchable
Quoted 75 lines · Dev Jain
Teach
folio_put_swapto handle a batch of consecutive pages. Note thatfolio_put_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
Signed-off-by Dev Jain <[email protected]>--- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 5 +++-- mm/swapfile.c | 13 +++++-------- 5 files changed, 16 insertions(+), 18 deletions(-)mm/memory.c +3
-3@@ -5002,7 +5002,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,folio_page (swapcache,0 ),folio_nr_pages (swapcache)); }else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully @@ -5011,12 +5011,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,folio_page (folio,0 ),folio_nr_pages (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 (folio, page, nr_pages); } VM_BUG_ON(!folio_test_anon(folio) ||mm/rmap.c +2
-2@@ -2293,7 +2293,7 @@ static bool try_to_unmap_one(struct folio *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, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }@@ -2301,7 +2301,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, subpage)) { -folio_put_swap (folio, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +0
-0
mm/shmem.c int shmem_writeout(struct folio *folio, struct swap_iocb **plug, · line 1719
Quoted 5 lines
mm/shmem.c +1
-1@@ -1716,7 +1716,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,/* 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,folio_page (folio,0 ),folio_nr_pages (folio));
I just realized that we already have a nr_pages variable available here, maybe you can just use that?
Feel free to ignore this if it might touch more code.
Quoted 13 lines
mm/shmem.c +1
-1}/* @@ -2196,7 +2196,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,folio_page (folio,0 ),folio_nr_pages (folio));swap_cache_del_folio (folio);/* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks
mm/shmem.c static int shmem_swapin_folio(struct inode *inode, pgoff_t index, · line 2429
Quoted 5 lines
mm/shmem.c +1
-1@@ -2426,7 +2426,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,folio_page (folio,0 ),folio_nr_pages (folio));
Same here, nr_pages seems good enough?
Re: [PATCH 7/9] mm/swapfile: Make folio_put_swap batchable
Quoted 103 lines · Kairui Song
Quoted 75 lines · Dev Jain
Teach
folio_put_swapto handle a batch of consecutive pages. Note thatfolio_put_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
Signed-off-by Dev Jain <[email protected]>--- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 5 +++-- mm/swapfile.c | 13 +++++-------- 5 files changed, 16 insertions(+), 18 deletions(-)mm/memory.c +3
-3@@ -5002,7 +5002,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,folio_page (swapcache,0 ),folio_nr_pages (swapcache)); }else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully @@ -5011,12 +5011,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,folio_page (folio,0 ),folio_nr_pages (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 (folio, page, nr_pages); } VM_BUG_ON(!folio_test_anon(folio) ||mm/rmap.c +2
-2@@ -2293,7 +2293,7 @@ static bool try_to_unmap_one(struct folio *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, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }@@ -2301,7 +2301,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, subpage)) { -folio_put_swap (folio, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +0
-0mm/shmem.c int shmem_writeout(struct folio *folio, struct swap_iocb **plug, · line 1719
Quoted 5 lines
mm/shmem.c +1
-1@@ -1716,7 +1716,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,/* 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,folio_page (folio,0 ),folio_nr_pages (folio));I just realized that we already have a
nr_pagesvariable available here, maybe you can just use that?Feel free to ignore this if it might touch more code.
Quoted 16 lines
mm/shmem.c +1
-1}/* @@ -2196,7 +2196,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,folio_page (folio,0 ),folio_nr_pages (folio));swap_cache_del_folio (folio);/* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks @@ -2426,7 +2426,7 @@ static int shmem_swapin_folio(struct inode *inode, pgoff_t index,if (sgp ==SGP_WRITE )folio_mark_accessed (folio);
Quoted 4 lines
mm/shmem.c static int shmem_swapin_folio(struct inode *inode, pgoff_t index, · line 2429
Quoted 2 lines
mm/shmem.c +1
-1-folio_put_swap (folio,NULL ); +folio_put_swap (folio,folio_page (folio,0 ),folio_nr_pages (folio));Same here,
nr_pagesseems good enough?
Yup, thanks for your observation.
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 7/9] mm/swapfile: Make folio_put_swap batchable
In reply to Dev JainQuoted 24 lines · Dev Jain
Teach
folio_put_swapto handle a batch of consecutive pages. Note thatfolio_put_swapalready can handle a subset of this:nr_pages== 1 andnr_pages==folio_nr_pages(folio). Generalize this to anynr_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, and explicitly pass subpage != NULL, and the number of pages required.
Signed-off-by Dev Jain <[email protected]>--- mm/memory.c | 6 +++--- mm/rmap.c | 4 ++-- mm/shmem.c | 6 +++--- mm/swap.h | 5 +++-- mm/swapfile.c | 13 +++++-------- 5 files changed, 16 insertions(+), 18 deletions(-)mm/memory.c +0
-0
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5005
Quoted 5 lines
mm/memory.c +1
-1@@ -5002,7 +5002,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,folio_page (swapcache,0 ),folio_nr_pages (swapcache));
Lord above HELPER. HELPER :) please.
I think in general, let's have the same refactoring in folio_put_swap() as I suggested for folio_dup_swap().
I'm not sure where this hellish subpage interface came from, I mean there must be a good reason but it seems kinda horrible.
Quoted 5 lines
mm/memory.c +0
-0}else if (!folio_test_anon (folio)) {/* * We currently only expect !anon folios that are fully @@ -5011,12 +5011,12 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)VM_WARN_ON_ONCE_FOLIO (folio_nr_pages (folio) != nr_pages, folio);
mm/memory.c vm_fault_t do_swap_page(struct vm_fault *vmf) · line 5019
Quoted 10 lines
mm/memory.c +2
-2VM_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,folio_page (folio,0 ),folio_nr_pages (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 (folio, page, nr_pages);
I'm confused as to why some callers use folio_nr_pages() and others nr_pages...
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2296
Quoted 13 lines
mm/memory.c +0
-0} VM_BUG_ON(!folio_test_anon(folio) ||mm/rmap.c +1
-1@@ -2293,7 +2293,7 @@ static bool try_to_unmap_one(struct folio *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, subpage); +folio_put_swap (folio, subpage,1 );
Again, as with the folio_dup_swap() refactoring I suggested in previous patch, something like folio_dup_swap_subpage() would be good here right?
Like folio_put_swap_subpage(folio, subpage)...
Quoted 35 lines
mm/rmap.c +1
-1set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }@@ -2301,7 +2301,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive &&folio_try_share_anon_rmap_pte (folio, subpage)) { -folio_put_swap (folio, subpage); +folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; }mm/shmem.c +2
-2@@ -1716,7 +1716,7 @@ int shmem_writeout(struct folio *folio, struct swap_iocb **plug,/* 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,folio_page (folio,0 ),folio_nr_pages (folio)); }/* @@ -2196,7 +2196,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,folio_page (folio,0 ),folio_nr_pages (folio));swap_cache_del_folio (folio);/* * Don't treat swapin error folio as alloced. Otherwise inode->i_blocks
mm/shmem.c static int shmem_swapin_folio(struct inode *inode, pgoff_t index, · line 2429
Quoted 5 lines
mm/shmem.c +1
-1@@ -2426,7 +2426,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,folio_page (folio,0 ),folio_nr_pages (folio));
Again, for emphasis, please do not repeatedly open code the exact same thing all over the place, 'don't repeat yourself' is a really good principle in programming in general. Now if one of these callers gets updated and the others not we're in a mess.
Abstract this please :)
Quoted 31 lines
mm/shmem.c +0
-0swap_cache_del_folio (folio);folio_mark_dirty (folio);put_swap_device (si);mm/swap.h +3
-2@@ -207,7 +207,7 @@ extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);*/ int folio_alloc_swap (struct folio *folio);int folio_dup_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages); -void folio_put_swap (struct folio *folio,struct page *subpage); +void folio_put_swap (struct folio *folio,struct page *subpage,unsigned int nr_pages);/* For internal use */ extern void __swap_cluster_free_entries(struct swap_info_struct *si,@@ -396,7 +396,8 @@ static inline int folio_dup_swap(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 (struct folio *folio,struct page *page, +unsigned int nr_pages) { }mm/swapfile.c +0
-0
mm/swapfile.c int folio_dup_swap(struct folio *folio, struct page *subpage, · line 1773
Quoted 5 lines
mm/swapfile.c +1
-1@@ -1770,25 +1770,22 @@ int folio_dup_swap(struct folio *folio, struct page *subpage,/** * folio_put_swap() - Decrease swap count of swap entries of a folio. * @folio: folio with swap entries bounded, must be in swap cache and locked. -* @subpage: if not NULL, only decrease the swap count of this subpage. +* @subpage: decrease the swap count of this subpage till nr_pages.
Again no nr_pages entry.
Quoted 13 lines
mm/swapfile.c +2
-2* * 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 *subpage) +void folio_put_swap (struct folio *folio,struct page *subpage, +unsigned int 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);
mm/swapfile.c int folio_dup_swap(struct folio *folio, struct page *subpage, · line 1790
Quoted 10 lines
mm/swapfile.c +1
-4VM_WARN_ON_FOLIO (!folio_test_locked (folio), folio);VM_WARN_ON_FOLIO (!folio_test_swapcache (folio), folio); -if (subpage) { - entry.val +=folio_page_idx (folio, subpage); - nr_pages =1 ; - } + entry.val +=folio_page_idx (folio, subpage);swap_put_entries_cluster (si,swp_offset (entry), nr_pages, false);
And yeah exact same comments re: refactoring as per folio_dup_swap().
Quoted 11 lines
mm/swapfile.c +1
-2}@@ -2334,7 +2331,7 @@ static int unuse_pte(struct vm_area_struct *vma, pmd_t *pmd, new_pte =pte_mkuffd_wp (new_pte); 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 (swapcache,folio_file_page (swapcache,swp_offset (entry)),1 ); out:if (pte)pte_unmap_unlock (pte, ptl); --
Quoted 2 lines
2.34.1
Thanks, Lorenzo
kernel test robot
build botRe: [PATCH 7/9] mm/swapfile: Make folio_put_swap batchable
In reply to Dev Jainkernel test robot noticed the following build warnings:
Hi Dev,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
url: https://github.com/intel-lab-lkp/linux/commits/Dev-Jain/mm-rmap-make-nr_pages-signed-in-try_to_unmap_one/20260310-153604 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything patch link: https://lore.kernel.org/r/20260310073013.4069309-8-dev.jain%40arm.com patch subject: [PATCH 7/9] mm/swapfile: Make folio_put_swap batchable config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20260318/[email protected]/config) compiler: m68k-linux-gcc (GCC) 15.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260318/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
Warning: mm/swapfile.c:1757 function parameter 'nr_pages' not described in 'folio_dup_swap'
>> Warning: mm/swapfile.c:1780 function parameter 'nr_pages' not described in 'folio_put_swap' Warning: mm/swapfile.c:1757 function parameter 'nr_pages' not described in 'folio_dup_swap' >> Warning: mm/swapfile.c:1780 function parameter 'nr_pages' not described in 'folio_put_swap'
Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
PATCH 8/9 mm/rmap: introduce folio_try_share_anon_rmap_ptes
In reply to Dev JainIn the quest of enabling 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. Add helpers to set and clear the PageAnonExclusive bit on a batch of nr_pages. 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.
--- include/linux/page-flags.h | 11 +++++++++++ include/linux/rmap.h | 28 ++++++++++++++++++++++++++-- mm/rmap.c | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-)
include/linux/page-flags.h +11 -0
@@ -1178,6 +1178,17 @@ static __always_inline void __ClearPageAnonExclusive(struct page *page)__clear_bit (PG_anon_exclusive, &PF_ANY (page,1 )->flags.f); } +static __always_inline void ClearPagesAnonExclusive(struct page *page, +unsigned int nr) +{ +for (;;) { +ClearPageAnonExclusive (page); +if (--nr ==0 ) +break ; + ++page; + } +} +#ifdef CONFIG_MMU #define __PG_MLOCKED (1UL << PG_mlocked)#else
include/linux/rmap.h +26 -2
@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level); +/* We only clear anon-exclusive from head page of PMD folio */ +if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; +/* device private folios cannot get pinned via GUP. */ if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);return 0 ; }
@@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,if (unlikely (folio_maybe_dma_pinned (folio)))return -EBUSY ; -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);/* * This is conceptually a smp_wmb() paired with the smp_rmb() in
@@ -804,6 +808,26 @@ static inline int folio_try_share_anon_rmap_pte(struct folio *folio,return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); } +/** +* 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 first mapped exclusive page of the batch +* @nr_pages: The number of pages to share (batch size) +* +* See folio_try_share_anon_rmap_pte for full description. +* +* Context: The caller needs to hold the page table lock and has to have the +* page table entries cleared/invalidated. Those PTEs used to map consecutive +* pages of the folio passed here. The PTEs are all in the same PMD and VMA. +*/ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned int nr) +{ +return __folio_try_share_anon_rmap (folio, page, nr,PGTABLE_LEVEL_PTE ); +} +/** * folio_try_share_anon_rmap_pmd - try marking an exclusive anonymous page * range mapped by a PMD possibly shared to
mm/rmap.c +1 -1
@@ -2300,7 +2300,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && -folio_try_share_anon_rmap_pte (folio, subpage)) { +folio_try_share_anon_rmap_ptes (folio, subpage,1 )) {folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort;
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 8/9] mm/rmap: introduce folio_try_share_anon_rmap_ptes
Quoted 24 lines · Dev Jain
In the quest of enabling 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. Add helpers to set and clear the PageAnonExclusive bit on a batch ofnr_pages. 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.Signed-off-by Dev Jain <[email protected]>--- include/linux/page-flags.h | 11 +++++++++++ include/linux/rmap.h | 28 ++++++++++++++++++++++++++-- mm/rmap.c | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-)include/linux/page-flags.h +0
-0
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1182
Quoted 5 lines
include/linux/page-flags.h +2
-0@@ -1178,6 +1178,17 @@ static __always_inline void __ClearPageAnonExclusive(struct page *page)__clear_bit (PG_anon_exclusive, &PF_ANY (page,1 )->flags.f); } +static __always_inline void ClearPagesAnonExclusive(struct page *page, +unsigned int nr)
You're randomly moving between nr and nr_pages, can we just consistently use nr_pages please.
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1186
Quoted 4 lines
include/linux/page-flags.h +4
-0+{ +for (;;) { +ClearPageAnonExclusive (page); +if (--nr ==0 )
You really require nr to != 0 here or otherwise you're going to be clearing 4 billion pages :)
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1190
Quoted 4 lines
include/linux/page-flags.h +4
-0+break ; + ++page; + } +}
Can we put this in mm.h or somewhere else please, and can we do away with this HorribleNamingConvention, this is new, we can 'get away' with making it something sensible :)
I wonder if we shouldn't also add a folio pointer here, and some VM_WARN_ON_ONCE()'s. Like:
static inline void folio_clear_page_batch(struct folio *folio,
struct page *first_subpage, unsigned int nr_pages)
{
struct page *subpage = first_subpage;
VM_WARN_ON_ONCE(!nr_pages);
VM_WARN_ON_ONCE(... check first_subpage in folio ...);
VM_WARN_ON_ONCE(... check first_subpage -> first_subpage + nr_pages in folio ...);
while (nr_pages--)
ClearPageAnonExclusive(subpage++);
}include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 715
Quoted 13 lines
include/linux/page-flags.h +1
-0+#ifdef CONFIG_MMU #define __PG_MLOCKED (1UL << PG_mlocked)#else include/linux/rmap.h +1
-0@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level); +/* We only clear anon-exclusive from head page of PMD folio */
Is this accurate? David? I thought anon exclusive was per-subpage for any large folio...?
If we're explicitly doing this for some reason here, then why introduce it?
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 721
Quoted 7 lines
include/linux/rmap.h +4
-1+if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; +/* device private folios cannot get pinned via GUP. */ if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);
I really kind of hate this 'we are looking at subpage X with variable page in folio Y, but we don't mention Y' thing. It's super confusing that we have a pointer to a thing which sometimes we deref and treat as a value we care about and sometimes treat as an array.
This pattern exists throughout all the batched stuff and I kind of hate it everywhere.
I guess the batching means that we are looking at a sub-folio range.
If C had a better type system we could somehow have a type that encoded this, but it doesn't :>)
But I wonder if we shouldn't just go ahead and rename page -> pages and be consistent about this?
Quoted 13 lines
include/linux/rmap.h +1
-1return 0 ; }@@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,if (unlikely (folio_maybe_dma_pinned (folio)))return -EBUSY ; -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);/* * This is conceptually a smp_wmb() paired with the smp_rmb() in
include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 814
Quoted 7 lines
include/linux/rmap.h +4
-0@@ -804,6 +808,26 @@ static inline int folio_try_share_anon_rmap_pte(struct folio *folio,return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); } +/** +* folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages +* mapped by PTEs possibly shared to prepare +* for KSM or temporary unmapping
This description is very confusing. 'Try marking exclusive anonymous pages [... marking them as what?] mapped by PTEs[, or (]possibly shared[, or )] to prepare for KSM[under what circumstances? Why mention KSM here?] or temporary unmapping [why temporary?]
OK I think you mean to say 'marking' them as 'possibly' shared.
But really by 'shared' you mean clearing anon exclusive right? So maybe the function name and description should reference that instead.
But this needs clarifying. This isn't an exercise in minimum number of words to describe the function.
Ohhh now I see this is what the comment is in folio_try_share_anon_rmap_pte() :P
Well, I wish we could update the original too ;) but OK this is fine as-is to matc that then.
include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 823
Quoted 9 lines
include/linux/rmap.h +9
-0+* @folio: The folio to share a mapping of +* @page: The first mapped exclusive page of the batch +* @nr_pages: The number of pages to share (batch size) +* +* See folio_try_share_anon_rmap_pte for full description. +* +* Context: The caller needs to hold the page table lock and has to have the +* page table entries cleared/invalidated. Those PTEs used to map consecutive +* pages of the folio passed here. The PTEs are all in the same PMD and VMA.
Can we VM_WARN_ON_ONCE() any of this? Not completely a necessity.
Quoted 15 lines
include/linux/rmap.h +7
-0+*/ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned int nr) +{ +return __folio_try_share_anon_rmap (folio, page, nr,PGTABLE_LEVEL_PTE ); +} +/** * folio_try_share_anon_rmap_pmd - try marking an exclusive anonymous page * range mapped by a PMD possibly shared to mm/rmap.c +0
-0
mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2303
Quoted 5 lines
mm/rmap.c +1
-1@@ -2300,7 +2300,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && -folio_try_share_anon_rmap_pte (folio, subpage)) { +folio_try_share_anon_rmap_ptes (folio, subpage,1 )) {
I guess this is because you intend to make this batched later with >1, but I don't see the point of adding this since folio_try_share_anon_rmap_pte() already does what you're doing here.
So why not just change this when you actually batch?
Buuuut.... haven't you not already changed this whole function to now 'jump' ahead if batched, so why are we only specifying nr_pages = 1 here?
Honestly I think this function needs to be fully refactored away from the appalling giant-ball-of-string mess it is now before we try to add in batching to be honest.
Let's not accumulate more technical debt.
Quoted 6 lines
mm/rmap.c +0
-1folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; --2.34.1
Thanks, Lorenzo
Re: [PATCH 8/9] mm/rmap: introduce folio_try_share_anon_rmap_ptes
Quoted 28 lines · Lorenzo Stoakes (Oracle)
Quoted 27 lines · Dev Jain
In the quest of enabling 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. Add helpers to set and clear the PageAnonExclusive bit on a batch ofnr_pages. 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.Signed-off-by Dev Jain <[email protected]>--- include/linux/page-flags.h | 11 +++++++++++ include/linux/rmap.h | 28 ++++++++++++++++++++++++++-- mm/rmap.c | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-)include/linux/page-flags.h +0
-0@@ -1178,6 +1178,17 @@ static __always_inline void __ClearPageAnonExclusive(struct page *page)__clear_bit (PG_anon_exclusive, &PF_ANY (page,1 )->flags.f); }
Quoted 5 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1182
Quoted 2 lines
include/linux/page-flags.h +2
-0+static __always_inline void ClearPagesAnonExclusive(struct page *page, +unsigned int nr)You're randomly moving between nr and
nr_pages, can we just consistently usenr_pagesplease.
Okay.
Quoted 8 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1186
Quoted 5 lines
include/linux/page-flags.h +4
-0+{ +for (;;) { +ClearPageAnonExclusive (page); +if (--nr ==0 )You really require nr to != 0 here or otherwise you're going to be clearing 4 billion pages :)
I'm following the pattern in pgtable.h, see set_ptes, clear_young_dirty_ptes, etc.
Quoted 7 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1190
Quoted 5 lines
include/linux/page-flags.h +4
-0+break ; + ++page; + } +}Can we put this in
mm.hor somewhere else please, and can we do away with this
What is the problem with page-flags.h? I am not very aware on which function to put in which header file semantically, so please educate me on this.
Quoted 2 lines
HorribleNamingConvention, this is new, we can 'get away' with making it something sensible :)
I'll name it folio_clear_pages_anon_exclusive.
Quoted 7 lines
I wonder if we shouldn't also add a folio pointer here, and some VM_WARN_ON_ONCE()'s. Like:
static inline void folio_clear_page_batch(struct folio *folio,
struct page*first_subpage, unsigned intnr_pages)
Quoted 6 lines
{
struct page *subpage = first_subpage;
VM_WARN_ON_ONCE(!nr_pages);
VM_WARN_ON_ONCE(... check first_subpage in folio ...);
VM_WARN_ON_ONCE(... check first_subpage -> first_subpage + nr_pages in folio ...);I like what you are saying, but __folio_rmap_sanity_checks in the caller checks exactly this :)
Quoted 17 lines
while (nr_pages--) ClearPageAnonExclusive(subpage++); }Quoted 12 lines
include/linux/page-flags.h +1
-0+#ifdef CONFIG_MMU #define __PG_MLOCKED (1UL << PG_mlocked)#else include/linux/rmap.h +0
-0@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level);
Quoted 4 lines
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 715
Quoted 1 line
include/linux/rmap.h +1
-0+/* We only clear anon-exclusive from head page of PMD folio */ Is this accurate? David? I thought anon exclusive was per-subpage for any large folio...?
The current behaviour is to do this only. I was also surprised with this, so I had dug in and found out:
https://lore.kernel.org/all/[email protected]/
where David says:
"Long story short: once
PTE-mapped, we have to track information about exclusivity per sub-page, but until then, we can just track it for the compound page in the head page and not having to update a whole bunch of subpages all of the time for a simple PMD mapping of a THP."
Quoted 19 lines
If we're explicitly doing this for some reason here, then why introduce it?
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 721
Quoted 7 lines
include/linux/rmap.h +4
-1+if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; +/* device private folios cannot get pinned via GUP. */ if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);I really kind of hate this 'we are looking at subpage X with variable page in folio Y, but we don't mention Y' thing. It's super confusing that we have a pointer to a thing which sometimes we deref and treat as a value we care about and sometimes treat as an array.
This pattern exists throughout all the batched stuff and I kind of hate it everywhere.
Quoted 7 lines
I guess the batching means that we are looking at a sub-folio range.
If C had a better type system we could somehow have a type that encoded this, but it doesn't :>)
But I wonder if we shouldn't just go ahead and rename page -> pages and be consistent about this?
Agreed. You are correct in saying that this function should receive struct folio to assert that we are essentially in a page array, and some sanity checking should happen. But the callers are already doing the checking in folio_rmap_sanity_checks. Let me think on this.
Quoted 50 lines
Quoted 14 lines
include/linux/rmap.h +1
-1return 0 ; }@@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,if (unlikely (folio_maybe_dma_pinned (folio)))return -EBUSY ; -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);/* * This is conceptually a smp_wmb() paired with the smp_rmb() in include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 814
Quoted 7 lines
include/linux/rmap.h +4
-0@@ -804,6 +808,26 @@ static inline int folio_try_share_anon_rmap_pte(struct folio *folio,return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); } +/** +* folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages +* mapped by PTEs possibly shared to prepare +* for KSM or temporary unmapping This description is very confusing. 'Try marking exclusive anonymous pages [... marking them as what?] mapped by PTEs[, or (]possibly shared[, or )] to prepare for KSM[under what circumstances? Why mention KSM here?] or temporary unmapping [why temporary?]
OK I think you mean to say 'marking' them as 'possibly' shared.
But really by 'shared' you mean clearing anon exclusive right? So maybe the function name and description should reference that instead.
But this needs clarifying. This isn't an exercise in minimum number of words to describe the function.
Ohhh now I see this is what the comment is in
folio_try_share_anon_rmap_pte():PWell, I wish we could update the original too ;) but OK this is fine as-is to matc that then.
include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 823
Quoted 9 lines
include/linux/rmap.h +9
-0+* @folio: The folio to share a mapping of +* @page: The first mapped exclusive page of the batch +* @nr_pages: The number of pages to share (batch size) +* +* See folio_try_share_anon_rmap_pte for full description. +* +* Context: The caller needs to hold the page table lock and has to have the +* page table entries cleared/invalidated. Those PTEs used to map consecutive +* pages of the folio passed here. The PTEs are all in the same PMD and VMA.
Quoted 1 line
Can we
VM_WARN_ON_ONCE()any of this? Not completely a necessity.
Again, we have WARN checks in folio_rmap_sanity_checks, and even in folio_pte_batch. I am afraid of duplication.
Quoted 26 lines
Quoted 16 lines
include/linux/rmap.h +7
-0+*/ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned int nr) +{ +return __folio_try_share_anon_rmap (folio, page, nr,PGTABLE_LEVEL_PTE ); +} +/** * folio_try_share_anon_rmap_pmd - try marking an exclusive anonymous page * range mapped by a PMD possibly shared to mm/rmap.c +0
-0mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2303
Quoted 5 lines
mm/rmap.c +1
-1@@ -2300,7 +2300,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && -folio_try_share_anon_rmap_pte (folio, subpage)) { +folio_try_share_anon_rmap_ptes (folio, subpage,1 )) {I guess this is because you intend to make this batched later with >1, but I don't see the point of adding this since
folio_try_share_anon_rmap_pte()already does what you're doing here.
Quoted 1 line
So why not just change this when you actually batch?
It is generally the consensus to introduce a new function along with its caller. Although one may argue that the caller introduced here is not a functional change (still passing nr_pages = 1). So I am fine doing what you suggest.
Quoted 3 lines
Buuuut.... haven't you not already changed this whole function to now 'jump' ahead if batched, so why are we only specifying
nr_pages= 1 here?
Because...please bear with the insanity :) currently we are in a ridiculous situation where
nr_pages can be > 1 for file folios, and lazyfree folios, *and* it is required that the VMA is non-uffd.
So, the "jump" thingy I was doing in the previous patch was adding support for file folios, belonging to uffd VMAs (see pte_install_uffd_wp_if_needed, we need to handle uffd-wp marker for file folios only, and also I should have mentioned "file folio" in the subject line of that patch, of course I missed that because reasoning through this code is very difficult)
To answer your question, currently for anon folios nr_pages == 1, so the jump is a no-op.
When I had discovered the uffd-wp bug some weeks back, I was pushing back against the idea of hacking around it by disabling batching for uffd-VMAs in folio_unmap_pte_batch, but solve it then and there properly. Now we have too many cases - first we added lazyfree support, then file-non-uffd support, my patch 5 adds file-uffd support, and last patch finally completes this with anon support.
Quoted 6 lines
Honestly I think this function needs to be fully refactored away from the appalling giant-ball-of-string mess it is now before we try to add in batching to be honest.
Let's not accumulate more technical debt.
I agree, I am happy to help in cleaning up this function.
Quoted 10 lines
Quoted 8 lines
mm/rmap.c +0
-1folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; --2.34.1
Thanks, Lorenzo
Re: [PATCH 8/9] mm/rmap: introduce folio_try_share_anon_rmap_ptes
Quoted 1 line · Dev Jain
[...]
Quoted 18 lines
Quoted 8 lines
Quoted 8 lines
include/linux/rmap.h +0
-0@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level);Quoted 4 lines
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 715
Quoted 1 line
include/linux/rmap.h +1
-0+/* We only clear anon-exclusive from head page of PMD folio */ Is this accurate? David? I thought anon exclusive was per-subpage for any large folio...?
The current behaviour is to do this only. I was also surprised with this, so I had dug in and found out:
Quoted 8 lines
where David says:
"Long story short: once
PTE-mapped, we have to track information about exclusivity per sub-page, but until then, we can just track it for the compound page in the head page and not having to update a whole bunch of subpages all of the time for a simple PMD mapping of a THP."
Thanks for digging.
One tiny thing:
Now we have a comment in PageAnonExclusive(), which says:
/* * HugeTLB stores this information on the head page; THP keeps it per * page */
This may confuse readers? Not your fault, just want to point it out.
Signature · 2 lines
Wei Yang Help you, Help me
Lorenzo Stoakes (Oracle)
maintainerRe: [PATCH 8/9] mm/rmap: introduce folio_try_share_anon_rmap_ptes
In reply to Dev JainQuoted 59 lines · Dev Jain
Quoted 28 lines · Lorenzo Stoakes (Oracle)
Quoted 27 lines · Dev Jain
In the quest of enabling 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. Add helpers to set and clear the PageAnonExclusive bit on a batch ofnr_pages. 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.Signed-off-by Dev Jain <[email protected]>--- include/linux/page-flags.h | 11 +++++++++++ include/linux/rmap.h | 28 ++++++++++++++++++++++++++-- mm/rmap.c | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-)include/linux/page-flags.h +0
-0@@ -1178,6 +1178,17 @@ static __always_inline void __ClearPageAnonExclusive(struct page *page)__clear_bit (PG_anon_exclusive, &PF_ANY (page,1 )->flags.f); }Quoted 5 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1182
Quoted 2 lines
include/linux/page-flags.h +2
-0+static __always_inline void ClearPagesAnonExclusive(struct page *page, +unsigned int nr)You're randomly moving between nr and
nr_pages, can we just consistently usenr_pagesplease.Okay.
Quoted 8 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1186
Quoted 5 lines
include/linux/page-flags.h +4
-0+{ +for (;;) { +ClearPageAnonExclusive (page); +if (--nr ==0 )You really require nr to != 0 here or otherwise you're going to be clearing 4 billion pages :)
I'm following the pattern in
pgtable.h, seeset_ptes,clear_young_dirty_ptes, etc.Quoted 7 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1190
Quoted 5 lines
include/linux/page-flags.h +4
-0+break ; + ++page; + } +}Can we put this in
mm.hor somewhere else please, and can we do away with this
Quoted 3 lines
What is the problem with
page-flags.h? I am not very aware on which function to put in which header file semantically, so please educate me on this.
It's a mess in there and this this doesn't really belong.
Quoted 5 lines
Quoted 3 lines
HorribleNamingConvention, this is new, we can 'get away' with making it something sensible :)
I'll name it
folio_clear_pages_anon_exclusive.
OK
Quoted 12 lines
Quoted 12 lines
I wonder if we shouldn't also add a folio pointer here, and some VM_WARN_ON_ONCE()'s. Like:
static inline void folio_clear_page_batch(struct folio *folio,
struct page*first_subpage, unsigned intnr_pages){ struct page *subpage = first_subpage;
Quoted 6 lines
Quoted 3 lines
VM_WARN_ON_ONCE(!nr_pages); VM_WARN_ON_ONCE(... check first_subpage in folio ...); VM_WARN_ON_ONCE(... check first_subpage -> first_subpage + nr_pages in folio ...);I like what you are saying, but
__folio_rmap_sanity_checksin the caller checks exactly this :)
This is a shared function that can't be assumed to always be called from a context where that has run. VM_WARN_ON_ONCE()'s are free in release kernels so I don't think it should be such an issue.
Quoted 28 lines
Quoted 18 lines
while (nr_pages--) ClearPageAnonExclusive(subpage++); }Quoted 12 lines
include/linux/page-flags.h +1
-0+#ifdef CONFIG_MMU #define __PG_MLOCKED (1UL << PG_mlocked)#else include/linux/rmap.h +0
-0@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level);Quoted 4 lines
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 715
Quoted 1 line
include/linux/rmap.h +1
-0+/* We only clear anon-exclusive from head page of PMD folio */ Is this accurate? David? I thought anon exclusive was per-subpage for any large folio...?
The current behaviour is to do this only. I was also surprised with this, so I had dug in and found out:
Quoted 7 lines
where David says:
"Long story short: once
PTE-mapped, we have to track information about exclusivity per sub-page, but until then, we can just track it for the compound page in the head page and not having to update a whole bunch of subpages all of the time for a simple PMD mapping of a THP."
OK
Quoted 26 lines
Quoted 26 lines
If we're explicitly doing this for some reason here, then why introduce it?
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 721
Quoted 7 lines
include/linux/rmap.h +4
-1+if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; +/* device private folios cannot get pinned via GUP. */ if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);I really kind of hate this 'we are looking at subpage X with variable page in folio Y, but we don't mention Y' thing. It's super confusing that we have a pointer to a thing which sometimes we deref and treat as a value we care about and sometimes treat as an array.
This pattern exists throughout all the batched stuff and I kind of hate it everywhere.
I guess the batching means that we are looking at a sub-folio range.
If C had a better type system we could somehow have a type that encoded this, but it doesn't :>)
Quoted 7 lines
Quoted 2 lines
But I wonder if we shouldn't just go ahead and rename page -> pages and be consistent about this?
Agreed. You are correct in saying that this function should receive struct folio to assert that we are essentially in a page array, and some sanity checking should happen. But the callers are already doing the checking in
folio_rmap_sanity_checks. Let me think on this.
You treat that like a license to never put an assert anywhere else in mm... In any case I'm not sure I mentioend an assert here, more so the pattern around folio v.s subpages.
Quoted 52 lines
Quoted 52 lines
Quoted 16 lines
include/linux/rmap.h +1
-1return 0 ; }@@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,if (unlikely (folio_maybe_dma_pinned (folio)))return -EBUSY ; -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);/* * This is conceptually a smp_wmb() paired with the smp_rmb() in include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 814
Quoted 7 lines
include/linux/rmap.h +4
-0@@ -804,6 +808,26 @@ static inline int folio_try_share_anon_rmap_pte(struct folio *folio,return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); } +/** +* folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages +* mapped by PTEs possibly shared to prepare +* for KSM or temporary unmapping This description is very confusing. 'Try marking exclusive anonymous pages [... marking them as what?] mapped by PTEs[, or (]possibly shared[, or )] to prepare for KSM[under what circumstances? Why mention KSM here?] or temporary unmapping [why temporary?]
OK I think you mean to say 'marking' them as 'possibly' shared.
But really by 'shared' you mean clearing anon exclusive right? So maybe the function name and description should reference that instead.
But this needs clarifying. This isn't an exercise in minimum number of words to describe the function.
Ohhh now I see this is what the comment is in
folio_try_share_anon_rmap_pte():PWell, I wish we could update the original too ;) but OK this is fine as-is to matc that then.
Quoted 9 lines
include/linux/rmap.h +9
-0+* @folio: The folio to share a mapping of +* @page: The first mapped exclusive page of the batch +* @nr_pages: The number of pages to share (batch size) +* +* See folio_try_share_anon_rmap_pte for full description. +* +* Context: The caller needs to hold the page table lock and has to have the +* page table entries cleared/invalidated. Those PTEs used to map consecutive +* pages of the folio passed here. The PTEs are all in the same PMD and VMA.
Quoted 4 lines
Quoted 1 line
Can we
VM_WARN_ON_ONCE()any of this? Not completely a necessity.Again, we have WARN checks in
folio_rmap_sanity_checks, and even infolio_pte_batch. I am afraid of duplication.
Why on earth are you bothering with 'context' then?
If having run folio_rmap_sanity_checks() means we never have to assert or think about state or context again then why bother?
I looked in __folio_rma_sanity_checks() and I see no:
Breaking it down:
Context: The caller needs to hold the page table lock and has to have the
page table entries cleared/invalidated. Those PTEs used to map consecutive pages of the folio passed here. The PTEs are all in the same PMD and VMA.
- Page table lock <-- NOT checked
- Page table entries cleared/invalidated <-- NOT checked
- PTEs passed in must map all pages in the folio? <-- you aren't passing in PTEs?
- PTEs are all in the same PMD <- You aren't passing in PTEs?
- PTES are all in the same VMA <- You aren't passing in PTEs?
So that's hardly a coherent picture no?
Quoted 27 lines
Quoted 27 lines
Quoted 17 lines
include/linux/rmap.h +7
-0+*/ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned int nr) +{ +return __folio_try_share_anon_rmap (folio, page, nr,PGTABLE_LEVEL_PTE ); +} +/** * folio_try_share_anon_rmap_pmd - try marking an exclusive anonymous page * range mapped by a PMD possibly shared to mm/rmap.c +0
-0mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2303
Quoted 5 lines
mm/rmap.c +1
-1@@ -2300,7 +2300,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && -folio_try_share_anon_rmap_pte (folio, subpage)) { +folio_try_share_anon_rmap_ptes (folio, subpage,1 )) {I guess this is because you intend to make this batched later with >1, but I don't see the point of adding this since
folio_try_share_anon_rmap_pte()already does what you're doing here.
Quoted 4 lines
Quoted 1 line
So why not just change this when you actually batch?
It is generally the consensus to introduce a new function along with its caller. Although one may argue that the caller introduced here is not
That's not always the case, sometimes it makes more sense and is cleaner to introduce it first.
All rules of thumb are open to sensible interpretation.
I'm not sure arbitrarily using the function in a way that makes no sense in the code is a good approach but this isn't the end of the world.
Quoted 22 lines
a functional change (still passing
nr_pages= 1). So I am fine doing what you suggest.Quoted 3 lines
Buuuut.... haven't you not already changed this whole function to now 'jump' ahead if batched, so why are we only specifying
nr_pages= 1 here?Because...please bear with the insanity :) currently we are in a ridiculous situation where
nr_pagescan be > 1 for file folios, and lazyfree folios, *and* it is required that the VMA is non-uffd.So, the "jump" thingy I was doing in the previous patch was adding support for file folios, belonging to uffd VMAs (see
pte_install_uffd_wp_if_needed, we need to handle uffd-wp marker for file folios only, and also I should have mentioned "file folio" in the subject line of that patch, of course I missed that because reasoning through this code is very difficult)To answer your question, currently for anon folios
nr_pages== 1, so the jump is a no-op.
Quoted 6 lines
When I had discovered the uffd-wp bug some weeks back, I was pushing back against the idea of hacking around it by disabling batching for uffd-VMAs in
folio_unmap_pte_batch, but solve it then and there properly. Now we have too many cases - first we added lazyfree support, then file-non-uffd support, my patch 5 adds file-uffd support, and last patch finally completes this with anon support.
I am not a fan of any of that, this just speaks to the need to clean this up before endlessly adding more functionality and piles of complexity and confusion.
Let's just add some patches to do things sanely please.
Quoted 10 lines
Quoted 8 lines
Honestly I think this function needs to be fully refactored away from the appalling giant-ball-of-string mess it is now before we try to add in batching to be honest.
Let's not accumulate more technical debt.
I agree, I am happy to help in cleaning up this function.
Right, well then this series needs to have some clean up patches in it first.
Quoted 12 lines
Quoted 11 lines
Quoted 9 lines
mm/rmap.c +0
-1folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; --2.34.1
Thanks, Lorenzo
Thanks< Lorenzo
Re: [PATCH 8/9] mm/rmap: introduce folio_try_share_anon_rmap_ptes
Quoted 127 lines · Lorenzo Stoakes (Oracle)
Quoted 59 lines · Dev Jain
Quoted 28 lines · Lorenzo Stoakes (Oracle)
Quoted 27 lines · Dev Jain
In the quest of enabling 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. Add helpers to set and clear the PageAnonExclusive bit on a batch ofnr_pages. 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.Signed-off-by Dev Jain <[email protected]>--- include/linux/page-flags.h | 11 +++++++++++ include/linux/rmap.h | 28 ++++++++++++++++++++++++++-- mm/rmap.c | 2 +- 3 files changed, 38 insertions(+), 3 deletions(-)include/linux/page-flags.h +0
-0@@ -1178,6 +1178,17 @@ static __always_inline void __ClearPageAnonExclusive(struct page *page)__clear_bit (PG_anon_exclusive, &PF_ANY (page,1 )->flags.f); }Quoted 5 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1182
Quoted 2 lines
include/linux/page-flags.h +2
-0+static __always_inline void ClearPagesAnonExclusive(struct page *page, +unsigned int nr)You're randomly moving between nr and
nr_pages, can we just consistently usenr_pagesplease.Okay.
Quoted 8 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1186
Quoted 5 lines
include/linux/page-flags.h +4
-0+{ +for (;;) { +ClearPageAnonExclusive (page); +if (--nr ==0 )You really require nr to != 0 here or otherwise you're going to be clearing 4 billion pages :)
I'm following the pattern in
pgtable.h, seeset_ptes,clear_young_dirty_ptes, etc.Quoted 7 lines
include/linux/page-flags.h static __always_inline void __ClearPageAnonExclusive(struct page *page) · line 1190
Quoted 5 lines
include/linux/page-flags.h +4
-0+break ; + ++page; + } +}Can we put this in
mm.hor somewhere else please, and can we do away with thisQuoted 3 lines
What is the problem with
page-flags.h? I am not very aware on which function to put in which header file semantically, so please educate me on this.It's a mess in there and this this doesn't really belong.
Quoted 5 lines
Quoted 3 lines
HorribleNamingConvention, this is new, we can 'get away' with making it something sensible :)
I'll name it
folio_clear_pages_anon_exclusive.OK
Quoted 12 lines
Quoted 12 lines
I wonder if we shouldn't also add a folio pointer here, and some VM_WARN_ON_ONCE()'s. Like:
static inline void folio_clear_page_batch(struct folio *folio,
struct page*first_subpage, unsigned intnr_pages){ struct page *subpage = first_subpage;Quoted 6 lines
Quoted 3 lines
VM_WARN_ON_ONCE(!nr_pages); VM_WARN_ON_ONCE(... check first_subpage in folio ...); VM_WARN_ON_ONCE(... check first_subpage -> first_subpage + nr_pages in folio ...);I like what you are saying, but
__folio_rmap_sanity_checksin the caller checks exactly this :)This is a shared function that can't be assumed to always be called from a context where that has run. VM_WARN_ON_ONCE()'s are free in release kernels so I don't think it should be such an issue.
Quoted 30 lines
Quoted 18 lines
while (nr_pages--) ClearPageAnonExclusive(subpage++); }Quoted 12 lines
include/linux/page-flags.h +1
-0+#ifdef CONFIG_MMU #define __PG_MLOCKED (1UL << PG_mlocked)#else include/linux/rmap.h +0
-0@@ -712,9 +712,13 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,VM_WARN_ON_FOLIO (!PageAnonExclusive (page), folio);__folio_rmap_sanity_checks (folio, page, nr_pages, level);Quoted 4 lines
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 715
Quoted 1 line
include/linux/rmap.h +1
-0+/* We only clear anon-exclusive from head page of PMD folio */ Is this accurate? David? I thought anon exclusive was per-subpage for any large folio...?
The current behaviour is to do this only. I was also surprised with this, so I had dug in and found out:
https://lore.kernel.org/all/[email protected]/
where David says:
Quoted 41 lines
Quoted 5 lines
"Long story short: once
PTE-mapped, we have to track information about exclusivity per sub-page, but until then, we can just track it for the compound page in the head page and not having to update a whole bunch of subpages all of the time for a simple PMD mapping of a THP."OK
Quoted 33 lines
Quoted 21 lines
If we're explicitly doing this for some reason here, then why introduce it?
include/linux/rmap.h static __always_inline int __folio_try_share_anon_rmap(struct folio *folio, · line 721
Quoted 7 lines
include/linux/rmap.h +4
-1+if (level ==PGTABLE_LEVEL_PMD ) + nr_pages =1 ; +/* device private folios cannot get pinned via GUP. */ if (unlikely (folio_is_device_private (folio))) { -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);I really kind of hate this 'we are looking at subpage X with variable page in folio Y, but we don't mention Y' thing. It's super confusing that we have a pointer to a thing which sometimes we deref and treat as a value we care about and sometimes treat as an array.
This pattern exists throughout all the batched stuff and I kind of hate it everywhere.
Quoted 7 lines
I guess the batching means that we are looking at a sub-folio range.
If C had a better type system we could somehow have a type that encoded this, but it doesn't :>)
But I wonder if we shouldn't just go ahead and rename page -> pages and be consistent about this?
Agreed. You are correct in saying that this function should receive struct folio to assert that we are essentially in a page array, and some sanity checking should happen. But the callers are already doing the checking in
folio_rmap_sanity_checks. Let me think on this.
I have no idea why I used the word "caller" here. Its been some weeks so hopefully my brain works more coherently after a vacation ...
See folio_add_anon_rmap_ptes, folio_remove_rmap_ptes, folio_try_dup_anon_rmap_ptes etc, all of them eventually assert the constraints in the deeper functions - __folio_add_rmap, __folio_remove_rmap, __folio_try_dup_anon_rmap - via __folio_rmap_sanity_checks.
So I meant to say that the batched function advertised to the rest of the mm code eventually checks all of this.
Indeed my argument regarding "folio_pte_batch() in the caller already checks this" is shaky as you correctly reason.
So, following the pattern in these functions, I can probably drop all of the "Context: " part, or just mention that the page table lock needs to be held.
Regarding how to assert that [page, page + nr_pages) lies in the folio ...
renaming page -> pages sounds somewhat confusing - usually when we mean "an array of pages", we pass a double pointer.
I think the main thing we are missing to document here is that all the batched functions present in the mm code have an unsaid prerequisite - the caller *always* first goes through folio_pte_batch_flags() - (if I am wrong on this then please correct me) the caller cannot construct an nr_pages and pass it to a batched function, without first checking whether those nr_pages are "uniform" in some bits, which is why we first go through folio_pte_batch_flags.
So one alternate solution is to update kerneldoc of every batched helper to say that the nr_pages passed here must be derived from folio_pte_batch_flags.
Another alternate I was thinking was to do something like
struct folio_subrange {
struct folio *folio;
struct page *first_page;
unsigned long nr_pages;
}and have a constructor which will do folio_subrange_init() and do the necessary checks. Then we change all batched functions to accept this. Not sure how convincing this is.
Quoted 186 lines
You treat that like a license to never put an assert anywhere else in mm... In any case I'm not sure I mentioend an assert here, more so the pattern around folio v.s subpages.
Quoted 52 lines
Quoted 52 lines
Quoted 16 lines
include/linux/rmap.h +1
-1return 0 ; }@@ -766,7 +770,7 @@ static __always_inline int __folio_try_share_anon_rmap(struct folio *folio,if (unlikely (folio_maybe_dma_pinned (folio)))return -EBUSY ; -ClearPageAnonExclusive (page); +ClearPagesAnonExclusive (page, nr_pages);/* * This is conceptually a smp_wmb() paired with the smp_rmb() in include/linux/rmap.h static inline int folio_try_share_anon_rmap_pte(struct folio *folio, · line 814
Quoted 7 lines
include/linux/rmap.h +4
-0@@ -804,6 +808,26 @@ static inline int folio_try_share_anon_rmap_pte(struct folio *folio,return __folio_try_share_anon_rmap (folio, page,1 ,PGTABLE_LEVEL_PTE ); } +/** +* folio_try_share_anon_rmap_ptes - try marking exclusive anonymous pages +* mapped by PTEs possibly shared to prepare +* for KSM or temporary unmapping This description is very confusing. 'Try marking exclusive anonymous pages [... marking them as what?] mapped by PTEs[, or (]possibly shared[, or )] to prepare for KSM[under what circumstances? Why mention KSM here?] or temporary unmapping [why temporary?]
OK I think you mean to say 'marking' them as 'possibly' shared.
But really by 'shared' you mean clearing anon exclusive right? So maybe the function name and description should reference that instead.
But this needs clarifying. This isn't an exercise in minimum number of words to describe the function.
Ohhh now I see this is what the comment is in
folio_try_share_anon_rmap_pte():PWell, I wish we could update the original too ;) but OK this is fine as-is to matc that then.
Quoted 9 lines
include/linux/rmap.h +9
-0+* @folio: The folio to share a mapping of +* @page: The first mapped exclusive page of the batch +* @nr_pages: The number of pages to share (batch size) +* +* See folio_try_share_anon_rmap_pte for full description. +* +* Context: The caller needs to hold the page table lock and has to have the +* page table entries cleared/invalidated. Those PTEs used to map consecutive +* pages of the folio passed here. The PTEs are all in the same PMD and VMA. Quoted 4 lines
Quoted 1 line
Can we
VM_WARN_ON_ONCE()any of this? Not completely a necessity.Again, we have WARN checks in
folio_rmap_sanity_checks, and even infolio_pte_batch. I am afraid of duplication.Why on earth are you bothering with 'context' then?
If having run
folio_rmap_sanity_checks()means we never have to assert or think about state or context again then why bother?I looked in
__folio_rma_sanity_checks()and I see no:Breaking it down:
Context: The caller needs to hold the page table lock and has to have the
page table entries cleared/invalidated. Those PTEs used to map consecutive pages of the folio passed here. The PTEs are all in the same PMD and VMA.- Page table lock <-- NOT checked
- Page table entries cleared/invalidated <-- NOT checked
- PTEs passed in must map all pages in the folio? <-- you aren't passing in PTEs?
- PTEs are all in the same PMD <- You aren't passing in PTEs?
- PTES are all in the same VMA <- You aren't passing in PTEs?So that's hardly a coherent picture no?
Quoted 27 lines
Quoted 27 lines
Quoted 17 lines
include/linux/rmap.h +7
-0+*/ +static inline int folio_try_share_anon_rmap_ptes (struct folio *folio, +struct page *page,unsigned int nr) +{ +return __folio_try_share_anon_rmap (folio, page, nr,PGTABLE_LEVEL_PTE ); +} +/** * folio_try_share_anon_rmap_pmd - try marking an exclusive anonymous page * range mapped by a PMD possibly shared to mm/rmap.c +0
-0mm/rmap.c static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma, · line 2303
Quoted 5 lines
mm/rmap.c +1
-1@@ -2300,7 +2300,7 @@ static bool try_to_unmap_one(struct folio *folio, struct vm_area_struct *vma,/* See folio_try_share_anon_rmap(): clear PTE first. */ if (anon_exclusive && -folio_try_share_anon_rmap_pte (folio, subpage)) { +folio_try_share_anon_rmap_ptes (folio, subpage,1 )) {I guess this is because you intend to make this batched later with >1, but I don't see the point of adding this since
folio_try_share_anon_rmap_pte()already does what you're doing here.Quoted 4 lines
Quoted 1 line
So why not just change this when you actually batch?
It is generally the consensus to introduce a new function along with its caller. Although one may argue that the caller introduced here is not
That's not always the case, sometimes it makes more sense and is cleaner to introduce it first.
All rules of thumb are open to sensible interpretation.
I'm not sure arbitrarily using the function in a way that makes no sense in the code is a good approach but this isn't the end of the world.
Quoted 22 lines
a functional change (still passing
nr_pages= 1). So I am fine doing what you suggest.Quoted 3 lines
Buuuut.... haven't you not already changed this whole function to now 'jump' ahead if batched, so why are we only specifying
nr_pages= 1 here?Because...please bear with the insanity :) currently we are in a ridiculous situation where
nr_pagescan be > 1 for file folios, and lazyfree folios, *and* it is required that the VMA is non-uffd.So, the "jump" thingy I was doing in the previous patch was adding support for file folios, belonging to uffd VMAs (see
pte_install_uffd_wp_if_needed, we need to handle uffd-wp marker for file folios only, and also I should have mentioned "file folio" in the subject line of that patch, of course I missed that because reasoning through this code is very difficult)To answer your question, currently for anon folios
nr_pages== 1, so the jump is a no-op.Quoted 6 lines
When I had discovered the uffd-wp bug some weeks back, I was pushing back against the idea of hacking around it by disabling batching for uffd-VMAs in
folio_unmap_pte_batch, but solve it then and there properly. Now we have too many cases - first we added lazyfree support, then file-non-uffd support, my patch 5 adds file-uffd support, and last patch finally completes this with anon support.I am not a fan of any of that, this just speaks to the need to clean this up before endlessly adding more functionality and piles of complexity and confusion.
Let's just add some patches to do things sanely please.
Quoted 10 lines
Quoted 8 lines
Honestly I think this function needs to be fully refactored away from the appalling giant-ball-of-string mess it is now before we try to add in batching to be honest.
Let's not accumulate more technical debt.
I agree, I am happy to help in cleaning up this function.
Right, well then this series needs to have some clean up patches in it first.
Quoted 12 lines
Quoted 11 lines
Quoted 9 lines
mm/rmap.c +0
-1folio_put_swap (folio, subpage,1 );set_pte_at (mm, address, pvmw.pte, pteval); goto walk_abort; --2.34.1
Thanks, Lorenzo
Thanks< Lorenzo
PATCH 9/9 mm/rmap: enable batch unmapping of anonymous folios
In reply to Dev JainEnable batch clearing of ptes, and batch swap setting of ptes for anon 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.
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. Hence export page_anon_exclusive_sub_batch() to internal.h and reuse it.
arch_unmap_one() is only defined for sparc64; I am not comfortable changing that bit of code to enable batching, 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), especially when I have no way to test the code. 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 in __commit_ttu_anon_swapbacked_folio() itself, instead of doing some horrible goto jumping at the callsite of commit_ttu_anon_swapbacked_folio(). Similarly, do the jumping-over-batch immediately after we succeed in the unmapping of the entire batch, and continue to the next (unlikely) iteration.
Add a comment at relevant places to say that we are on a device-exclusive entry and not a present entry.
--- mm/internal.h | 26 ++++++++ mm/mprotect.c | 17 ----- mm/rmap.c | 170 +++++++++++++++++++++++++++++++++++--------------- 3 files changed, 144 insertions(+), 69 deletions(-)
mm/internal.h +26 -0
@@ -393,6 +393,32 @@ static inline unsigned int folio_pte_batch_flags(struct folio *folio,unsigned int folio_pte_batch (struct folio *folio,pte_t *ptep,pte_t pte,unsigned int max_nr); +/** +* page_anon_exclusive_sub_batch - Determine length of consecutive exclusive +* or maybe shared pages +* @start_idx: Starting index of the page array to scan from +* @max_len: Maximum length to look at +* @first_page: First page of the page array +* @expected_anon_exclusive: Whether to look for exclusive or !exclusive pages +* +* Determines length of consecutive ptes, pointing to pages being the same +* w.r.t the PageAnonExclusive bit. +* +* Context: The ptes point to consecutive pages of the same large folio. The +* ptes belong to the same PMD and VMA. +*/ +static inline int page_anon_exclusive_sub_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; +} +/** * pte_move_swp_offset - Move the swap entry offset field of a swap pte * forward or backward by delta
mm/mprotect.c +0 -17
@@ -138,23 +138,6 @@ static void prot_commit_flush_ptes(struct vm_area_struct *vma, unsigned long addtlb_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 int page_anon_exclusive_sub_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(),
mm/rmap.c +118 -52
216 lines not on this page · raw message
Signature · 1 line
2.34.1
Lorenzo Stoakes (Oracle)
maintainerQuoted 46 lines · Dev Jain
Speed up unmapping of anonymous large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry at [1]) 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_MB256
#defineSIZE_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;
Quoted 4 lines
}
On arm64, only showing one of the middle values in the distribution:
This doesn't seem very statistically valid.
How about you give median, stddev etc.? Variance matters too.
Quoted 5 lines
without patch:
madvise(MADV_PAGEOUT)took 52192959 ns (52.193 ms)with patch:
madvise(MADV_PAGEOUT)took 26676625 ns (26.677 ms)
You have a habit of only giving data on arm64, and not mentioning whether you've tested on any other arch/setup.
I've commented on this before so I'm a bit disappointed you've done the exact same thing here again. Especially since you've previously introduced regressions this way.
Please can you test this on (hardware!) x86-64 _at least_ as well and confirm you aren't regressing anything for 4K pages?
Quoted 34 lines
[1] https://lore.kernel.org/all/[email protected]/
---
Based on mm-unstablebb420884e9e0. mm-selftests pass.Dev Jain (9):
mm/rmap: makenr_pagessigned intry_to_unmap_onemm/rmap: initializenr_pagesto 1 at loop start intry_to_unmap_onemm/rmap: refactor lazyfree unmap commit path to
commit_ttu_lazyfree_folio()
mm/memory: Batch set uffd-wp markers during zapping mm/rmap: batch unmap folios belonging to uffd-wp VMAs mm/swapfile: Makefolio_dup_swapbatchable
mm/swapfile: Makefolio_put_swapbatchable
mm/rmap: introducefolio_try_share_anon_rmap_ptesmm/rmap: enable batch unmapping of anonymous foliosinclude/linux/mm_inline.h | 37 +++-- include/linux/page-flags.h | 11 ++ include/linux/rmap.h | 38 ++++- mm/internal.h | 26 ++++ mm/memory.c | 26 +--- mm/mprotect.c | 17 --- mm/rmap.c | 274 ++++++++++++++++++++++++------------- mm/shmem.c | 8 +- mm/swap.h | 10 +- mm/swapfile.c | 25 ++-- 10 files changed, 298 insertions(+), 174 deletions(-)--
2.34.1
Quoted 49 lines · Lorenzo Stoakes (Oracle)
Quoted 48 lines · Dev Jain
Speed up unmapping of anonymous large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry at [1]) 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_MB256
#defineSIZE_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;}
Quoted 6 lines
Quoted 2 lines
On arm64, only showing one of the middle values in the distribution:
This doesn't seem very statistically valid.
How about you give median, stddev etc.? Variance matters too.
Okay.
Quoted 9 lines
Quoted 6 lines
without patch:
madvise(MADV_PAGEOUT)took 52192959 ns (52.193 ms)with patch:
madvise(MADV_PAGEOUT)took 26676625 ns (26.677 ms)You have a habit of only giving data on arm64, and not mentioning whether you've tested on any other
arch/setup.
I did do an x86 build, forgot to mention that.
I didn't do the numbers thinking this patchset is quite generic and has got nothing to do with the arm64 cont bit - but arguably I should have.
Quoted 7 lines
I've commented on this before so I'm a bit disappointed you've done the exact same thing here again. Especially since you've previously introduced regressions this way.
Please can you test this on (hardware!) x86-64
_atleast_as well and confirm you aren't regressing anything for 4K pages?
Lemme go and manage that :)
Quoted 35 lines
Quoted 35 lines
[1] https://lore.kernel.org/all/[email protected]/
---
Based on mm-unstablebb420884e9e0. mm-selftests pass.Dev Jain (9):
mm/rmap: makenr_pagessigned intry_to_unmap_onemm/rmap: initializenr_pagesto 1 at loop start intry_to_unmap_onemm/rmap: refactor lazyfree unmap commit path to
commit_ttu_lazyfree_folio()
mm/memory: Batch set uffd-wp markers during zapping mm/rmap: batch unmap folios belonging to uffd-wp VMAs mm/swapfile: Makefolio_dup_swapbatchable
mm/swapfile: Makefolio_put_swapbatchable
mm/rmap: introducefolio_try_share_anon_rmap_ptesmm/rmap: enable batch unmapping of anonymous foliosinclude/linux/mm_inline.h | 37 +++-- include/linux/page-flags.h | 11 ++ include/linux/rmap.h | 38 ++++- mm/internal.h | 26 ++++ mm/memory.c | 26 +--- mm/mprotect.c | 17 --- mm/rmap.c | 274 ++++++++++++++++++++++++------------- mm/shmem.c | 8 +- mm/swap.h | 10 +- mm/swapfile.c | 25 ++-- 10 files changed, 298 insertions(+), 174 deletions(-)--
2.34.1
Lance Yang
reviewerQuoted 48 lines · Dev Jain
Speed up unmapping of anonymous large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry at [1]) 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_MB256
#defineSIZE_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; }
Quoted 7 lines
On arm64, only showing one of the middle values in the distribution:
without patch:
madvise(MADV_PAGEOUT)took 52192959 ns (52.193 ms)with patch:
madvise(MADV_PAGEOUT)took 26676625 ns (26.677 ms)
Good numbers! Just tested on x86 KVM with THP=never, no performance regression observed.
Cheers,
Lance
Quoted 52 lines · Lance Yang
Quoted 50 lines · Dev Jain
Speed up unmapping of anonymous large folios by clearing the ptes, and setting swap ptes, in one go.
The following benchmark (stolen from Barry at [1]) 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_MB256
#defineSIZE_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; }On arm64, only showing one of the middle values in the distribution:
Quoted 8 lines
Quoted 5 lines
without patch:
madvise(MADV_PAGEOUT)took 52192959 ns (52.193 ms)with patch:
madvise(MADV_PAGEOUT)took 26676625 ns (26.677 ms)Good numbers! Just tested on x86 KVM with THP=never, no performance regression observed.
Thanks Lance!
Although still I'll try to get no-regression numbers and perf-boost numbers on x86 myself and post it in next version.
Quoted 4 lines
Cheers,
Lance