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,
kernel-team@meta.com, Usama Arif <usama.arif@linux.dev>
Subject: [PATCH v6 08/12] mm: handle PMD swap entries in MADV_WILLNEED
Date: Tue, 18 Aug 2026 06:09:49 -0700 [thread overview]
Message-ID: <20260818131202.494754-9-usama.arif@linux.dev> (raw)
In-Reply-To: <20260818131202.494754-1-usama.arif@linux.dev>
swapin_walk_pmd_entry() walks PTEs and skips non-present PMDs, so
MADV_WILLNEED is a no-op on a PMD swap entry.
Handle PMD swap entries under pmd_trans_huge_lock(). If the covered
swap-cache range already has a PMD-sized folio, there is nothing left
to prefetch. If the range has split cache state, or any covered slot
currently has a zswap entry, split the PMD swap entry and ask the
walker to retry so the PTE path can handle the individual slots.
Otherwise pin the swap device and read the folio in at PMD order via
swapin_sync(BIT(HPAGE_PMD_ORDER)). This keeps the subsequent fault on
the do_huge_pmd_swap_page() path and avoids order-0 readahead
needlessly splitting the PMD swap entry.
Any failure of the PMD-order swapin splits the entry and retries
through the PTE path. That covers losing a race with per-slot
swap-cache population (-EBUSY) after dropping the PMD lock, but also
the -ENOMEM that a PMD-order allocation can easily hit: leaving the
entry alone would make MADV_WILLNEED prefetch nothing at all for the
range, while the PTE path can still read the 512 slots at order 0.
If per-page zswap state reappears during the read, remove the failed
clean PMD-sized folio from swap cache before splitting so the PTE path
can load each slot. This uses folio_trylock(): the lock is only free
once the read has completed, so MADV_WILLNEED never blocks on in-flight
I/O, and unlike testing folio_test_locked() directly it cannot race
with an unrelated lock holder.
Signed-off-by: Usama Arif <usama.arif@linux.dev>
---
mm/madvise.c | 106 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 106 insertions(+)
diff --git a/mm/madvise.c b/mm/madvise.c
index 16b39a06b038f..3ef1af1e76daa 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>
@@ -185,6 +186,93 @@ 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;
+
+ 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 (!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 failed PMD-order zswap load
+ * leaves the folio clean and not uptodate; drop it from the swap cache
+ * so the PTE retry can load the per-page state. Another thread may
+ * have removed it already, so revalidate the association first.
+ */
+ if (!folio_trylock(folio))
+ goto out_put;
+
+ if (!folio_test_uptodate(folio) &&
+ zswap_is_present(entry, HPAGE_PMD_NR)) {
+ 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)
{
@@ -194,6 +282,23 @@ 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)) {
+ __split_huge_pmd(vma, pmd, start, false);
+ walk->action = ACTION_AGAIN;
+ }
+ goto ret;
+ }
+ spin_unlock(ptl);
+ }
+
for (addr = start; addr < end; addr += PAGE_SIZE) {
pte_t pte;
softleaf_t entry;
@@ -222,6 +327,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-08-18 13:12 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-18 13:09 [PATCH v6 00/12] mm: PMD-level swap entries for anonymous THPs Usama Arif
2026-08-18 13:09 ` [PATCH v6 01/12] mm: rename pmd_to_softleaf_folio() to pmd_softleaf_to_folio() Usama Arif
2026-08-18 14:24 ` David Hildenbrand (Arm)
2026-08-18 13:09 ` [PATCH v6 02/12] mm: add PMD swap entry detection support Usama Arif
2026-08-18 14:40 ` David Hildenbrand (Arm)
2026-08-18 13:09 ` [PATCH v6 03/12] mm: add PMD swap entry splitting support Usama Arif
2026-08-18 17:53 ` David Hildenbrand (Arm)
2026-08-18 13:09 ` [PATCH v6 04/12] mm: handle PMD swap entries in fork path Usama Arif
2026-08-18 13:09 ` [PATCH v6 05/12] mm: zswap: add range lookup for large-folio swapin Usama Arif
2026-08-18 13:09 ` [PATCH v6 06/12] mm: swap in PMD swap entries as whole THPs during swapoff Usama Arif
2026-08-18 13:09 ` [PATCH v6 07/12] mm: handle PMD swap entries in non-present PMD walkers Usama Arif
2026-08-18 13:09 ` Usama Arif [this message]
2026-08-18 13:09 ` [PATCH v6 09/12] mm: handle PMD swap entries in UFFDIO_MOVE Usama Arif
2026-08-18 13:09 ` [PATCH v6 10/12] mm: handle PMD swap entry faults on swap-in Usama Arif
2026-08-18 13:09 ` [PATCH v6 11/12] mm: install PMD swap entries on swap-out Usama Arif
2026-08-18 13:09 ` [PATCH v6 12/12] selftests/mm: add PMD swap entry tests 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=20260818131202.494754-9-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=nico.pache@linux.dev \
--cc=nphamcs@gmail.com \
--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