* [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-07 12:59 [PATCH v2 0/4] mm: honour compact_unevictable_allowed in mlock and CMA paths Wandun Chen
@ 2026-07-07 12:59 ` Wandun Chen
2026-07-07 13:44 ` Lorenzo Stoakes
2026-07-07 12:59 ` [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio Wandun Chen
` (2 subsequent siblings)
3 siblings, 1 reply; 21+ messages in thread
From: Wandun Chen @ 2026-07-07 12:59 UTC (permalink / raw)
To: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
From: Wandun Chen <chenwandun@lixiang.com>
When compact_unevictable_allowed=0, unevictable pages should not be
migrated. However, mlock_folio_batch in the mlock[all] syscall introduces
a race, mlock_folio() sets PG_mlocked immediately but defers PG_unevictable
to mlock_folio_batch(), causing pages that are about to become unevictable
to be migrated, which violates the intent of compact_unevictable_allowed,
and causes spike latency in RT kernels [1].
In order to fix this, migration is forbidden for pages mapped into VMAs
marked with VM_LOCKED. In addition, two early-return paths are introduced,
filter out mlocked pages, return early to avoid unnecessary operations.
Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
Reported-by: Alexander Krabler <Alexander.Krabler@kuka.com>
Closes: https://lore.kernel.org/all/DU0PR01MB10385345F7153F334100981888259A@DU0PR01MB10385.eurprd01.prod.exchangelabs.com/ [1]
Suggested-by: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
Link: https://lore.kernel.org/linux-rt-users/33275585-f2db-4779-89f0-3ae24b455a67@suse.cz/#t
---
include/linux/compaction.h | 6 ++++++
include/linux/rmap.h | 3 +++
mm/compaction.c | 8 +++++++-
mm/migrate.c | 23 +++++++++++++++++++----
mm/rmap.c | 12 +++++++++---
5 files changed, 44 insertions(+), 8 deletions(-)
diff --git a/include/linux/compaction.h b/include/linux/compaction.h
index f29ef0653546..04e60f65b976 100644
--- a/include/linux/compaction.h
+++ b/include/linux/compaction.h
@@ -106,6 +106,7 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,
extern void __meminit kcompactd_run(int nid);
extern void __meminit kcompactd_stop(int nid);
extern void wakeup_kcompactd(pg_data_t *pgdat, int order, int highest_zoneidx);
+extern bool compaction_allow_unevictable(void);
#else
static inline void reset_isolation_suitable(pg_data_t *pgdat)
@@ -131,6 +132,11 @@ static inline void wakeup_kcompactd(pg_data_t *pgdat,
{
}
+static inline bool compaction_allow_unevictable(void)
+{
+ return true;
+}
+
#endif /* CONFIG_COMPACTION */
struct node;
diff --git a/include/linux/rmap.h b/include/linux/rmap.h
index 8dc0871e5f00..359c7426b6b9 100644
--- a/include/linux/rmap.h
+++ b/include/linux/rmap.h
@@ -102,6 +102,9 @@ enum ttu_flags {
* do a final flush if necessary */
TTU_RMAP_LOCKED = 0x80, /* do not grab rmap lock:
* caller holds it */
+ TTU_RESPECT_MLOCK = 0x100,/* leave VM_LOCKED vmas mapped instead
+ * of installing a migration entry
+ */
};
#ifdef CONFIG_MMU
diff --git a/mm/compaction.c b/mm/compaction.c
index f08765ade014..5d256930e389 100644
--- a/mm/compaction.c
+++ b/mm/compaction.c
@@ -1116,7 +1116,8 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
is_unevictable = folio_test_unevictable(folio);
/* Compaction might skip unevictable pages but CMA takes them */
- if (!(mode & ISOLATE_UNEVICTABLE) && is_unevictable)
+ if (!(mode & ISOLATE_UNEVICTABLE) &&
+ (is_unevictable || folio_test_mlocked(folio)))
goto isolate_fail_put;
/*
@@ -1898,6 +1899,11 @@ typedef enum {
* compactable pages.
*/
static int sysctl_compact_unevictable_allowed __read_mostly = CONFIG_COMPACT_UNEVICTABLE_DEFAULT;
+
+bool compaction_allow_unevictable(void)
+{
+ return sysctl_compact_unevictable_allowed;
+}
/*
* Tunable for proactive compaction. It determines how
* aggressively the kernel should compact memory in the
diff --git a/mm/migrate.c b/mm/migrate.c
index a786549551e3..3a15eb13e82b 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -1202,7 +1202,7 @@ static void migrate_folio_done(struct folio *src,
static int migrate_folio_unmap(new_folio_t get_new_folio,
free_folio_t put_new_folio, unsigned long private,
struct folio *src, struct folio **dstp, enum migrate_mode mode,
- struct list_head *ret)
+ struct list_head *ret, enum migrate_reason reason)
{
struct folio *dst;
int rc = -EAGAIN;
@@ -1210,6 +1210,7 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
struct anon_vma *anon_vma = NULL;
bool locked = false;
bool dst_locked = false;
+ enum ttu_flags ttu = 0;
dst = get_new_folio(src, private);
if (!dst)
@@ -1249,9 +1250,15 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
folio_lock(src);
}
locked = true;
- if (folio_test_mlocked(src))
+ if (folio_test_mlocked(src)) {
old_folio_state |= FOLIO_WAS_MLOCKED;
+ if (reason == MR_COMPACTION && !compaction_allow_unevictable()) {
+ rc = -EBUSY;
+ goto out;
+ }
+ }
+
if (folio_test_writeback(src)) {
/*
* Only in the case of a full synchronous migration is it
@@ -1324,7 +1331,14 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
/* Establish migration ptes */
VM_BUG_ON_FOLIO(folio_test_anon(src) &&
!folio_test_ksm(src) && !anon_vma, src);
- try_to_migrate(src, mode == MIGRATE_ASYNC ? TTU_BATCH_FLUSH : 0);
+
+ if (mode == MIGRATE_ASYNC)
+ ttu |= TTU_BATCH_FLUSH;
+
+ if (reason == MR_COMPACTION && !compaction_allow_unevictable())
+ ttu |= TTU_RESPECT_MLOCK;
+
+ try_to_migrate(src, ttu);
old_folio_state |= FOLIO_WAS_MAPPED;
}
@@ -1905,7 +1919,8 @@ static int migrate_pages_batch(struct list_head *from,
}
rc = migrate_folio_unmap(get_new_folio, put_new_folio,
- private, folio, &dst, mode, ret_folios);
+ private, folio, &dst, mode, ret_folios,
+ reason);
/*
* The rules are:
* 0: folio will be put on unmap_folios list,
diff --git a/mm/rmap.c b/mm/rmap.c
index 0fb7a1b82cf3..3cb7f6337d38 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -2420,6 +2420,9 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
unsigned long pfn;
unsigned long hsz = 0;
+ if ((flags & TTU_RESPECT_MLOCK) && (vma->vm_flags & VM_LOCKED))
+ return false;
+
/*
* When racing against e.g. zap_pte_range() on another cpu,
* in between its ptep_get_and_clear_full() and folio_remove_rmap_*(),
@@ -2741,11 +2744,14 @@ void try_to_migrate(struct folio *folio, enum ttu_flags flags)
};
/*
- * Migration always ignores mlock and only supports TTU_RMAP_LOCKED and
- * TTU_SPLIT_HUGE_PMD, TTU_SYNC, and TTU_BATCH_FLUSH flags.
+ * Migration normally ignores mlock, but TTU_RESPECT_MLOCK asks it to
+ * leave folios mapped into VM_LOCKED vmas alone. Only TTU_RMAP_LOCKED,
+ * TTU_SPLIT_HUGE_PMD, TTU_SYNC, TTU_BATCH_FLUSH and TTU_RESPECT_MLOCK
+ * are supported.
*/
if (WARN_ON_ONCE(flags & ~(TTU_RMAP_LOCKED | TTU_SPLIT_HUGE_PMD |
- TTU_SYNC | TTU_BATCH_FLUSH)))
+ TTU_SYNC | TTU_BATCH_FLUSH |
+ TTU_RESPECT_MLOCK)))
return;
if (folio_is_zone_device(folio) &&
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-07 12:59 ` [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction Wandun Chen
@ 2026-07-07 13:44 ` Lorenzo Stoakes
2026-07-07 13:55 ` Lorenzo Stoakes
2026-07-09 3:31 ` Wandun
0 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-07 13:44 UTC (permalink / raw)
To: Wandun Chen
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
(Being really nitty, your subject line is too long)
Please don't reference legacy VMA flags for newer patches 'do not migrate
folios mapped into mlocked VMAs...' works just as well.
On Tue, Jul 07, 2026 at 08:59:22PM +0800, Wandun Chen wrote:
> From: Wandun Chen <chenwandun@lixiang.com>
>
> When compact_unevictable_allowed=0, unevictable pages should not be
> migrated. However, mlock_folio_batch in the mlock[all] syscall introduces
> a race, mlock_folio() sets PG_mlocked immediately but defers PG_unevictable
> to mlock_folio_batch(), causing pages that are about to become unevictable
> to be migrated, which violates the intent of compact_unevictable_allowed,
> and causes spike latency in RT kernels [1].
>
> In order to fix this, migration is forbidden for pages mapped into VMAs
> marked with VM_LOCKED. In addition, two early-return paths are introduced,
Please don't reference legacy VMA flags. -> VMA_LOCKED_BIT.
> filter out mlocked pages, return early to avoid unnecessary operations.
>
> Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
Hmmmm why do you think my patch caused this? That was just a folio conversion?
Also I didn't think we liked having fixes spotted about a series with non-fixes
tags?
> Reported-by: Alexander Krabler <Alexander.Krabler@kuka.com>
> Closes: https://lore.kernel.org/all/DU0PR01MB10385345F7153F334100981888259A@DU0PR01MB10385.eurprd01.prod.exchangelabs.com/ [1]
> Suggested-by: Vlastimil Babka <vbabka@suse.cz>
> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
> Link: https://lore.kernel.org/linux-rt-users/33275585-f2db-4779-89f0-3ae24b455a67@suse.cz/#t
> ---
> include/linux/compaction.h | 6 ++++++
> include/linux/rmap.h | 3 +++
> mm/compaction.c | 8 +++++++-
> mm/migrate.c | 23 +++++++++++++++++++----
> mm/rmap.c | 12 +++++++++---
> 5 files changed, 44 insertions(+), 8 deletions(-)
>
> diff --git a/include/linux/compaction.h b/include/linux/compaction.h
> index f29ef0653546..04e60f65b976 100644
> --- a/include/linux/compaction.h
> +++ b/include/linux/compaction.h
> @@ -106,6 +106,7 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,
> extern void __meminit kcompactd_run(int nid);
> extern void __meminit kcompactd_stop(int nid);
> extern void wakeup_kcompactd(pg_data_t *pgdat, int order, int highest_zoneidx);
> +extern bool compaction_allow_unevictable(void);
Don't use extern. We remove extern as we go it's not needed.
>
> #else
> static inline void reset_isolation_suitable(pg_data_t *pgdat)
> @@ -131,6 +132,11 @@ static inline void wakeup_kcompactd(pg_data_t *pgdat,
> {
> }
>
> +static inline bool compaction_allow_unevictable(void)
> +{
> + return true;
> +}
> +
> #endif /* CONFIG_COMPACTION */
>
> struct node;
> diff --git a/include/linux/rmap.h b/include/linux/rmap.h
> index 8dc0871e5f00..359c7426b6b9 100644
> --- a/include/linux/rmap.h
> +++ b/include/linux/rmap.h
> @@ -102,6 +102,9 @@ enum ttu_flags {
> * do a final flush if necessary */
> TTU_RMAP_LOCKED = 0x80, /* do not grab rmap lock:
> * caller holds it */
> + TTU_RESPECT_MLOCK = 0x100,/* leave VM_LOCKED vmas mapped instead
-> VMA_LOCKED_BIT please. Also maybe just say mlock'd?
> + * of installing a migration entry
> + */
> };
>
> #ifdef CONFIG_MMU
> diff --git a/mm/compaction.c b/mm/compaction.c
> index f08765ade014..5d256930e389 100644
> --- a/mm/compaction.c
> +++ b/mm/compaction.c
> @@ -1116,7 +1116,8 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
> is_unevictable = folio_test_unevictable(folio);
>
> /* Compaction might skip unevictable pages but CMA takes them */
> - if (!(mode & ISOLATE_UNEVICTABLE) && is_unevictable)
> + if (!(mode & ISOLATE_UNEVICTABLE) &&
> + (is_unevictable || folio_test_mlocked(folio)))
Maybe just change is_unevictable to include this check?
Like:
is_unevictable = folio_test_unevictable(folio) ||
folio_test_mlocked(folio);
?
Also later you have:
if (((mode & ISOLATE_ASYNC_MIGRATE) && is_dirty) ||
(mapping && is_unevictable)) {
...
Which doesn't account for mlock as-is? Is that correct?
> goto isolate_fail_put;
>
> /*
> @@ -1898,6 +1899,11 @@ typedef enum {
> * compactable pages.
> */
> static int sysctl_compact_unevictable_allowed __read_mostly = CONFIG_COMPACT_UNEVICTABLE_DEFAULT;
> +
> +bool compaction_allow_unevictable(void)
> +{
> + return sysctl_compact_unevictable_allowed;
> +}
You add this helper but isolate_migratepages() still references
sysctl_compact_unevictable_allowed directly?
> /*
> * Tunable for proactive compaction. It determines how
> * aggressively the kernel should compact memory in the
> diff --git a/mm/migrate.c b/mm/migrate.c
> index a786549551e3..3a15eb13e82b 100644
> --- a/mm/migrate.c
> +++ b/mm/migrate.c
> @@ -1202,7 +1202,7 @@ static void migrate_folio_done(struct folio *src,
> static int migrate_folio_unmap(new_folio_t get_new_folio,
> free_folio_t put_new_folio, unsigned long private,
> struct folio *src, struct folio **dstp, enum migrate_mode mode,
> - struct list_head *ret)
> + struct list_head *ret, enum migrate_reason reason)
> {
> struct folio *dst;
> int rc = -EAGAIN;
> @@ -1210,6 +1210,7 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
> struct anon_vma *anon_vma = NULL;
> bool locked = false;
> bool dst_locked = false;
> + enum ttu_flags ttu = 0;
You reference ttu only in an if-block below no? So why are you declaring
this here? Move it to the if-block.
>
> dst = get_new_folio(src, private);
> if (!dst)
> @@ -1249,9 +1250,15 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
> folio_lock(src);
> }
> locked = true;
> - if (folio_test_mlocked(src))
> + if (folio_test_mlocked(src)) {
> old_folio_state |= FOLIO_WAS_MLOCKED;
>
> + if (reason == MR_COMPACTION && !compaction_allow_unevictable()) {
This should really be a helper since you repeat yourself and it's not
obvious what this is checking.
Like:
static migrate_mlock_allowed(enum migrate_reason reason)
{
/* Only compaction is disallowed. */
if (reason != MR_COMPACTION)
return true;
/* If we can compact unevictable folios, we are ok. */
if (compaction_allow_unevictable())
return true;
/* Conservative: if any folio could be mlock()'d, disallow. */
return false;
}
Then you could self-document what you're checking and avoid code duplication below.
> + rc = -EBUSY;
> + goto out;
> + }
> + }
> +
> if (folio_test_writeback(src)) {
> /*
> * Only in the case of a full synchronous migration is it
> @@ -1324,7 +1331,14 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
> /* Establish migration ptes */
> VM_BUG_ON_FOLIO(folio_test_anon(src) &&
> !folio_test_ksm(src) && !anon_vma, src);
Useful to convert VM_BUG_*() -> VM_WARN_*() (possibly _ONCE() here also) as we go!
> - try_to_migrate(src, mode == MIGRATE_ASYNC ? TTU_BATCH_FLUSH : 0);
> +
> + if (mode == MIGRATE_ASYNC)
> + ttu |= TTU_BATCH_FLUSH;
> +
> + if (reason == MR_COMPACTION && !compaction_allow_unevictable())
See above about deduplicating.
> + ttu |= TTU_RESPECT_MLOCK;
Hmm. I don't love 'respect mlock'. I guess we only know about the reason
being compaction here.
But I'm confused anyway. We have the folio, why aren't we just checking for
PG_mlocked() here instead of getting the rmap to see if it's mapped
anywhere with VMA_LOCKED_BIT?
> +
> + try_to_migrate(src, ttu);
> old_folio_state |= FOLIO_WAS_MAPPED;
> }
>
> @@ -1905,7 +1919,8 @@ static int migrate_pages_batch(struct list_head *from,
> }
>
> rc = migrate_folio_unmap(get_new_folio, put_new_folio,
> - private, folio, &dst, mode, ret_folios);
> + private, folio, &dst, mode, ret_folios,
> + reason);
> /*
> * The rules are:
> * 0: folio will be put on unmap_folios list,
> diff --git a/mm/rmap.c b/mm/rmap.c
> index 0fb7a1b82cf3..3cb7f6337d38 100644
> --- a/mm/rmap.c
> +++ b/mm/rmap.c
> @@ -2420,6 +2420,9 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
> unsigned long pfn;
> unsigned long hsz = 0;
>
> + if ((flags & TTU_RESPECT_MLOCK) && (vma->vm_flags & VM_LOCKED))
Please use the modern API for VMA flags. So:
if ((flags & TTU_RESPECT_MLOCK) && vma_test(VMA_LOCKED_BIT))
> + return false;
> +
> /*
> * When racing against e.g. zap_pte_range() on another cpu,
> * in between its ptep_get_and_clear_full() and folio_remove_rmap_*(),
> @@ -2741,11 +2744,14 @@ void try_to_migrate(struct folio *folio, enum ttu_flags flags)
> };
>
> /*
> - * Migration always ignores mlock and only supports TTU_RMAP_LOCKED and
> - * TTU_SPLIT_HUGE_PMD, TTU_SYNC, and TTU_BATCH_FLUSH flags.
> + * Migration normally ignores mlock, but TTU_RESPECT_MLOCK asks it to
> + * leave folios mapped into VM_LOCKED vmas alone. Only TTU_RMAP_LOCKED,
Again -> VMA_LOCKED_BIT.
> + * TTU_SPLIT_HUGE_PMD, TTU_SYNC, TTU_BATCH_FLUSH and TTU_RESPECT_MLOCK
> + * are supported.
> */
> if (WARN_ON_ONCE(flags & ~(TTU_RMAP_LOCKED | TTU_SPLIT_HUGE_PMD |
> - TTU_SYNC | TTU_BATCH_FLUSH)))
> + TTU_SYNC | TTU_BATCH_FLUSH |
> + TTU_RESPECT_MLOCK)))
> return;
>
> if (folio_is_zone_device(folio) &&
> --
> 2.43.0
>
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-07 13:44 ` Lorenzo Stoakes
@ 2026-07-07 13:55 ` Lorenzo Stoakes
2026-07-09 8:25 ` Wandun
2026-07-09 3:31 ` Wandun
1 sibling, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-07 13:55 UTC (permalink / raw)
To: Wandun Chen
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On Tue, Jul 07, 2026 at 02:44:50PM +0100, Lorenzo Stoakes wrote:
> See above about deduplicating.
>
> > + ttu |= TTU_RESPECT_MLOCK;
>
> Hmm. I don't love 'respect mlock'. I guess we only know about the reason
> being compaction here.
>
> But I'm confused anyway. We have the folio, why aren't we just checking for
> PG_mlocked() here instead of getting the rmap to see if it's mapped
> anywhere with VMA_LOCKED_BIT?
Also, since compaction_allow_unevictable() is a function that is accessible
elsewhere, you could literally just have a TTU_MIGRATION here instead and have
the rmap logic call compaction_allow_unevictable() instead rather than this.
And then you could adapt the function I suggested before not to take a reason
parameter but rather a 'is_migration' one instead possibly and then pass (ttu &
TTU_MIGRATION) in.
BUT. I still question whether this is at all needed since you have the folio you
can check for PG_mlocked...
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-07 13:55 ` Lorenzo Stoakes
@ 2026-07-09 8:25 ` Wandun
2026-07-09 15:00 ` Lorenzo Stoakes
0 siblings, 1 reply; 21+ messages in thread
From: Wandun @ 2026-07-09 8:25 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 7/7/26 21:55, Lorenzo Stoakes wrote:
> On Tue, Jul 07, 2026 at 02:44:50PM +0100, Lorenzo Stoakes wrote:
>> See above about deduplicating.
>>
>>> + ttu |= TTU_RESPECT_MLOCK;
>>
>> Hmm. I don't love 'respect mlock'. I guess we only know about the reason
>> being compaction here.
>>
>> But I'm confused anyway. We have the folio, why aren't we just checking for
>> PG_mlocked() here instead of getting the rmap to see if it's mapped
>> anywhere with VMA_LOCKED_BIT?
>
> Also, since compaction_allow_unevictable() is a function that is accessible
> elsewhere, you could literally just have a TTU_MIGRATION here instead and have
> the rmap logic call compaction_allow_unevictable() instead rather than this.
Do you mean:
1. change TTU_RESPECT_MLOCK to TTU_MIGRATION, that'is OK.
2. move call compaction_allow_unevictable() to try_to_migrate_one? but as you suggested
migrate_mlock_allowed function, it already called compaction_allow_unevictable()
in order to determine whether TTU_MIGRATION needs to be added. Did I misunderstand
something somewhere?
>
> And then you could adapt the function I suggested before not to take a reason
> parameter but rather a 'is_migration' one instead possibly and then pass (ttu &
> TTU_MIGRATION) in.
>
> BUT. I still question whether this is at all needed since you have the folio you
> can check for PG_mlocked...
I described a race scenario at this link:
https://lore.kernel.org/lkml/c372e68f-2bfc-45b6-a431-01bf55717247@gmail.com/
>
> Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-09 8:25 ` Wandun
@ 2026-07-09 15:00 ` Lorenzo Stoakes
0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-09 15:00 UTC (permalink / raw)
To: Wandun
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On Thu, Jul 09, 2026 at 04:25:27PM +0800, Wandun wrote:
>
>
> On 7/7/26 21:55, Lorenzo Stoakes wrote:
> > On Tue, Jul 07, 2026 at 02:44:50PM +0100, Lorenzo Stoakes wrote:
> >> See above about deduplicating.
> >>
> >>> + ttu |= TTU_RESPECT_MLOCK;
> >>
> >> Hmm. I don't love 'respect mlock'. I guess we only know about the reason
> >> being compaction here.
> >>
> >> But I'm confused anyway. We have the folio, why aren't we just checking for
> >> PG_mlocked() here instead of getting the rmap to see if it's mapped
> >> anywhere with VMA_LOCKED_BIT?
> >
> > Also, since compaction_allow_unevictable() is a function that is accessible
> > elsewhere, you could literally just have a TTU_MIGRATION here instead and have
> > the rmap logic call compaction_allow_unevictable() instead rather than this.
> Do you mean:
> 1. change TTU_RESPECT_MLOCK to TTU_MIGRATION, that'is OK.
> 2. move call compaction_allow_unevictable() to try_to_migrate_one? but as you suggested
> migrate_mlock_allowed function, it already called compaction_allow_unevictable()
> in order to determine whether TTU_MIGRATION needs to be added. Did I misunderstand
> something somewhere?
The next paragraph addresses this.
>
> >
> > And then you could adapt the function I suggested before not to take a reason
> > parameter but rather a 'is_migration' one instead possibly and then pass (ttu &
> > TTU_MIGRATION) in.
> >
> > BUT. I still question whether this is at all needed since you have the folio you
> > can check for PG_mlocked...
>
> I described a race scenario at this link:
> https://lore.kernel.org/lkml/c372e68f-2bfc-45b6-a431-01bf55717247@gmail.com/
OK thanks. You really have to spell this out in the code in a comment, this is
really non-obvious.
I will try to look at your reply in due course (am between jobs atm).
>
>
> >
> > Cheers, Lorenzo
>
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-07 13:44 ` Lorenzo Stoakes
2026-07-07 13:55 ` Lorenzo Stoakes
@ 2026-07-09 3:31 ` Wandun
2026-07-09 16:10 ` Zi Yan
1 sibling, 1 reply; 21+ messages in thread
From: Wandun @ 2026-07-09 3:31 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 7/7/26 21:44, Lorenzo Stoakes wrote:
> (Being really nitty, your subject line is too long)
>
> Please don't reference legacy VMA flags for newer patches 'do not migrate
> folios mapped into mlocked VMAs...' works just as well.
Got it.
>
> On Tue, Jul 07, 2026 at 08:59:22PM +0800, Wandun Chen wrote:
>> From: Wandun Chen <chenwandun@lixiang.com>
>>
>> When compact_unevictable_allowed=0, unevictable pages should not be
>> migrated. However, mlock_folio_batch in the mlock[all] syscall introduces
>> a race, mlock_folio() sets PG_mlocked immediately but defers PG_unevictable
>> to mlock_folio_batch(), causing pages that are about to become unevictable
>> to be migrated, which violates the intent of compact_unevictable_allowed,
>> and causes spike latency in RT kernels [1].
>>
>> In order to fix this, migration is forbidden for pages mapped into VMAs
>> marked with VM_LOCKED. In addition, two early-return paths are introduced,
>
> Please don't reference legacy VMA flags. -> VMA_LOCKED_BIT.
Got it.
>
>> filter out mlocked pages, return early to avoid unnecessary operations.
>>
>> Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
>
> Hmmmm why do you think my patch caused this? That was just a folio conversion?
Oh, I made a mistake, your patch was just a folio conversion.
Batching mlocked page was introduced in v5.18 by:
commit 2fbb0c10d1e8 ("mm/munlock: mlock_page() munlock_page() batch by pagevec")
Setting sysctl_compact_unevictable_allowed to zero was introduced in v5.7 by:
commit 6923aa0d8c62 ("mm/compaction: Disable compact_unevictable_allowed on RT")
So this issue exist after v5.18.
>
> Also I didn't think we liked having fixes spotted about a series with non-fixes
> tags?
Got it, will split this series in next version.
>
>> Reported-by: Alexander Krabler <Alexander.Krabler@kuka.com>
>> Closes: https://lore.kernel.org/all/DU0PR01MB10385345F7153F334100981888259A@DU0PR01MB10385.eurprd01.prod.exchangelabs.com/ [1]
>> Suggested-by: Vlastimil Babka <vbabka@suse.cz>
>> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
>> Link: https://lore.kernel.org/linux-rt-users/33275585-f2db-4779-89f0-3ae24b455a67@suse.cz/#t
>> ---
>> include/linux/compaction.h | 6 ++++++
>> include/linux/rmap.h | 3 +++
>> mm/compaction.c | 8 +++++++-
>> mm/migrate.c | 23 +++++++++++++++++++----
>> mm/rmap.c | 12 +++++++++---
>> 5 files changed, 44 insertions(+), 8 deletions(-)
>>
>> diff --git a/include/linux/compaction.h b/include/linux/compaction.h
>> index f29ef0653546..04e60f65b976 100644
>> --- a/include/linux/compaction.h
>> +++ b/include/linux/compaction.h
>> @@ -106,6 +106,7 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,
>> extern void __meminit kcompactd_run(int nid);
>> extern void __meminit kcompactd_stop(int nid);
>> extern void wakeup_kcompactd(pg_data_t *pgdat, int order, int highest_zoneidx);
>> +extern bool compaction_allow_unevictable(void);
>
> Don't use extern. We remove extern as we go it's not needed.
Got it.
>
>>
>> #else
>> static inline void reset_isolation_suitable(pg_data_t *pgdat)
>> @@ -131,6 +132,11 @@ static inline void wakeup_kcompactd(pg_data_t *pgdat,
>> {
>> }
>>
>> +static inline bool compaction_allow_unevictable(void)
>> +{
>> + return true;
>> +}
>> +
>> #endif /* CONFIG_COMPACTION */
>>
>> struct node;
>> diff --git a/include/linux/rmap.h b/include/linux/rmap.h
>> index 8dc0871e5f00..359c7426b6b9 100644
>> --- a/include/linux/rmap.h
>> +++ b/include/linux/rmap.h
>> @@ -102,6 +102,9 @@ enum ttu_flags {
>> * do a final flush if necessary */
>> TTU_RMAP_LOCKED = 0x80, /* do not grab rmap lock:
>> * caller holds it */
>> + TTU_RESPECT_MLOCK = 0x100,/* leave VM_LOCKED vmas mapped instead
>
> -> VMA_LOCKED_BIT please. Also maybe just say mlock'd?
Got it.
>
>> + * of installing a migration entry
>> + */
>> };
>>
>> #ifdef CONFIG_MMU
>> diff --git a/mm/compaction.c b/mm/compaction.c
>> index f08765ade014..5d256930e389 100644
>> --- a/mm/compaction.c
>> +++ b/mm/compaction.c
>> @@ -1116,7 +1116,8 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
>> is_unevictable = folio_test_unevictable(folio);
>>
>> /* Compaction might skip unevictable pages but CMA takes them */
>> - if (!(mode & ISOLATE_UNEVICTABLE) && is_unevictable)
>> + if (!(mode & ISOLATE_UNEVICTABLE) &&
>> + (is_unevictable || folio_test_mlocked(folio)))
>
> Maybe just change is_unevictable to include this check?
>
> Like:
>
> is_unevictable = folio_test_unevictable(folio) ||
> folio_test_mlocked(folio);
>
> ?
Sounds good, I'll fold mlock into is_unevictable so both checks
stay consistent.
>
> Also later you have:
>
> if (((mode & ISOLATE_ASYNC_MIGRATE) && is_dirty) ||
> (mapping && is_unevictable)) {
> ...
>
> Which doesn't account for mlock as-is? Is that correct?
IIUC, The is_unevictable check here mainly serves as a cheap pre-filter
for inaccessible mappings (which are always unevictable folio).
It's unrelated to the mlock.
>
>
>
>> goto isolate_fail_put;
>>
>> /*
>> @@ -1898,6 +1899,11 @@ typedef enum {
>> * compactable pages.
>> */
>> static int sysctl_compact_unevictable_allowed __read_mostly = CONFIG_COMPACT_UNEVICTABLE_DEFAULT;
>> +
>> +bool compaction_allow_unevictable(void)
>> +{
>> + return sysctl_compact_unevictable_allowed;
>> +}
>
> You add this helper but isolate_migratepages() still references
> sysctl_compact_unevictable_allowed directly?
I'll route that reference through compaction_allow_unevictable()
in next version.
>
>> /*
>> * Tunable for proactive compaction. It determines how
>> * aggressively the kernel should compact memory in the
>> diff --git a/mm/migrate.c b/mm/migrate.c
>> index a786549551e3..3a15eb13e82b 100644
>> --- a/mm/migrate.c
>> +++ b/mm/migrate.c
>> @@ -1202,7 +1202,7 @@ static void migrate_folio_done(struct folio *src,
>> static int migrate_folio_unmap(new_folio_t get_new_folio,
>> free_folio_t put_new_folio, unsigned long private,
>> struct folio *src, struct folio **dstp, enum migrate_mode mode,
>> - struct list_head *ret)
>> + struct list_head *ret, enum migrate_reason reason)
>> {
>> struct folio *dst;
>> int rc = -EAGAIN;
>> @@ -1210,6 +1210,7 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>> struct anon_vma *anon_vma = NULL;
>> bool locked = false;
>> bool dst_locked = false;
>> + enum ttu_flags ttu = 0;
>
> You reference ttu only in an if-block below no? So why are you declaring
> this here? Move it to the if-block.
Got it. I'll move it to the if-block in next version.
>
>>
>> dst = get_new_folio(src, private);
>> if (!dst)
>> @@ -1249,9 +1250,15 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>> folio_lock(src);
>> }
>> locked = true;
>> - if (folio_test_mlocked(src))
>> + if (folio_test_mlocked(src)) {
>> old_folio_state |= FOLIO_WAS_MLOCKED;
>>
>> + if (reason == MR_COMPACTION && !compaction_allow_unevictable()) {
>
> This should really be a helper since you repeat yourself and it's not
> obvious what this is checking.
>
> Like:
>
> static migrate_mlock_allowed(enum migrate_reason reason)
> {
> /* Only compaction is disallowed. */
> if (reason != MR_COMPACTION)
> return true;
>
> /* If we can compact unevictable folios, we are ok. */
> if (compaction_allow_unevictable())
> return true;
>
> /* Conservative: if any folio could be mlock()'d, disallow. */
> return false;
> }
>
> Then you could self-document what you're checking and avoid code duplication below.
Got it, it is more clear, thanks.
>
>> + rc = -EBUSY;
>> + goto out;
>> + }
>> + }
>> +
>> if (folio_test_writeback(src)) {
>> /*
>> * Only in the case of a full synchronous migration is it
>> @@ -1324,7 +1331,14 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>> /* Establish migration ptes */
>> VM_BUG_ON_FOLIO(folio_test_anon(src) &&
>> !folio_test_ksm(src) && !anon_vma, src);
>
> Useful to convert VM_BUG_*() -> VM_WARN_*() (possibly _ONCE() here also) as we go!
Got it.
>
>> - try_to_migrate(src, mode == MIGRATE_ASYNC ? TTU_BATCH_FLUSH : 0);
>> +
>> + if (mode == MIGRATE_ASYNC)
>> + ttu |= TTU_BATCH_FLUSH;
>> +
>> + if (reason == MR_COMPACTION && !compaction_allow_unevictable())
>
> See above about deduplicating.
>
>> + ttu |= TTU_RESPECT_MLOCK;
>
> Hmm. I don't love 'respect mlock'. I guess we only know about the reason
> being compaction here.
Right, we only know about the reason.
>
> But I'm confused anyway. We have the folio, why aren't we just checking for
> PG_mlocked() here instead of getting the rmap to see if it's mapped
> anywhere with VMA_LOCKED_BIT?
There was a race scenario without patch 02, vma may already marked with
VMA_LOCKED_BIT but folio has't marked with mlocked, such as below:
CPUA: mlock() CPUB: compaction / migration
mmap_write_lock()
mlock_fixup set VM_CLOKED
mlock_pte_range
mlock_folio(page N)
isolate page N+50 (mlock hasn't reached it)
migrate_folio_unmap
folio_test_mlocked() --> false, but VMA:VM_LOCKED
try_to_migrate()
rmap_walk(P) [anon_vma rwsem / i_mmap_rwsem, read]
try_to_migrate_one -->install migration entry
...reaches page N+50
skip migration entry (without patch 02)
mmap_write_unlock()
access page N + 50 --> wait for migration complete
migrate_folios_move
....
migrate complete
If apply patch 02, mlock itself will wait migration complete, so checking
VMA_LOCKED_BIT in the rmap path is no longer necessary, but this logic is
retained to avoid unnecessary migration operations.
>
>> +
>> + try_to_migrate(src, ttu);
>> old_folio_state |= FOLIO_WAS_MAPPED;
>> }
>>
>> @@ -1905,7 +1919,8 @@ static int migrate_pages_batch(struct list_head *from,
>> }
>>
>> rc = migrate_folio_unmap(get_new_folio, put_new_folio,
>> - private, folio, &dst, mode, ret_folios);
>> + private, folio, &dst, mode, ret_folios,
>> + reason);
>> /*
>> * The rules are:
>> * 0: folio will be put on unmap_folios list,
>> diff --git a/mm/rmap.c b/mm/rmap.c
>> index 0fb7a1b82cf3..3cb7f6337d38 100644
>> --- a/mm/rmap.c
>> +++ b/mm/rmap.c
>> @@ -2420,6 +2420,9 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
>> unsigned long pfn;
>> unsigned long hsz = 0;
>>
>> + if ((flags & TTU_RESPECT_MLOCK) && (vma->vm_flags & VM_LOCKED))
>
> Please use the modern API for VMA flags. So:
>
> if ((flags & TTU_RESPECT_MLOCK) && vma_test(VMA_LOCKED_BIT))
Got it.
>
>> + return false;
>> +
>> /*
>> * When racing against e.g. zap_pte_range() on another cpu,
>> * in between its ptep_get_and_clear_full() and folio_remove_rmap_*(),
>> @@ -2741,11 +2744,14 @@ void try_to_migrate(struct folio *folio, enum ttu_flags flags)
>> };
>>
>> /*
>> - * Migration always ignores mlock and only supports TTU_RMAP_LOCKED and
>> - * TTU_SPLIT_HUGE_PMD, TTU_SYNC, and TTU_BATCH_FLUSH flags.
>> + * Migration normally ignores mlock, but TTU_RESPECT_MLOCK asks it to
>> + * leave folios mapped into VM_LOCKED vmas alone. Only TTU_RMAP_LOCKED,
>
> Again -> VMA_LOCKED_BIT.
>
>> + * TTU_SPLIT_HUGE_PMD, TTU_SYNC, TTU_BATCH_FLUSH and TTU_RESPECT_MLOCK
>> + * are supported.
>> */
>> if (WARN_ON_ONCE(flags & ~(TTU_RMAP_LOCKED | TTU_SPLIT_HUGE_PMD |
>> - TTU_SYNC | TTU_BATCH_FLUSH)))
>> + TTU_SYNC | TTU_BATCH_FLUSH |
>> + TTU_RESPECT_MLOCK)))
>> return;
>>
>> if (folio_is_zone_device(folio) &&
>> --
>> 2.43.0
>>
>
> Thanks, Lorenzo
Thank you for the detailed review, Lorenzo.
many of the comments are very meaningful :)
Best regards
Wandun
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction
2026-07-09 3:31 ` Wandun
@ 2026-07-09 16:10 ` Zi Yan
0 siblings, 0 replies; 21+ messages in thread
From: Zi Yan @ 2026-07-09 16:10 UTC (permalink / raw)
To: Wandun
Cc: Lorenzo Stoakes, vbabka, david, rostedt, mhiramat,
Alexander.Krabler, hughd, fvdl, bigeasy, linux-mm, linux-kernel,
linux-trace-kernel, linux-rt-devel, akpm, surenb, mhocko,
jackmanb, hannes, riel, liam, harry, jannh, lance.yang,
mathieu.desnoyers, matthew.brost, joshua.hahnjy, rakie.kim,
byungchul, gourry, ying.huang, apopple, pfalcato
On 8 Jul 2026, at 23:31, Wandun wrote:
> On 7/7/26 21:44, Lorenzo Stoakes wrote:
>> (Being really nitty, your subject line is too long)
>>
>> Please don't reference legacy VMA flags for newer patches 'do not migrate
>> folios mapped into mlocked VMAs...' works just as well.
> Got it.
>>
>> On Tue, Jul 07, 2026 at 08:59:22PM +0800, Wandun Chen wrote:
>>> From: Wandun Chen <chenwandun@lixiang.com>
>>>
>>> When compact_unevictable_allowed=0, unevictable pages should not be
>>> migrated. However, mlock_folio_batch in the mlock[all] syscall introduces
>>> a race, mlock_folio() sets PG_mlocked immediately but defers PG_unevictable
>>> to mlock_folio_batch(), causing pages that are about to become unevictable
>>> to be migrated, which violates the intent of compact_unevictable_allowed,
>>> and causes spike latency in RT kernels [1].
>>>
>>> In order to fix this, migration is forbidden for pages mapped into VMAs
>>> marked with VM_LOCKED. In addition, two early-return paths are introduced,
>>
>> Please don't reference legacy VMA flags. -> VMA_LOCKED_BIT.
> Got it.
>>
>>> filter out mlocked pages, return early to avoid unnecessary operations.
>>>
>>> Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
>>
>> Hmmmm why do you think my patch caused this? That was just a folio conversion?
>
> Oh, I made a mistake, your patch was just a folio conversion.
>
> Batching mlocked page was introduced in v5.18 by:
> commit 2fbb0c10d1e8 ("mm/munlock: mlock_page() munlock_page() batch by pagevec")
>
> Setting sysctl_compact_unevictable_allowed to zero was introduced in v5.7 by:
> commit 6923aa0d8c62 ("mm/compaction: Disable compact_unevictable_allowed on RT")
>
> So this issue exist after v5.18.
>
>>
>> Also I didn't think we liked having fixes spotted about a series with non-fixes
>> tags?
> Got it, will split this series in next version.
>>
>>> Reported-by: Alexander Krabler <Alexander.Krabler@kuka.com>
>>> Closes: https://lore.kernel.org/all/DU0PR01MB10385345F7153F334100981888259A@DU0PR01MB10385.eurprd01.prod.exchangelabs.com/ [1]
>>> Suggested-by: Vlastimil Babka <vbabka@suse.cz>
>>> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
>>> Link: https://lore.kernel.org/linux-rt-users/33275585-f2db-4779-89f0-3ae24b455a67@suse.cz/#t
>>> ---
>>> include/linux/compaction.h | 6 ++++++
>>> include/linux/rmap.h | 3 +++
>>> mm/compaction.c | 8 +++++++-
>>> mm/migrate.c | 23 +++++++++++++++++++----
>>> mm/rmap.c | 12 +++++++++---
>>> 5 files changed, 44 insertions(+), 8 deletions(-)
>>>
>>> diff --git a/include/linux/compaction.h b/include/linux/compaction.h
>>> index f29ef0653546..04e60f65b976 100644
>>> --- a/include/linux/compaction.h
>>> +++ b/include/linux/compaction.h
>>> @@ -106,6 +106,7 @@ bool compaction_zonelist_suitable(struct alloc_context *ac, int order,
>>> extern void __meminit kcompactd_run(int nid);
>>> extern void __meminit kcompactd_stop(int nid);
>>> extern void wakeup_kcompactd(pg_data_t *pgdat, int order, int highest_zoneidx);
>>> +extern bool compaction_allow_unevictable(void);
>>
>> Don't use extern. We remove extern as we go it's not needed.
> Got it.
>>
>>>
>>> #else
>>> static inline void reset_isolation_suitable(pg_data_t *pgdat)
>>> @@ -131,6 +132,11 @@ static inline void wakeup_kcompactd(pg_data_t *pgdat,
>>> {
>>> }
>>>
>>> +static inline bool compaction_allow_unevictable(void)
>>> +{
>>> + return true;
>>> +}
>>> +
>>> #endif /* CONFIG_COMPACTION */
>>>
>>> struct node;
>>> diff --git a/include/linux/rmap.h b/include/linux/rmap.h
>>> index 8dc0871e5f00..359c7426b6b9 100644
>>> --- a/include/linux/rmap.h
>>> +++ b/include/linux/rmap.h
>>> @@ -102,6 +102,9 @@ enum ttu_flags {
>>> * do a final flush if necessary */
>>> TTU_RMAP_LOCKED = 0x80, /* do not grab rmap lock:
>>> * caller holds it */
>>> + TTU_RESPECT_MLOCK = 0x100,/* leave VM_LOCKED vmas mapped instead
>>
>> -> VMA_LOCKED_BIT please. Also maybe just say mlock'd?
> Got it.
>>
>>> + * of installing a migration entry
>>> + */
>>> };
>>>
>>> #ifdef CONFIG_MMU
>>> diff --git a/mm/compaction.c b/mm/compaction.c
>>> index f08765ade014..5d256930e389 100644
>>> --- a/mm/compaction.c
>>> +++ b/mm/compaction.c
>>> @@ -1116,7 +1116,8 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
>>> is_unevictable = folio_test_unevictable(folio);
>>>
>>> /* Compaction might skip unevictable pages but CMA takes them */
>>> - if (!(mode & ISOLATE_UNEVICTABLE) && is_unevictable)
>>> + if (!(mode & ISOLATE_UNEVICTABLE) &&
>>> + (is_unevictable || folio_test_mlocked(folio)))
>>
>> Maybe just change is_unevictable to include this check?
>>
>> Like:
>>
>> is_unevictable = folio_test_unevictable(folio) ||
>> folio_test_mlocked(folio);
>>
>> ?
> Sounds good, I'll fold mlock into is_unevictable so both checks
> stay consistent.
>>
>> Also later you have:
>>
>> if (((mode & ISOLATE_ASYNC_MIGRATE) && is_dirty) ||
>> (mapping && is_unevictable)) {
>> ...
>>
>> Which doesn't account for mlock as-is? Is that correct?
> IIUC, The is_unevictable check here mainly serves as a cheap pre-filter
> for inaccessible mappings (which are always unevictable folio).
> It's unrelated to the mlock.
>
>>
>>
>>
>>> goto isolate_fail_put;
>>>
>>> /*
>>> @@ -1898,6 +1899,11 @@ typedef enum {
>>> * compactable pages.
>>> */
>>> static int sysctl_compact_unevictable_allowed __read_mostly = CONFIG_COMPACT_UNEVICTABLE_DEFAULT;
>>> +
>>> +bool compaction_allow_unevictable(void)
>>> +{
>>> + return sysctl_compact_unevictable_allowed;
>>> +}
>>
>> You add this helper but isolate_migratepages() still references
>> sysctl_compact_unevictable_allowed directly?
> I'll route that reference through compaction_allow_unevictable()
> in next version.
>>
>>> /*
>>> * Tunable for proactive compaction. It determines how
>>> * aggressively the kernel should compact memory in the
>>> diff --git a/mm/migrate.c b/mm/migrate.c
>>> index a786549551e3..3a15eb13e82b 100644
>>> --- a/mm/migrate.c
>>> +++ b/mm/migrate.c
>>> @@ -1202,7 +1202,7 @@ static void migrate_folio_done(struct folio *src,
>>> static int migrate_folio_unmap(new_folio_t get_new_folio,
>>> free_folio_t put_new_folio, unsigned long private,
>>> struct folio *src, struct folio **dstp, enum migrate_mode mode,
>>> - struct list_head *ret)
>>> + struct list_head *ret, enum migrate_reason reason)
>>> {
>>> struct folio *dst;
>>> int rc = -EAGAIN;
>>> @@ -1210,6 +1210,7 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>>> struct anon_vma *anon_vma = NULL;
>>> bool locked = false;
>>> bool dst_locked = false;
>>> + enum ttu_flags ttu = 0;
>>
>> You reference ttu only in an if-block below no? So why are you declaring
>> this here? Move it to the if-block.
> Got it. I'll move it to the if-block in next version.
>>
>>>
>>> dst = get_new_folio(src, private);
>>> if (!dst)
>>> @@ -1249,9 +1250,15 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>>> folio_lock(src);
>>> }
>>> locked = true;
>>> - if (folio_test_mlocked(src))
>>> + if (folio_test_mlocked(src)) {
>>> old_folio_state |= FOLIO_WAS_MLOCKED;
>>>
>>> + if (reason == MR_COMPACTION && !compaction_allow_unevictable()) {
>>
>> This should really be a helper since you repeat yourself and it's not
>> obvious what this is checking.
>>
>> Like:
>>
>> static migrate_mlock_allowed(enum migrate_reason reason)
>> {
>> /* Only compaction is disallowed. */
>> if (reason != MR_COMPACTION)
>> return true;
>>
>> /* If we can compact unevictable folios, we are ok. */
>> if (compaction_allow_unevictable())
>> return true;
>>
>> /* Conservative: if any folio could be mlock()'d, disallow. */
>> return false;
>> }
>>
>> Then you could self-document what you're checking and avoid code duplication below.
> Got it, it is more clear, thanks.
>>
>>> + rc = -EBUSY;
>>> + goto out;
>>> + }
>>> + }
>>> +
>>> if (folio_test_writeback(src)) {
>>> /*
>>> * Only in the case of a full synchronous migration is it
>>> @@ -1324,7 +1331,14 @@ static int migrate_folio_unmap(new_folio_t get_new_folio,
>>> /* Establish migration ptes */
>>> VM_BUG_ON_FOLIO(folio_test_anon(src) &&
>>> !folio_test_ksm(src) && !anon_vma, src);
>>
>> Useful to convert VM_BUG_*() -> VM_WARN_*() (possibly _ONCE() here also) as we go!
> Got it.
>>
>>> - try_to_migrate(src, mode == MIGRATE_ASYNC ? TTU_BATCH_FLUSH : 0);
>>> +
>>> + if (mode == MIGRATE_ASYNC)
>>> + ttu |= TTU_BATCH_FLUSH;
>>> +
>>> + if (reason == MR_COMPACTION && !compaction_allow_unevictable())
>>
>> See above about deduplicating.
>>
>>> + ttu |= TTU_RESPECT_MLOCK;
>>
>> Hmm. I don't love 'respect mlock'. I guess we only know about the reason
>> being compaction here.
> Right, we only know about the reason.
>>
>> But I'm confused anyway. We have the folio, why aren't we just checking for
>> PG_mlocked() here instead of getting the rmap to see if it's mapped
>> anywhere with VMA_LOCKED_BIT?
>
> There was a race scenario without patch 02, vma may already marked with
> VMA_LOCKED_BIT but folio has't marked with mlocked, such as below:
>
>
> CPUA: mlock() CPUB: compaction / migration
>
> mmap_write_lock()
> mlock_fixup set VM_CLOKED
> mlock_pte_range
> mlock_folio(page N)
>
> isolate page N+50 (mlock hasn't reached it)
> migrate_folio_unmap
> folio_test_mlocked() --> false, but VMA:VM_LOCKED
> try_to_migrate()
> rmap_walk(P) [anon_vma rwsem / i_mmap_rwsem, read]
> try_to_migrate_one -->install migration entry
> ...reaches page N+50
> skip migration entry (without patch 02)
> mmap_write_unlock()
>
>
> access page N + 50 --> wait for migration complete
>
> migrate_folios_move
> ....
> migrate complete
>
>
>
> If apply patch 02, mlock itself will wait migration complete, so checking
> VMA_LOCKED_BIT in the rmap path is no longer necessary, but this logic is
> retained to avoid unnecessary migration operations.
>
For this race condition, since VMA has VM_LOCKED_BIT set, maybe you can
extend rwc->invalid_vma semantics to return 1:SKIP, 0:OK, -1:STOP.
Then install an invalid_vma function when !compaction_allow_unevictable().
The invalid_vma function returns -1:STOP for any vma with VM_LOCKED_BIT set.
__rmap_walk_file() and rmap_walk_anon() will need to break when -1:STOP
is returned.
This approach will not require a new TTU flag, although it requires more
code changes.
Best Regards,
Yan, Zi
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio
2026-07-07 12:59 [PATCH v2 0/4] mm: honour compact_unevictable_allowed in mlock and CMA paths Wandun Chen
2026-07-07 12:59 ` [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction Wandun Chen
@ 2026-07-07 12:59 ` Wandun Chen
2026-07-07 14:33 ` Lorenzo Stoakes
2026-07-07 12:59 ` [PATCH v2 3/4] mm/migrate: add tracepoint for folios unmapped during migration Wandun Chen
2026-07-07 12:59 ` [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range Wandun Chen
3 siblings, 1 reply; 21+ messages in thread
From: Wandun Chen @ 2026-07-07 12:59 UTC (permalink / raw)
To: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
From: Wandun Chen <chenwandun@lixiang.com>
In RT kernels, sysctl_compact_unevictable_allowed is false by default,
when the mlock/mlockall system call try to lock all the present page,
the mlock_pte_range function skips non-present entries. If these
non-present entries are migration entries, and the migration is not
guaranteed to have completed before the mlock/mlockall, it may result
in a page fault on subsequent access, which then waits for the
migration to finish, causing spike latency in RT kernels.
Fix it by waiting for the migration to complete during the mlock/mlockall
syscall when sysctl_compact_unevictable_allowed is false.
Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
Suggested-by: Vlastimil Babka <vbabka@suse.cz>
Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
Link: https://lore.kernel.org/lkml/c8793c0f-7156-4cb7-9e6e-7909397e2fff@kernel.org/#t
---
mm/mlock.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/mm/mlock.c b/mm/mlock.c
index 97e49038d8d3..ac65de40b22b 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -25,6 +25,7 @@
#include <linux/memcontrol.h>
#include <linux/mm_inline.h>
#include <linux/secretmem.h>
+#include <linux/compaction.h>
#include "internal.h"
@@ -361,8 +362,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
ptl = pmd_trans_huge_lock(pmd, vma);
if (ptl) {
- if (!pmd_present(*pmd))
+ if (!pmd_present(*pmd)) {
+ if (unlikely((vma->vm_flags & VM_LOCKED) &&
+ !compaction_allow_unevictable() &&
+ softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
+ spin_unlock(ptl);
+ pmd_migration_entry_wait(vma->vm_mm, pmd);
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
goto out;
+ }
if (is_huge_zero_pmd(*pmd))
goto out;
folio = pmd_folio(*pmd);
@@ -383,8 +393,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
for (pte = start_pte; addr != end; pte++, addr += PAGE_SIZE) {
ptent = ptep_get(pte);
- if (!pte_present(ptent))
+ if (!pte_present(ptent)) {
+ if (unlikely((vma->vm_flags & VM_LOCKED) &&
+ !compaction_allow_unevictable() &&
+ softleaf_is_migration(softleaf_from_pte(ptent)))) {
+ pte_unmap_unlock(start_pte, ptl);
+ migration_entry_wait(vma->vm_mm, pmd, addr);
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
continue;
+ }
folio = vm_normal_folio(vma, addr, ptent);
if (!folio || folio_is_zone_device(folio))
continue;
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio
2026-07-07 12:59 ` [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio Wandun Chen
@ 2026-07-07 14:33 ` Lorenzo Stoakes
2026-07-09 8:42 ` Sebastian Andrzej Siewior
2026-07-09 11:50 ` Wandun
0 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-07 14:33 UTC (permalink / raw)
To: Wandun Chen
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On Tue, Jul 07, 2026 at 08:59:23PM +0800, Wandun Chen wrote:
> From: Wandun Chen <chenwandun@lixiang.com>
>
> In RT kernels, sysctl_compact_unevictable_allowed is false by default,
> when the mlock/mlockall system call try to lock all the present page,
> the mlock_pte_range function skips non-present entries. If these
> non-present entries are migration entries, and the migration is not
> guaranteed to have completed before the mlock/mlockall, it may result
> in a page fault on subsequent access, which then waits for the
> migration to finish, causing spike latency in RT kernels.
Is this really noticable and measurable?
I suppose not too many ranges should be mock
>
> Fix it by waiting for the migration to complete during the mlock/mlockall
> syscall when sysctl_compact_unevictable_allowed is false.
Probably better to reference the sysctl itself rather than an arbitrary varible
name.
>
> Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
Again I have no idea why you chose this as the fixes target, especially for
this?
But in any case, why is this separate from the previous commit? I sthis an
entirely separate fix?
> Suggested-by: Vlastimil Babka <vbabka@suse.cz>
> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
> Link: https://lore.kernel.org/lkml/c8793c0f-7156-4cb7-9e6e-7909397e2fff@kernel.org/#t
A nit but please strip that #t.
> ---
> mm/mlock.c | 23 +++++++++++++++++++++--
> 1 file changed, 21 insertions(+), 2 deletions(-)
>
> diff --git a/mm/mlock.c b/mm/mlock.c
> index 97e49038d8d3..ac65de40b22b 100644
> --- a/mm/mlock.c
> +++ b/mm/mlock.c
> @@ -25,6 +25,7 @@
> #include <linux/memcontrol.h>
> #include <linux/mm_inline.h>
> #include <linux/secretmem.h>
> +#include <linux/compaction.h>
>
> #include "internal.h"
>
> @@ -361,8 +362,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
>
> ptl = pmd_trans_huge_lock(pmd, vma);
> if (ptl) {
> - if (!pmd_present(*pmd))
> + if (!pmd_present(*pmd)) {
> + if (unlikely((vma->vm_flags & VM_LOCKED) &&
-> vma_test(vma, VMA_LOCKED_BIT)...
What is the basis for your unlikely()? Don't use likely()/unlikely() just
because it feels right. Leave them out unless you have actual profiling data to
prove it.
Remove it please.
But I'm also confused here - why are you checking for VMAs that already had
VMA_LOCKED_BIT set when you are about to set it?
Surely this shouldn't be predicated on VMA_LOCKED_BIT?
> + !compaction_allow_unevictable() &&
Why do we care about compaction here? Are we assuming the softleaf migration
entry is present only for compaction reasons?
So these migration entries could be there for any reason? Does it matter? Is it
such a big deal, on mlock, to wait for migration entries? I think probably not.
But then maybe some weird workload gets affected... hmm.
> + softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
Use pmd_is_migration_entry()? :)
And technically you should use pmdp_get() I think? Maybe a situation where we
don't care/need the READ_ONCE() though.
> + spin_unlock(ptl);
> + pmd_migration_entry_wait(vma->vm_mm, pmd);
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
This code is really disgusting, let's please not just copy/paste open-coded
nested horror shows like this.
A good rule of thumb is if you see a bunch of code 'poking out' like this and
certainly if you copy/paste it or something very much like it, break it out into
a function.
So:
static bool wait_for_migration(const struct vm_area_struct *vma, softleaf_t entry)
{
>>>> As above I'm not sure should even be checking this?
if (!vma_test(vma, VMA_LOCKED_BIT))
return false;
>>>> As above I'm not sure this makes sense?
if (compaction_allowed_unevictable())
return false;
return softleaf_is_migration(entry);
}
Then call it like:
if (ptl) {
const pmd_t pmd = pmdp_get(pmd);
if (!pmd_present(pmd)) {
const softleaf_t entry = softleaf_from_pmd(pmd);
if (!wait_for_migration(vma, entry))
goto out;
spin_unlock(ptl);
pmd_migration_entry_wait(vma->vm_mm, pmd);
walk->action = ACTION_AGAIN;
return 0;
}
if (is_huge_zero_pmd(pmd))
goto out;
(etc. going *pmd -> pmd)
But we could probably do even better than that, and assuming your dubious checks
above aren't needed, we don't even need a helper then:
if (ptl) {
const pmd_t pmd = pmdp_get(pmd); // possibly just *pmd?
const softleaf_t entry = softleaf_from_pmd(pmd);
if (softleaf_is_migration(entry)) {
/* Wait for migration entries. */
spin_unlock(ptl);
pmd_migration_entry_wait(vma->vm_mm, pmd);
walk->action = ACTION_AGAIN;
return 0;
}
if (!pmd_present(pmd))
goto out;
etc.
And similar for the PTE stuff below.
This works because softleaf_from_pmd() (and pte) will give you a 'none' softleaf
if the entry is not a softlaf entry. So you can unconditionally call it on a
PMD.
> goto out;
> + }
> if (is_huge_zero_pmd(*pmd))
> goto out;
> folio = pmd_folio(*pmd);
> @@ -383,8 +393,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
>
> for (pte = start_pte; addr != end; pte++, addr += PAGE_SIZE) {
> ptent = ptep_get(pte);
> - if (!pte_present(ptent))
> + if (!pte_present(ptent)) {
> + if (unlikely((vma->vm_flags & VM_LOCKED) &&
> + !compaction_allow_unevictable() &&
> + softleaf_is_migration(softleaf_from_pte(ptent)))) {
> + pte_unmap_unlock(start_pte, ptl);
> + migration_entry_wait(vma->vm_mm, pmd, addr);
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> continue;
> + }
> folio = vm_normal_folio(vma, addr, ptent);
> if (!folio || folio_is_zone_device(folio))
> continue;
> --
> 2.43.0
>
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio
2026-07-07 14:33 ` Lorenzo Stoakes
@ 2026-07-09 8:42 ` Sebastian Andrzej Siewior
2026-07-09 11:50 ` Wandun
1 sibling, 0 replies; 21+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-07-09 8:42 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: Wandun Chen, vbabka, david, rostedt, mhiramat, Alexander.Krabler,
hughd, fvdl, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 2026-07-07 15:33:32 [+0100], Lorenzo Stoakes wrote:
> On Tue, Jul 07, 2026 at 08:59:23PM +0800, Wandun Chen wrote:
> > From: Wandun Chen <chenwandun@lixiang.com>
> >
> > In RT kernels, sysctl_compact_unevictable_allowed is false by default,
> > when the mlock/mlockall system call try to lock all the present page,
> > the mlock_pte_range function skips non-present entries. If these
> > non-present entries are migration entries, and the migration is not
> > guaranteed to have completed before the mlock/mlockall, it may result
> > in a page fault on subsequent access, which then waits for the
> > migration to finish, causing spike latency in RT kernels.
>
> Is this really noticable and measurable?
You want to avoid page faults like this which is why you do mlock in the
first place.
If the thread has a RT priority and it blocks here then other RT threads
will be scheduled and your migration thread, which will resolve this, is
SCHED_OTHER and the last one in the line. Assuming that thread has a 1ms
cycle/ deadline then it will likely miss it.
> I suppose not too many ranges should be mock
Sebastian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio
2026-07-07 14:33 ` Lorenzo Stoakes
2026-07-09 8:42 ` Sebastian Andrzej Siewior
@ 2026-07-09 11:50 ` Wandun
1 sibling, 0 replies; 21+ messages in thread
From: Wandun @ 2026-07-09 11:50 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 7/7/26 22:33, Lorenzo Stoakes wrote:
> On Tue, Jul 07, 2026 at 08:59:23PM +0800, Wandun Chen wrote:
>> From: Wandun Chen <chenwandun@lixiang.com>
>>
>> In RT kernels, sysctl_compact_unevictable_allowed is false by default,
>> when the mlock/mlockall system call try to lock all the present page,
>> the mlock_pte_range function skips non-present entries. If these
>> non-present entries are migration entries, and the migration is not
>> guaranteed to have completed before the mlock/mlockall, it may result
>> in a page fault on subsequent access, which then waits for the
>> migration to finish, causing spike latency in RT kernels.
>
> Is this really noticable and measurable?
Alexander Krabler reported a 1.1ms latency in [1]
https://lore.kernel.org/all/DU0PR01MB10385345F7153F334100981888259A@DU0PR01MB10385.eurprd01.prod.exchangelabs.com/
>
> I suppose not too many ranges should be mock
>
>>
>> Fix it by waiting for the migration to complete during the mlock/mlockall
>> syscall when sysctl_compact_unevictable_allowed is false.
>
> Probably better to reference the sysctl itself rather than an arbitrary varible
> name.
Got it.
>
>>
>> Fixes: 90d07210ab55 ("mm: mlock: use folios and a folio batch internally")
>
> Again I have no idea why you chose this as the fixes target, especially for
> this?
You're right, the Fixes tag was wrong.
Setting sysctl_compact_unevictable_allowed to zero was introduced in v5.7 by:
commit 6923aa0d8c62 ("mm/compaction: Disable compact_unevictable_allowed on RT")
Before this commit, mlocked page can be migrated, and dont need to wait for
migration complete.
After this commit, if don't wait for migration complete, this may result in
latency spikes.
(The batch mlock feature increases the probability of hitting migration entries.)
Will update Fixes tag in next version.
>
> But in any case, why is this separate from the previous commit? I sthis an
> entirely separate fix?
These two patches address two aspects of the same problem,
and I split them into separate patches mainly to make review easier.
Patch 1 mainly prevents pages to be migrated due to batch mlock;
patch 2 mainly ensures that pages undergoing migration have finished
migrating once the mlock system call completes.
>
>> Suggested-by: Vlastimil Babka <vbabka@suse.cz>
>> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
>> Link: https://lore.kernel.org/lkml/c8793c0f-7156-4cb7-9e6e-7909397e2fff@kernel.org/#t
>
> A nit but please strip that #t.
Got it.
>
>> ---
>> mm/mlock.c | 23 +++++++++++++++++++++--
>> 1 file changed, 21 insertions(+), 2 deletions(-)
>>
>> diff --git a/mm/mlock.c b/mm/mlock.c
>> index 97e49038d8d3..ac65de40b22b 100644
>> --- a/mm/mlock.c
>> +++ b/mm/mlock.c
>> @@ -25,6 +25,7 @@
>> #include <linux/memcontrol.h>
>> #include <linux/mm_inline.h>
>> #include <linux/secretmem.h>
>> +#include <linux/compaction.h>
>>
>> #include "internal.h"
>>
>> @@ -361,8 +362,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
>>
>> ptl = pmd_trans_huge_lock(pmd, vma);
>> if (ptl) {
>> - if (!pmd_present(*pmd))
>> + if (!pmd_present(*pmd)) {
>> + if (unlikely((vma->vm_flags & VM_LOCKED) &&
>
> -> vma_test(vma, VMA_LOCKED_BIT)...
>
> What is the basis for your unlikely()? Don't use likely()/unlikely() just
> because it feels right. Leave them out unless you have actual profiling data to
> prove it.
>
> Remove it please.
Got it.
>
> But I'm also confused here - why are you checking for VMAs that already had
> VMA_LOCKED_BIT set when you are about to set it?
>
> Surely this shouldn't be predicated on VMA_LOCKED_BIT?
munlock also would call mlock_pte_range, if page is under undergoing migration,
munlock dont need to wait migration complete.
>
>
>> + !compaction_allow_unevictable() &&
>
> Why do we care about compaction here? Are we assuming the softleaf migration
> entry is present only for compaction reasons?
>
> So these migration entries could be there for any reason? Does it matter? Is it
> such a big deal, on mlock, to wait for migration entries? I think probably not.
>
> But then maybe some weird workload gets affected... hmm.
Indeed, there are workloads that are affected by this.
In RT kernels, RT processes typically use mlock to prevent their memory from
being swapped out, and sysctl_compact_unevictable_is set to default 0 in the
RT kernels, so mlocked memory won't be migrated and won't encounter migration
entries on access.
Using compaction_allow_unevictable here is mainly to check whether the system
is latency-sensitive. In RT kernels, sysctl_compact_unevictable_is set to 0 by
default.
If there is no waiting, the migration may won't have completed by the time of
the subsequent memory access, and then we would have to wait for migration
to finish in the page fault path. Since pmd_migration_entry_wait/migration_entry_wait
has no priority-propagation mechanism, if the process executing mlock is an
RT process, this can lead to priority inversion and latency spike occurs.
>
>> + softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
>
> Use pmd_is_migration_entry()? :)
Got it.
>
> And technically you should use pmdp_get() I think? Maybe a situation where we
> don't care/need the READ_ONCE() though.
>
>> + spin_unlock(ptl);
>> + pmd_migration_entry_wait(vma->vm_mm, pmd);
>> + walk->action = ACTION_AGAIN;
>> + return 0;
>> + }
>
> This code is really disgusting, let's please not just copy/paste open-coded
> nested horror shows like this.
>
> A good rule of thumb is if you see a bunch of code 'poking out' like this and
> certainly if you copy/paste it or something very much like it, break it out into
> a function.
Got it, will do in next version.
>
> So:
>
> static bool wait_for_migration(const struct vm_area_struct *vma, softleaf_t entry)
> {
>>>>> As above I'm not sure should even be checking this?
> if (!vma_test(vma, VMA_LOCKED_BIT))
> return false;
>>>>> As above I'm not sure this makes sense?
> if (compaction_allowed_unevictable())
> return false;
>
> return softleaf_is_migration(entry);
> }
>
> Then call it like:
>
> if (ptl) {
> const pmd_t pmd = pmdp_get(pmd);
>
> if (!pmd_present(pmd)) {
> const softleaf_t entry = softleaf_from_pmd(pmd);
>
> if (!wait_for_migration(vma, entry))
> goto out;
>
> spin_unlock(ptl);
> pmd_migration_entry_wait(vma->vm_mm, pmd);
> walk->action = ACTION_AGAIN;
> return 0;
> }
> if (is_huge_zero_pmd(pmd))
> goto out;
>
> (etc. going *pmd -> pmd)
>
> But we could probably do even better than that, and assuming your dubious checks
> above aren't needed, we don't even need a helper then:
>
> if (ptl) {
> const pmd_t pmd = pmdp_get(pmd); // possibly just *pmd?
> const softleaf_t entry = softleaf_from_pmd(pmd);
>
> if (softleaf_is_migration(entry)) {
> /* Wait for migration entries. */
> spin_unlock(ptl);
> pmd_migration_entry_wait(vma->vm_mm, pmd);
> walk->action = ACTION_AGAIN;
> return 0;
> }
> if (!pmd_present(pmd))
> goto out;
> etc.
>
> And similar for the PTE stuff below.
>
> This works because softleaf_from_pmd() (and pte) will give you a 'none' softleaf
> if the entry is not a softlaf entry. So you can unconditionally call it on a
> PMD.
Got it.
>
>> goto out;
>> + }
>> if (is_huge_zero_pmd(*pmd))
>> goto out;
>> folio = pmd_folio(*pmd);
>> @@ -383,8 +393,17 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
>>
>> for (pte = start_pte; addr != end; pte++, addr += PAGE_SIZE) {
>> ptent = ptep_get(pte);
>> - if (!pte_present(ptent))
>> + if (!pte_present(ptent)) {
>> + if (unlikely((vma->vm_flags & VM_LOCKED) &&
>> + !compaction_allow_unevictable() &&
>> + softleaf_is_migration(softleaf_from_pte(ptent)))) {
>> + pte_unmap_unlock(start_pte, ptl);
>> + migration_entry_wait(vma->vm_mm, pmd, addr);
>> + walk->action = ACTION_AGAIN;
>> + return 0;
>> + }
>> continue;
>> + }
>> folio = vm_normal_folio(vma, addr, ptent);
>> if (!folio || folio_is_zone_device(folio))
>> continue;
>> --
>> 2.43.0
>>
>
> Thanks, Lorenzo
Thanks again.
Wandun
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 3/4] mm/migrate: add tracepoint for folios unmapped during migration
2026-07-07 12:59 [PATCH v2 0/4] mm: honour compact_unevictable_allowed in mlock and CMA paths Wandun Chen
2026-07-07 12:59 ` [PATCH v2 1/4] mm/migrate: do not migrate folios mapped into VM_LOCKED VMAs under compaction Wandun Chen
2026-07-07 12:59 ` [PATCH v2 2/4] mm/mlock: wait for migration to finish when mlocking a folio Wandun Chen
@ 2026-07-07 12:59 ` Wandun Chen
2026-07-07 12:59 ` [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range Wandun Chen
3 siblings, 0 replies; 21+ messages in thread
From: Wandun Chen @ 2026-07-07 12:59 UTC (permalink / raw)
To: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
From: Wandun Chen <chenwandun@lixiang.com>
Add mm_migrate_unmap_folio tracepoint, fired in try_to_migrate_one()
after a folio's mapping has been replaced by a migration entry.
It records the pfn, address, folio flags and vm_flags, making mlocked
folios under migration easy to observe.
Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
---
include/trace/events/migrate.h | 29 +++++++++++++++++++++++++++++
mm/rmap.c | 4 ++++
2 files changed, 33 insertions(+)
diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h
index 15ee2ef201b5..1264593ecaee 100644
--- a/include/trace/events/migrate.h
+++ b/include/trace/events/migrate.h
@@ -6,6 +6,7 @@
#define _TRACE_MIGRATE_H
#include <linux/tracepoint.h>
+#include <trace/events/mmflags.h>
#define MIGRATE_MODE \
EM( MIGRATE_ASYNC, "MIGRATE_ASYNC") \
@@ -137,6 +138,34 @@ DEFINE_EVENT(migration_pte, set_migration_pte,
TP_ARGS(addr, pte, order)
);
+TRACE_EVENT(mm_migrate_unmap_folio,
+
+ TP_PROTO(unsigned long pfn, unsigned long addr,
+ unsigned long page_flags, unsigned long vm_flags),
+
+ TP_ARGS(pfn, addr, page_flags, vm_flags),
+
+ TP_STRUCT__entry(
+ __field(unsigned long, pfn)
+ __field(unsigned long, addr)
+ __field(unsigned long, page_flags)
+ __field(unsigned long, vm_flags)
+ ),
+
+ TP_fast_assign(
+ __entry->pfn = pfn;
+ __entry->addr = addr;
+ __entry->page_flags = page_flags;
+ __entry->vm_flags = vm_flags;
+ ),
+
+ TP_printk("pfn=0x%lx addr=0x%lx page_flags=%s vm_flags=%s",
+ __entry->pfn,
+ __entry->addr,
+ show_page_flags(__entry->page_flags & PAGEFLAGS_MASK),
+ show_vma_flags(__entry->vm_flags))
+);
+
DEFINE_EVENT(migration_pte, remove_migration_pte,
TP_PROTO(unsigned long addr, unsigned long pte, int order),
TP_ARGS(addr, pte, order)
diff --git a/mm/rmap.c b/mm/rmap.c
index 3cb7f6337d38..b110548bebae 100644
--- a/mm/rmap.c
+++ b/mm/rmap.c
@@ -2716,6 +2716,10 @@ static bool try_to_migrate_one(struct folio *folio, struct vm_area_struct *vma,
hugetlb_remove_rmap(folio);
else
folio_remove_rmap_pte(folio, subpage, vma);
+
+ trace_mm_migrate_unmap_folio(folio_pfn(folio), address,
+ folio->flags.f, vma->vm_flags);
+
if (vma->vm_flags & VM_LOCKED)
mlock_drain_local();
folio_put(folio);
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 12:59 [PATCH v2 0/4] mm: honour compact_unevictable_allowed in mlock and CMA paths Wandun Chen
` (2 preceding siblings ...)
2026-07-07 12:59 ` [PATCH v2 3/4] mm/migrate: add tracepoint for folios unmapped during migration Wandun Chen
@ 2026-07-07 12:59 ` Wandun Chen
2026-07-07 13:36 ` Wandun
` (2 more replies)
3 siblings, 3 replies; 21+ messages in thread
From: Wandun Chen @ 2026-07-07 12:59 UTC (permalink / raw)
To: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
From: Wandun Chen <chenwandun@lixiang.com>
The region covered by mlock[all] may contain CMA pages. cma_alloc installs
migration entries in the page table, if a memory access occurs at this
point, it must wait for the migration to complete, which may cause
latency spikes on the RT kernels.
Try to move the migration cost into the mlock[all] caller, which is
typically a setup path. So reduce the chance of latency spikes on RT
kernels by migrating the currently mapped CMA pages out of CMA region.
Suggested-by: Frank van der Linden <fvdl@google.com>
Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
Link: https://lore.kernel.org/all/CAPTztWZpnX1j8-7yeppVUsxE=O9hbVeqricDjZt8_pnN7a-kBQ@mail.gmail.com/#t
---
mm/mlock.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 118 insertions(+), 1 deletion(-)
diff --git a/mm/mlock.c b/mm/mlock.c
index ac65de40b22b..f56c685533f5 100644
--- a/mm/mlock.c
+++ b/mm/mlock.c
@@ -25,6 +25,7 @@
#include <linux/memcontrol.h>
#include <linux/mm_inline.h>
#include <linux/secretmem.h>
+#include <linux/migrate.h>
#include <linux/compaction.h>
#include "internal.h"
@@ -428,6 +429,119 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
return 0;
}
+#ifdef CONFIG_CMA
+static int mlock_collect_migratable_pte_range(pmd_t *pmd, unsigned long addr,
+ unsigned long end, struct mm_walk *walk)
+{
+ struct vm_area_struct *vma = walk->vma;
+ struct list_head *folio_list = walk->private;
+ spinlock_t *ptl;
+ pte_t *start_pte, *pte;
+ pte_t ptent;
+ struct folio *folio;
+ unsigned int step = 1;
+
+ if (!(vma->vm_flags & VM_LOCKED))
+ return 0;
+
+ ptl = pmd_trans_huge_lock(pmd, vma);
+ if (ptl) {
+ if (!pmd_present(*pmd)) {
+ if (unlikely(softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
+ spin_unlock(ptl);
+ pmd_migration_entry_wait(vma->vm_mm, pmd);
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
+ goto out;
+ }
+ if (is_huge_zero_pmd(*pmd))
+ goto out;
+ folio = pmd_folio(*pmd);
+ if (folio_is_zone_device(folio))
+ goto out;
+ if (is_migrate_cma_page(&folio->page))
+ isolate_folio_to_list(folio, folio_list);
+ goto out;
+ }
+
+ start_pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
+ if (!start_pte) {
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
+
+ for (pte = start_pte; addr != end; pte += step, addr += step * PAGE_SIZE) {
+ step = 1;
+ ptent = ptep_get(pte);
+ if (!pte_present(ptent)) {
+ if (unlikely(softleaf_is_migration(softleaf_from_pte(ptent)))) {
+ pte_unmap_unlock(start_pte, ptl);
+ migration_entry_wait(vma->vm_mm, pmd, addr);
+ walk->action = ACTION_AGAIN;
+ return 0;
+ }
+ continue;
+ }
+ folio = vm_normal_folio(vma, addr, ptent);
+ if (!folio || folio_is_zone_device(folio))
+ continue;
+ step = folio_mlock_step(folio, pte, addr, end);
+ if (is_migrate_cma_page(&folio->page))
+ continue;
+ isolate_folio_to_list(folio, folio_list);
+ }
+ pte_unmap(start_pte);
+out:
+ spin_unlock(ptl);
+ cond_resched();
+ return 0;
+}
+
+static const struct mm_walk_ops mlock_collect_migratable_ops = {
+ .pmd_entry = mlock_collect_migratable_pte_range,
+ .walk_lock = PGWALK_RDLOCK,
+};
+
+static void mlock_migrate_cma_range(unsigned long start, unsigned long len)
+{
+ struct mm_struct *mm = current->mm;
+ unsigned long end = start + len;
+ LIST_HEAD(folio_list);
+ struct migration_target_control mtc = {
+ .nid = NUMA_NO_NODE,
+ .gfp_mask = GFP_HIGHUSER | __GFP_NOWARN,
+ .reason = MR_SYSCALL,
+ };
+
+ if (compaction_allow_unevictable())
+ return;
+
+ lru_cache_disable();
+
+ if (mmap_read_lock_killable(mm))
+ goto out;
+
+ walk_page_range(mm, start, end, &mlock_collect_migratable_ops,
+ &folio_list);
+ mmap_read_unlock(mm);
+
+ if (list_empty(&folio_list))
+ goto out;
+
+ if (migrate_pages(&folio_list, alloc_migration_target, NULL,
+ (unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL))
+ putback_movable_pages(&folio_list);
+out:
+ lru_cache_enable();
+}
+#else
+static inline void mlock_migrate_cma_range(unsigned long start,
+ unsigned long len)
+{
+}
+#endif /* CONFIG_CMA */
+
/*
* mlock_vma_pages_range() - mlock any pages already in the range,
* or munlock all pages in the range.
@@ -678,6 +792,7 @@ static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t fla
error = __mm_populate(start, len, 0);
if (error)
return __mlock_posix_error_return(error);
+ mlock_migrate_cma_range(start, len);
return 0;
}
@@ -790,8 +905,10 @@ SYSCALL_DEFINE1(mlockall, int, flags)
capable(CAP_IPC_LOCK))
ret = apply_mlockall_flags(flags);
mmap_write_unlock(current->mm);
- if (!ret && (flags & MCL_CURRENT))
+ if (!ret && (flags & MCL_CURRENT)) {
mm_populate(0, TASK_SIZE);
+ mlock_migrate_cma_range(0, TASK_SIZE);
+ }
return ret;
}
--
2.43.0
^ permalink raw reply related [flat|nested] 21+ messages in thread* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 12:59 ` [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range Wandun Chen
@ 2026-07-07 13:36 ` Wandun
2026-07-07 14:57 ` Lorenzo Stoakes
2026-07-07 14:54 ` Lorenzo Stoakes
2026-07-09 10:04 ` David Hildenbrand (Arm)
2 siblings, 1 reply; 21+ messages in thread
From: Wandun @ 2026-07-07 13:36 UTC (permalink / raw)
To: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 7/7/26 20:59, Wandun Chen wrote:
> From: Wandun Chen <chenwandun@lixiang.com>
>
> The region covered by mlock[all] may contain CMA pages. cma_alloc installs
> migration entries in the page table, if a memory access occurs at this
> point, it must wait for the migration to complete, which may cause
> latency spikes on the RT kernels.
>
> Try to move the migration cost into the mlock[all] caller, which is
> typically a setup path. So reduce the chance of latency spikes on RT
> kernels by migrating the currently mapped CMA pages out of CMA region.
>
> Suggested-by: Frank van der Linden <fvdl@google.com>
> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
> Link: https://lore.kernel.org/all/CAPTztWZpnX1j8-7yeppVUsxE=O9hbVeqricDjZt8_pnN7a-kBQ@mail.gmail.com/#t
> ---
> mm/mlock.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 118 insertions(+), 1 deletion(-)
>
> diff --git a/mm/mlock.c b/mm/mlock.c
> index ac65de40b22b..f56c685533f5 100644
> --- a/mm/mlock.c
> +++ b/mm/mlock.c
> @@ -25,6 +25,7 @@
> #include <linux/memcontrol.h>
> #include <linux/mm_inline.h>
> #include <linux/secretmem.h>
> +#include <linux/migrate.h>
> #include <linux/compaction.h>
>
> #include "internal.h"
> @@ -428,6 +429,119 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
> return 0;
> }
>
> +#ifdef CONFIG_CMA
> +static int mlock_collect_migratable_pte_range(pmd_t *pmd, unsigned long addr,
> + unsigned long end, struct mm_walk *walk)
> +{
> + struct vm_area_struct *vma = walk->vma;
> + struct list_head *folio_list = walk->private;
> + spinlock_t *ptl;
> + pte_t *start_pte, *pte;
> + pte_t ptent;
> + struct folio *folio;
> + unsigned int step = 1;
> +
> + if (!(vma->vm_flags & VM_LOCKED))
> + return 0;
> +
> + ptl = pmd_trans_huge_lock(pmd, vma);
> + if (ptl) {
> + if (!pmd_present(*pmd)) {
> + if (unlikely(softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
> + spin_unlock(ptl);
> + pmd_migration_entry_wait(vma->vm_mm, pmd);
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> + goto out;
> + }
> + if (is_huge_zero_pmd(*pmd))
> + goto out;
> + folio = pmd_folio(*pmd);
> + if (folio_is_zone_device(folio))
> + goto out;
> + if (is_migrate_cma_page(&folio->page))
> + isolate_folio_to_list(folio, folio_list);
> + goto out;
> + }
> +
> + start_pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
> + if (!start_pte) {
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> +
> + for (pte = start_pte; addr != end; pte += step, addr += step * PAGE_SIZE) {
> + step = 1;
> + ptent = ptep_get(pte);
> + if (!pte_present(ptent)) {
> + if (unlikely(softleaf_is_migration(softleaf_from_pte(ptent)))) {
> + pte_unmap_unlock(start_pte, ptl);
> + migration_entry_wait(vma->vm_mm, pmd, addr);
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> + continue;
> + }
> + folio = vm_normal_folio(vma, addr, ptent);
> + if (!folio || folio_is_zone_device(folio))
> + continue;
> + step = folio_mlock_step(folio, pte, addr, end);
> + if (is_migrate_cma_page(&folio->page))
Here should be, sorry about this.
if (!is_migrate_cma_page(&folio->page))
continue;
Best regards
Wandun
> + continue;
> + isolate_folio_to_list(folio, folio_list);
> + }
> + pte_unmap(start_pte);
> +out:
> + spin_unlock(ptl);
> + cond_resched();
> + return 0;
> +}
> +
> +static const struct mm_walk_ops mlock_collect_migratable_ops = {
> + .pmd_entry = mlock_collect_migratable_pte_range,
> + .walk_lock = PGWALK_RDLOCK,
> +};
> +
> +static void mlock_migrate_cma_range(unsigned long start, unsigned long len)
> +{
> + struct mm_struct *mm = current->mm;
> + unsigned long end = start + len;
> + LIST_HEAD(folio_list);
> + struct migration_target_control mtc = {
> + .nid = NUMA_NO_NODE,
> + .gfp_mask = GFP_HIGHUSER | __GFP_NOWARN,
> + .reason = MR_SYSCALL,
> + };
> +
> + if (compaction_allow_unevictable())
> + return;
> +
> + lru_cache_disable();
> +
> + if (mmap_read_lock_killable(mm))
> + goto out;
> +
> + walk_page_range(mm, start, end, &mlock_collect_migratable_ops,
> + &folio_list);
> + mmap_read_unlock(mm);
> +
> + if (list_empty(&folio_list))
> + goto out;
> +
> + if (migrate_pages(&folio_list, alloc_migration_target, NULL,
> + (unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL))
> + putback_movable_pages(&folio_list);
> +out:
> + lru_cache_enable();
> +}
> +#else
> +static inline void mlock_migrate_cma_range(unsigned long start,
> + unsigned long len)
> +{
> +}
> +#endif /* CONFIG_CMA */
> +
> /*
> * mlock_vma_pages_range() - mlock any pages already in the range,
> * or munlock all pages in the range.
> @@ -678,6 +792,7 @@ static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t fla
> error = __mm_populate(start, len, 0);
> if (error)
> return __mlock_posix_error_return(error);
> + mlock_migrate_cma_range(start, len);
> return 0;
> }
>
> @@ -790,8 +905,10 @@ SYSCALL_DEFINE1(mlockall, int, flags)
> capable(CAP_IPC_LOCK))
> ret = apply_mlockall_flags(flags);
> mmap_write_unlock(current->mm);
> - if (!ret && (flags & MCL_CURRENT))
> + if (!ret && (flags & MCL_CURRENT)) {
> mm_populate(0, TASK_SIZE);
> + mlock_migrate_cma_range(0, TASK_SIZE);
> + }
>
> return ret;
> }
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 13:36 ` Wandun
@ 2026-07-07 14:57 ` Lorenzo Stoakes
0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-07 14:57 UTC (permalink / raw)
To: Wandun
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On Tue, Jul 07, 2026 at 09:36:17PM +0800, Wandun wrote:
<snip>
> > diff --git a/mm/mlock.c b/mm/mlock.c
> > index ac65de40b22b..f56c685533f5 100644
> > --- a/mm/mlock.c
> > +++ b/mm/mlock.c
<snip>
> > +static int mlock_collect_migratable_pte_range(pmd_t *pmd, unsigned long addr,
> > + unsigned long end, struct mm_walk *walk)
> > +{
> > + struct vm_area_struct *vma = walk->vma;
> > + struct list_head *folio_list = walk->private;
> > + spinlock_t *ptl;
> > + pte_t *start_pte, *pte;
> > + pte_t ptent;
> > + struct folio *folio;
> > + unsigned int step = 1;
> > +
> > + if (!(vma->vm_flags & VM_LOCKED))
> > + return 0;
> > +
> > + ptl = pmd_trans_huge_lock(pmd, vma);
> > + if (ptl) {
> > + if (!pmd_present(*pmd)) {
> > + if (unlikely(softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
> > + spin_unlock(ptl);
> > + pmd_migration_entry_wait(vma->vm_mm, pmd);
> > + walk->action = ACTION_AGAIN;
> > + return 0;
> > + }
> > + goto out;
> > + }
> > + if (is_huge_zero_pmd(*pmd))
> > + goto out;
> > + folio = pmd_folio(*pmd);
> > + if (folio_is_zone_device(folio))
> > + goto out;
> > + if (is_migrate_cma_page(&folio->page))
> > + isolate_folio_to_list(folio, folio_list);
> > + goto out;
> > + }
> > +
> > + start_pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
> > + if (!start_pte) {
> > + walk->action = ACTION_AGAIN;
> > + return 0;
> > + }
> > +
> > + for (pte = start_pte; addr != end; pte += step, addr += step * PAGE_SIZE) {
> > + step = 1;
> > + ptent = ptep_get(pte);
> > + if (!pte_present(ptent)) {
> > + if (unlikely(softleaf_is_migration(softleaf_from_pte(ptent)))) {
> > + pte_unmap_unlock(start_pte, ptl);
> > + migration_entry_wait(vma->vm_mm, pmd, addr);
> > + walk->action = ACTION_AGAIN;
> > + return 0;
> > + }
> > + continue;
> > + }
> > + folio = vm_normal_folio(vma, addr, ptent);
> > + if (!folio || folio_is_zone_device(folio))
> > + continue;
> > + step = folio_mlock_step(folio, pte, addr, end);
> > + if (is_migrate_cma_page(&folio->page))
> Here should be, sorry about this.
> if (!is_migrate_cma_page(&folio->page))
> continue;
But above you do the:
if (is_migrate_cma_page(&folio->page))
isolate_folio_to_list(folio, folio_list);
Pattern :) so these should be consistent. But you're copy/pasting and need to
not do that so it's kind of by the by.
Anyway, yes I assumed you meant this. But see the main review, I am not really a
fan of the implementation, overall.
>
> Best regards
> Wandun
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 12:59 ` [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range Wandun Chen
2026-07-07 13:36 ` Wandun
@ 2026-07-07 14:54 ` Lorenzo Stoakes
2026-07-09 9:13 ` Sebastian Andrzej Siewior
2026-07-09 10:04 ` David Hildenbrand (Arm)
2 siblings, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes @ 2026-07-07 14:54 UTC (permalink / raw)
To: Wandun Chen
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On Tue, Jul 07, 2026 at 08:59:25PM +0800, Wandun Chen wrote:
> From: Wandun Chen <chenwandun@lixiang.com>
>
> The region covered by mlock[all] may contain CMA pages. cma_alloc installs
> migration entries in the page table, if a memory access occurs at this
> point, it must wait for the migration to complete, which may cause
> latency spikes on the RT kernels.
>
> Try to move the migration cost into the mlock[all] caller, which is
> typically a setup path. So reduce the chance of latency spikes on RT
> kernels by migrating the currently mapped CMA pages out of CMA region.
'reduce the chances of latency' so do you have any data to back this invasive
change or not?
And for RT, but nothing in here at all checks for RT? You're using this
compaction sysctl as an RT check somehow? That's gross.
This doesn't feel like the right solution.
>
> Suggested-by: Frank van der Linden <fvdl@google.com>
> Signed-off-by: Wandun Chen <chenwandun@lixiang.com>
> Link: https://lore.kernel.org/all/CAPTztWZpnX1j8-7yeppVUsxE=O9hbVeqricDjZt8_pnN7a-kBQ@mail.gmail.com/#t
> ---
> mm/mlock.c | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 118 insertions(+), 1 deletion(-)
>
> diff --git a/mm/mlock.c b/mm/mlock.c
> index ac65de40b22b..f56c685533f5 100644
> --- a/mm/mlock.c
> +++ b/mm/mlock.c
> @@ -25,6 +25,7 @@
> #include <linux/memcontrol.h>
> #include <linux/mm_inline.h>
> #include <linux/secretmem.h>
> +#include <linux/migrate.h>
> #include <linux/compaction.h>
>
> #include "internal.h"
> @@ -428,6 +429,119 @@ static int mlock_pte_range(pmd_t *pmd, unsigned long addr,
> return 0;
> }
>
> +#ifdef CONFIG_CMA
Ugh yuck. This is horrible. Why are we polluting mlock.c with CMA stuff?
Also no comment?
> +static int mlock_collect_migratable_pte_range(pmd_t *pmd, unsigned long addr,
> + unsigned long end, struct mm_walk *walk)
> +{
You've literally copy/pasted mlock_pte_range(), this is disgusting.
Please don't copy/paste code like this, this isn't PHP, it's the kernel, it's
completely unacceptable.
> + struct vm_area_struct *vma = walk->vma;
> + struct list_head *folio_list = walk->private;
> + spinlock_t *ptl;
> + pte_t *start_pte, *pte;
> + pte_t ptent;
> + struct folio *folio;
> + unsigned int step = 1;
> +
> + if (!(vma->vm_flags & VM_LOCKED))
Once again vma_test(vma, VMA_LOCKED_BIT) please.
But also, again (since you copy/pasted), why? You're literally mlocking here...
You going for that 'mlocking already mlocked ranges' sweet spot or am I missing
something?
> + return 0;
> +
> + ptl = pmd_trans_huge_lock(pmd, vma);
> + if (ptl) {
> + if (!pmd_present(*pmd)) {
> + if (unlikely(softleaf_is_migration(softleaf_from_pmd(*pmd)))) {
OK so you're not actually checking for CMA here at all, you're just doing the
migration wait stuff... again here? What?
> + spin_unlock(ptl);
> + pmd_migration_entry_wait(vma->vm_mm, pmd);
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> + goto out;
> + }
> + if (is_huge_zero_pmd(*pmd))
> + goto out;
> + folio = pmd_folio(*pmd);
> + if (folio_is_zone_device(folio))
> + goto out;
> + if (is_migrate_cma_page(&folio->page))
Err isn't this per-page, and you just checked that this is a huge folio, and
you're only checking the first page? That seems wrong?
> + isolate_folio_to_list(folio, folio_list);
Then you just take the whole folio?
It's really horrible that you're burying this in copy/pasted code from the rest.
> + goto out;
> + }
> +
> + start_pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
> + if (!start_pte) {
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
> +
> + for (pte = start_pte; addr != end; pte += step, addr += step * PAGE_SIZE) {
> + step = 1;
> + ptent = ptep_get(pte);
> + if (!pte_present(ptent)) {
> + if (unlikely(softleaf_is_migration(softleaf_from_pte(ptent)))) {
> + pte_unmap_unlock(start_pte, ptl);
> + migration_entry_wait(vma->vm_mm, pmd, addr);
BTW I also wonder if we have guaranteed forward progress here if say a migration
happened to migrate back and forth again across a range...?
> + walk->action = ACTION_AGAIN;
> + return 0;
> + }
Again, since you copy/pasted, same comment - this is disgusting.
And again I'm confused why you're waiting here again after you did it previously
due to the previous commit that already does it?
And why for a non-CMA range, if somebody happens to set CONFIG_CMA they now get
this done twice?
> + continue;
> + }
> + folio = vm_normal_folio(vma, addr, ptent);
> + if (!folio || folio_is_zone_device(folio))
> + continue;
> + step = folio_mlock_step(folio, pte, addr, end);
> + if (is_migrate_cma_page(&folio->page))
> + continue;
You mean ! surely?
Why are you inverting this vs. the above? you replied to the patch so maybe you
correct this there.
> + isolate_folio_to_list(folio, folio_list);
And again you bury the actual point of this in one easily missed line and zero
comments.
No.
> + }
> + pte_unmap(start_pte);
> +out:
> + spin_unlock(ptl);
> + cond_resched();
> + return 0;
> +}
Yeah this is just completely unacceptable. You have to find a way to deduplicate
code. Copy/paste code dumps are not ok.
But at the same time, you're dumping CMA crap in core mm mlock code which is
horrible, you've also dumped some migration code so you're really mixing things
up here horribly, and it doesn't seem justified?
> +
> +static const struct mm_walk_ops mlock_collect_migratable_ops = {
> + .pmd_entry = mlock_collect_migratable_pte_range,
> + .walk_lock = PGWALK_RDLOCK,
> +};
> +
> +static void mlock_migrate_cma_range(unsigned long start, unsigned long len)
> +{
> + struct mm_struct *mm = current->mm;
> + unsigned long end = start + len;
> + LIST_HEAD(folio_list);
> + struct migration_target_control mtc = {
> + .nid = NUMA_NO_NODE,
> + .gfp_mask = GFP_HIGHUSER | __GFP_NOWARN,
> + .reason = MR_SYSCALL,
> + };
> +
> + if (compaction_allow_unevictable())
> + return;
Again you're assuming compaction for some reason. Why?
It feels like you're just gating specific behaviour for your workload on this
flag and assuming that's ok.
> +
> + lru_cache_disable();
What, why?
> +
> + if (mmap_read_lock_killable(mm))
> + goto out;
OK so you got a fatal signal and you don't bother telling anybody about it and
just skip migration?...
> +
Weird whitespace...
> + walk_page_range(mm, start, end, &mlock_collect_migratable_ops,
> + &folio_list);
> + mmap_read_unlock(mm);
> +
> + if (list_empty(&folio_list))
> + goto out;
> +
> + if (migrate_pages(&folio_list, alloc_migration_target, NULL,
> + (unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL))
> + putback_movable_pages(&folio_list);
> +out:
> + lru_cache_enable();
> +}
> +#else
> +static inline void mlock_migrate_cma_range(unsigned long start,
inline in a .c file? Why? Drop it.
> + unsigned long len)
> +{
> +}
> +#endif /* CONFIG_CMA */
> +
> /*
> * mlock_vma_pages_range() - mlock any pages already in the range,
> * or munlock all pages in the range.
> @@ -678,6 +792,7 @@ static __must_check int do_mlock(unsigned long start, size_t len, vm_flags_t fla
> error = __mm_populate(start, len, 0);
> if (error)
> return __mlock_posix_error_return(error);
> + mlock_migrate_cma_range(start, len);
Err, unconditionally?
> return 0;
> }
>
> @@ -790,8 +905,10 @@ SYSCALL_DEFINE1(mlockall, int, flags)
> capable(CAP_IPC_LOCK))
> ret = apply_mlockall_flags(flags);
> mmap_write_unlock(current->mm);
> - if (!ret && (flags & MCL_CURRENT))
> + if (!ret && (flags & MCL_CURRENT)) {
> mm_populate(0, TASK_SIZE);
> + mlock_migrate_cma_range(0, TASK_SIZE);
Err what? Why are you doing this? You're forcing a wait on migration of
literally everything across the whole of the process whether or not they're CMA
ranges, but as a one time thing?
> + }
>
> return ret;
> }
> --
> 2.43.0
>
In general this feels like the wrong solution for a specific workload that
sticks horrible stuff in core mm and I don't really love it :)
Thanks, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 14:54 ` Lorenzo Stoakes
@ 2026-07-09 9:13 ` Sebastian Andrzej Siewior
2026-07-09 12:25 ` Wandun
0 siblings, 1 reply; 21+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-07-09 9:13 UTC (permalink / raw)
To: Lorenzo Stoakes
Cc: Wandun Chen, vbabka, david, rostedt, mhiramat, Alexander.Krabler,
hughd, fvdl, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel, akpm, surenb, mhocko, jackmanb, hannes, ziy, riel,
liam, harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 2026-07-07 15:54:57 [+0100], Lorenzo Stoakes wrote:
> On Tue, Jul 07, 2026 at 08:59:25PM +0800, Wandun Chen wrote:
> > From: Wandun Chen <chenwandun@lixiang.com>
> >
> > The region covered by mlock[all] may contain CMA pages. cma_alloc installs
> > migration entries in the page table, if a memory access occurs at this
> > point, it must wait for the migration to complete, which may cause
> > latency spikes on the RT kernels.
> >
> > Try to move the migration cost into the mlock[all] caller, which is
> > typically a setup path. So reduce the chance of latency spikes on RT
> > kernels by migrating the currently mapped CMA pages out of CMA region.
>
> 'reduce the chances of latency' so do you have any data to back this invasive
> change or not?
The application gets pages assigned which belong a CMA area. If the
pages are in need by the CMA then those pages are replaced with other
pages during a migration phase. Since there is no guarantee how long
this will take and is also subject to general scheduling in the system
it will be measurable and painful once hit.
> And for RT, but nothing in here at all checks for RT? You're using this
> compaction sysctl as an RT check somehow? That's gross.
The man-page for mlock says that it guarantees to stay in RAM and/ or
preventing to be moved to swap area. I would however argue that
replacing physical pages in the background should fall under this since
the application can't access them and is blocked while trying. The notes
section (in the man-page) lists "real-time applications" and
"deterministic timing". Therefore I think it makes sense to do this
unconditionally for mlock areas regardless of the sysctl knob.
Security related application probably only care that their memory does
not hit the swap area and probably wouldn't mind 10ms delay.
> This doesn't feel like the right solution.
Sebastian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-09 9:13 ` Sebastian Andrzej Siewior
@ 2026-07-09 12:25 ` Wandun
0 siblings, 0 replies; 21+ messages in thread
From: Wandun @ 2026-07-09 12:25 UTC (permalink / raw)
To: Sebastian Andrzej Siewior
Cc: vbabka, david, rostedt, mhiramat, Alexander.Krabler, hughd, fvdl,
linux-mm, linux-kernel, linux-trace-kernel, linux-rt-devel, akpm,
surenb, mhocko, jackmanb, hannes, ziy, riel, liam, harry, jannh,
lance.yang, mathieu.desnoyers, matthew.brost, joshua.hahnjy,
rakie.kim, byungchul, gourry, ying.huang, apopple, pfalcato
On 7/9/26 17:13, Sebastian Andrzej Siewior wrote:
> On 2026-07-07 15:54:57 [+0100], Lorenzo Stoakes wrote:
>> On Tue, Jul 07, 2026 at 08:59:25PM +0800, Wandun Chen wrote:
>>> From: Wandun Chen <chenwandun@lixiang.com>
>>>
>>> The region covered by mlock[all] may contain CMA pages. cma_alloc installs
>>> migration entries in the page table, if a memory access occurs at this
>>> point, it must wait for the migration to complete, which may cause
>>> latency spikes on the RT kernels.
>>>
>>> Try to move the migration cost into the mlock[all] caller, which is
>>> typically a setup path. So reduce the chance of latency spikes on RT
>>> kernels by migrating the currently mapped CMA pages out of CMA region.
>>
>> 'reduce the chances of latency' so do you have any data to back this invasive
>> change or not?
>
> The application gets pages assigned which belong a CMA area. If the
> pages are in need by the CMA then those pages are replaced with other
> pages during a migration phase. Since there is no guarantee how long
> this will take and is also subject to general scheduling in the system
> it will be measurable and painful once hit.
>
>> And for RT, but nothing in here at all checks for RT? You're using this
>> compaction sysctl as an RT check somehow? That's gross.
>
> The man-page for mlock says that it guarantees to stay in RAM and/ or
> preventing to be moved to swap area. I would however argue that
> replacing physical pages in the background should fall under this since
> the application can't access them and is blocked while trying. The notes
> section (in the man-page) lists "real-time applications" and
> "deterministic timing". Therefore I think it makes sense to do this
> unconditionally for mlock areas regardless of the sysctl knob.
> Security related application probably only care that their memory does
> not hit the swap area and probably wouldn't mind 10ms delay.
Thanks for helping to explain, that helps a lot, Sebastian.
Wandun
>
>> This doesn't feel like the right solution.
>
> Sebastian
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-07 12:59 ` [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range Wandun Chen
2026-07-07 13:36 ` Wandun
2026-07-07 14:54 ` Lorenzo Stoakes
@ 2026-07-09 10:04 ` David Hildenbrand (Arm)
2026-07-09 13:15 ` Sebastian Andrzej Siewior
2 siblings, 1 reply; 21+ messages in thread
From: David Hildenbrand (Arm) @ 2026-07-09 10:04 UTC (permalink / raw)
To: Wandun Chen, vbabka, rostedt, mhiramat, Alexander.Krabler, hughd,
fvdl, bigeasy, linux-mm, linux-kernel, linux-trace-kernel,
linux-rt-devel
Cc: akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 7/7/26 14:59, Wandun Chen wrote:
> From: Wandun Chen <chenwandun@lixiang.com>
>
> The region covered by mlock[all] may contain CMA pages. cma_alloc installs
What about ZONE_MOVABLE where memory is supposed to be migratable?
Also, what about drivers that mmap() CMA memory to user space, and
__mm_populate()->populate_vma_page_range() would actually try mlocking them, and
they actually must remain on CMA areas?
--
Cheers,
David
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 4/4] mm/mlock: migrate folios out of CMA when mlocking a range
2026-07-09 10:04 ` David Hildenbrand (Arm)
@ 2026-07-09 13:15 ` Sebastian Andrzej Siewior
0 siblings, 0 replies; 21+ messages in thread
From: Sebastian Andrzej Siewior @ 2026-07-09 13:15 UTC (permalink / raw)
To: David Hildenbrand (Arm)
Cc: Wandun Chen, vbabka, rostedt, mhiramat, Alexander.Krabler, hughd,
fvdl, linux-mm, linux-kernel, linux-trace-kernel, linux-rt-devel,
akpm, surenb, mhocko, jackmanb, hannes, ziy, ljs, riel, liam,
harry, jannh, lance.yang, mathieu.desnoyers, matthew.brost,
joshua.hahnjy, rakie.kim, byungchul, gourry, ying.huang, apopple,
pfalcato
On 2026-07-09 12:04:33 [+0200], David Hildenbrand (Arm) wrote:
> On 7/7/26 14:59, Wandun Chen wrote:
> > From: Wandun Chen <chenwandun@lixiang.com>
> >
> > The region covered by mlock[all] may contain CMA pages. cma_alloc installs
>
> What about ZONE_MOVABLE where memory is supposed to be migratable?
Would it be bad if the pages would not be movable anymore? Does this
effect just memory-hotplug or something else, too?
> Also, what about drivers that mmap() CMA memory to user space, and
> __mm_populate()->populate_vma_page_range() would actually try mlocking them, and
> they actually must remain on CMA areas?
It should be safe to skip those. They belong to device and they
shouldn't be affected by anything including getting swapped out.
Sebastian
^ permalink raw reply [flat|nested] 21+ messages in thread