All of lore.kernel.org
 help / color / mirror / Atom feed
From: Baoquan He <baoquan.he@linux.dev>
To: Nhat Pham <nphamcs@gmail.com>, chrisl@kernel.org
Cc: linux-mm@kvack.org, akpm@linux-foundation.org,
	kasong@tencent.com, baohua@kernel.org, youngjun.park@lge.com,
	hannes@cmpxchg.org, yosry@kernel.org, david@kernel.org,
	shikemeng@huaweicloud.com, chengming.zhou@linux.dev,
	linux-kernel@vger.kernel.org
Subject: Re: [RFC PATCH 03/11] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc
Date: Wed, 5 Aug 2026 16:08:31 +0800	[thread overview]
Message-ID: <anLvfyS3S_uc6OYx@MiWiFi-R3L-srv> (raw)
In-Reply-To: <amoYawUn3y2eFbPS@MiWiFi-R3L-srv>

On 07/29/26 at 11:12pm, Baoquan He wrote:
> On 07/27/26 at 08:05am, Nhat Pham wrote:
> > On Mon, Jul 27, 2026 at 7:05 AM Baoquan He <baoquan.he@linux.dev> wrote:
...snip...
> > > +#ifdef CONFIG_XSWAP
> > > +static int xswap_map_clusters(struct swap_info_struct *si,
> > > +                             unsigned long start_idx, unsigned long nr)
> > > +{
> > > +       unsigned long start_addr = (unsigned long)si->cluster_info +
> > > +                                  (size_t)start_idx * sizeof(struct swap_cluster_info);
> > > +       unsigned long end_addr = start_addr + (size_t)nr * sizeof(struct swap_cluster_info);
> > > +       /*
> > > +        * vm_area_map_pages() requires that start and end be page-aligned.
> > > +        * If start_addr falls within a page that was already mapped by a
> > > +        * previous batch (grow path), round it up to skip the already-mapped
> > > +        * partial page.  Always round end_addr up so the vmap page table walk
> > > +        * terminates correctly (the walk loop exits when addr == end, and addr
> > > +        * advances by PAGE_SIZE each iteration).
> > > +        */
> > > +       unsigned long vm_start = PAGE_ALIGN(start_addr);
> > > +       unsigned long vm_end = PAGE_ALIGN(end_addr);
> > > +       unsigned long npages;
> > > +       struct page **pages;
> > > +       unsigned long i;
> > > +
> > > +       if (vm_start >= vm_end) {
> > > +               /* All requested clusters fall within already-mapped pages. */
> > > +               for (i = start_idx; i < start_idx + nr; i++)
> > > +                       spin_lock_init(&si->cluster_info[i].lock);
> > > +               WRITE_ONCE(si->nr_clusters_mapped, start_idx + nr);
> > > +               return 0;
> > > +       }
> > > +
> > > +       npages = (vm_end - vm_start) >> PAGE_SHIFT;
> > > +
> > > +       pages = kmalloc_array(npages, sizeof(*pages), GFP_KERNEL);
> > > +       if (!pages)
> > > +               return -ENOMEM;
> > > +
> > > +       for (i = 0; i < npages; i++) {
> > > +               /*
> > > +                * __GFP_ZERO is critical: cluster_info structs contain pointer
> > > +                * fields (extend_table, zero_bitmap, memcg_table, table) that
> > > +                * must start as NULL.  Without zeroing, stale data from a
> > > +                * previous user of the page would look like valid pointers.
> > > +                */
> > > +               pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
> > > +               if (!pages[i])
> > > +                       goto fail;
> > > +       }
> > > +
> > > +       if (vm_area_map_pages(si->cluster_vm, vm_start, vm_end, pages)) {
> > > +               i = npages; /* free all pages on failure */
> > > +               goto fail;
> > 
> > I was evaluating whether your vmalloc array can be extended to support
> > kernel-driven dynamic growth at least (either as a slot-in replacement
> > for xarray, or as a follow-up optimization if it's too complicated),
> > and I stumble this mapping action.
> > 
> > Seems like it does not take any GFP flag argument, and under the hood
> > it calls GFP_KERNEL. Would this be safe in the swap allocation path
> > (where you slotted it in in patch 4)? Kairui used a more precise set
> > of flags in this path, for e.g for swap table allocation:
> > 
> > ret = swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
> >    GFP_KERNEL);
> > 
> > 
> > I'm guessing this has to do with the fact that we often entered
> > swapping out paths with PF_MEMALLOC... Is there any risk of deadlock
> > etc.?

Hi Chris,

I gave the kmem_cache approach a try, but ended up keeping VM_SPARSE.
The kmem_cache approach has two structural problems:

1. With individually kmem_cache_alloc()'d clusters, ci - si->cluster_info
   no longer works — cluster_info is a pointer array, not a contiguous
   array.  Each struct swap_cluster_info would need a new field to store
   its own index. VM_SPARSE preserves the contiguous array layout, so
   ci - si->cluster_info continues to work for O(1) index lookup everywhere.

2. The struct swap_cluster_info ** pointer array itself costs extra
   memory: 8 bytes per cluster.  That's 8 KB for a 1 GB swap device,
   and 8 MB for a 1 TB device — paid upfront at swapon, regardless of
   how many clusters are actually used.  VM_SPARSE needs no such
   indirection array; the cluster_info is addressed directly through
   the vmalloc area.

By comparison, the introduced change in struct swap_cluster_info isn't
that great.

How the GFP concern is addressed
--------------------------------
Hi Nhat,

You were right about the three layers of hardcoded GFP_KERNEL in v1.
In the current code (the version I'll post as RFC v2), each is fixed:

1. alloc_page() and kmalloc_array() now use:

     __GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL

   This matches the pattern Kairui established in
   swap_cluster_alloc_table().  __GFP_NOMEMALLOC prevents the
   pfmemalloc reserve bypass during reclaim.

2. The internal page-table allocations inside vmap_pages_range()
   use GFP_PGTABLE_KERNEL and are not directly controllable from
   the caller.  To prevent these from recursing into swap reclaim,
   the entire allocation block is wrapped with:

     noreclaim_flags = memalloc_noreclaim_save();
     ... alloc_page / kmalloc_array / vm_area_map_pages ...
     memalloc_noreclaim_restore(noreclaim_flags);

   memalloc_noreclaim_save() ensures __GFP_FS and __GFP_IO are
   cleared for all allocations within the scope, so the page-table
   allocation cannot recurse into filesystem or swap reclaim.

3. The residual risk is that the page-table allocations still lack
   __GFP_NOMEMALLOC, meaning they could theoretically dip into
   emergency reserves under PF_MEMALLOC.  However, each grow
   operation maps at most one PTE page (XSWAP_GROW_CLUSTERS
   clusters per page), so the total order-0 allocation is tiny —
   typically a single page-table page.  This seems acceptable
   compared to the complexity of plumbing a GFP parameter through
   the entire vmap_pages_range() call chain.

A variant vm_area_map_pages_gfp that passes the caller's GFP context
down to the page-table allocations. That cleanly solves the problem
for all VM_SPARSE users, not just xswap.  I'm happy to work on that
if people think it's the right direction, but I'd prefer to keep it
as a separate improvement rather than blocking this series on it.

The shrink path also avoids this problem entirely: it runs in a
workqueue context where PF_MEMALLOC is never set, so GFP_KERNEL
is correct there.

Thanks
Baoquan

> 
> Thanks for catching this.  You're right — there are actually three
> layers of hardcoded GFP_KERNEL in the VM_SPARSE path:
> 
> 1. alloc_page(GFP_KERNEL | __GFP_ZERO) in xswap_map_clusters() for the
> backing pages
> 2. vmap_pages_range() hardcodes GFP_KERNEL (passed only to KMSAN, but
> still semantically wrong)
> 3. The page-table allocations inside vmap_pages_range_noflush_walk()
> (pte_alloc_kernel_track etc.) use GFP_PGTABLE_KERNEL, which is
> GFP_KERNEL | __GFP_ZERO — also lacking __GFP_NOMEMALLOC, and
> completely invisible to the caller
> 
> When PF_MEMALLOC is set (as it is during reclaim), __gfp_pfmemalloc_flags()
> returns ALLOC_NO_WATERMARKS for any allocation without __GFP_NOMEMALLOC,
> meaning those allocations can consume all emergency reserves — including
> the reserves that the reclaim machinery itself needs to make forward
> progress.  That is a real deadlock risk.
> 
> One way is to fix them one by one. Another way is to drop the VM_SPARSE
> approach entirely and implemented Chris's suggestion instead: cluster_info
> is now a struct swap_cluster_info ** pointer array, with each cluster
> individually kmem_cache_zalloc()'d. The grow path (xswap_grow_clusters)
> now uses:
> 
> kmem_cache_zalloc(swap_cluster_cachep,
>         __GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL)
> matching the pattern Kairui established in swap_cluster_alloc_table().
> All allocations are fully GFP-controllable — no hidden vmap page-table
> allocations, no hardcoded GFP_PGTABLE_KERNEL.
> 
> With kmem_cache, shrink is just kmem_cache_free() — each cluster is
> independently allocated and freed. Seems the logic is simpler. If no
> objection, I will give it a shot soon.
> 
> 
> Thanks
> Baoquan


  reply	other threads:[~2026-08-05  8:08 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-27 13:50 [RFC PATCH 00/11] mm, swap: dynamic cluster management for xswap devices Baoquan He
2026-07-27 13:50 ` [RFC PATCH 01/11] mm: xswap support for zswap Baoquan He
2026-07-27 14:04 ` [RFC PATCH 02/11] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Baoquan He
2026-07-27 14:04   ` [RFC PATCH 03/11] mm, swap: add xswap cluster grow via VM_SPARSE vmalloc Baoquan He
2026-07-27 15:05     ` Nhat Pham
2026-07-29 15:12       ` Baoquan He
2026-08-05  8:08         ` Baoquan He [this message]
2026-07-27 16:09     ` Chris Li
2026-07-29 14:36       ` Baoquan He
2026-07-30  0:15         ` Baoquan He
2026-07-27 14:04   ` [RFC PATCH 04/11] mm, swap: add xswap grow trigger on cluster allocation Baoquan He
2026-07-27 14:04   ` [RFC PATCH 05/11] mm, swap: add xswap_try_shrink and shrink trigger on cluster free Baoquan He
2026-07-27 14:04   ` [RFC PATCH 06/11] mm, swap: free backing pages in xswap_unmap_clusters Baoquan He
2026-07-27 14:04   ` [RFC PATCH 07/11] mm, swap: add nr_free_tail for O(1) xswap shrink detection Baoquan He
2026-07-27 14:04   ` [RFC PATCH 08/11] mm, swap: add adjustable runtime ceiling (nr_clusters) for xswap Baoquan He
2026-07-27 14:05   ` [RFC PATCH 09/11] mm, swap: add debugfs knob for xswap per-device cluster limit Baoquan He
2026-07-27 14:05   ` [RFC PATCH 10/11] mm, swap: defer xswap shrink to workqueue to avoid lock recursion Baoquan He
2026-07-27 14:05   ` [RFC PATCH 11/11] mm, swap: serialize xswap map/unmap with a mutex Baoquan He
2026-07-27 15:13     ` Nhat Pham
2026-08-05  8:15       ` Baoquan He
2026-07-27 15:26   ` [RFC PATCH 02/11] mm, swap: add CONFIG_XSWAP and xswap fields to swap_info_struct Chris Li
2026-07-29 14:26     ` Baoquan He
2026-07-27 15:48 ` [RFC PATCH 00/11] mm, swap: dynamic cluster management for xswap devices Nhat Pham
2026-07-29 16:25   ` Baoquan He

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=anLvfyS3S_uc6OYx@MiWiFi-R3L-srv \
    --to=baoquan.he@linux.dev \
    --cc=akpm@linux-foundation.org \
    --cc=baohua@kernel.org \
    --cc=chengming.zhou@linux.dev \
    --cc=chrisl@kernel.org \
    --cc=david@kernel.org \
    --cc=hannes@cmpxchg.org \
    --cc=kasong@tencent.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=nphamcs@gmail.com \
    --cc=shikemeng@huaweicloud.com \
    --cc=yosry@kernel.org \
    --cc=youngjun.park@lge.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.