Thread (119 messages) flat view 119 messages, 9 authors, 9d ago

Re: [RFC PATCH 19/57] mm/collapse: install a PMD leaf as the terminal layer

From: Jann Horn <jannh@google.com>
Date: 2026-08-25 16:52:56
Also in: bpf, linux-kselftest, linux-mm, lkml

On Mon, Aug 17, 2026 at 12:46 AM Kiryl Shutsemau [off-list ref] wrote:
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
What do you mean by "atomic" and "no pmd_none() window ever exists"?
Is that supposed to be with respect to a subset of readers?

While the PMD table spinlock is held, you clear the PMD entry
(pmdp_collapse_flush) and set it to a new value
(map_anon_folio_pmd_nopf). But codepaths that walk page tables don't
take that spinlock unless they already know they're in a THP case.

zap_pmd_range() does not lock the PMD table before checking
pmd_none(), and if that is true, it skips over the PMD. I think this
means that THP collapse can race with MADV_DONTNEED or zap_vma_range()
such that the zap wrongly has no effect?
stay held down at pte level by the migration entries throughout.  It is
But you don't have a migration entry at the PMD level, right?
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
I'm lost, what is "the tree"?
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;
To be clear, we can concurrently rmap-walk into the PMD, right?
Because we might be looking at a different folio which was created in
the parent process or something like that? And so a
page_vma_mapped_walk() without PVMW_SYNC might look at stale PTEs?
(Which may or may not be fine, but would not be what this commit
message claims.)
 - non-rmap pte walkers see migration entries;
 - pmd-level observers see either the old table or the leaf, never an
   intermediate;
(or pmd_none(), see above)
+       old_pmd = pmdp_collapse_flush(vma, cand->addr, pmd);
+       old_table = pmd_pgtable(old_pmd);
[...]
+       pgtable_trans_huge_deposit(mm, pmd, cand->deposit);
+       map_anon_folio_pmd_nopf(cand->new_folio, pmd, vma, cand->addr);
[...]
+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);
What does old_table contain at this point - migration entries? I
wonder if we should zero all the PTEs inside it before dropping its
spinlock and scheduling its freeing, so that a concurrent
pte_offset_map_rw_nolock()+spin_lock() wouldn't see dangling migration
entries. (I'm not sure if we actually have a codepath that does that,
but even if not, this seems like it might cause problems later.)
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help