* [PATCH v2 0/3] mm/khugepaged: bound mmap_lock hold time when walking an mm's VMAs
@ 2026-11-05 20:57 Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
` (2 more replies)
0 siblings, 3 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-05 20:57 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes (ARM)
This series introduces a couple small changes to khugepaged to improve its
behaviour both in the face of mm->mmap_lock contention and to reduce that
lock contention when possible.
Looking at the first change - if the mmap lock is contended, an attempted
mmap trylock in collapse_scan_mm_slot() will result in the scan being
aborted wherever it happens to be, and when next the mm is processed it
starts from the beginning again.
If an mm has a lot of ineligible VMAs and is undergoing significant
mmap_lock contention, collapsing the eligible VMAs there can take a very
long time.
Patch 2 of the series fixes this (patch 1 is a prerequisite) by keeping
track of where khugepaged was in the mm before aborting the scan.
This issue was noticed during some work in which an mm was instantiated
with a large number of small VMAs under significant mmap_lock contention,
during which collapse was observed to take 10.8s. With this patch applied
it took 3.6s to collapse the same mm.
Finally, patch 3 uses this to have khugepaged release + reacquire the mmap
read lock if it is contended after walking a large number of VMAs.
This was also observed as part of the previously mentioned work in the form
of mmap lock stalls, which patch 3 resolves. The reader stealing the lock
back from a queued writer on re-acquisition is addressed separately by the
rwsem change at [0].
Importantly, none of the changes introduces any regressions for khugepaged
anywhere. It is all upside, and implemented conservatively.
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
v2:
* Removed mmap_lock_nosteal patch (was 3/5) and sent a general rwsem patch
separately, as per Peter.
* Removed what was previously 4/5 as with 3/5 gone it was no longer
necessary.
* Put 2/3 commit message on a diet, as per David.
* Updated 3/3 to count every VMA rather than only skipped ones as per David.
* Updated 3/3 to use a sensibly calculated maximum VMA scan count renamed to
KHUGEPAGED_MAX_VMA_SCAN_BATCH as per David.
* Updated 3/3 to only release the mmap lock if it is contended.
* Updated 3/3 to resume from the per-mm cursor after re-acquiring the lock
rather than the now stale VMA.
* Updated 3/3 to treat a VMA whose aligned end wraps the address space as
the last in the mm, as per Lance.
* Fixed 3/3 KHUGEPAGED_MAX_VMA_SCAN_BATCH to derive from the pages_to_scan
budget passed to collapse_scan_mm_slot().
v1:
https://lore.kernel.org/r/20261103-khugepaged-latency-fix-v1-0-a07ab16ac860@kernel.org
[0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
---
Lorenzo Stoakes (ARM) (3):
Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot"
mm/khugepaged: scan mm's more efficiently under mmap_lock contention
mm/khugepaged: release contended mmap read lock after scanning enough VMAs
mm/khugepaged.c | 120 +++++++++++++++++++++++++++++++++++++++-----------------
1 file changed, 84 insertions(+), 36 deletions(-)
---
base-commit: 34f88a114888c9dba49e45f2b0c2a3e83c4c871c
change-id: 20261029-khugepaged-latency-fix-40495d66ea50
Cheers,
--
Lorenzo Stoakes (ARM) <ljs@kernel.org>
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot"
2026-11-05 20:57 [PATCH v2 0/3] mm/khugepaged: bound mmap_lock hold time when walking an mm's VMAs Lorenzo Stoakes (ARM)
@ 2026-11-05 20:57 ` Lorenzo Stoakes (ARM)
2026-11-05 21:47 ` Jose A. Perez de Azpillaga
2026-11-06 9:41 ` Baolin Wang
2026-11-05 20:57 ` [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
2 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-05 20:57 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes (ARM)
This partially reverts commit b4c9ffb54b32 ("mm/khugepaged: remove
definition of struct khugepaged_mm_slot"), as a subsequent commit needs to
resurrect this type to store additional data alongside the mm_slot.
The stated intent of the commit was to address a NULL check in
__khugepaged_exit():
mm_slot = mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
slot can be NULL and mm_slot_entry() is an alias for container_of(), so
this is container_of() a NULL value.
This was benign as there was only 1 field in khugepaged_mm_slot (offsetting
NULL at 0 results in NULL), however it was right to fix this, so retain the
fix.
It also unwrapped KMEM_CACHE() in khugepaged_init(). This was a mistake,
and was itself reverted in commit c14bdcc9f274 ("mm/khugepaged: use
KMEM_CACHE()"). Therefore, retain the KMEM_CACHE() form in this revert.
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/khugepaged.c | 57 ++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 38 insertions(+), 19 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index da91762f6358..aad8797f8d5a 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -66,6 +66,14 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
static struct kmem_cache *mm_slot_cache __ro_after_init;
+/**
+ * struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
+ * @slot: hash lookup from mm to mm_slot
+ */
+struct khugepaged_mm_slot {
+ struct mm_slot slot;
+};
+
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
@@ -76,7 +84,7 @@ static struct kmem_cache *mm_slot_cache __ro_after_init;
*/
struct khugepaged_scan {
struct list_head mm_head;
- struct mm_slot *mm_slot;
+ struct khugepaged_mm_slot *mm_slot;
unsigned long address;
};
@@ -386,7 +394,7 @@ int hugepage_madvise(struct vm_area_struct *vma,
int __init khugepaged_init(void)
{
- mm_slot_cache = KMEM_CACHE(mm_slot, 0);
+ mm_slot_cache = KMEM_CACHE(khugepaged_mm_slot, 0);
if (!mm_slot_cache)
return -ENOMEM;
@@ -446,20 +454,22 @@ static bool hugepage_enabled(void)
void __khugepaged_enter(struct mm_struct *mm)
{
+ struct khugepaged_mm_slot *mm_slot;
struct mm_slot *slot;
int wakeup;
/* __khugepaged_exit() must not run from under us */
VM_BUG_ON_MM(collapse_test_exit(mm), mm);
- slot = mm_slot_alloc(mm_slot_cache);
- if (!slot)
+ mm_slot = mm_slot_alloc(mm_slot_cache);
+ if (!mm_slot)
return;
if (unlikely(mm_flags_test_and_set(MMF_VM_HUGEPAGE, mm))) {
- mm_slot_free(mm_slot_cache, slot);
+ mm_slot_free(mm_slot_cache, mm_slot);
return;
}
+ slot = &mm_slot->slot;
spin_lock(&khugepaged_mm_lock);
mm_slot_insert(mm_slots_hash, mm, slot);
@@ -504,12 +514,15 @@ void khugepaged_enter_vma(struct vm_area_struct *vma,
void __khugepaged_exit(struct mm_struct *mm)
{
+ struct khugepaged_mm_slot *mm_slot;
struct mm_slot *slot;
int free = 0;
spin_lock(&khugepaged_mm_lock);
slot = mm_slot_lookup(mm_slots_hash, mm);
- if (slot && khugepaged_scan.mm_slot != slot) {
+
+ mm_slot = slot ? mm_slot_entry(slot, struct khugepaged_mm_slot, slot) : NULL;
+ if (mm_slot && khugepaged_scan.mm_slot != mm_slot) {
mm_slot_remove(slot);
free = 1;
}
@@ -517,9 +530,9 @@ void __khugepaged_exit(struct mm_struct *mm)
if (free) {
mm_flags_clear(MMF_VM_HUGEPAGE, mm);
- mm_slot_free(mm_slot_cache, slot);
+ mm_slot_free(mm_slot_cache, mm_slot);
mmdrop(mm);
- } else if (slot) {
+ } else if (mm_slot) {
/*
* This is required to serialize against
* collapse_test_exit() (which is guaranteed to run
@@ -1773,8 +1786,9 @@ static enum scan_result collapse_scan_anon(struct vm_area_struct *vma,
return result;
}
-static void collect_mm_slot(struct mm_slot *slot)
+static void collect_mm_slot(struct khugepaged_mm_slot *mm_slot)
{
+ struct mm_slot *slot = &mm_slot->slot;
struct mm_struct *mm = slot->mm;
lockdep_assert_held(&khugepaged_mm_lock);
@@ -1790,7 +1804,7 @@ static void collect_mm_slot(struct mm_slot *slot)
*/
/* khugepaged_mm_lock actually not necessary for the below */
- mm_slot_free(mm_slot_cache, slot);
+ mm_slot_free(mm_slot_cache, mm_slot);
mmdrop(mm);
}
}
@@ -2891,6 +2905,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
__acquires(&khugepaged_mm_lock)
{
struct vma_iterator vmi;
+ struct khugepaged_mm_slot *mm_slot;
struct mm_slot *slot;
struct mm_struct *mm;
struct vm_area_struct *vma;
@@ -2900,12 +2915,14 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
*result = SCAN_FAIL;
if (khugepaged_scan.mm_slot) {
- slot = khugepaged_scan.mm_slot;
+ mm_slot = khugepaged_scan.mm_slot;
+ slot = &mm_slot->slot;
} else {
slot = list_first_entry(&khugepaged_scan.mm_head,
struct mm_slot, mm_node);
+ mm_slot = mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
khugepaged_scan.address = 0;
- khugepaged_scan.mm_slot = slot;
+ khugepaged_scan.mm_slot = mm_slot;
}
spin_unlock(&khugepaged_mm_lock);
@@ -2981,7 +2998,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
breakouterloop_mmap_lock:
spin_lock(&khugepaged_mm_lock);
- VM_BUG_ON(khugepaged_scan.mm_slot != slot);
+ VM_BUG_ON(khugepaged_scan.mm_slot != mm_slot);
/*
* Release the current mm_slot if this mm is about to die, or
* if we scanned all vmas of this mm, or THP got disabled.
@@ -2993,14 +3010,16 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
* mm_slot not pointing to the exiting mm.
*/
if (!list_is_last(&slot->mm_node, &khugepaged_scan.mm_head)) {
- khugepaged_scan.mm_slot = list_next_entry(slot, mm_node);
+ slot = list_next_entry(slot, mm_node);
+ khugepaged_scan.mm_slot =
+ mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
khugepaged_scan.address = 0;
} else {
khugepaged_scan.mm_slot = NULL;
khugepaged_full_scans++;
}
- collect_mm_slot(slot);
+ collect_mm_slot(mm_slot);
}
trace_mm_khugepaged_scan(mm, cc->progress - progress_prev,
@@ -3090,7 +3109,7 @@ static void khugepaged_wait_work(void)
static int khugepaged(void *none)
{
- struct mm_slot *slot;
+ struct khugepaged_mm_slot *mm_slot;
set_freezable();
set_user_nice(current, MAX_NICE);
@@ -3101,10 +3120,10 @@ static int khugepaged(void *none)
}
spin_lock(&khugepaged_mm_lock);
- slot = khugepaged_scan.mm_slot;
+ mm_slot = khugepaged_scan.mm_slot;
khugepaged_scan.mm_slot = NULL;
- if (slot)
- collect_mm_slot(slot);
+ if (mm_slot)
+ collect_mm_slot(mm_slot);
spin_unlock(&khugepaged_mm_lock);
return 0;
}
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention
2026-11-05 20:57 [PATCH v2 0/3] mm/khugepaged: bound mmap_lock hold time when walking an mm's VMAs Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
@ 2026-11-05 20:57 ` Lorenzo Stoakes (ARM)
2026-11-05 21:48 ` Jose A. Perez de Azpillaga
2026-11-06 9:31 ` Baolin Wang
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
2 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-05 20:57 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes (ARM)
khugepaged scans VMAs in collapse_scan_mm_slot() in the same mm until it is
about to be torn down, prctl(PR_SET_THP_DISABLE) disables THP for it,
mm->mmap_lock contention occurs, or all its VMAs have been scanned.
When lock contention causes the function to bail out, khugepaged doesn't
keep track of where it left off, so when the mm is next scanned, it starts
from address 0 again.
If there's a lot of mmap_lock contention this can significantly slow down
collapse later in the mm, since the /sys/.../pages_to_scan budget can run
out before the end of the mm is reached.
The budget is incremented (as tracked in cc->progress) at a rate of 1 page
per ineligible VMA, but the budget is only checked once eligible VMAs are
encountered.
Then, it only takes the combined total of ineligible VMAs and eligible but
non-collapsible pages to exceed the /sys/.../pages_to_scan budget for the
rest of the mm to be skipped.
For a system with 4 KiB page size and default sysctl settings this only
requires 4096 ineligible VMAs/uncollapsible pages to be skipped before this
happens.
Resolve the issue by simply tracking the last examined address in the
khugepaged_mm_slot object, so when the mm is resumed, the scan starts from
where it left off.
Additionally, skip to the next candidate if a VMA cannot be collapsed.
This issue was noticed during some work in which an mm was instantiated
with a large number of small VMAs under significant mmap_lock contention,
during which collapse was observed to take 10.8s. With this patch applied
it took 3.6s to collapse the same mm.
Also, while here, update a couple VM_BUG_ON()'s to VM_WARN_ON_ONCE()'s.
This patch is also a prerequisite for the next patch in the series.
Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
Reviewed-by: Lance Yang <lance.yang@linux.dev>
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/khugepaged.c | 38 ++++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index aad8797f8d5a..216c4b8d28c4 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -69,23 +69,23 @@ static struct kmem_cache *mm_slot_cache __ro_after_init;
/**
* struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
* @slot: hash lookup from mm to mm_slot
+ * @address: the next address inside that to be scanned
*/
struct khugepaged_mm_slot {
struct mm_slot slot;
+ unsigned long address;
};
/**
* struct khugepaged_scan - cursor for scanning
* @mm_head: the head of the mm list to scan
* @mm_slot: the current mm_slot we are scanning
- * @address: the next address inside that to be scanned
*
* There is only the one khugepaged_scan instance of this cursor structure.
*/
struct khugepaged_scan {
struct list_head mm_head;
struct khugepaged_mm_slot *mm_slot;
- unsigned long address;
};
static struct khugepaged_scan khugepaged_scan = {
@@ -2921,7 +2921,6 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
slot = list_first_entry(&khugepaged_scan.mm_head,
struct mm_slot, mm_node);
mm_slot = mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
- khugepaged_scan.address = 0;
khugepaged_scan.mm_slot = mm_slot;
}
spin_unlock(&khugepaged_mm_lock);
@@ -2939,7 +2938,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
if (unlikely(collapse_test_exit_or_disable(mm)))
goto breakouterloop;
- vma_iter_init(&vmi, mm, khugepaged_scan.address);
+ vma_iter_init(&vmi, mm, mm_slot->address);
for_each_vma(vmi, vma) {
unsigned long hstart, hend;
@@ -2948,38 +2947,38 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
cc->progress++;
break;
}
- if (!collapse_possible_orders(vma, vma->vm_flags,
- TVA_KHUGEPAGED)) {
+ if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
+ /* Unable to collapse, skip to the next candidate. */
+ mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
cc->progress++;
continue;
}
hstart = ALIGN(vma->vm_start, HPAGE_PMD_SIZE);
hend = ALIGN_DOWN(vma->vm_end, HPAGE_PMD_SIZE);
- if (khugepaged_scan.address > hend) {
+ if (mm_slot->address > hend) {
cc->progress++;
continue;
}
- if (khugepaged_scan.address < hstart)
- khugepaged_scan.address = hstart;
- VM_BUG_ON(khugepaged_scan.address & ~HPAGE_PMD_MASK);
+ if (mm_slot->address < hstart)
+ mm_slot->address = hstart;
+ VM_WARN_ON_ONCE(mm_slot->address & ~HPAGE_PMD_MASK);
- while (khugepaged_scan.address < hend) {
+ while (mm_slot->address < hend) {
bool lock_dropped = false;
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm)))
goto breakouterloop;
- VM_WARN_ON_ONCE(khugepaged_scan.address < hstart ||
- khugepaged_scan.address + HPAGE_PMD_SIZE >
- hend);
+ VM_WARN_ON_ONCE(mm_slot->address < hstart ||
+ mm_slot->address + HPAGE_PMD_SIZE > hend);
- *result = collapse_single_pmd(khugepaged_scan.address,
+ *result = collapse_single_pmd(mm_slot->address,
vma, &lock_dropped, cc);
if (*result == SCAN_SUCCEED)
khugepaged_pages_collapsed++;
/* move to next address */
- khugepaged_scan.address += HPAGE_PMD_SIZE;
+ mm_slot->address += HPAGE_PMD_SIZE;
if (lock_dropped)
/*
* We released mmap_lock so break loop. Note
@@ -2993,12 +2992,16 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
goto breakouterloop;
}
}
+ /* Every VMA has been examined, so reset. */
+ if (!vma)
+ mm_slot->address = 0;
+
breakouterloop:
mmap_read_unlock(mm); /* exit_mmap will destroy ptes after this */
breakouterloop_mmap_lock:
spin_lock(&khugepaged_mm_lock);
- VM_BUG_ON(khugepaged_scan.mm_slot != mm_slot);
+ VM_WARN_ON_ONCE(khugepaged_scan.mm_slot != mm_slot);
/*
* Release the current mm_slot if this mm is about to die, or
* if we scanned all vmas of this mm, or THP got disabled.
@@ -3013,7 +3016,6 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
slot = list_next_entry(slot, mm_node);
khugepaged_scan.mm_slot =
mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
- khugepaged_scan.address = 0;
} else {
khugepaged_scan.mm_slot = NULL;
khugepaged_full_scans++;
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 20:57 [PATCH v2 0/3] mm/khugepaged: bound mmap_lock hold time when walking an mm's VMAs Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention Lorenzo Stoakes (ARM)
@ 2026-11-05 20:57 ` Lorenzo Stoakes (ARM)
2026-11-05 21:53 ` Jose A. Perez de Azpillaga
` (3 more replies)
2 siblings, 4 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-05 20:57 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel, Lorenzo Stoakes (ARM)
khugepaged walks an mm's VMAs under the mmap read lock in
collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
them if encountered.
It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
when these are encountered, however only checking this budget once an
eligible PMD-spanning VMA is encountered.
It can therefore hold the mmap read lock for a long time if there are a
large number of ineligible VMAs present in an mm, over which time no mmap
write locks (and thus no VMA write locks) can be acquired.
Address this by checking whether the mmap lock is contended after every
KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
pages_to_scan budget, and if so dropping and re-acquiring the mmap read
lock before resuming the walk from the per-mm cursor.
As the walk is resumed from the cursor rather than the VMA that was current
when the lock was dropped, the cursor must never move backwards. A VMA
ending at the top of the address space causes ALIGN() to wrap the cursor to
zero, so treat such a VMA as the last in the mm rather than skipping past
it.
This was tested locally with ~1M ineligible VMAs mapped ahead of a
collapsible region of memory, and resulted in a reduced mmap_lock hold time
of 170us down from 95ms.
This was also observed to reduce mmap_lock stalls in concurrent threads of
a similar duration. Note that a reader re-acquiring the lock can currently
steal it from a writer that is queued but not yet running, so the full
benefit to writers depends on the separate rwsem change at [0].
Local measurements indicated no change for ordinary use cases, so this is
all upside.
This work came out of investigations into mmap stalls when getting latency
numbers for an unrelated patch series.
[0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
---
mm/khugepaged.c | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/mm/khugepaged.c b/mm/khugepaged.c
index 216c4b8d28c4..eaa01dae5a03 100644
--- a/mm/khugepaged.c
+++ b/mm/khugepaged.c
@@ -66,6 +66,12 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
static struct kmem_cache *mm_slot_cache __ro_after_init;
+/*
+ * The number of VMAs to walk before checking for, and relieving, mmap lock
+ * contention, as a function of the pages_to_scan budget.
+ */
+#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) ((pages_to_scan) / 4)
+
/**
* struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
* @slot: hash lookup from mm to mm_slot
@@ -2910,6 +2916,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
struct mm_struct *mm;
struct vm_area_struct *vma;
unsigned int progress_prev = cc->progress;
+ unsigned int nr_vmas_batch = 0;
lockdep_assert_held(&khugepaged_mm_lock);
*result = SCAN_FAIL;
@@ -2942,15 +2949,35 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
for_each_vma(vmi, vma) {
unsigned long hstart, hend;
+ /* Relieve mmap lock contention after walking a batch of VMAs. */
+ if (nr_vmas_batch++ >= KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)) {
+ nr_vmas_batch = 0;
+ if (mmap_lock_is_contended(mm)) {
+ mmap_read_unlock(mm);
+ cond_resched();
+ mmap_read_lock(mm);
+ vma_iter_set(&vmi, mm_slot->address);
+ continue;
+ }
+ }
+
cond_resched();
if (unlikely(collapse_test_exit_or_disable(mm))) {
cc->progress++;
break;
}
if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
- /* Unable to collapse, skip to the next candidate. */
- mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
+ const unsigned long next =
+ ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
+
cc->progress++;
+ /* Check for overflow. */
+ if (unlikely(next < vma->vm_end)) {
+ vma = NULL;
+ break;
+ }
+ /* Unable to collapse, skip to the next candidate. */
+ mm_slot->address = next;
continue;
}
hstart = ALIGN(vma->vm_start, HPAGE_PMD_SIZE);
--
2.56.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot"
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
@ 2026-11-05 21:47 ` Jose A. Perez de Azpillaga
2026-11-06 9:41 ` Baolin Wang
1 sibling, 0 replies; 21+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-11-05 21:47 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/5/26 9:57 PM, Lorenzo Stoakes (ARM) wrote:
> This partially reverts commit b4c9ffb54b32 ("mm/khugepaged: remove
> definition of struct khugepaged_mm_slot"), as a subsequent commit needs to
> resurrect this type to store additional data alongside the mm_slot.
>
> The stated intent of the commit was to address a NULL check in
> __khugepaged_exit():
>
> mm_slot = mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
>
> slot can be NULL and mm_slot_entry() is an alias for container_of(), so
> this is container_of() a NULL value.
>
> This was benign as there was only 1 field in khugepaged_mm_slot (offsetting
> NULL at 0 results in NULL), however it was right to fix this, so retain the
> fix.
>
> It also unwrapped KMEM_CACHE() in khugepaged_init(). This was a mistake,
> and was itself reverted in commit c14bdcc9f274 ("mm/khugepaged: use
> KMEM_CACHE()"). Therefore, retain the KMEM_CACHE() form in this revert.
>
> Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
LGTM:
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
--
cheers,
jose a. p-a
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention
2026-11-05 20:57 ` [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention Lorenzo Stoakes (ARM)
@ 2026-11-05 21:48 ` Jose A. Perez de Azpillaga
2026-11-06 9:31 ` Baolin Wang
1 sibling, 0 replies; 21+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-11-05 21:48 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/5/26 9:57 PM, Lorenzo Stoakes (ARM) wrote:
> khugepaged scans VMAs in collapse_scan_mm_slot() in the same mm until it is
> about to be torn down, prctl(PR_SET_THP_DISABLE) disables THP for it,
> mm->mmap_lock contention occurs, or all its VMAs have been scanned.
>
> When lock contention causes the function to bail out, khugepaged doesn't
> keep track of where it left off, so when the mm is next scanned, it starts
> from address 0 again.
>
> If there's a lot of mmap_lock contention this can significantly slow down
> collapse later in the mm, since the /sys/.../pages_to_scan budget can run
> out before the end of the mm is reached.
>
> The budget is incremented (as tracked in cc->progress) at a rate of 1 page
> per ineligible VMA, but the budget is only checked once eligible VMAs are
> encountered.
>
> Then, it only takes the combined total of ineligible VMAs and eligible but
> non-collapsible pages to exceed the /sys/.../pages_to_scan budget for the
> rest of the mm to be skipped.
>
> For a system with 4 KiB page size and default sysctl settings this only
> requires 4096 ineligible VMAs/uncollapsible pages to be skipped before this
> happens.
>
> Resolve the issue by simply tracking the last examined address in the
> khugepaged_mm_slot object, so when the mm is resumed, the scan starts from
> where it left off.
>
> Additionally, skip to the next candidate if a VMA cannot be collapsed.
>
> This issue was noticed during some work in which an mm was instantiated
> with a large number of small VMAs under significant mmap_lock contention,
> during which collapse was observed to take 10.8s. With this patch applied
> it took 3.6s to collapse the same mm.
>
> Also, while here, update a couple VM_BUG_ON()'s to VM_WARN_ON_ONCE()'s.
>
> This patch is also a prerequisite for the next patch in the series.
>
> Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
LGTM:
Reviewed-by: Jose A. Perez de Azpillaga <azpijr@gmail.com>
--
cheers,
jose a. p-a
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
@ 2026-11-05 21:53 ` Jose A. Perez de Azpillaga
2026-11-06 10:03 ` Lorenzo Stoakes (ARM)
2026-11-06 2:12 ` Vernon Yang
` (2 subsequent siblings)
3 siblings, 1 reply; 21+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-11-05 21:53 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/5/26 9:57 PM, Lorenzo Stoakes (ARM) wrote:
> khugepaged walks an mm's VMAs under the mmap read lock in
> collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
> them if encountered.
>
> It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
> when these are encountered, however only checking this budget once an
> eligible PMD-spanning VMA is encountered.
>
> It can therefore hold the mmap read lock for a long time if there are a
> large number of ineligible VMAs present in an mm, over which time no mmap
> write locks (and thus no VMA write locks) can be acquired.
>
> Address this by checking whether the mmap lock is contended after every
> KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
> pages_to_scan budget, and if so dropping and re-acquiring the mmap read
> lock before resuming the walk from the per-mm cursor.
>
> As the walk is resumed from the cursor rather than the VMA that was current
> when the lock was dropped, the cursor must never move backwards. A VMA
> ending at the top of the address space causes ALIGN() to wrap the cursor to
> zero, so treat such a VMA as the last in the mm rather than skipping past
> it.
>
> This was tested locally with ~1M ineligible VMAs mapped ahead of a
> collapsible region of memory, and resulted in a reduced mmap_lock hold time
> of 170us down from 95ms.
>
> This was also observed to reduce mmap_lock stalls in concurrent threads of
> a similar duration. Note that a reader re-acquiring the lock can currently
> steal it from a writer that is queued but not yet running, so the full
> benefit to writers depends on the separate rwsem change at [0].
>
> Local measurements indicated no change for ordinary use cases, so this is
> all upside.
>
> This work came out of investigations into mmap stalls when getting latency
> numbers for an unrelated patch series.
>
> [0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/khugepaged.c | 31 +++++++++++++++++++++++++++++--
> 1 file changed, 29 insertions(+), 2 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 216c4b8d28c4..eaa01dae5a03 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -66,6 +66,12 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
>
> static struct kmem_cache *mm_slot_cache __ro_after_init;
>
> +/*
> + * The number of VMAs to walk before checking for, and relieving, mmap lock
> + * contention, as a function of the pages_to_scan budget.
> + */
> +#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) ((pages_to_scan) / 4)
> +
> /**
> * struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
> * @slot: hash lookup from mm to mm_slot
> @@ -2910,6 +2916,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> struct mm_struct *mm;
> struct vm_area_struct *vma;
> unsigned int progress_prev = cc->progress;
> + unsigned int nr_vmas_batch = 0;
>
> lockdep_assert_held(&khugepaged_mm_lock);
> *result = SCAN_FAIL;
> @@ -2942,15 +2949,35 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> for_each_vma(vmi, vma) {
> unsigned long hstart, hend;
>
> + /* Relieve mmap lock contention after walking a batch of VMAs. */
> + if (nr_vmas_batch++ >= KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)) {
> + nr_vmas_batch = 0;
> + if (mmap_lock_is_contended(mm)) {
> + mmap_read_unlock(mm);
> + cond_resched();
> + mmap_read_lock(mm);
> + vma_iter_set(&vmi, mm_slot->address);
> + continue;
> + }
> + }
> +
with pages_to_scan below 4 the batch is 0, so under constant contention
we keep dropping the lock at the cursor and never scan anything, don't we?
--
cheers,
jose a. p-a
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
2026-11-05 21:53 ` Jose A. Perez de Azpillaga
@ 2026-11-06 2:12 ` Vernon Yang
2026-11-06 9:29 ` Vernon Yang
2026-11-06 9:38 ` Baolin Wang
2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
3 siblings, 1 reply; 21+ messages in thread
From: Vernon Yang @ 2026-11-06 2:12 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Thu, Nov 05, 2026 at 08:57:24PM +0000, Lorenzo Stoakes (ARM) wrote:
> khugepaged walks an mm's VMAs under the mmap read lock in
> collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
> them if encountered.
>
> It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
> when these are encountered, however only checking this budget once an
> eligible PMD-spanning VMA is encountered.
>
> It can therefore hold the mmap read lock for a long time if there are a
> large number of ineligible VMAs present in an mm, over which time no mmap
> write locks (and thus no VMA write locks) can be acquired.
>
> Address this by checking whether the mmap lock is contended after every
> KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
> pages_to_scan budget, and if so dropping and re-acquiring the mmap read
> lock before resuming the walk from the per-mm cursor.
>
> As the walk is resumed from the cursor rather than the VMA that was current
> when the lock was dropped, the cursor must never move backwards. A VMA
> ending at the top of the address space causes ALIGN() to wrap the cursor to
> zero, so treat such a VMA as the last in the mm rather than skipping past
> it.
>
> This was tested locally with ~1M ineligible VMAs mapped ahead of a
> collapsible region of memory, and resulted in a reduced mmap_lock hold time
> of 170us down from 95ms.
>
> This was also observed to reduce mmap_lock stalls in concurrent threads of
> a similar duration. Note that a reader re-acquiring the lock can currently
> steal it from a writer that is queued but not yet running, so the full
> benefit to writers depends on the separate rwsem change at [0].
>
> Local measurements indicated no change for ordinary use cases, so this is
> all upside.
>
> This work came out of investigations into mmap stalls when getting latency
> numbers for an unrelated patch series.
>
> [0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
> mm/khugepaged.c | 31 +++++++++++++++++++++++++++++--
> 1 file changed, 29 insertions(+), 2 deletions(-)
>
> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> index 216c4b8d28c4..eaa01dae5a03 100644
> --- a/mm/khugepaged.c
> +++ b/mm/khugepaged.c
> @@ -66,6 +66,12 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
>
> static struct kmem_cache *mm_slot_cache __ro_after_init;
>
> +/*
> + * The number of VMAs to walk before checking for, and relieving, mmap lock
> + * contention, as a function of the pages_to_scan budget.
> + */
> +#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) ((pages_to_scan) / 4)
> +
When pages_to_scan to equate 1/2/3, KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)
always to 0, we will never scan anything.
The following code will solve this problem.
#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) max((pages_to_scan) / 4, 1)
> /**
> * struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
> * @slot: hash lookup from mm to mm_slot
> @@ -2910,6 +2916,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> struct mm_struct *mm;
> struct vm_area_struct *vma;
> unsigned int progress_prev = cc->progress;
> + unsigned int nr_vmas_batch = 0;
>
> lockdep_assert_held(&khugepaged_mm_lock);
> *result = SCAN_FAIL;
> @@ -2942,15 +2949,35 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> for_each_vma(vmi, vma) {
> unsigned long hstart, hend;
>
> + /* Relieve mmap lock contention after walking a batch of VMAs. */
> + if (nr_vmas_batch++ >= KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)) {
> + nr_vmas_batch = 0;
> + if (mmap_lock_is_contended(mm)) {
> + mmap_read_unlock(mm);
> + cond_resched();
> + mmap_read_lock(mm);
> + vma_iter_set(&vmi, mm_slot->address);
> + continue;
> + }
> + }
> +
> cond_resched();
> if (unlikely(collapse_test_exit_or_disable(mm))) {
> cc->progress++;
> break;
> }
> if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> - /* Unable to collapse, skip to the next candidate. */
> - mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> + const unsigned long next =
> + ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> +
> cc->progress++;
> + /* Check for overflow. */
> + if (unlikely(next < vma->vm_end)) {
I think you want it to be like that.
if (unlikely(next >= vma->vm_end)) {
^^
> + vma = NULL;
> + break;
> + }
> + /* Unable to collapse, skip to the next candidate. */
> + mm_slot->address = next;
> continue;
> }
> hstart = ALIGN(vma->vm_start, HPAGE_PMD_SIZE);
>
> --
> 2.56.0
>
>
--
Cheers,
Vernon
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 2:12 ` Vernon Yang
@ 2026-11-06 9:29 ` Vernon Yang
2026-11-06 10:49 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 21+ messages in thread
From: Vernon Yang @ 2026-11-06 9:29 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Fri, Nov 06, 2026 at 10:12:46AM +0800, Vernon Yang wrote:
> On Thu, Nov 05, 2026 at 08:57:24PM +0000, Lorenzo Stoakes (ARM) wrote:
> > if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> > - /* Unable to collapse, skip to the next candidate. */
> > - mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > + const unsigned long next =
> > + ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > +
> > cc->progress++;
> > + /* Check for overflow. */
> > + if (unlikely(next < vma->vm_end)) {
>
> I think you want it to be like that.
>
> if (unlikely(next >= vma->vm_end)) {
> ^^
Don’t read this comment please. Your code is right.
The VMA at the top of mm, after aligning the end values according to the
PMD, may overflow to 0.
It might be easier to understand if this coding is used.
if (unlikely(next == 0)) {
--
Cheers,
Vernon
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention
2026-11-05 20:57 ` [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention Lorenzo Stoakes (ARM)
2026-11-05 21:48 ` Jose A. Perez de Azpillaga
@ 2026-11-06 9:31 ` Baolin Wang
1 sibling, 0 replies; 21+ messages in thread
From: Baolin Wang @ 2026-11-06 9:31 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/6/26 4:57 AM, Lorenzo Stoakes (ARM) wrote:
> khugepaged scans VMAs in collapse_scan_mm_slot() in the same mm until it is
> about to be torn down, prctl(PR_SET_THP_DISABLE) disables THP for it,
> mm->mmap_lock contention occurs, or all its VMAs have been scanned.
>
> When lock contention causes the function to bail out, khugepaged doesn't
> keep track of where it left off, so when the mm is next scanned, it starts
> from address 0 again.
>
> If there's a lot of mmap_lock contention this can significantly slow down
> collapse later in the mm, since the /sys/.../pages_to_scan budget can run
> out before the end of the mm is reached.
>
> The budget is incremented (as tracked in cc->progress) at a rate of 1 page
> per ineligible VMA, but the budget is only checked once eligible VMAs are
> encountered.
>
> Then, it only takes the combined total of ineligible VMAs and eligible but
> non-collapsible pages to exceed the /sys/.../pages_to_scan budget for the
> rest of the mm to be skipped.
>
> For a system with 4 KiB page size and default sysctl settings this only
> requires 4096 ineligible VMAs/uncollapsible pages to be skipped before this
> happens.
>
> Resolve the issue by simply tracking the last examined address in the
> khugepaged_mm_slot object, so when the mm is resumed, the scan starts from
> where it left off.
>
> Additionally, skip to the next candidate if a VMA cannot be collapsed.
>
> This issue was noticed during some work in which an mm was instantiated
> with a large number of small VMAs under significant mmap_lock contention,
> during which collapse was observed to take 10.8s. With this patch applied
> it took 3.6s to collapse the same mm.
Nice results!
>
> Also, while here, update a couple VM_BUG_ON()'s to VM_WARN_ON_ONCE()'s.
>
> This patch is also a prerequisite for the next patch in the series.
>
> Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
Make sense to me.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
2026-11-05 21:53 ` Jose A. Perez de Azpillaga
2026-11-06 2:12 ` Vernon Yang
@ 2026-11-06 9:38 ` Baolin Wang
2026-11-06 10:52 ` Lorenzo Stoakes (ARM)
2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
3 siblings, 1 reply; 21+ messages in thread
From: Baolin Wang @ 2026-11-06 9:38 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/6/26 4:57 AM, Lorenzo Stoakes (ARM) wrote:
> khugepaged walks an mm's VMAs under the mmap read lock in
> collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
> them if encountered.
>
> It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
> when these are encountered, however only checking this budget once an
> eligible PMD-spanning VMA is encountered.
>
> It can therefore hold the mmap read lock for a long time if there are a
> large number of ineligible VMAs present in an mm, over which time no mmap
> write locks (and thus no VMA write locks) can be acquired.
>
> Address this by checking whether the mmap lock is contended after every
> KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
> pages_to_scan budget, and if so dropping and re-acquiring the mmap read
> lock before resuming the walk from the per-mm cursor.
>
> As the walk is resumed from the cursor rather than the VMA that was current
> when the lock was dropped, the cursor must never move backwards. A VMA
> ending at the top of the address space causes ALIGN() to wrap the cursor to
> zero, so treat such a VMA as the last in the mm rather than skipping past
> it.
>
> This was tested locally with ~1M ineligible VMAs mapped ahead of a
> collapsible region of memory, and resulted in a reduced mmap_lock hold time
> of 170us down from 95ms.
>
> This was also observed to reduce mmap_lock stalls in concurrent threads of
> a similar duration. Note that a reader re-acquiring the lock can currently
> steal it from a writer that is queued but not yet running, so the full
> benefit to writers depends on the separate rwsem change at [0].
>
> Local measurements indicated no change for ordinary use cases, so this is
> all upside.
>
> This work came out of investigations into mmap stalls when getting latency
> numbers for an unrelated patch series.
>
> [0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
[snip]
> if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> - /* Unable to collapse, skip to the next candidate. */
> - mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> + const unsigned long next =
> + ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> +
> cc->progress++;
> + /* Check for overflow. */
> + if (unlikely(next < vma->vm_end)) {
> + vma = NULL;
> + break;
> + }
> + /* Unable to collapse, skip to the next candidate. */
> + mm_slot->address = next;
> continue;
> }
IMO, these changes seem to belong in patch 2?
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot"
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
2026-11-05 21:47 ` Jose A. Perez de Azpillaga
@ 2026-11-06 9:41 ` Baolin Wang
1 sibling, 0 replies; 21+ messages in thread
From: Baolin Wang @ 2026-11-06 9:41 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM),
David Hildenbrand, Zi Yan, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On 11/6/26 4:57 AM, Lorenzo Stoakes (ARM) wrote:
> This partially reverts commit b4c9ffb54b32 ("mm/khugepaged: remove
> definition of struct khugepaged_mm_slot"), as a subsequent commit needs to
> resurrect this type to store additional data alongside the mm_slot.
>
> The stated intent of the commit was to address a NULL check in
> __khugepaged_exit():
>
> mm_slot = mm_slot_entry(slot, struct khugepaged_mm_slot, slot);
>
> slot can be NULL and mm_slot_entry() is an alias for container_of(), so
> this is container_of() a NULL value.
>
> This was benign as there was only 1 field in khugepaged_mm_slot (offsetting
> NULL at 0 results in NULL), however it was right to fix this, so retain the
> fix.
>
> It also unwrapped KMEM_CACHE() in khugepaged_init(). This was a mistake,
> and was itself reverted in commit c14bdcc9f274 ("mm/khugepaged: use
> KMEM_CACHE()"). Therefore, retain the KMEM_CACHE() form in this revert.
>
> Reviewed-by: David Hildenbrand (Arm) <david@kernel.org>
> Reviewed-by: Lance Yang <lance.yang@linux.dev>
> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> ---
LGTM.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 21:53 ` Jose A. Perez de Azpillaga
@ 2026-11-06 10:03 ` Lorenzo Stoakes (ARM)
2026-11-06 10:29 ` Jose A. Perez de Azpillaga
0 siblings, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 10:03 UTC (permalink / raw)
To: Jose A. Perez de Azpillaga
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Thu, Nov 05, 2026 at 10:53:13PM +0100, Jose A. Perez de Azpillaga wrote:
> On 11/5/26 9:57 PM, Lorenzo Stoakes (ARM) wrote:
> > khugepaged walks an mm's VMAs under the mmap read lock in
> > collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
> > them if encountered.
> >
> > It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
> > when these are encountered, however only checking this budget once an
> > eligible PMD-spanning VMA is encountered.
> >
> > It can therefore hold the mmap read lock for a long time if there are a
> > large number of ineligible VMAs present in an mm, over which time no mmap
> > write locks (and thus no VMA write locks) can be acquired.
> >
> > Address this by checking whether the mmap lock is contended after every
> > KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
> > pages_to_scan budget, and if so dropping and re-acquiring the mmap read
> > lock before resuming the walk from the per-mm cursor.
> >
> > As the walk is resumed from the cursor rather than the VMA that was current
> > when the lock was dropped, the cursor must never move backwards. A VMA
> > ending at the top of the address space causes ALIGN() to wrap the cursor to
> > zero, so treat such a VMA as the last in the mm rather than skipping past
> > it.
> >
> > This was tested locally with ~1M ineligible VMAs mapped ahead of a
> > collapsible region of memory, and resulted in a reduced mmap_lock hold time
> > of 170us down from 95ms.
> >
> > This was also observed to reduce mmap_lock stalls in concurrent threads of
> > a similar duration. Note that a reader re-acquiring the lock can currently
> > steal it from a writer that is queued but not yet running, so the full
> > benefit to writers depends on the separate rwsem change at [0].
> >
> > Local measurements indicated no change for ordinary use cases, so this is
> > all upside.
> >
> > This work came out of investigations into mmap stalls when getting latency
> > numbers for an unrelated patch series.
> >
> > [0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
> >
> > Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
> > ---
> > mm/khugepaged.c | 31 +++++++++++++++++++++++++++++--
> > 1 file changed, 29 insertions(+), 2 deletions(-)
> >
> > diff --git a/mm/khugepaged.c b/mm/khugepaged.c
> > index 216c4b8d28c4..eaa01dae5a03 100644
> > --- a/mm/khugepaged.c
> > +++ b/mm/khugepaged.c
> > @@ -66,6 +66,12 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
> >
> > static struct kmem_cache *mm_slot_cache __ro_after_init;
> >
> > +/*
> > + * The number of VMAs to walk before checking for, and relieving, mmap lock
> > + * contention, as a function of the pages_to_scan budget.
> > + */
> > +#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) ((pages_to_scan) / 4)
> > +
> > /**
> > * struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
> > * @slot: hash lookup from mm to mm_slot
> > @@ -2910,6 +2916,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> > struct mm_struct *mm;
> > struct vm_area_struct *vma;
> > unsigned int progress_prev = cc->progress;
> > + unsigned int nr_vmas_batch = 0;
> >
> > lockdep_assert_held(&khugepaged_mm_lock);
> > *result = SCAN_FAIL;
> > @@ -2942,15 +2949,35 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
> > for_each_vma(vmi, vma) {
> > unsigned long hstart, hend;
> >
> > + /* Relieve mmap lock contention after walking a batch of VMAs. */
> > + if (nr_vmas_batch++ >= KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)) {
> > + nr_vmas_batch = 0;
> > + if (mmap_lock_is_contended(mm)) {
> > + mmap_read_unlock(mm);
> > + cond_resched();
> > + mmap_read_lock(mm);
> > + vma_iter_set(&vmi, mm_slot->address);
> > + continue;
> > + }
> > + }
> > +
>
> with pages_to_scan below 4 the batch is 0, so under constant contention
> we keep dropping the lock at the cursor and never scan anything, don't we?
This feels like repackaged AI review...
The mmap lock won't be eternally contended and if it is, with pages_to_scan
under 4, I think that's fine.
Alternatively could make that >= an == and the post-++ a pre-++ and the 0 will
never be hit in this case.
Or could have a minimum cap.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 10:03 ` Lorenzo Stoakes (ARM)
@ 2026-11-06 10:29 ` Jose A. Perez de Azpillaga
2026-11-06 10:33 ` Lorenzo Stoakes (ARM)
0 siblings, 1 reply; 21+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-11-06 10:29 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On 11/6/26 11:03 AM, Lorenzo Stoakes (ARM) wrote:
> On Thu, Nov 05, 2026 at 10:53:13PM +0100, Jose A. Perez de Azpillaga wrote:
>> On 11/5/26 9:57 PM, Lorenzo Stoakes (ARM) wrote:
>>> khugepaged walks an mm's VMAs under the mmap read lock in
>>> collapse_scan_mm_slot(), checking for ineligible VMAs first, and skipping
>>> them if encountered.
>>>
>>> It charges cc->progress 1 page against the /sys/.../pages_to_scan budget
>>> when these are encountered, however only checking this budget once an
>>> eligible PMD-spanning VMA is encountered.
>>>
>>> It can therefore hold the mmap read lock for a long time if there are a
>>> large number of ineligible VMAs present in an mm, over which time no mmap
>>> write locks (and thus no VMA write locks) can be acquired.
>>>
>>> Address this by checking whether the mmap lock is contended after every
>>> KHUGEPAGED_MAX_VMA_SCAN_BATCH VMAs walked, that is a quarter of the
>>> pages_to_scan budget, and if so dropping and re-acquiring the mmap read
>>> lock before resuming the walk from the per-mm cursor.
>>>
>>> As the walk is resumed from the cursor rather than the VMA that was current
>>> when the lock was dropped, the cursor must never move backwards. A VMA
>>> ending at the top of the address space causes ALIGN() to wrap the cursor to
>>> zero, so treat such a VMA as the last in the mm rather than skipping past
>>> it.
>>>
>>> This was tested locally with ~1M ineligible VMAs mapped ahead of a
>>> collapsible region of memory, and resulted in a reduced mmap_lock hold time
>>> of 170us down from 95ms.
>>>
>>> This was also observed to reduce mmap_lock stalls in concurrent threads of
>>> a similar duration. Note that a reader re-acquiring the lock can currently
>>> steal it from a writer that is queued but not yet running, so the full
>>> benefit to writers depends on the separate rwsem change at [0].
>>>
>>> Local measurements indicated no change for ordinary use cases, so this is
>>> all upside.
>>>
>>> This work came out of investigations into mmap stalls when getting latency
>>> numbers for an unrelated patch series.
>>>
>>> [0]: https://lore.kernel.org/all/20261105-rwsem-dont-steal-from-queued-writer-v1-1-275e070f426f@kernel.org/
>>>
>>> Signed-off-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
>>> ---
>>> mm/khugepaged.c | 31 +++++++++++++++++++++++++++++--
>>> 1 file changed, 29 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/mm/khugepaged.c b/mm/khugepaged.c
>>> index 216c4b8d28c4..eaa01dae5a03 100644
>>> --- a/mm/khugepaged.c
>>> +++ b/mm/khugepaged.c
>>> @@ -66,6 +66,12 @@ static DEFINE_READ_MOSTLY_HASHTABLE(mm_slots_hash, MM_SLOTS_HASH_BITS);
>>>
>>> static struct kmem_cache *mm_slot_cache __ro_after_init;
>>>
>>> +/*
>>> + * The number of VMAs to walk before checking for, and relieving, mmap lock
>>> + * contention, as a function of the pages_to_scan budget.
>>> + */
>>> +#define KHUGEPAGED_MAX_VMA_SCAN_BATCH(pages_to_scan) ((pages_to_scan) / 4)
>>> +
>>> /**
>>> * struct khugepaged_mm_slot - khugepaged information per mm that is being scanned
>>> * @slot: hash lookup from mm to mm_slot
>>> @@ -2910,6 +2916,7 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
>>> struct mm_struct *mm;
>>> struct vm_area_struct *vma;
>>> unsigned int progress_prev = cc->progress;
>>> + unsigned int nr_vmas_batch = 0;
>>>
>>> lockdep_assert_held(&khugepaged_mm_lock);
>>> *result = SCAN_FAIL;
>>> @@ -2942,15 +2949,35 @@ static void collapse_scan_mm_slot(unsigned int progress_max,
>>> for_each_vma(vmi, vma) {
>>> unsigned long hstart, hend;
>>>
>>> + /* Relieve mmap lock contention after walking a batch of VMAs. */
>>> + if (nr_vmas_batch++ >= KHUGEPAGED_MAX_VMA_SCAN_BATCH(progress_max)) {
>>> + nr_vmas_batch = 0;
>>> + if (mmap_lock_is_contended(mm)) {
>>> + mmap_read_unlock(mm);
>>> + cond_resched();
>>> + mmap_read_lock(mm);
>>> + vma_iter_set(&vmi, mm_slot->address);
>>> + continue;
>>> + }
>>> + }
>>> +
>>
>> with pages_to_scan below 4 the batch is 0, so under constant contention
>> we keep dropping the lock at the cursor and never scan anything, don't we?
>
> This feels like repackaged AI review...
>
> The mmap lock won't be eternally contended and if it is, with pages_to_scan
> under 4, I think that's fine.
>
> Alternatively could make that >= an == and the post-++ a pre-++ and the 0 will
> never be hit in this case.
>
> Or could have a minimum cap.
with >= a minimum cap needs to be 2, since the cursor can stop at hend
inside the VMA we just scanned and the walk lands on it again after the
drop. with == only batch - 1 VMAs get scanned per cycle, so a batch of 1
scans nothing, today's batch-0 loop at pages_to_scan 4..7, and a batch
of 2 hits the hend case. it's safe from 3 up.
--
cheers,
jose a. p-a
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 10:29 ` Jose A. Perez de Azpillaga
@ 2026-11-06 10:33 ` Lorenzo Stoakes (ARM)
2026-11-06 10:49 ` Jose A. Perez de Azpillaga
0 siblings, 1 reply; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 10:33 UTC (permalink / raw)
To: Jose A. Perez de Azpillaga
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Fri, Nov 06, 2026 at 11:29:00AM +0100, Jose A. Perez de Azpillaga wrote:
> >> with pages_to_scan below 4 the batch is 0, so under constant contention
> >> we keep dropping the lock at the cursor and never scan anything, don't we?
> >
> > This feels like repackaged AI review...
> >
> > The mmap lock won't be eternally contended and if it is, with pages_to_scan
> > under 4, I think that's fine.
> >
> > Alternatively could make that >= an == and the post-++ a pre-++ and the 0 will
> > never be hit in this case.
> >
> > Or could have a minimum cap.
>
> with >= a minimum cap needs to be 2, since the cursor can stop at hend
> inside the VMA we just scanned and the walk lands on it again after the
> drop. with == only batch - 1 VMAs get scanned per cycle, so a batch of 1
> scans nothing, today's batch-0 loop at pages_to_scan 4..7, and a batch
> of 2 hits the hend case. it's safe from 3 up.
Please don't copy/paste LLM responses. I'm sure it's in good faith, but it's not
helpful. I will run any changes through AI myself.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 10:33 ` Lorenzo Stoakes (ARM)
@ 2026-11-06 10:49 ` Jose A. Perez de Azpillaga
0 siblings, 0 replies; 21+ messages in thread
From: Jose A. Perez de Azpillaga @ 2026-11-06 10:49 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On 11/6/26 11:33 AM, Lorenzo Stoakes (ARM) wrote:
> On Fri, Nov 06, 2026 at 11:29:00AM +0100, Jose A. Perez de Azpillaga wrote:
>>>> with pages_to_scan below 4 the batch is 0, so under constant contention
>>>> we keep dropping the lock at the cursor and never scan anything, don't we?
>>>
>>> This feels like repackaged AI review...
>>>
>>> The mmap lock won't be eternally contended and if it is, with pages_to_scan
>>> under 4, I think that's fine.
>>>
>>> Alternatively could make that >= an == and the post-++ a pre-++ and the 0 will
>>> never be hit in this case.
>>>
>>> Or could have a minimum cap.
>>
>> with >= a minimum cap needs to be 2, since the cursor can stop at hend
>> inside the VMA we just scanned and the walk lands on it again after the
>> drop. with == only batch - 1 VMAs get scanned per cycle, so a batch of 1
>> scans nothing, today's batch-0 loop at pages_to_scan 4..7, and a batch
>> of 2 hits the hend case. it's safe from 3 up.
>
> Please don't copy/paste LLM responses. I'm sure it's in good faith, but it's not
> helpful. I will run any changes through AI myself.
it wasn't my intention. I am bad at writing reviews, so AI assists me to
reword it, so it's polished, and I still check all the output. now
onward, maybe my reviews get a bit clumsy, and not polished. I will stop
rewording them.
--
cheers,
jose a. p-a
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 9:29 ` Vernon Yang
@ 2026-11-06 10:49 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 10:49 UTC (permalink / raw)
To: Vernon Yang
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Fri, Nov 06, 2026 at 05:29:45PM +0800, Vernon Yang wrote:
> On Fri, Nov 06, 2026 at 10:12:46AM +0800, Vernon Yang wrote:
> > On Thu, Nov 05, 2026 at 08:57:24PM +0000, Lorenzo Stoakes (ARM) wrote:
> > > if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> > > - /* Unable to collapse, skip to the next candidate. */
> > > - mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > > + const unsigned long next =
> > > + ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > > +
> > > cc->progress++;
> > > + /* Check for overflow. */
> > > + if (unlikely(next < vma->vm_end)) {
> >
> > I think you want it to be like that.
> >
> > if (unlikely(next >= vma->vm_end)) {
> > ^^
>
> Don’t read this comment please. Your code is right.
>
> The VMA at the top of mm, after aligning the end values according to the
> PMD, may overflow to 0.
>
> It might be easier to understand if this coding is used.
>
> if (unlikely(next == 0)) {
I'd rather keep < as it's the general form used for overflow checks and I don't
want to bake in assumptions about the nature of the overflow or make somebody
have to reason about that :)
>
> --
> Cheers,
> Vernon
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 9:38 ` Baolin Wang
@ 2026-11-06 10:52 ` Lorenzo Stoakes (ARM)
0 siblings, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 10:52 UTC (permalink / raw)
To: Baolin Wang
Cc: David Hildenbrand, Zi Yan, Liam R. Howlett, Nico Pache,
Ryan Roberts, Dev Jain, Barry Song, Lance Yang, Usama Arif,
Kiryl Shutsemau, Andrew Morton, linux-mm, linux-kernel
On Fri, Nov 06, 2026 at 05:38:27PM +0800, Baolin Wang wrote:
> > if (!collapse_possible_orders(vma, vma->vm_flags, TVA_KHUGEPAGED)) {
> > - /* Unable to collapse, skip to the next candidate. */
> > - mm_slot->address = ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > + const unsigned long next =
> > + ALIGN(vma->vm_end, HPAGE_PMD_SIZE);
> > +
> > cc->progress++;
> > + /* Check for overflow. */
> > + if (unlikely(next < vma->vm_end)) {
> > + vma = NULL;
> > + break;
> > + }
> > + /* Unable to collapse, skip to the next candidate. */
> > + mm_slot->address = next;
> > continue;
> > }
>
> IMO, these changes seem to belong in patch 2?
You're right, will move them there on next respin, thanks!
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
` (2 preceding siblings ...)
2026-11-06 9:38 ` Baolin Wang
@ 2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
2026-11-06 12:23 ` Lorenzo Stoakes (ARM)
2026-11-06 13:26 ` Vernon Yang
3 siblings, 2 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 12:07 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On reflection, I think this would be better implemented as something like:
static void collapse_scan_mm_slot(unsigned int progress_max, ...)
{
unsigned int long lock_start = jiffies;
...
for_each_vma(vmi, vma) {
...
if (time_after(jiffies, lock_start) && mmap_lock_is_contended(mm)) {
mmap_read_unlock(mm);
cond_resched();
mmap_read_lock(mm);
lock_start = jiffies;
vma_iter_set(&vmi, mm_slot->address);
continue;
}
}
}
Which avoids magic numbers, and means if we hold the lock for 1-10ms (depending
on HZ), which is a long time in the kernel to hold a lock, AND the lock is
contended, we drop/reacquire.
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
@ 2026-11-06 12:23 ` Lorenzo Stoakes (ARM)
2026-11-06 13:26 ` Vernon Yang
1 sibling, 0 replies; 21+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-11-06 12:23 UTC (permalink / raw)
To: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton
Cc: linux-mm, linux-kernel
On Fri, Nov 06, 2026 at 12:07:53PM +0000, Lorenzo Stoakes (ARM) wrote:
> On reflection, I think this would be better implemented as something like:
>
> static void collapse_scan_mm_slot(unsigned int progress_max, ...)
> {
> unsigned int long lock_start = jiffies;
The rare unsigned int long type, rarely deployed but super effective when it is
:P
(sorry losing my mind, should be an unsigned long)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs
2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
2026-11-06 12:23 ` Lorenzo Stoakes (ARM)
@ 2026-11-06 13:26 ` Vernon Yang
1 sibling, 0 replies; 21+ messages in thread
From: Vernon Yang @ 2026-11-06 13:26 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: David Hildenbrand, Zi Yan, Baolin Wang, Liam R. Howlett,
Nico Pache, Ryan Roberts, Dev Jain, Barry Song, Lance Yang,
Usama Arif, Kiryl Shutsemau, Andrew Morton, linux-mm,
linux-kernel
On Fri, Nov 06, 2026 at 12:07:53PM +0000, Lorenzo Stoakes (ARM) wrote:
> On reflection, I think this would be better implemented as something like:
>
> static void collapse_scan_mm_slot(unsigned int progress_max, ...)
> {
> unsigned int long lock_start = jiffies;
> ...
> for_each_vma(vmi, vma) {
> ...
> if (time_after(jiffies, lock_start) && mmap_lock_is_contended(mm)) {
> mmap_read_unlock(mm);
> cond_resched();
> mmap_read_lock(mm);
> lock_start = jiffies;
> vma_iter_set(&vmi, mm_slot->address);
> continue;
> }
> }
> }
>
> Which avoids magic numbers, and means if we hold the lock for 1-10ms (depending
> on HZ), which is a long time in the kernel to hold a lock, AND the lock is
> contended, we drop/reacquire.
Looks good to me. Is the test result of this solution the same as the
current one?
--
Cheers,
Vernon
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-11-06 13:26 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 20:57 [PATCH v2 0/3] mm/khugepaged: bound mmap_lock hold time when walking an mm's VMAs Lorenzo Stoakes (ARM)
2026-11-05 20:57 ` [PATCH v2 1/3] Revert "mm/khugepaged: remove definition of struct khugepaged_mm_slot" Lorenzo Stoakes (ARM)
2026-11-05 21:47 ` Jose A. Perez de Azpillaga
2026-11-06 9:41 ` Baolin Wang
2026-11-05 20:57 ` [PATCH v2 2/3] mm/khugepaged: scan mm's more efficiently under mmap_lock contention Lorenzo Stoakes (ARM)
2026-11-05 21:48 ` Jose A. Perez de Azpillaga
2026-11-06 9:31 ` Baolin Wang
2026-11-05 20:57 ` [PATCH v2 3/3] mm/khugepaged: release contended mmap read lock after scanning enough VMAs Lorenzo Stoakes (ARM)
2026-11-05 21:53 ` Jose A. Perez de Azpillaga
2026-11-06 10:03 ` Lorenzo Stoakes (ARM)
2026-11-06 10:29 ` Jose A. Perez de Azpillaga
2026-11-06 10:33 ` Lorenzo Stoakes (ARM)
2026-11-06 10:49 ` Jose A. Perez de Azpillaga
2026-11-06 2:12 ` Vernon Yang
2026-11-06 9:29 ` Vernon Yang
2026-11-06 10:49 ` Lorenzo Stoakes (ARM)
2026-11-06 9:38 ` Baolin Wang
2026-11-06 10:52 ` Lorenzo Stoakes (ARM)
2026-11-06 12:07 ` Lorenzo Stoakes (ARM)
2026-11-06 12:23 ` Lorenzo Stoakes (ARM)
2026-11-06 13:26 ` Vernon Yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®