From: Kiryl Shutsemau <kirill@shutemov.name>
To: akpm@linux-foundation.org, david@kernel.org, ljs@kernel.org,
nico.pache@linux.dev
Cc: baolin.wang@linux.alibaba.com, baohua@kernel.org,
dev.jain@arm.com, hughd@google.com, lance.yang@linux.dev,
liam@infradead.org, mhocko@suse.com, rppt@kernel.org,
ryan.roberts@arm.com, shuah@kernel.org, surenb@google.com,
usama.arif@linux.dev, vbabka@kernel.org, ziy@nvidia.com,
usama.anjum@arm.com, agordeev@linux.ibm.com, linux-mm@kvack.org,
linux-kselftest@vger.kernel.org, linux-kernel@vger.kernel.org,
kas@kernel.org, jannh@google.com, willy@infradead.org,
pfalcato@suse.de, rostedt@goodmis.org, mhiramat@kernel.org,
linux-trace-kernel@vger.kernel.org, bpf@vger.kernel.org
Subject: [RFC PATCH 11/57] mm/collapse: sketch the passes of a round
Date: Sun, 16 Aug 2026 23:45:23 +0100 [thread overview]
Message-ID: <20260816224609.308019-12-kirill@shutemov.name> (raw)
In-Reply-To: <20260816224609.308019-1-kirill@shutemov.name>
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
A round is a sequence of passes over the same batch, and their order is
most of the design. It falls into three parts:
- before any lock, the allocations that may sleep: destination folios,
and the page table a PMD-order candidate deposits;
- under mmap_read, revalidation and fault-in, which may have to give the
lock up;
- from the freeze onwards, a stretch that has to run to completion.
Each pass in that last part works on what the one before it left, and
every barrier raised has to be lowered again. The destinations still
missing are asked for there too, without reclaim: a faulter on a frozen
source would wait for the allocation.
Lay that sequence out, with every pass a stub but one. collapse_round()
takes mmap_read for the middle of it: a collapse is called without the
lock, and takes its own for each round. It brackets the frozen window in
one mmu-notifier invalidate over the whole batch.
That invalidate needs a span before any pass has a body, so
collapse_revalidate() settles it from the start, in cc->batch_start and
cc->batch_end. The span is taken over the candidates rather than off the
ends of the array: a region refused at one order can re-enter selection
at a lower one, so a round is not address-ordered and candidates[0] need
not be the lowest.
A fault-in that had to sleep comes back with the lock dropped, reported
as SCAN_LOCK_DROPPED. The round takes the lock again and runs the pass
afresh, a bounded number of times, rather than sending the batch back to
selection.
The stubs do nothing, so the round does nothing.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
include/trace/events/huge_memory.h | 1 +
mm/collapse.c | 207 +++++++++++++++++++++++++++++
mm/collapse.h | 9 ++
3 files changed, 217 insertions(+)
diff --git a/include/trace/events/huge_memory.h b/include/trace/events/huge_memory.h
index 5a48c5406cce..778f5a56956c 100644
--- a/include/trace/events/huge_memory.h
+++ b/include/trace/events/huge_memory.h
@@ -24,6 +24,7 @@
EM( SCAN_PAGE_COUNT, "not_suitable_page_count") \
EM( SCAN_PAGE_LRU, "page_not_in_lru") \
EM( SCAN_PAGE_LOCK, "page_locked") \
+ EM( SCAN_LOCK_DROPPED, "lock_dropped") \
EM( SCAN_PAGE_ANON, "page_not_anon") \
EM( SCAN_PAGE_LAZYFREE, "page_lazyfree") \
EM( SCAN_PAGE_COMPOUND, "page_compound") \
diff --git a/mm/collapse.c b/mm/collapse.c
index ad9e5a447854..25c0f72a9a68 100644
--- a/mm/collapse.c
+++ b/mm/collapse.c
@@ -94,6 +94,14 @@
*/
#define COLLAPSE_BATCH_BYTES SZ_32M
+/*
+ * How many times a round runs the fault-in pass. A fault that has to wait drops
+ * the lock, and running the pass again costs a walk of the batch but buys at
+ * least one completed swap-in; readahead brings a cluster in at a time, so this
+ * covers a PMD's default max_ptes_swap.
+ */
+#define COLLAPSE_FAULTIN_PASSES 8
+
/* Windows in one table at the finest order collapse cuts */
#define COLLAPSE_TABLE_WINDOWS (HPAGE_PMD_NR >> COLLAPSE_MIN_MTHP_ORDER)
@@ -118,6 +126,21 @@ struct collapse_candidate {
enum scan_result result;
};
+static unsigned long candidate_start(const struct collapse_candidate *cand)
+{
+ return cand->addr;
+}
+
+static unsigned long candidate_size(const struct collapse_candidate *cand)
+{
+ return PAGE_SIZE << cand->order;
+}
+
+static unsigned long candidate_end(const struct collapse_candidate *cand)
+{
+ return candidate_start(cand) + candidate_size(cand);
+}
+
/* Where a candidate sits in the table, in the PTE offsets selection counts in */
static unsigned int candidate_offset(const struct collapse_candidate *cand,
unsigned long pmd_addr)
@@ -140,6 +163,134 @@ int collapse_control_init(struct collapse_control *cc)
return 0;
}
+/*
+ * The scan and the allocation both dropped mmap_lock, so nothing seen before it
+ * can be trusted: find the VMA and the PTE table again, and check they still
+ * allow every provisioned candidate.
+ *
+ * This is also where the batch's span is settled, for the invalidate the round
+ * issues over it.
+ */
+static enum scan_result collapse_revalidate(struct vm_area_struct *vma,
+ unsigned long pmd_addr,
+ struct collapse_control *cc,
+ pmd_t **pmdp)
+{
+ unsigned int i;
+
+ cc->batch_start = ULONG_MAX;
+ cc->batch_end = 0;
+
+ for (i = 0; i < cc->nr_candidates; i++) {
+ struct collapse_candidate *cand = &cc->candidates[i];
+
+ cc->batch_start = min(cc->batch_start, candidate_start(cand));
+ cc->batch_end = max(cc->batch_end, candidate_end(cand));
+ }
+
+ return SCAN_SUCCEED;
+}
+
+/*
+ * Make every source the round needs present and exclusively owned by this mm,
+ * by faulting it in as an ordinary access would. Sleeps, and drops mmap_lock on
+ * failure, since a fault may have to be retried with it released.
+ *
+ * Anything faulted in lands on a per-CPU LRU batch, holding a reference the
+ * freeze cannot account for, so the freeze drains those batches before it
+ * starts.
+ */
+static enum scan_result collapse_faultin(struct vm_area_struct *vma,
+ struct collapse_control *cc,
+ pmd_t *pmd)
+{
+ return SCAN_SUCCEED;
+}
+
+/*
+ * Raise the two barriers on the sources of every candidate: migration entries in
+ * their PTEs, then a frozen refcount. Takes the table's ptl once for the whole
+ * batch, and flushes the TLB once before dropping it. A candidate whose sources
+ * moved is dropped here.
+ */
+static void collapse_freeze(struct vm_area_struct *vma,
+ struct collapse_control *cc, pmd_t *pmd)
+{
+}
+
+/*
+ * Allocate ahead of the freeze for the candidates whose light allocation missed
+ * last round. This is where reclaim belongs: nothing is held or frozen, so a
+ * long compaction costs only khugepaged's own progress -- which is why the
+ * mechanism this replaces allocated here too. Having asked the allocator to try
+ * hard, a miss now is a failure.
+ */
+static void collapse_reserve(struct mm_struct *mm, struct collapse_control *cc)
+{
+}
+
+/*
+ * Secure the page table the PMD terminal layer deposits. This stays ahead of the
+ * freeze because pte_alloc_one() allocates with GFP_PGTABLE_USER and takes no gfp
+ * to strip: order-0 or not, it may reclaim and sleep, which is what the window
+ * exists to keep out. The destination folio has a light gfp to fall back on and
+ * so can be deferred; this has none.
+ */
+static void collapse_deposit(struct mm_struct *mm, struct collapse_control *cc)
+{
+}
+
+/*
+ * Give the frozen candidates that still need one a destination folio, without
+ * reclaim: a faulter on their sources would wait for it.
+ *
+ * A miss here is not a failure, as long as a retry could do better: the
+ * candidate keeps its freeze and asks for the reclaiming gfp, which
+ * collapse_reserve() uses before the next round freezes anything. When the
+ * policy forbids reclaim there is nothing better to retry with, so the miss is
+ * the answer, and a smaller order over the same region is the better next move.
+ */
+static void collapse_provision(struct mm_struct *mm,
+ struct collapse_control *cc)
+{
+}
+
+/*
+ * Copy the frozen sources into their destinations. Nothing can reach either
+ * side, so this needs no page-table lock, and it sleeps.
+ */
+static void collapse_copy(struct vm_area_struct *vma,
+ struct collapse_control *cc)
+{
+}
+
+/* Publish each destination folio in place of the sources it replaces */
+static void collapse_install(struct vm_area_struct *vma,
+ struct collapse_control *cc, pmd_t *pmd)
+{
+}
+
+/*
+ * Lower the barriers the freeze raised, on the sources of an installed candidate
+ * and on those of one that got no further.
+ */
+static void collapse_putback(struct vm_area_struct *vma,
+ struct collapse_control *cc)
+{
+}
+
+/*
+ * Settle whatever the round reached: account what was installed, release what
+ * was not, and give every candidate the result selection will classify. Returns
+ * how many candidates were installed.
+ */
+static unsigned int collapse_finish(struct mm_struct *mm,
+ struct collapse_control *cc,
+ enum scan_result result)
+{
+ return 0;
+}
+
/*
* Carry one batch of candidates through the passes. Every candidate comes back
* with a result of its own: the passes before the freeze mark what they refuse
@@ -149,6 +300,62 @@ int collapse_control_init(struct collapse_control *cc)
static void collapse_round(struct mm_struct *mm, unsigned long pmd_addr,
struct collapse_control *cc)
{
+ unsigned int passes = COLLAPSE_FAULTIN_PASSES;
+ struct mmu_notifier_range range;
+ struct vm_area_struct *vma;
+ enum scan_result result;
+ pmd_t *pmd;
+
+ collapse_reserve(mm, cc);
+ collapse_deposit(mm, cc);
+
+retry:
+ mmap_read_lock(mm);
+
+ vma = find_vma(mm, pmd_addr);
+ if (!vma) {
+ result = SCAN_VMA_NULL;
+ goto out_unlock;
+ }
+
+ result = collapse_revalidate(vma, pmd_addr, cc, &pmd);
+ if (result != SCAN_SUCCEED)
+ goto out_unlock;
+
+ result = collapse_faultin(vma, cc, pmd);
+ /*
+ * A fault dropped the lock to wait, as a swap-in does. The swap-in it
+ * started is still running and the walk skips whatever has arrived, so
+ * take the lock again rather than send the batch back to selection. The
+ * VMA and the table are looked up afresh: both may have changed.
+ */
+ if (result == SCAN_LOCK_DROPPED && --passes)
+ goto retry;
+ if (result != SCAN_SUCCEED)
+ goto out; /* the callee released mmap_lock */
+
+ /* One invalidate window spans the batch, as collapse_revalidate() left it */
+ mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm,
+ cc->batch_start, cc->batch_end);
+ mmu_notifier_invalidate_range_start(&range);
+
+ /*
+ * None of these can fail as a whole: the freeze takes the sources it
+ * can and drops the candidates it cannot, and each pass after it works
+ * on what the one before left, so every barrier raised is lowered again.
+ */
+ collapse_freeze(vma, cc, pmd);
+ collapse_provision(mm, cc);
+ collapse_copy(vma, cc);
+ collapse_install(vma, cc, pmd);
+ collapse_putback(vma, cc);
+
+ mmu_notifier_invalidate_range_end(&range);
+
+out_unlock:
+ mmap_read_unlock(mm);
+out:
+ collapse_finish(mm, cc, result);
}
/*
diff --git a/mm/collapse.h b/mm/collapse.h
index 1159ed39b9eb..c61db86dc6c2 100644
--- a/mm/collapse.h
+++ b/mm/collapse.h
@@ -29,6 +29,7 @@ enum scan_result {
SCAN_PAGE_COUNT,
SCAN_PAGE_LRU,
SCAN_PAGE_LOCK,
+ SCAN_LOCK_DROPPED,
SCAN_PAGE_ANON,
SCAN_PAGE_LAZYFREE,
SCAN_PAGE_COMPOUND,
@@ -126,6 +127,14 @@ struct collapse_control {
/* The candidate windows collected for the current round */
struct collapse_candidate *candidates;
unsigned int nr_candidates;
+
+ /*
+ * What the candidates the round still means to freeze span, settled by
+ * collapse_revalidate() as it walks them. A round is not necessarily
+ * address-ordered, so this cannot be read off the ends of the array.
+ */
+ unsigned long batch_start;
+ unsigned long batch_end;
};
int collapse_control_init(struct collapse_control *cc);
--
2.54.0
next prev parent reply other threads:[~2026-08-16 22:46 UTC|newest]
Thread overview: 65+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-16 22:45 [RFC PATCH 00/57] mm/collapse: rebuild collapse on migration primitives Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 01/57] mm: add pte_folio() Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 02/57] mm: add pte_none_or_zero() Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 03/57] mm/collapse: add collapse.h for the shared collapse state Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 04/57] mm/collapse: rename mthp_present_ptes to eligible_ptes Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 05/57] mm/collapse: state what a collapse may do in the policy Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 06/57] mm/collapse: move the smallest collapse order to collapse.h Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 07/57] mm/collapse: sketch the new anonymous collapse engine Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 08/57] mm/collapse: scan a table for what a collapse could use Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 09/57] mm/collapse: collect candidate windows into a round Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 10/57] mm/collapse: run a round and feed the outcomes back Kiryl Shutsemau
2026-08-16 22:45 ` Kiryl Shutsemau [this message]
2026-08-16 22:45 ` [RFC PATCH 12/57] mm/collapse: allocate a destination per candidate Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 13/57] mm/collapse: revalidate a round against the VMA Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 14/57] mm/collapse: fault the sources in before the freeze Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 15/57] mm/collapse: check what a candidate would freeze Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 16/57] mm/collapse: freeze the sources behind migration entries Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 17/57] mm/collapse: copy the sources into the destinations Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 18/57] mm/collapse: install the destinations at PTE level Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 19/57] mm/collapse: install a PMD leaf as the terminal layer Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 20/57] mm/collapse: put the sources back Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 21/57] mm/collapse: settle whatever the round reached Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 22/57] mm/collapse: walk a table with a selection cursor Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 23/57] mm/collapse: give a refused region a second chance Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 24/57] mm/collapse: report each candidate's outcome to tracing Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 25/57] mm/collapse: collapse anonymous memory with the new engine Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 26/57] mm/collapse: give collapse_single_pmd() the range to work on Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 27/57] mm/collapse: scan the windows a VMA can hold Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 28/57] mm/collapse: remove the mechanism the engine replaces Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 29/57] mm/collapse: move what a collapse is judged on into collapse.c Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 30/57] mm/collapse: name the max_ptes ceiling after collapse Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 31/57] mm/khugepaged: count collapses where khugepaged makes them Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 32/57] mm/collapse: move the file collapse into collapse.c Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 33/57] mm/collapse: split collapse into a scan and a run Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 34/57] mm/collapse: implement MADV_COLLAPSE in madvise.c Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 35/57] mm/madvise: drop MADV_COLLAPSE's redundant mm reference Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 36/57] mm/collapse: report what the scan found Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 37/57] mm/collapse: report what the fault-in pass paid Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 38/57] mm/collapse: report the round, and what it made faulters wait Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 39/57] mm/collapse: name the file collapse's tracepoints after collapse Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 40/57] mm/collapse: remove the tracepoints of the mechanism that is gone Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 41/57] mm/collapse: give collapse its own trace header Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 42/57] mm/collapse: allow error injection into the freeze Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 43/57] mm/khugepaged: check the scan budget before the work, not after Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 44/57] mm/khugepaged: hold the address space open across a scan Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 45/57] mm/collapse: take a per-VMA read lock for the round Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 46/57] mm/khugepaged: scan under a per-VMA read lock Kiryl Shutsemau
2026-08-16 22:45 ` [RFC PATCH 47/57] mm/madvise: collapse " Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 48/57] mm/collapse: assert the mm reference the engine relies on Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 49/57] mm/khugepaged: drop the mmap_lock barrier from __khugepaged_exit() Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 50/57] selftests/mm: attribute collapses by candidate event alone Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 51/57] selftests/mm: cover collapse inside a sub-PMD VMA Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 52/57] selftests/mm: cover a hole-y window in " Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 53/57] selftests/mm: cover collapse of mlocked ranges Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 54/57] selftests/mm: cover collapse beside a MADV_FREE'd page Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 55/57] selftests/mm: cover collapse beside a pinned page Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 56/57] selftests/mm: cover the scaled max_ptes_shared limit Kiryl Shutsemau
2026-08-16 22:46 ` [RFC PATCH 57/57] MAINTAINERS: add an entry for collapse Kiryl Shutsemau
2026-08-17 8:04 ` Lorenzo Stoakes (ARM)
2026-08-17 8:08 ` David Hildenbrand (Arm)
2026-08-17 10:12 ` Kiryl Shutsemau
2026-08-17 2:02 ` [RFC PATCH 00/57] mm/collapse: rebuild collapse on migration primitives Zi Yan
2026-08-17 10:07 ` Kiryl Shutsemau
2026-08-17 8:52 ` Lorenzo Stoakes (ARM)
2026-08-17 13:38 ` Kiryl Shutsemau
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260816224609.308019-12-kirill@shutemov.name \
--to=kirill@shutemov.name \
--cc=agordeev@linux.ibm.com \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=bpf@vger.kernel.org \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=hughd@google.com \
--cc=jannh@google.com \
--cc=kas@kernel.org \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=ljs@kernel.org \
--cc=mhiramat@kernel.org \
--cc=mhocko@suse.com \
--cc=nico.pache@linux.dev \
--cc=pfalcato@suse.de \
--cc=rostedt@goodmis.org \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=shuah@kernel.org \
--cc=surenb@google.com \
--cc=usama.anjum@arm.com \
--cc=usama.arif@linux.dev \
--cc=vbabka@kernel.org \
--cc=willy@infradead.org \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox