* [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 15:38 ` Lorenzo Stoakes (ARM)
2026-07-20 14:29 ` [PATCH 2/8] mm/khugepaged: factor out page cache folio reading Pedro Falcato
` (7 subsequent siblings)
8 siblings, 1 reply; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
Separate out complex folio-related logic from the main collapse_file()
loop, and introduce a new helper struct to help marshal arguments back
and forth.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 189 ++++++++++++++++++++++++++++--------------------
1 file changed, 112 insertions(+), 77 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 27e8f3077e80..d4de507ac001 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2218,6 +2218,92 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)
i_mmap_unlock_read(mapping);
}
+struct collapse_file_state {
+ /* in-out parameter */
+ struct folio *folio;
+ /* in parameters */
+ struct address_space *mapping;
+ struct file *file;
+ struct xa_state *xas;
+ /* collapse end index */
+ pgoff_t end;
+ unsigned int is_shmem : 1;
+};
+
+static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
+{
+ struct address_space *mapping = state->mapping;
+ enum scan_result result = SCAN_SUCCEED;
+ const int is_shmem = state->is_shmem;
+ struct folio *folio = state->folio;
+
+ if (is_shmem) {
+ if (xa_is_value(folio) || !folio_test_uptodate(folio)) {
+ xas_unlock_irq(state->xas);
+ /* swap in or instantiate fallocated page */
+ if (shmem_get_folio(mapping->host, index, 0,
+ &folio, SGP_NOALLOC))
+ result = SCAN_FAIL;
+ /* drain lru cache to help folio_isolate_lru() */
+ lru_add_drain();
+ goto xa_unlocked;
+ } else if (folio_trylock(folio)) {
+ folio_get(folio);
+ } else {
+ result = SCAN_PAGE_LOCK;
+ goto xa_locked;
+ }
+ } else { /* !is_shmem */
+ if (!folio || xa_is_value(folio)) {
+ xas_unlock_irq(state->xas);
+ page_cache_sync_readahead(mapping, &state->file->f_ra,
+ state->file, index,
+ state->end - index);
+ /* drain lru cache to help folio_isolate_lru() */
+ lru_add_drain();
+ folio = filemap_lock_folio(mapping, index);
+ if (IS_ERR(folio))
+ result = SCAN_FAIL;
+ goto xa_unlocked;
+ } else if (folio_test_dirty(folio)) {
+ /*
+ * This page is dirty because it hasn't
+ * been flushed since first write.
+ *
+ * Trigger async flush for read-only files and
+ * hope the writeback is done when khugepaged
+ * revisits this page. Writable files can have
+ * their folios dirty at any time; blindly
+ * flushing them would cause undesirable
+ * system-wide writeback.
+ *
+ * This is a one-off situation. We are not
+ * forcing writeback in loop.
+ */
+ xas_unlock_irq(state->xas);
+ if (!inode_is_open_for_write(mapping->host))
+ filemap_flush(mapping);
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
+ goto xa_unlocked;
+ } else if (folio_test_writeback(folio)) {
+ xas_unlock_irq(state->xas);
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
+ goto xa_unlocked;
+ } else if (folio_trylock(folio)) {
+ folio_get(folio);
+ } else {
+ result = SCAN_PAGE_LOCK;
+ goto xa_locked;
+ }
+ }
+
+xa_locked:
+ xas_unlock_irq(state->xas);
+xa_unlocked:
+ state->folio = folio;
+ return result;
+}
+
/**
* collapse_file - collapse filemap/tmpfs/shmem pages into huge one.
*
@@ -2255,6 +2341,13 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
enum scan_result result = SCAN_SUCCEED;
int nr_none = 0;
bool is_shmem = shmem_file(file);
+ struct collapse_file_state state = {
+ .is_shmem = is_shmem,
+ .xas = &xas,
+ .mapping = mapping,
+ .file = file,
+ .end = end,
+ };
/*
* MADV_COLLAPSE ignores shmem huge config, so do not check shmem
@@ -2298,87 +2391,29 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
folio = xas_load(&xas);
VM_BUG_ON(index != xas.xa_index);
- if (is_shmem) {
- if (!folio) {
- /*
- * Stop if extent has been truncated or
- * hole-punched, and is now completely
- * empty.
- */
- if (index == start) {
- if (!xas_next_entry(&xas, end - 1)) {
- result = SCAN_TRUNCATED;
- goto xa_locked;
- }
- }
- nr_none++;
- index++;
- continue;
- }
-
- if (xa_is_value(folio) || !folio_test_uptodate(folio)) {
- xas_unlock_irq(&xas);
- /* swap in or instantiate fallocated page */
- if (shmem_get_folio(mapping->host, index, 0,
- &folio, SGP_NOALLOC)) {
- result = SCAN_FAIL;
- goto xa_unlocked;
- }
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
- } else if (folio_trylock(folio)) {
- folio_get(folio);
- xas_unlock_irq(&xas);
- } else {
- result = SCAN_PAGE_LOCK;
- goto xa_locked;
- }
- } else { /* !is_shmem */
- if (!folio || xa_is_value(folio)) {
- xas_unlock_irq(&xas);
- page_cache_sync_readahead(mapping, &file->f_ra,
- file, index,
- end - index);
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
- folio = filemap_lock_folio(mapping, index);
- if (IS_ERR(folio)) {
- result = SCAN_FAIL;
- goto xa_unlocked;
+ if (is_shmem && !folio) {
+ /*
+ * Stop if extent has been truncated or
+ * hole-punched, and is now completely
+ * empty.
+ */
+ if (index == start) {
+ if (!xas_next_entry(&xas, end - 1)) {
+ result = SCAN_TRUNCATED;
+ goto xa_locked;
}
- } else if (folio_test_dirty(folio)) {
- /*
- * This page is dirty because it hasn't
- * been flushed since first write.
- *
- * Trigger async flush for read-only files and
- * hope the writeback is done when khugepaged
- * revisits this page. Writable files can have
- * their folios dirty at any time; blindly
- * flushing them would cause undesirable
- * system-wide writeback.
- *
- * This is a one-off situation. We are not
- * forcing writeback in loop.
- */
- xas_unlock_irq(&xas);
- if (!inode_is_open_for_write(mapping->host))
- filemap_flush(mapping);
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- goto xa_unlocked;
- } else if (folio_test_writeback(folio)) {
- xas_unlock_irq(&xas);
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- goto xa_unlocked;
- } else if (folio_trylock(folio)) {
- folio_get(folio);
- xas_unlock_irq(&xas);
- } else {
- result = SCAN_PAGE_LOCK;
- goto xa_locked;
}
+ nr_none++;
+ index++;
+ continue;
}
+ /* At this point folio can be NULL, or a value. */
+ state.folio = folio;
+ result = prepare_collapse_file_folio(index, &state);
+ folio = state.folio;
+ if (result != SCAN_SUCCEED)
+ goto xa_unlocked;
/*
* The folio must be locked, so we can drop the i_pages lock
* without racing with truncate.
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file
2026-07-20 14:29 ` [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file Pedro Falcato
@ 2026-07-20 15:38 ` Lorenzo Stoakes (ARM)
2026-07-20 15:39 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 15+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-20 15:38 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, 20 Jul 2026 15:29:06 +0100, Pedro Falcato <pfalcato@suse.de> wrote:
> Separate out complex folio-related logic from the main collapse_file()
> loop, and introduce a new helper struct to help marshal arguments back
> and forth.
>
> Signed-off-by: Pedro Falcato <pfalcato@suse.de>
THANK YOU for doing this :) even looking into this bit of code is horrifying so
I can see you're putting yourself through it to make these changes... :)
I checked carefully and the logic looks to be identical, just various nits
etc. below.
>
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 27e8f3077e80..d4de507ac001 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -2218,6 +2218,92 @@ static void retract_page_tables(struct address_space *mapping, pgoff_t pgoff)
> i_mmap_unlock_read(mapping);
> }
>
> +struct collapse_file_state {
You had me at helper struct ;)
> + /* in-out parameter */
I think we can drop this comment, though hm it is useful to know this is updated
and the 'product' of the operation.
Maybe something like:
struct collapse_file_state {
/* Inputs */
struct file *file;
struct address_space *mapping;
struct xa_state *xas;
const pgoff_t pgoff_start, pgoff_end;
struct folio *folio;
};
Renaming index -> pgoff_start and end -> pgoff_end and keeping them both in the
struct rather than passing the index as a separate parameter implies the folio
is separate?
(Though any such renames should be a separate patch...)
> + struct folio *folio;
> + /* in parameters */
> + struct address_space *mapping;
> + struct file *file;
> + struct xa_state *xas;
Can we just make these input fields const + have the code tell you they're input
only? Though perhaps callers expect non-const (esp. the pointers, ugh.
> + /* collapse end index */
This feels a little superfluous? Feels odd not to have a start too but I guess
not necessary.
> + pgoff_t end;
> + unsigned int is_shmem : 1;
> +};
> +
> +static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
NIT - this is 101 chars :)
Either:
static enum scan_result
prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
OR:
static enum scan_result prepare_collapse_file_folio(pgoff_t index,
struct collapse_file_state *state)
Resolves this :)
Also - why are we passing index and not just putting that in the helper struct?
> [ ... skip 54 lines ... ]
> + } else if (folio_test_writeback(folio)) {
> + xas_unlock_irq(state->xas);
> + result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
> + goto xa_unlocked;
> + } else if (folio_trylock(folio)) {
> + folio_get(folio);
I see you drop the xas_unlock_irq(&xas) here because xa_locked has you covered
below, slight inconsistency in terms of the mechnical change but I think
unavoidable.
> + } else {
> + result = SCAN_PAGE_LOCK;
> + goto xa_locked;
I wonder if we could do away with this goto? That makes it match the
removed code less but you are already having to do that as above.
> + }
> + }
> +
> +xa_locked:
> + xas_unlock_irq(state->xas);
> +xa_unlocked:
> + state->folio = folio;
> + return result;
> +}
I'm guessing this will get some more cleanup in subsequent patches? As a little
long as-is.
But obviously makes sense to separate out into broadly mechanical moves first.
> @@ -2298,87 +2391,29 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
> [ ... skip 95 lines ... ]
> }
>
> + /* At this point folio can be NULL, or a value. */
> + state.folio = folio;
> + result = prepare_collapse_file_folio(index, &state);
> + folio = state.folio;
This is a little ugly, but I think probably necessarily at this point in the
operation!
> + if (result != SCAN_SUCCEED)
> + goto xa_unlocked;
> /*
> * The folio must be locked, so we can drop the i_pages lock
> * without racing with truncate.
I looked through the rest of the logic and it all seems to be correct!
Cheers, Lorenzo
NIT: This is 101 chars,
NIT: This is 101 chars,
--
Lorenzo Stoakes (ARM) <ljs@kernel.org>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file
2026-07-20 15:38 ` Lorenzo Stoakes (ARM)
@ 2026-07-20 15:39 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 15+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-20 15:39 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, Jul 20, 2026 at 04:38:55PM +0100, Lorenzo Stoakes (ARM) wrote:
> NIT: This is 101 chars,
>
> NIT: This is 101 chars,
(Please forgive this, b4 review converted my nits into tags and I couldn't undo
it so just sent it :P)
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 2/8] mm/khugepaged: factor out page cache folio reading
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
2026-07-20 14:29 ` [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 16:53 ` Lorenzo Stoakes (ARM)
2026-07-20 14:29 ` [PATCH 3/8] mm/khugepaged: factor out and simplify dirty/writeback handling Pedro Falcato
` (6 subsequent siblings)
8 siblings, 1 reply; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
The two mechanisms (shmem, filemap) are quite similar and only change in
subtleties. There is no need to have two variants. Factor this out into
its own separate function, shared by both paths.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 67 ++++++++++++++++++++++++++++++++-----------------
1 file changed, 44 insertions(+), 23 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index d4de507ac001..4cc6917a55c7 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2230,42 +2230,63 @@ struct collapse_file_state {
unsigned int is_shmem : 1;
};
+static struct folio *collapse_read_folio(pgoff_t index, struct collapse_file_state *state)
+{
+ const bool may_bring_uptodate = state->is_shmem;
+ struct folio *folio = state->folio;
+
+ if (!folio || xa_is_value(folio) ||
+ (may_bring_uptodate && !folio_test_uptodate(folio))) {
+ xas_unlock_irq(state->xas);
+ if (state->is_shmem) {
+ /* swap in or instantiate fallocated page */
+ if (shmem_get_folio(state->mapping->host, index, 0,
+ &folio, SGP_NOALLOC))
+ return NULL;
+ } else {
+ page_cache_sync_readahead(state->mapping, &state->file->f_ra,
+ state->file, index,
+ state->end - index);
+ folio = filemap_lock_folio(state->mapping, index);
+ if (IS_ERR(folio))
+ return NULL;
+ }
+ }
+
+ return folio;
+}
+
static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
{
struct address_space *mapping = state->mapping;
enum scan_result result = SCAN_SUCCEED;
const int is_shmem = state->is_shmem;
- struct folio *folio = state->folio;
+ struct folio *folio;
+
+ folio = collapse_read_folio(index, state);
+ if (!folio)
+ return SCAN_FAIL;
+ if (folio != state->folio) {
+ /*
+ * collapse_read_folio() got a new folio. This folio is locked
+ * and ref'd up. Nothing tricky (dirty, writeback, etc) can
+ * happen, so bail now. But before that, drain the local LRU
+ * add batch. Otherwise, it's very possible folio_isolate_lru()
+ * will not succeed.
+ */
+ lru_add_drain();
+ goto xa_unlocked;
+ }
if (is_shmem) {
- if (xa_is_value(folio) || !folio_test_uptodate(folio)) {
- xas_unlock_irq(state->xas);
- /* swap in or instantiate fallocated page */
- if (shmem_get_folio(mapping->host, index, 0,
- &folio, SGP_NOALLOC))
- result = SCAN_FAIL;
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
- goto xa_unlocked;
- } else if (folio_trylock(folio)) {
+ if (folio_trylock(folio)) {
folio_get(folio);
} else {
result = SCAN_PAGE_LOCK;
goto xa_locked;
}
} else { /* !is_shmem */
- if (!folio || xa_is_value(folio)) {
- xas_unlock_irq(state->xas);
- page_cache_sync_readahead(mapping, &state->file->f_ra,
- state->file, index,
- state->end - index);
- /* drain lru cache to help folio_isolate_lru() */
- lru_add_drain();
- folio = filemap_lock_folio(mapping, index);
- if (IS_ERR(folio))
- result = SCAN_FAIL;
- goto xa_unlocked;
- } else if (folio_test_dirty(folio)) {
+ if (folio_test_dirty(folio)) {
/*
* This page is dirty because it hasn't
* been flushed since first write.
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 2/8] mm/khugepaged: factor out page cache folio reading
2026-07-20 14:29 ` [PATCH 2/8] mm/khugepaged: factor out page cache folio reading Pedro Falcato
@ 2026-07-20 16:53 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 15+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-07-20 16:53 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Nico Pache, Ryan Roberts, Dev Jain,
Barry Song, Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, 20 Jul 2026 15:29:07 +0100, Pedro Falcato <pfalcato@suse.de> wrote:
> The two mechanisms (shmem, filemap) are quite similar and only change in
> subtleties. There is no need to have two variants. Factor this out into
> its own separate function, shared by both paths.
I think this commit message could have some more detail, it's difficult to
figure out what's new, what's moved, etc. so mentioning that and the broader
purpose of why you're doing this (iteratively improving file collapse in
khugepaged) would be nice.
Also there are multiple subtleties in this patch as noted below, none of which
you've described here.
You should describe every bit of logic you're changing compared to the original
and why that's OK.
Or ideally really, separate out the mechnical changes from the logical ones.
>
> Signed-off-by: Pedro Falcato <pfalcato@suse.de>
It's a little tricky to figure out what's moved, what's new, etc. don't be
afraid to break out into even more commits to help reviewers :)
>
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index d4de507ac001..4cc6917a55c7 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -2230,42 +2230,63 @@ struct collapse_file_state {
> unsigned int is_shmem : 1;
> };
>
It'd be nice to add some comments to these bits!
I noticed that you add them later in the series but it'd be good to add them
earlier and build them out as you go.
> +static struct folio *collapse_read_folio(pgoff_t index, struct collapse_file_state *state)
90 chars here be nice to try to stick to ~80 :) simliar comment to last patch.
Also the name seems to imply you're _always_ reading which isn't the case.
Not sure we need the collapse_ either. Something like 'maybe_read_folio()' with
the state implying what it's used for?
> +{
> + const bool may_bring_uptodate = state->is_shmem;
It's nice to make this self-documenting, but we could make life easier for
ourselves and avoid the (apparent) inconsistentcy of using this and
state->is_shmem separately by just doing the full check here:
const bool bring_uptodate = folio && state->is_shmem &&
!folio_test_uptodate(folio);
(putting the folio decl before it ofc)
> + struct folio *folio = state->folio;
> +
> + if (!folio || xa_is_value(folio) ||
> + (may_bring_uptodate && !folio_test_uptodate(folio))) {
This X or Y or (A and B) is confusing, separating out bring_uptodate makes it
easier.
Can make this a guard clause like:
/* No reason to read the folio. */
if (folio && !xa_is_value(folio) && !bring_uptodate)
return folio;
Which becomes its own form of self-documentation.
But I'm concerned whether this is correct.
For the shmem case you're going from (xa_is_value(folio) ||
!folio_test_uptodate(folio)) to (!folio || xa_is_value(folio) ||
!folio_test_uptodate(folio).
Again you do need to justify these changes somewhere.
> + xas_unlock_irq(state->xas);
Be nice to have a comment as to why we're unlocking this.
> + if (state->is_shmem) {
> + /* swap in or instantiate fallocated page */
I know it was in the original code but this comment feels superfluous.
> + if (shmem_get_folio(state->mapping->host, index, 0,
> + &folio, SGP_NOALLOC))
> + return NULL;
Looking through the logic I see that this returns NULL which gets checked first
below and results in SCAN_FAIL, but it's not obvious when first reading the
patch :)
Argues for more info in commit msg + separation of patches.
> [ ... skip 16 lines ... ]
> enum scan_result result = SCAN_SUCCEED;
> const int is_shmem = state->is_shmem;
> - struct folio *folio = state->folio;
> + struct folio *folio;
> +
> + folio = collapse_read_folio(index, state);
Again would be nice to have index in state :)
> + if (!folio)
> + return SCAN_FAIL;
Previously we always did the LRU drain regardless of whether shmem_get_folio()
or page_cache_sync_readahead() failed or not, now you're not doing that.
If an intentional change, you should explain why.
It'd also be nice to separate out mechnical moves from logic changes, as it's
hard to keep track of the new stuff you're doing vs. the stuff you're just moving around.
> + if (folio != state->folio) {
Is this always the case when the operation succeeds?
You're going from always doing LRU drain and exit early to - only if folio !=
state->folio which is a logic change AFAICT.
> + /*
> + * collapse_read_folio() got a new folio. This folio is locked
> + * and ref'd up. Nothing tricky (dirty, writeback, etc) can
> + * happen, so bail now. But before that, drain the local LRU
> + * add batch. Otherwise, it's very possible folio_isolate_lru()
> + * will not succeed.
Hmm :)
I feel we could collapse this into something more succinct like:
/*
* We read a new folio which is now locked and pinned. Drain so
* folio_isolate_lru() sees it and bail.
*/
> + */
> + lru_add_drain();
This is now out of order compared to the code it replaces for the !is_shmem
case. Is that OK?
Another thing that'd be nice to cover in the commit msg, it does need to be
justified.
> [ ... skip 31 lines ... ]
> - goto xa_unlocked;
> - } else if (folio_test_dirty(folio)) {
> + if (folio_test_dirty(folio)) {
> /*
> * This page is dirty because it hasn't
> * been flushed since first write.
Th resulting code in prepare_collapse_file_folio() at this point is pretty
horrible, I realise it might be an interim state but I'm sure it could be done
better, for instance separating out the is_shmem vs. !is_shmem logic into helper
functions etc.
Let's definitely try to split this up into more patches so it's easier to follow
on respin please!
Thanks, Lorenzo
--
Lorenzo Stoakes (ARM) <ljs@kernel.org>
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 3/8] mm/khugepaged: factor out and simplify dirty/writeback handling
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
2026-07-20 14:29 ` [PATCH 1/8] mm/khugepaged: separate out windy folio logic from collapse_file Pedro Falcato
2026-07-20 14:29 ` [PATCH 2/8] mm/khugepaged: factor out page cache folio reading Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 14:29 ` [PATCH 4/8] mm/khugepaged: simplify prepare folio locking and exit paths Pedro Falcato
` (5 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
Dirty/writeback folios can easily share most of the branch. These checks
are still gated under !is_shmem due to shmem not really having issues
with dirty folios.
While here, reflow the comment.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 46 ++++++++++++++++++++++++----------------------
1 file changed, 24 insertions(+), 22 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 4cc6917a55c7..ed5d23317177 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2262,6 +2262,7 @@ static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collap
enum scan_result result = SCAN_SUCCEED;
const int is_shmem = state->is_shmem;
struct folio *folio;
+ bool dirty;
folio = collapse_read_folio(index, state);
if (!folio)
@@ -2278,39 +2279,40 @@ static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collap
goto xa_unlocked;
}
- if (is_shmem) {
- if (folio_trylock(folio)) {
- folio_get(folio);
- } else {
- result = SCAN_PAGE_LOCK;
- goto xa_locked;
- }
- } else { /* !is_shmem */
- if (folio_test_dirty(folio)) {
+ if (!is_shmem) {
+ dirty = folio_test_dirty(folio);
+ if (dirty || folio_test_writeback(folio)) {
/*
- * This page is dirty because it hasn't
- * been flushed since first write.
+ * This folio is either dirty or under writeback.
+ * khugepaged cannot operate on such folios.
*
- * Trigger async flush for read-only files and
- * hope the writeback is done when khugepaged
- * revisits this page. Writable files can have
- * their folios dirty at any time; blindly
- * flushing them would cause undesirable
+ * For dirty folios, trigger async flush for
+ * read-only files and hope the writeback is done
+ * when khugepaged revisits this page. Writable
+ * files can have their folios dirty at any time;
+ * blindly flushing them would cause undesirable
* system-wide writeback.
*
* This is a one-off situation. We are not
* forcing writeback in loop.
*/
xas_unlock_irq(state->xas);
- if (!inode_is_open_for_write(mapping->host))
+ if (dirty && !inode_is_open_for_write(mapping->host))
filemap_flush(mapping);
result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
goto xa_unlocked;
- } else if (folio_test_writeback(folio)) {
- xas_unlock_irq(state->xas);
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- goto xa_unlocked;
- } else if (folio_trylock(folio)) {
+ }
+ }
+
+ if (is_shmem) {
+ if (folio_trylock(folio)) {
+ folio_get(folio);
+ } else {
+ result = SCAN_PAGE_LOCK;
+ goto xa_locked;
+ }
+ } else { /* !is_shmem */
+ if (folio_trylock(folio)) {
folio_get(folio);
} else {
result = SCAN_PAGE_LOCK;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 4/8] mm/khugepaged: simplify prepare folio locking and exit paths
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (2 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 3/8] mm/khugepaged: factor out and simplify dirty/writeback handling Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 14:29 ` [PATCH 5/8] mm/khugepaged: add kerneldoc to prepare_collapse_file_folio() Pedro Falcato
` (4 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
folio_trylock() + folio_get() do the exact same thing on both is_shmem
and !is_shmem paths. Unify them into one path.
At the end of the day, there are two large invariants for the function:
- xarray lock is not held at exit. Either it was dropped on error, or
dropped on success/IO.
- success always means a locked-and-ref'd-up folio.
After this transformation, it's trivial to notice how simple the error
paths are, so do away with the gotos and hoist some code.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 36 +++++++++++++-----------------------
1 file changed, 13 insertions(+), 23 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index ed5d23317177..d170b64378e6 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2259,8 +2259,8 @@ static struct folio *collapse_read_folio(pgoff_t index, struct collapse_file_sta
static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
{
struct address_space *mapping = state->mapping;
- enum scan_result result = SCAN_SUCCEED;
const int is_shmem = state->is_shmem;
+ enum scan_result ret = SCAN_SUCCEED;
struct folio *folio;
bool dirty;
@@ -2276,7 +2276,8 @@ static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collap
* will not succeed.
*/
lru_add_drain();
- goto xa_unlocked;
+ state->folio = folio;
+ return SCAN_SUCCEED;
}
if (!is_shmem) {
@@ -2299,32 +2300,21 @@ static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collap
xas_unlock_irq(state->xas);
if (dirty && !inode_is_open_for_write(mapping->host))
filemap_flush(mapping);
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- goto xa_unlocked;
+ return SCAN_PAGE_DIRTY_OR_WRITEBACK;
}
}
- if (is_shmem) {
- if (folio_trylock(folio)) {
- folio_get(folio);
- } else {
- result = SCAN_PAGE_LOCK;
- goto xa_locked;
- }
- } else { /* !is_shmem */
- if (folio_trylock(folio)) {
- folio_get(folio);
- } else {
- result = SCAN_PAGE_LOCK;
- goto xa_locked;
- }
- }
-xa_locked:
+ /*
+ * Note: trylock + simple folio_get() is safe here due to the i_pages
+ * lock being held; this excludes truncation happening in parallel.
+ */
+ if (!folio_trylock(folio))
+ ret = SCAN_PAGE_LOCK;
+ else
+ folio_get(folio);
xas_unlock_irq(state->xas);
-xa_unlocked:
- state->folio = folio;
- return result;
+ return ret;
}
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 5/8] mm/khugepaged: add kerneldoc to prepare_collapse_file_folio()
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (3 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 4/8] mm/khugepaged: simplify prepare folio locking and exit paths Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 14:29 ` [PATCH 6/8] mm/khugepaged: hoist isolation into collapse_isolate_folio() Pedro Falcato
` (3 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
The input-output expectations are not entirely clear, thus document it.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index d170b64378e6..db1f765b6ead 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2256,6 +2256,21 @@ static struct folio *collapse_read_folio(pgoff_t index, struct collapse_file_sta
return folio;
}
+
+/**
+ * prepare_collapse_file_folio() - Prepare the collapsing of a single file folio
+ * @index: folio index in the page cache
+ * @state: helper struct of state being passed back and forth.
+ *
+ * prepare_collapse_file_folio() helps prepare the THP collapsing of file folios
+ * (both shmem and !shmem), including the reading of folios, initial tests for
+ * dirty/writeback, locking, etc.
+ *
+ * Context: Expects @state->mapping->i_pages->xa_lock to be held. Releases the same xa_lock.
+ * On success, returns a refcounted and locked folio in @state->folio (that may
+ * not be the same that was passed in).
+ * Return: Appropriate scan_result value (on success, SCAN_SUCCEED).
+ */
static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collapse_file_state *state)
{
struct address_space *mapping = state->mapping;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 6/8] mm/khugepaged: hoist isolation into collapse_isolate_folio()
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (4 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 5/8] mm/khugepaged: add kerneldoc to prepare_collapse_file_folio() Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 14:29 ` [PATCH 7/8] mm/khugepaged: hoist more code " Pedro Falcato
` (2 subsequent siblings)
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 118 +++++++++++++++++++++++++++---------------------
1 file changed, 66 insertions(+), 52 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index db1f765b6ead..429e2c5833d0 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2332,6 +2332,69 @@ static enum scan_result prepare_collapse_file_folio(pgoff_t index, struct collap
return ret;
}
+static enum scan_result collapse_isolate_folio(struct collapse_file_state *state)
+{
+ struct folio *folio = state->folio;
+ enum scan_result result;
+
+ /*
+ * The folio must be locked, so we can drop the i_pages lock
+ * without racing with truncate.
+ */
+ VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
+
+ /* make sure the folio is up to date */
+ if (unlikely(!folio_test_uptodate(folio))) {
+ result = SCAN_FAIL;
+ goto out_unlock;
+ }
+
+ /*
+ * If file was truncated then extended, or hole-punched, before
+ * we locked the first folio, then a THP might be there already.
+ * This will be discovered on the first iteration.
+ */
+ if (is_pmd_order(folio_order(folio))) {
+ result = SCAN_PTE_MAPPED_HUGEPAGE;
+ goto out_unlock;
+ }
+
+ if (folio_mapping(folio) != state->mapping) {
+ result = SCAN_TRUNCATED;
+ goto out_unlock;
+ }
+
+ if (!state->is_shmem && (folio_test_dirty(folio) ||
+ folio_test_writeback(folio))) {
+ /*
+ * khugepaged only works on clean file-backed folios,
+ * so this folio is dirty because it hasn't been flushed
+ * since first write.
+ */
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
+ goto out_unlock;
+ }
+
+ if (!folio_isolate_lru(folio)) {
+ result = SCAN_DEL_PAGE_LRU;
+ goto out_unlock;
+ }
+
+ if (!filemap_release_folio(folio, GFP_KERNEL)) {
+ result = SCAN_PAGE_HAS_PRIVATE;
+ folio_putback_lru(folio);
+ goto out_unlock;
+ }
+
+ if (folio_mapped(folio))
+ try_to_unmap(folio, TTU_IGNORE_MLOCK | TTU_BATCH_FLUSH);
+ return SCAN_SUCCEED;
+out_unlock:
+ folio_unlock(folio);
+ folio_put(folio);
+ return result;
+}
+
/**
* collapse_file - collapse filemap/tmpfs/shmem pages into huge one.
*
@@ -2442,61 +2505,12 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
folio = state.folio;
if (result != SCAN_SUCCEED)
goto xa_unlocked;
- /*
- * The folio must be locked, so we can drop the i_pages lock
- * without racing with truncate.
- */
- VM_BUG_ON_FOLIO(!folio_test_locked(folio), folio);
-
- /* make sure the folio is up to date */
- if (unlikely(!folio_test_uptodate(folio))) {
- result = SCAN_FAIL;
- goto out_unlock;
- }
-
- /*
- * If file was truncated then extended, or hole-punched, before
- * we locked the first folio, then a THP might be there already.
- * This will be discovered on the first iteration.
- */
- if (is_pmd_order(folio_order(folio))) {
- result = SCAN_PTE_MAPPED_HUGEPAGE;
- goto out_unlock;
- }
-
- if (folio_mapping(folio) != mapping) {
- result = SCAN_TRUNCATED;
- goto out_unlock;
- }
-
- if (!is_shmem && (folio_test_dirty(folio) ||
- folio_test_writeback(folio))) {
- /*
- * khugepaged only works on clean file-backed folios,
- * so this folio is dirty because it hasn't been flushed
- * since first write.
- */
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- goto out_unlock;
- }
- if (!folio_isolate_lru(folio)) {
- result = SCAN_DEL_PAGE_LRU;
- goto out_unlock;
- }
-
- if (!filemap_release_folio(folio, GFP_KERNEL)) {
- result = SCAN_PAGE_HAS_PRIVATE;
- folio_putback_lru(folio);
- goto out_unlock;
- }
-
- if (folio_mapped(folio))
- try_to_unmap(folio,
- TTU_IGNORE_MLOCK | TTU_BATCH_FLUSH);
+ result = collapse_isolate_folio(&state);
+ if (result != SCAN_SUCCEED)
+ goto xa_unlocked;
xas_lock_irq(&xas);
-
VM_BUG_ON_FOLIO(folio != xa_load(xas.xa, index), folio);
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 7/8] mm/khugepaged: hoist more code into collapse_isolate_folio()
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (5 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 6/8] mm/khugepaged: hoist isolation into collapse_isolate_folio() Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 14:29 ` [PATCH 8/8] mm/khugepaged: fix and flesh out try_to_unmap_flush() comment Pedro Falcato
2026-07-20 15:21 ` [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Nico Pache
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
Checking expected refcounts or dirtyness has no need for the i_pages
xarray lock.
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 83 ++++++++++++++++++++++---------------------------
1 file changed, 37 insertions(+), 46 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 429e2c5833d0..a09e4e4e1943 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2382,13 +2382,48 @@ static enum scan_result collapse_isolate_folio(struct collapse_file_state *state
if (!filemap_release_folio(folio, GFP_KERNEL)) {
result = SCAN_PAGE_HAS_PRIVATE;
- folio_putback_lru(folio);
- goto out_unlock;
+ goto out_putback;
}
if (folio_mapped(folio))
try_to_unmap(folio, TTU_IGNORE_MLOCK | TTU_BATCH_FLUSH);
+ /*
+ * We control 2 + nr_pages references to the folio:
+ * - we hold a pin on it;
+ * - nr_pages reference from page cache;
+ * - one from lru_isolate_folio;
+ * If those are the only references, then any new usage
+ * of the folio will have to fetch it from the page
+ * cache. That requires locking the folio to handle
+ * truncate, so any new usage will be blocked until we
+ * unlock folio after collapse/during rollback.
+ */
+ if (folio_ref_count(folio) != 2 + folio_nr_pages(folio)) {
+ result = SCAN_PAGE_COUNT;
+ goto out_putback;
+ }
+
+ /*
+ * At this point, the folio is locked and unmapped. If the PTE
+ * was dirty, try_to_unmap() has transferred the dirty bit to
+ * the folio and we must not collapse it into a clean
+ * file-backed folio.
+ *
+ * If the folio is clean here, no one can write it until we
+ * drop the folio lock. A write through a stale TLB entry came
+ * from a clean PTE and must fault because the PTE has been
+ * cleared; the fault path has to take the folio lock before
+ * installing a writable mapping. Buffered write paths also
+ * have to take the folio lock before modifying file contents
+ * without a mapping, typically via write_begin_get_folio().
+ */
+ if (!state->is_shmem && folio_test_dirty(folio)) {
+ result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
+ goto out_putback;
+ }
return SCAN_SUCCEED;
+out_putback:
+ folio_putback_lru(folio);
out_unlock:
folio_unlock(folio);
folio_put(folio);
@@ -2513,55 +2548,11 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
xas_lock_irq(&xas);
VM_BUG_ON_FOLIO(folio != xa_load(xas.xa, index), folio);
- /*
- * We control 2 + nr_pages references to the folio:
- * - we hold a pin on it;
- * - nr_pages reference from page cache;
- * - one from lru_isolate_folio;
- * If those are the only references, then any new usage
- * of the folio will have to fetch it from the page
- * cache. That requires locking the folio to handle
- * truncate, so any new usage will be blocked until we
- * unlock folio after collapse/during rollback.
- */
- if (folio_ref_count(folio) != 2 + folio_nr_pages(folio)) {
- result = SCAN_PAGE_COUNT;
- xas_unlock_irq(&xas);
- folio_putback_lru(folio);
- goto out_unlock;
- }
-
- /*
- * At this point, the folio is locked and unmapped. If the PTE
- * was dirty, try_to_unmap() has transferred the dirty bit to
- * the folio and we must not collapse it into a clean
- * file-backed folio.
- *
- * If the folio is clean here, no one can write it until we
- * drop the folio lock. A write through a stale TLB entry came
- * from a clean PTE and must fault because the PTE has been
- * cleared; the fault path has to take the folio lock before
- * installing a writable mapping. Buffered write paths also
- * have to take the folio lock before modifying file contents
- * without a mapping, typically via write_begin_get_folio().
- */
- if (!is_shmem && folio_test_dirty(folio)) {
- result = SCAN_PAGE_DIRTY_OR_WRITEBACK;
- xas_unlock_irq(&xas);
- folio_putback_lru(folio);
- goto out_unlock;
- }
-
/*
* Accumulate the folios that are being collapsed.
*/
list_add_tail(&folio->lru, &pagelist);
index += folio_nr_pages(folio);
- continue;
-out_unlock:
- folio_unlock(folio);
- folio_put(folio);
- goto xa_unlocked;
}
xa_locked:
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* [PATCH 8/8] mm/khugepaged: fix and flesh out try_to_unmap_flush() comment
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (6 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 7/8] mm/khugepaged: hoist more code " Pedro Falcato
@ 2026-07-20 14:29 ` Pedro Falcato
2026-07-20 15:21 ` [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Nico Pache
8 siblings, 0 replies; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 14:29 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Lorenzo Stoakes
Cc: Pedro Falcato, Zi Yan, Baolin Wang, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
linux-mm, linux-kernel
Instead of vaguely handwaving that it should probably be done, flesh out
any correctness concerns with try_to_unmap batching, flushing and its
interaction with the rest of collapse_file().
Signed-off-by: Pedro Falcato <pfalcato@suse.de>
---
mm/khugepaged.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index a09e4e4e1943..1999e094df20 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -2560,15 +2560,19 @@ static enum scan_result collapse_file(struct mm_struct *mm, unsigned long addr,
xa_unlocked:
/*
- * If collapse is successful, flush must be done now before copying.
- * If collapse is unsuccessful, does flush actually need to be done?
- * Do it anyway, to clear the state.
+ * try_to_unmap() flush must be done now before copying, regardless
+ * of success or not. In case of success, folios are about to be
+ * copied and collapsed onto a single large folio. In that case,
+ * stale TLB entries need to be flushed out, so no racing write may
+ * get lost. In case of failure, stale TLB entries need to be flushed
+ * out before putting the folio (which can possibly free it).
*/
try_to_unmap_flush();
if (result == SCAN_SUCCEED && nr_none &&
!shmem_charge(mapping->host, nr_none))
result = SCAN_FAIL;
+
if (result != SCAN_SUCCEED) {
nr_none = 0;
goto rollback;
--
2.55.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH 0/8] mm/khugepaged: collapse_file() cleanups
2026-07-20 14:29 [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Pedro Falcato
` (7 preceding siblings ...)
2026-07-20 14:29 ` [PATCH 8/8] mm/khugepaged: fix and flesh out try_to_unmap_flush() comment Pedro Falcato
@ 2026-07-20 15:21 ` Nico Pache
2026-07-20 19:49 ` Pedro Falcato
8 siblings, 1 reply; 15+ messages in thread
From: Nico Pache @ 2026-07-20 15:21 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, Jul 20, 2026 at 8:33 AM Pedro Falcato <pfalcato@suse.de> wrote:
>
> Here are a bunch of cleanups for collapse_file() that attempt to aid
> code readability and understanding. This is part 1 out of N, and only
> attempts to address the main collapse loop. The exit path is still a
> mess but I figure it's much easier to review if I send this batch as-is,
> versus sending the whole thing.
Hi Pedro,
I have already prepped a cleanup series for collapse_file().
I was waiting for my work here
https://lore.kernel.org/lkml/20260715025941.1571316-1-npache@redhat.com/
to be reviewed (and in mm-unstable) before sending it out.
Our series have several similarities; for reference, here is my version.
https://gitlab.com/npache/linux/-/commits/khugepaged_file_refactor?ref_type=heads
Cheers,
-- Nico
>
> Based on top of latest mm-unstable.
>
> Cc: Zi Yan <ziy@nvidia.com>
> Cc: Baolin Wang <baolin.wang@linux.alibaba.com>
> Cc: "Liam R. Howlett" <liam@infradead.org>
> Cc: Nico Pache <npache@redhat.com>
> Cc: Ryan Roberts <ryan.roberts@arm.com>
> Cc: Dev Jain <dev.jain@arm.com>
> Cc: Barry Song <baohua@kernel.org>
> Cc: Lance Yang <lance.yang@linux.dev>
> Cc: Usama Arif <usama.arif@linux.dev>
> Cc: linux-mm@kvack.org
> Cc: linux-kernel@vger.kernel.org
> Pedro Falcato (8):
> mm/khugepaged: separate out windy folio logic from collapse_file
> mm/khugepaged: factor out page cache folio reading
> mm/khugepaged: factor out and simplify dirty/writeback handling
> mm/khugepaged: simplify prepare folio locking and exit paths
> mm/khugepaged: add kerneldoc to prepare_collapse_file_folio()
> mm/khugepaged: hoist isolation into collapse_isolate_folio()
> mm/khugepaged: hoist more code into collapse_isolate_folio()
> mm/khugepaged: fix and flesh out try_to_unmap_flush() comment
>
> mm/khugepaged.c | 424 ++++++++++++++++++++++++++++--------------------
> 1 file changed, 248 insertions(+), 176 deletions(-)
>
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH 0/8] mm/khugepaged: collapse_file() cleanups
2026-07-20 15:21 ` [PATCH 0/8] mm/khugepaged: collapse_file() cleanups Nico Pache
@ 2026-07-20 19:49 ` Pedro Falcato
2026-07-20 20:14 ` Nico Pache
0 siblings, 1 reply; 15+ messages in thread
From: Pedro Falcato @ 2026-07-20 19:49 UTC (permalink / raw)
To: Nico Pache
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, Jul 20, 2026 at 09:21:11AM -0600, Nico Pache wrote:
> On Mon, Jul 20, 2026 at 8:33 AM Pedro Falcato <pfalcato@suse.de> wrote:
> >
> > Here are a bunch of cleanups for collapse_file() that attempt to aid
> > code readability and understanding. This is part 1 out of N, and only
> > attempts to address the main collapse loop. The exit path is still a
> > mess but I figure it's much easier to review if I send this batch as-is,
> > versus sending the whole thing.
>
> Hi Pedro,
>
> I have already prepped a cleanup series for collapse_file().
>
> I was waiting for my work here
> https://lore.kernel.org/lkml/20260715025941.1571316-1-npache@redhat.com/
> to be reviewed (and in mm-unstable) before sending it out.
>
> Our series have several similarities; for reference, here is my version.
> https://gitlab.com/npache/linux/-/commits/khugepaged_file_refactor?ref_type=heads
Ugh, this is awkward. Do you have any suggestions on next steps?
I can try and take in some of your work on top of this series. But that might
be hairy.
This situation really does suck (I've definitely been there), but I would
prefer not to drop the patchset.
--
Pedro
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 0/8] mm/khugepaged: collapse_file() cleanups
2026-07-20 19:49 ` Pedro Falcato
@ 2026-07-20 20:14 ` Nico Pache
0 siblings, 0 replies; 15+ messages in thread
From: Nico Pache @ 2026-07-20 20:14 UTC (permalink / raw)
To: Pedro Falcato
Cc: Andrew Morton, David Hildenbrand, Lorenzo Stoakes, Zi Yan,
Baolin Wang, Liam R. Howlett, Ryan Roberts, Dev Jain, Barry Song,
Lance Yang, Usama Arif, linux-mm, linux-kernel
On Mon, Jul 20, 2026 at 1:49 PM Pedro Falcato <pfalcato@suse.de> wrote:
>
> On Mon, Jul 20, 2026 at 09:21:11AM -0600, Nico Pache wrote:
> > On Mon, Jul 20, 2026 at 8:33 AM Pedro Falcato <pfalcato@suse.de> wrote:
> > >
> > > Here are a bunch of cleanups for collapse_file() that attempt to aid
> > > code readability and understanding. This is part 1 out of N, and only
> > > attempts to address the main collapse loop. The exit path is still a
> > > mess but I figure it's much easier to review if I send this batch as-is,
> > > versus sending the whole thing.
> >
> > Hi Pedro,
> >
> > I have already prepped a cleanup series for collapse_file().
> >
> > I was waiting for my work here
> > https://lore.kernel.org/lkml/20260715025941.1571316-1-npache@redhat.com/
> > to be reviewed (and in mm-unstable) before sending it out.
> >
> > Our series have several similarities; for reference, here is my version.
> > https://gitlab.com/npache/linux/-/commits/khugepaged_file_refactor?ref_type=heads
>
> Ugh, this is awkward. Do you have any suggestions on next steps?
Let me review your series and see what differences stand out. You
referenced part 1 of N; I think my series covers many of the other
parts. I basically broke down each stage of collapse_file() into
helpers, and cleaned up each part respectively.
Whatever we decide, as long as collapse_file() gets the proper love im
ok with it. I did, however, communicate that I was cleaning this up to
David, Lorenzo, and others.
>
> I can try and take in some of your work on top of this series. But that might
> be hairy.
>
> This situation really does suck (I've definitely been there), but I would
> prefer not to drop the patchset.
Yeah that might be hard. However, there are other parts I cleaned up
that you haven't touched in this series so perhaps some of those apply
cleanly.
Cheers,
-- Nico
>
> --
> Pedro
>
^ permalink raw reply [flat|nested] 15+ messages in thread