Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Usama Arif <usama.arif@linux.dev>
To: Kairui Song <ryncsn@gmail.com>
Cc: Andrew Morton <akpm@linux-foundation.org>,
	david@kernel.org, chrisl@kernel.org, ljs@kernel.org,
	ziy@nvidia.com, linux-mm@kvack.org, 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, npache@redhat.com,
	"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,
	kernel-team@meta.com
Subject: Re: [PATCH v3 09/11] mm: handle PMD swap entry faults on swap-in
Date: Mon, 6 Jul 2026 22:06:43 +0100	[thread overview]
Message-ID: <d0d47811-44b2-490f-ad57-f9b57aa70a0a@linux.dev> (raw)
In-Reply-To: <CAMgjq7CQ+1gsptFYH+Y0GEqyYj+5QpAKjtZLekRS4h+k4f8QvQ@mail.gmail.com>



On 06/07/2026 18:56, Kairui Song wrote:
> On Sat, Jul 4, 2026 at 1:41 AM Usama Arif <usama.arif@linux.dev> wrote:
>>
>> Add do_huge_pmd_swap_page() and dispatch to it from __handle_mm_fault()
>> when vmf->orig_pmd encodes a swap entry.  The handler resolves the
>> entire 2 MB mapping in one shot, mirroring do_swap_page() (PTE path)
>> at PMD granularity:
>>
>>   - Look up the folio in the swap cache; on a miss, allocate a
>>     PMD-order folio via swap_cache_alloc_folio() and read from swap.
>>
>>   - After locking, re-validate that the folio still corresponds to our
>>     entry and is still PMD-sized.  Between the unlocked cache lookup
>>     and the lock, a racing swap-in on the same entry may have removed
>>     it from the cache via folio_free_swap(), or reclaim / memory_failure
>>     / deferred-split may have split the folio into smaller folios.
>>
>>   - Restore soft_dirty and uffd_wp from the swap PMD.  Map writable
>>     only when the entry was exclusive, the VMA permits writes, and
>>     uffd-wp is not armed.  Drop the exclusive marker when the cached
>>     folio is under writeback to an SWP_STABLE_WRITES backend (zram,
>>     encrypted) so the PMD is mapped read-only; a later write COWs
>>     into a fresh folio rather than corrupting the in-flight writeback.
>>     Mirrors do_swap_page().
>>
>>   - When the resulting PMD is read-only but the fault was a write,
>>     update vmf->orig_pmd and call wp_huge_pmd() in the same handler
>>     to COW immediately rather than forcing a second fault.  Mask
>>     VM_FAULT_FALLBACK from its return: a PMD-COW that splits to
>>     PTE-level is normal, but the bit is part of VM_FAULT_ERROR and
>>     arch fault handlers BUG() on it without SIGBUS/HWPOISON/SIGSEGV.
>>     Requires exposing wp_huge_pmd() via mm/internal.h.
>>
>>   - Free the swap slot via should_try_to_free_swap() (hoisted from
>>     mm/memory.c into mm/internal.h so PTE- and PMD-level swap-in
>>     share the heuristic).
>>
>> When PMD-order resources are unavailable (folio allocation fails,
>> the cached folio was split, memcg charge fails, or swapin_folio()
>> races) split the PMD swap entry into 512 PTE swap entries via
>> __split_huge_pmd() and return 0.  The fault retries and do_swap_page()
>> takes over per-PTE.  This avoids returning VM_FAULT_OOM for transient
>> PMD-order allocation failures.
>>
>> Signed-off-by: Usama Arif <usama.arif@linux.dev>
> 
> Hello Usama
> 
> Amazing work.

Hi Kairui,

Thanks! and Thanks for the review comments!

> 
>> ---
>>  include/linux/huge_mm.h |   9 ++
>>  mm/huge_memory.c        | 216 ++++++++++++++++++++++++++++++++++++++++
>>  mm/internal.h           |  36 +++++++
>>  mm/memory.c             |  40 +-------
>>  4 files changed, 265 insertions(+), 36 deletions(-)
>>
>> diff --git a/include/linux/huge_mm.h b/include/linux/huge_mm.h
>> index 1487bf4af1a7..9ec475ccfc91 100644
>> --- a/include/linux/huge_mm.h
>> +++ b/include/linux/huge_mm.h
>> @@ -531,6 +531,15 @@ vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf);
>>
>>  vm_fault_t do_huge_pmd_device_private(struct vm_fault *vmf);
>>
>> +#ifdef CONFIG_THP_SWAP
>> +vm_fault_t do_huge_pmd_swap_page(struct vm_fault *vmf);
>> +#else
>> +static inline vm_fault_t do_huge_pmd_swap_page(struct vm_fault *vmf)
>> +{
>> +       return 0;
>> +}
>> +#endif
>> +
>>  extern struct folio *huge_zero_folio;
>>  extern unsigned long huge_zero_pfn;
>>
>> diff --git a/mm/huge_memory.c b/mm/huge_memory.c
>> index fdc1a503c609..5fa60324a2f0 100644
>> --- a/mm/huge_memory.c
>> +++ b/mm/huge_memory.c
>> @@ -41,6 +41,7 @@
>>  #include <linux/pgalloc.h>
>>  #include <linux/pgalloc_tag.h>
>>  #include <linux/pagewalk.h>
>> +#include <linux/zswap.h>
>>
>>  #include <asm/tlb.h>
>>  #include "internal.h"
>> @@ -2312,6 +2313,221 @@ vm_fault_t do_huge_pmd_numa_page(struct vm_fault *vmf)
>>         return 0;
>>  }
>>
>> +#ifdef CONFIG_THP_SWAP
>> +/**
>> + * do_huge_pmd_swap_page() - Handle a fault on a PMD-level swap entry.
>> + * @vmf: Fault context. vmf->orig_pmd contains the swap PMD.
>> + *
>> + * A PMD swap entry is a compact encoding for HPAGE_PMD_NR consecutive swap
>> + * slots. If the swap cache still has one PMD-sized folio covering the range,
>> + * map it directly at PMD level. If the range has been split into per-page
>> + * cache state, or zswap may have per-page state for it, split the PMD swap
>> + * entry and retry at PTE granularity.
>> + *
>> + * Return: VM_FAULT_* flags.
>> + */
>> +vm_fault_t do_huge_pmd_swap_page(struct vm_fault *vmf)
>> +{
>> +       struct vm_area_struct *vma = vmf->vma;
>> +       struct mm_struct *mm = vma->vm_mm;
>> +       struct folio *folio;
>> +       struct page *page;
>> +       struct swap_info_struct *si;
>> +       unsigned long haddr = vmf->address & HPAGE_PMD_MASK;
>> +       softleaf_t entry;
>> +       swp_entry_t swp_entry;
>> +       pmd_t pmd;
>> +       vm_fault_t ret = 0;
>> +       bool exclusive;
>> +       rmap_t rmap_flags = RMAP_NONE;
>> +       enum swap_pmd_cache cache_state;
>> +
>> +       entry = softleaf_from_pmd(vmf->orig_pmd);
>> +       if (unlikely(!softleaf_is_swap(entry)))
>> +               return 0;
>> +
>> +       swp_entry = entry;
>> +
>> +       /* Prevent swapoff from happening to us. */
>> +       si = get_swap_device(swp_entry);
>> +       if (unlikely(!si))
>> +               return 0;
>> +
>> +       cache_state = swap_pmd_cache_lookup(swp_entry, &folio);
>> +       if (cache_state == SWAP_PMD_CACHE_SPLIT)
>> +               goto split_fallback;
>> +       if (!folio) {
>> +               /*
>> +                * PMD swap entries encode ordinary per-page swap slots. If any
>> +                * slot is in zswap, split and let the PTE swap path load the
>> +                * range per page. Otherwise the range is all on disk and can be
>> +                * read back as one PMD-sized folio.
>> +                */
>> +               if (zswap_range_has_entry(swp_entry, HPAGE_PMD_NR))
>> +                       goto split_fallback;
>> +
>> +               folio = swapin_sync(swp_entry, GFP_HIGHUSER_MOVABLE,
>> +                                   BIT(HPAGE_PMD_ORDER), vmf, NULL, 0);
> 
> Hmm, so here we always do a sync swapin which means there is no
> readahead, kind of make sense since PMD is huge, readahead might be
> not helpful and a order 0 readahead breaks following PMD. But maybe
> worth mention it in the commit message at least so it's easiler to
> follow. (partly because the name swapin_sync is a bit confusing at the
> moment, we could change that name as we improve swap readahead later).

Yes, I’ll add this to the commit message. Thanks!

Yes readahead won't be useful for PMD as you said.

> 
> And just an idea that will it be suffient if we just use the return
> value of swapin_sync directly? If a smaller folio covers the PMD start
> entry, the smaller folio is returned and you catched that just fine.
> If a smaller folio covers a follow-up sub-entry or a sub-entry is
> freed, -EBUSY is returned here and you just do the split. And it
> doesn't do the allocation for the first lookup check, so there should
> be no thrashing issue.

The main issue over here is zswap.
zswap still stores the range as order-0 objects. If we call PMD-order
swapin_sync() when the cache is empty but any slot is in zswap, zswap_load()
fails the large-folio load and leaves a non-uptodate PMD-sized folio in
swapcache. Splitting the PMD after that is not enough unless we also delete that
failed large folio, because the subsequent PTE fault can find the non-uptodate
large folio and SIGBUS instead of loading the individual zswap entries.


> 
>> +               if (IS_ERR_OR_NULL(folio))
>> +                       goto split_fallback;
>> +
>> +               /* Had to read from swap area: Major fault */
>> +               ret = VM_FAULT_MAJOR;
>> +               count_vm_event(PGMAJFAULT);
>> +               count_memcg_event_mm(mm, PGMAJFAULT);
>> +       }
>> +
>> +       ret |= folio_lock_or_retry(folio, vmf);
> 
> With PMD sized folio on a slower device, this try lock could fail much
> more frequently, leading to repeated faults. That's not a new issue
> and I remember Barry talked about it at the LSFMM this year, just I'm
> not sure if that will get much worse for PMD?

Agreed that PMD-sized IO can keep the folio locked longer, especially on
slow swap devices. (Which hopefully are dying away :))
The current behavior matches do_swap_page(): on the normal retryable fault
path it drops the fault lock and waits for the folio unlock before returning
VM_FAULT_RETRY, so it should not be a tight retry loop.
The wait can be longer for 2MB IO, but the retry usually comes back to
either an uptodate folio or a PMD that another fault already  installed.
I think this is the right baseline behavior, and any improvement here
belongs with the broader swap readahead / async swapin work.


  reply	other threads:[~2026-07-06 21:06 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-03 17:38 [PATCH v3 00/11] mm: PMD-level swap entries for anonymous THPs Usama Arif
2026-07-03 17:38 ` [PATCH v3 01/11] mm: add PMD swap entry detection support Usama Arif
2026-07-03 17:38 ` [PATCH v3 02/11] mm: add PMD swap entry splitting support Usama Arif
2026-07-03 17:38 ` [PATCH v3 03/11] mm: handle PMD swap entries in fork path Usama Arif
2026-07-03 17:38 ` [PATCH v3 04/11] mm: zswap: add range lookup for large-folio swapin Usama Arif
2026-07-06 21:36   ` Yosry Ahmed
2026-07-07 10:08     ` Usama Arif
2026-07-03 17:38 ` [PATCH v3 05/11] mm: swap in PMD swap entries as whole THPs during swapoff Usama Arif
2026-07-03 17:38 ` [PATCH v3 06/11] mm: handle PMD swap entries in non-present PMD walkers Usama Arif
2026-07-03 17:38 ` [PATCH v3 07/11] mm: handle PMD swap entries in MADV_WILLNEED Usama Arif
2026-07-03 17:38 ` [PATCH v3 08/11] mm: handle PMD swap entries in UFFDIO_MOVE Usama Arif
2026-07-03 17:38 ` [PATCH v3 09/11] mm: handle PMD swap entry faults on swap-in Usama Arif
2026-07-06 17:56   ` Kairui Song
2026-07-06 21:06     ` Usama Arif [this message]
2026-07-03 17:38 ` [PATCH v3 10/11] mm: install PMD swap entries on swap-out Usama Arif
2026-07-03 17:38 ` [PATCH v3 11/11] selftests/mm: add PMD swap entry tests Usama Arif
2026-07-04  6:27   ` kernel test robot
2026-07-04  8:30   ` kernel test robot
2026-07-05  1:43 ` [PATCH v3 00/11] mm: PMD-level swap entries for anonymous THPs Andrew Morton

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=d0d47811-44b2-490f-ad57-f9b57aa70a0a@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=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=npache@redhat.com \
    --cc=nphamcs@gmail.com \
    --cc=riel@surriel.com \
    --cc=ryan.roberts@arm.com \
    --cc=ryncsn@gmail.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=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