From: Usama Arif <usama.arif@linux.dev>
To: Andrew Morton <akpm@linux-foundation.org>,
david@kernel.org, chrisl@kernel.org, kasong@tencent.com,
ljs@kernel.org, ziy@nvidia.com, linux-mm@kvack.org
Cc: ying.huang@linux.alibaba.com, Baoquan He <baoquan.he@linux.dev>,
willy@infradead.org, youngjun.park@lge.com, hannes@cmpxchg.org,
riel@surriel.com, shakeel.butt@linux.dev, alex@ghiti.fr,
kas@kernel.org, baohua@kernel.org, dev.jain@arm.com,
baolin.wang@linux.alibaba.com, Nico Pache <nico.pache@linux.dev>,
Liam R. Howlett <liam@infradead.org>,
ryan.roberts@arm.com, Vlastimil Babka <vbabka@kernel.org>,
lance.yang@linux.dev, linux-kernel@vger.kernel.org,
nphamcs@gmail.com, shikemeng@huaweicloud.com, yosry@kernel.org,
qi.zheng@linux.dev, luizcap@redhat.com, kernel-team@meta.com,
Usama Arif <usama.arif@linux.dev>
Subject: [RESEND v7 24/29] mm: handle PMD swap entries in MADV_WILLNEED
Date: Mon, 14 Sep 2026 05:28:14 -0700 [thread overview]
Message-ID: <20260914122950.3283997-25-usama.arif@linux.dev> (raw)
In-Reply-To: <20260914122950.3283997-1-usama.arif@linux.dev>
swapin_walk_pmd_entry() walks PTEs and cannot descend into a non-present
PMD, so MADV_WILLNEED is a no-op on a PMD swap entry. Reading the slots
back one at a time would not be much better: order-0 readahead populates
per-page swap-cache state, and the fault that follows would then have to
split the entry the prefetch was supposed to help.
Prefetch the whole range as one PMD-order folio instead, leaving the entry
in place so the eventual fault still takes do_huge_pmd_swap_page(). Check
the THP policy first: the range was PMD-mapped when it was swapped out, but
MADV_NOHUGEPAGE or the sysfs knob may have changed since, and reading at
PMD order would then hand do_swap_page() a PMD-sized folio to map with
PTEs.
A split cache, per-page zswap state, or a failed PMD-order read or
allocation falls back to PTEs. Leaving the entry alone would prefetch
nothing at all, while the PTE path can still read the slots at order 0. A
folio that failed to read is dropped from the swap cache first, so the PTE
retry re-reads each slot rather than every later fault returning SIGBUS.
Only split if the PMD still looks like the entry that was observed - the
decision is taken after the PMD lock has been dropped, so a racing fault
may have swapped the range back in as a THP. The test is lockless, so it
narrows that window rather than closing it.
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
mm/madvise.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 139 insertions(+)
diff --git a/mm/madvise.c b/mm/madvise.c
index 64019668d69fb..ec1cb1b71070a 100644
--- a/mm/madvise.c
+++ b/mm/madvise.c
@@ -33,6 +33,7 @@
#include <linux/shmem_fs.h>
#include <linux/mmu_notifier.h>
#include <linux/swap_ops.h>
+#include <linux/zswap.h>
#include <asm/tlb.h>
@@ -194,6 +195,108 @@ static int madvise_update_vma(vm_flags_t new_flags,
}
#ifdef CONFIG_SWAP
+/*
+ * Prefetch a whole PMD swap entry as one PMD-order folio.
+ *
+ * Called with the PMD lock held; always drops it. Returns true when the
+ * caller should ask the walker to retry so the PTE path can handle the
+ * covered slots individually.
+ */
+static bool swapin_pmd_swap_entry(struct vm_area_struct *vma, pmd_t *pmd,
+ unsigned long addr, softleaf_t entry,
+ spinlock_t *ptl)
+{
+ struct vm_fault vmf = {
+ .vma = vma,
+ .address = addr,
+ .real_address = addr,
+ .pmd = pmd,
+ };
+ enum swap_pmd_cache cache_state;
+ struct swap_info_struct *si;
+ struct folio *folio;
+ bool split = false;
+
+ /*
+ * The range was PMD-mapped when it was swapped out, but the policy may
+ * have changed since: MADV_NOHUGEPAGE, or the sysfs knob. Reading it
+ * back at PMD order would then hand do_swap_page() a PMD-sized folio to
+ * map with PTEs, which is exactly what the policy forbids. Split now
+ * and let the PTE path prefetch at order 0 - the next fault would split
+ * the entry anyway, so nothing is lost that the VMA still permits.
+ */
+ if (!thp_vma_allowable_order(vma, vma->vm_flags, TVA_PAGEFAULT,
+ HPAGE_PMD_ORDER)) {
+ spin_unlock(ptl);
+ return true;
+ }
+
+ cache_state = swap_pmd_cache_lookup(entry, &folio);
+ if (cache_state == SWAP_PMD_CACHE_HUGE) {
+ /* Already cached as one PMD-sized folio, nothing to do. */
+ folio_put(folio);
+ spin_unlock(ptl);
+ return false;
+ }
+ if (cache_state == SWAP_PMD_CACHE_SPLIT ||
+ zswap_is_present(entry, HPAGE_PMD_NR)) {
+ spin_unlock(ptl);
+ return true;
+ }
+
+ /*
+ * Pin the swap device under the PMD lock so the PMD-swap-entry
+ * observation keeps the entry valid for swapin_sync().
+ */
+ si = get_swap_device(entry);
+ spin_unlock(ptl);
+ if (IS_ERR_OR_NULL(si))
+ return false;
+
+ folio = swapin_sync(entry, GFP_HIGHUSER_MOVABLE, BIT(HPAGE_PMD_ORDER),
+ &vmf, NULL, 0);
+
+ /*
+ * Fall back to PTE-order swapin: a PMD-order failure does not mean
+ * that individual slots cannot be read.
+ */
+ if (IS_ERR_OR_NULL(folio)) {
+ split = true;
+ goto out;
+ }
+
+ if (folio_nr_pages(folio) != HPAGE_PMD_NR) {
+ split = true;
+ goto out_put;
+ }
+
+ /*
+ * A trylock only succeeds once the read has completed, so this never
+ * blocks MADV_WILLNEED on in-flight I/O. A read that failed - a
+ * PMD-order zswap load that found per-page state, or an I/O error -
+ * leaves the folio clean and not uptodate. Drop it from the swap cache
+ * so the PTE retry reads each slot again; leaving it there would make
+ * the next fault return VM_FAULT_SIGBUS. Another thread may have
+ * removed it already, so revalidate the association first.
+ */
+ if (!folio_trylock(folio))
+ goto out_put;
+
+ if (!folio_test_uptodate(folio)) {
+ if (folio_matches_swap_entry(folio, entry))
+ swap_cache_del_folio(folio);
+ split = true;
+ }
+ folio_unlock(folio);
+
+out_put:
+ folio_put(folio);
+out:
+ /* Keep the device pinned until the last use of @entry. */
+ put_swap_device(si);
+ return split;
+}
+
static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start,
unsigned long end, struct mm_walk *walk)
{
@@ -203,6 +306,41 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start,
spinlock_t *ptl;
unsigned long addr;
+ ptl = pmd_trans_huge_lock(pmd, vma);
+ if (ptl) {
+ pmd_t pmdval = *pmd;
+
+ if (pmd_is_swap_entry(pmdval)) {
+ /* swapin_pmd_swap_entry() always drops the PMD lock. */
+ if (!swapin_pmd_swap_entry(vma, pmd, start,
+ softleaf_from_pmd(pmdval),
+ ptl))
+ goto ret;
+ /*
+ * Only split if this still looks like the entry we
+ * observed. The fallback was decided after the PMD lock
+ * was dropped, so a racing fault may have swapped the
+ * range back in as a THP, and splitting that would
+ * demote a perfectly good huge mapping for an advisory
+ * hint. The test is lockless, so it narrows that window
+ * rather than closing it.
+ */
+ if (pmd_same(pmdval, pmdp_get_lockless(pmd))) {
+ __split_huge_pmd(vma, pmd, start);
+ walk->action = ACTION_AGAIN;
+ goto ret;
+ }
+ /*
+ * Somebody else changed the PMD. Leave it alone and let
+ * the PTE loop below deal with whatever is there now;
+ * it simply finds no page table if the range came back
+ * as a THP.
+ */
+ } else {
+ spin_unlock(ptl);
+ }
+ }
+
for (addr = start; addr < end; addr += PAGE_SIZE) {
pte_t pte;
softleaf_t entry;
@@ -231,6 +369,7 @@ static int swapin_walk_pmd_entry(pmd_t *pmd, unsigned long start,
if (ptep)
pte_unmap_unlock(ptep, ptl);
swap_read_submit(&ctx);
+ret:
cond_resched();
return 0;
--
2.53.0-Meta
next prev parent reply other threads:[~2026-09-14 12:32 UTC|newest]
Thread overview: 35+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-14 12:27 [RESEND v7 00/29] mm: PMD-level swap entries for anonymous THPs Usama Arif
2026-09-14 12:27 ` [RESEND v7 01/29] mm: rename pmd_to_softleaf_folio() to pmd_softleaf_to_folio() Usama Arif
2026-09-14 21:17 ` Barry Song
2026-09-15 3:06 ` Baoquan He
2026-09-15 3:34 ` Qi Zheng
2026-09-14 12:27 ` [RESEND v7 02/29] arm64: mm: add PMD swap-exclusive helpers Usama Arif
2026-09-14 12:27 ` [RESEND v7 03/29] loongarch: " Usama Arif
2026-09-14 12:27 ` [RESEND v7 04/29] powerpc: " Usama Arif
2026-09-14 12:27 ` [RESEND v7 05/29] riscv: " Usama Arif
2026-09-14 12:27 ` [RESEND v7 06/29] s390: " Usama Arif
2026-09-14 12:27 ` [RESEND v7 07/29] x86: " Usama Arif
2026-09-14 12:27 ` [RESEND v7 08/29] mm: recognize PMD swap entries in the softleaf layer Usama Arif
2026-09-14 12:27 ` [RESEND v7 09/29] mm/debug_vm_pgtable: test PMD swap-exclusive helpers Usama Arif
2026-09-14 12:28 ` [RESEND v7 10/29] mm: make PMD migration-entry splitting explicit Usama Arif
2026-09-14 12:28 ` [RESEND v7 11/29] mm: split PMD swap entries into PTE swap entries Usama Arif
2026-09-14 12:28 ` [RESEND v7 12/29] mm: handle PMD swap entries in fork path Usama Arif
2026-09-14 12:28 ` [RESEND v7 13/29] mm: zswap: reject high-order swap cache allocations backed by zswap Usama Arif
2026-09-14 12:28 ` [RESEND v7 14/29] mm: swap in PMD swap entries as whole THPs during swapoff Usama Arif
2026-09-14 12:28 ` [RESEND v7 15/29] fs/proc: account PMD swap entries in smaps Usama Arif
2026-09-14 12:28 ` [RESEND v7 16/29] mm: handle soft-dirty and uffd-wp on PMD swap entries Usama Arif
2026-09-14 12:28 ` [RESEND v7 17/29] mm/hmm: fault PMD swap entries on demand Usama Arif
2026-09-14 12:28 ` [RESEND v7 18/29] mm: free PMD swap entries in zap_huge_pmd() Usama Arif
2026-09-14 12:28 ` [RESEND v7 19/29] mm/madvise: free PMD swap entries with MADV_FREE Usama Arif
2026-09-14 12:28 ` [RESEND v7 20/29] mm/madvise: skip PMD swap entries for MADV_COLD and MADV_PAGEOUT Usama Arif
2026-09-14 12:28 ` [RESEND v7 21/29] mm/madvise: keep PMD swap entries whole for MADV_GUARD_INSTALL/REMOVE Usama Arif
2026-09-14 12:28 ` [RESEND v7 22/29] mm/mincore: report PMD swap-cache residency Usama Arif
2026-09-14 12:28 ` [RESEND v7 23/29] mm/khugepaged: treat PMD swap entries as mapped THPs Usama Arif
2026-09-14 12:28 ` Usama Arif [this message]
2026-09-14 12:28 ` [RESEND v7 25/29] mm: handle PMD swap entries in UFFDIO_MOVE Usama Arif
2026-09-14 12:28 ` [RESEND v7 26/29] mm: don't PTE-batch a swap-in over a hardware-poisoned subpage Usama Arif
2026-09-14 12:28 ` [RESEND v7 27/29] mm: handle PMD swap entry faults on swap-in Usama Arif
2026-09-14 12:28 ` [RESEND v7 28/29] mm: install PMD swap entries on swap-out Usama Arif
2026-09-14 12:28 ` [RESEND v7 29/29] selftests/mm: add PMD swap entry tests Usama Arif
2026-09-15 3:32 ` [RESEND v7 00/29] mm: PMD-level swap entries for anonymous THPs Andrew Morton
2026-09-15 14:09 ` Usama Arif
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=20260914122950.3283997-25-usama.arif@linux.dev \
--to=usama.arif@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=alex@ghiti.fr \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=chrisl@kernel.org \
--cc=david@kernel.org \
--cc=dev.jain@arm.com \
--cc=hannes@cmpxchg.org \
--cc=kas@kernel.org \
--cc=kasong@tencent.com \
--cc=kernel-team@meta.com \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=luizcap@redhat.com \
--cc=nico.pache@linux.dev \
--cc=nphamcs@gmail.com \
--cc=qi.zheng@linux.dev \
--cc=riel@surriel.com \
--cc=ryan.roberts@arm.com \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=vbabka@kernel.org \
--cc=willy@infradead.org \
--cc=ying.huang@linux.alibaba.com \
--cc=yosry@kernel.org \
--cc=youngjun.park@lge.com \
--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
all inboxes | Powered by JetHome®