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 19/57] mm/collapse: install a PMD leaf as the terminal layer
Date: Sun, 16 Aug 2026 23:45:31 +0100 [thread overview]
Message-ID: <20260816224609.308019-20-kirill@shutemov.name> (raw)
In-Reply-To: <20260816224609.308019-1-kirill@shutemov.name>
From: "Kiryl Shutsemau (Meta)" <kas@kernel.org>
Fill in the PMD install. Under the pmd lock, with the pte ptl nested
inside it: verify, detach the table with pmdp_collapse_flush(), deposit a
fresh one and map the leaf.
That is one atomic section, so no pmd_none() window ever exists: faults
stay held down at pte level by the migration entries throughout. It is
what lets PMD collapse run under mmap_read like everything else here.
Two things force that nesting, which is the one the tree already uses to
reinstall a table. A racing zap of a frozen entry takes the pte ptl, so
the verify has to hold it. And the table must not come apart between
verify and detach, which is the pmd lock's job.
Nothing leaves the section early, aborts included. An abort only
restores PTEs and would need no pmd-level exclusion of its own, except
that its pte pointer came from pte_offset_map_rw_nolock(), whose caller
must establish that the pmd is stable.
The deposited table is the freshly allocated one, never the table just
detached. A deposited table has to be quiescent, because whoever
withdraws it frees it immediately with nothing to hold a lockless walker
off first, and a table that has never been reachable is quiescent by
construction.
The detached one is not: GUP-fast and RCU pte walks that read the old PMD
may still be inside it, and on broadcast-TLBI architectures the flush
expels nobody. Quiescing it would need an IPI, which has nowhere to go
here -- outside the pmd lock it opens the pmd_none() window this design
does not have, inside it is a broadcast under a spinlock. So the
detached table goes to pte_free_defer(), which holds the free until those
walkers finish. One transient table page per PMD collapse is the cost.
No anon_vma_lock_write() is taken, unlike the mechanism being replaced:
- rmap walks on the sources are unreachable, their refcounts frozen and
their folio locks held from freeze to putback;
- non-rmap pte walkers see migration entries;
- pmd-level observers see either the old table or the leaf, never an
intermediate;
- fork, mremap and munmap take mmap_write, which the mmap_read held here
excludes.
Assisted-by: Claude-Code:claude-opus-5
Signed-off-by: Kiryl Shutsemau (Meta) <kas@kernel.org>
---
mm/collapse.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 118 insertions(+)
diff --git a/mm/collapse.c b/mm/collapse.c
index 842adc30aeb0..ab7476471b8d 100644
--- a/mm/collapse.c
+++ b/mm/collapse.c
@@ -1232,6 +1232,124 @@ static bool collapse_verify_candidate(struct collapse_candidate *cand,
static void collapse_install_pmd(struct vm_area_struct *vma,
struct collapse_control *cc, pmd_t *pmd)
{
+ struct collapse_candidate *cand = &cc->candidates[0];
+ struct mm_struct *mm = vma->vm_mm;
+ spinlock_t *pmd_ptl, *pte_ptl;
+ pgtable_t old_table = NULL;
+ unsigned int nr_populated;
+ pmd_t old_pmd, pmdval;
+ pte_t *pte;
+
+ if (cand->state != CAND_FROZEN)
+ return;
+
+ /* No destination: the provision pass could not spare one */
+ if (!cand->new_folio) {
+ pte = pte_offset_map_lock(mm, pmd, cand->addr, &pte_ptl);
+ collapse_abort_candidate(vma, cand, pte);
+ if (pte)
+ pte_unmap_unlock(pte, pte_ptl);
+ return;
+ }
+
+ /*
+ * The pte ptl nests inside the pmd lock, the nesting the tree already
+ * uses for reinstalling a table: a racing zap of a frozen entry takes
+ * the pte ptl, so the verify must hold it, and the table must not come
+ * apart between verify and detach. pmd_same() rechecks are unnecessary,
+ * the pmd lock being held across the whole section.
+ */
+ pmd_ptl = pmd_lock(mm, pmd);
+ pte = pte_offset_map_rw_nolock(mm, pmd, cand->addr, &pmdval, &pte_ptl);
+ if (!pte) {
+ /* Table gone under us; see collapse_abort_candidate() on @pte */
+ spin_unlock(pmd_ptl);
+ cand->result = SCAN_NO_PTE_TABLE;
+ collapse_abort_candidate(vma, cand, NULL);
+ return;
+ }
+ if (pte_ptl != pmd_ptl)
+ spin_lock_nested(pte_ptl, SINGLE_DEPTH_NESTING);
+
+ /*
+ * Every exit is inside that section, the aborts as much as the install.
+ * An abort needs no pmd-level exclusion of its own; it only restores
+ * PTEs. But the table it works on came from pte_offset_map_rw_nolock(),
+ * which leaves its caller to establish that the pmd is stable, and the
+ * held pmd lock is what does that here.
+ */
+ if (cand->result != SCAN_SUCCEED) {
+ /* Machine check during the copy */
+ collapse_abort_candidate(vma, cand, pte);
+ goto out_unlock;
+ }
+
+ if (!collapse_verify_candidate(cand, pte, &nr_populated)) {
+ cand->result = SCAN_PTE_NON_PRESENT;
+ collapse_abort_candidate(vma, cand, pte);
+ goto out_unlock;
+ }
+
+ /*
+ * Nothing fallible sits past here. No anon_vma_lock_write either: rmap
+ * walks on the sources are unreachable -- refcounts frozen, folio locks
+ * held from freeze to putback -- non-rmap pte walkers see migration
+ * entries, pmd-level observers see the old table or the leaf and never an
+ * intermediate, and fork, mremap and munmap take mmap_write, which our
+ * mmap_read excludes.
+ *
+ * The flush inside pmdp_collapse_flush() is the round's second over this
+ * range: the freeze displaced every leaf here and flushed before dropping
+ * the ptl, and the verify above proved nothing has been mapped since.
+ * What it covers is the paging-structure caches -- a CPU may still hold
+ * the pmd-to-table link, for a table that is about to be freed -- which
+ * is why the helper shoots down a pte range rather than a pmd.
+ */
+ old_pmd = pmdp_collapse_flush(vma, cand->addr, pmd);
+ old_table = pmd_pgtable(old_pmd);
+
+ /*
+ * The smp_wmb() in __folio_mark_uptodate() orders the copied data before
+ * the install below publishes it.
+ */
+ __folio_mark_uptodate(cand->new_folio);
+
+ /*
+ * Deposit a freshly allocated table, not the one just detached: a
+ * deposited table has to be quiescent, because whoever withdraws it frees
+ * it immediately (zap_huge_pmd()) with nothing to hold a lockless walker
+ * off first. A table that has never been reachable is quiescent by
+ * construction, which is why collapse_alloc() secured one.
+ *
+ * The detached table is not. GUP-fast and RCU pte walks that read the
+ * old PMD before pmdp_collapse_flush() may still be inside it, and on
+ * broadcast-TLBI arches that flush expels nobody. Quiescing it would
+ * take an IPI (tlb_remove_table_sync_one()), which has nowhere to go
+ * here: outside the pmd lock it opens a pmd_none window a fault can fill,
+ * inside it is a broadcast under a spinlock. So it goes to
+ * pte_free_defer(), which holds the free until those walkers finish, as
+ * retract_page_tables() does. One transient table page per PMD collapse
+ * is what that costs.
+ */
+ pgtable_trans_huge_deposit(mm, pmd, cand->deposit);
+ map_anon_folio_pmd_nopf(cand->new_folio, pmd, vma, cand->addr);
+
+ /* Slots with no source gain anon memory that no zap accounted */
+ if (nr_populated)
+ add_mm_counter(mm, MM_ANONPAGES, nr_populated);
+ cand->deposit = NULL;
+ cand->new_folio = NULL; /* ownership: the mapping */
+ cand->state = CAND_INSTALLED;
+
+out_unlock:
+ if (pte_ptl != pmd_ptl)
+ spin_unlock(pte_ptl);
+ pte_unmap(pte);
+ spin_unlock(pmd_ptl);
+
+ /* The deposit balanced the detached table, so the count is already right */
+ if (old_table)
+ pte_free_defer(mm, old_table);
}
/* Publish each destination folio in place of the sources it replaces */
--
2.54.0
next prev parent reply other threads:[~2026-08-16 22:46 UTC|newest]
Thread overview: 64+ 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 ` [RFC PATCH 11/57] mm/collapse: sketch the passes of a round Kiryl Shutsemau
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 ` Kiryl Shutsemau [this message]
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)
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-20-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