* Re: [PATCH] mm/list_lru: drain before clearing xarray entry on reparent
From: Muchun Song @ 2026-06-01 9:54 UTC (permalink / raw)
To: Shakeel Butt
Cc: Andrew Morton, Johannes Weiner, Dave Chinner, Roman Gushchin,
Qi Zheng, Kairui Song, Meta kernel team, linux-mm, cgroups,
linux-kernel, Chris Mason
In-Reply-To: <20260601063408.2879011-1-shakeel.butt@linux.dev>
> On Jun 1, 2026, at 14:34, Shakeel Butt <shakeel.butt@linux.dev> wrote:
>
> memcg_reparent_list_lrus() clears the dying memcg's xarray entry with
> xas_store(&xas, NULL) before reparenting its per-node lists into the
> parent. This opens a window where a concurrent list_lru_del() arriving
> for the dying memcg sees xa_load() == NULL, walks to the parent in
> lock_list_lru_of_memcg(), takes the parent's per-node lock, and calls
> list_del_init() on an item still physically linked on the dying
> memcg's list.
>
> If another in-flight thread holds the dying memcg's per-node lock at
> the same moment (another list_lru_del, or a list_lru_walk_one running
> an isolate callback), both threads modify ->next/->prev pointers on the
> same physical list under different locks. Adjacent items can corrupt
> each other's links.
>
> Fix it by reversing the order: reparent each per-node list and mark the
> child's list lru dead and then clear the xarray entry. Any concurrent
> list_lru op that finds the still-set xarray entry either takes the dying
> memcg's per-node lock (synchronizing with the drain) or sees LONG_MIN
> and walks to the parent, where the items now live.
>
> Fixes: fb56fdf8b9a2 ("mm/list_lru: split the lock to per-cgroup scope")
> Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
> Reported-by: Chris Mason <clm@fb.com>
> ---
> mm/list_lru.c | 20 +++++++++-----------
> 1 file changed, 9 insertions(+), 11 deletions(-)
>
> diff --git a/mm/list_lru.c b/mm/list_lru.c
> index dd29bcf8eb5f..ae55a52307db 100644
> --- a/mm/list_lru.c
> +++ b/mm/list_lru.c
> @@ -473,26 +473,24 @@ void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *paren
> mutex_lock(&list_lrus_mutex);
> list_for_each_entry(lru, &memcg_list_lrus, list) {
> struct list_lru_memcg *mlru;
> - XA_STATE(xas, &lru->xa, memcg->kmemcg_id);
>
> - /*
> - * Lock the Xarray to ensure no on going list_lru_memcg
> - * allocation and further allocation will see css_is_dying().
> - */
> - xas_lock_irq(&xas);
> - mlru = xas_store(&xas, NULL);
> - xas_unlock_irq(&xas);
> + mlru = xa_load(&lru->xa, memcg->kmemcg_id);
> if (!mlru)
> continue;
Is it possible that concurrent threads running memcg_list_lru_alloc() could
allocate a new mlru after this check passes? This could happen because the
threads haven't noticed css_is_dying() yet. We would consequently miss the
reparent operation for this list. So xas_lock_irq is necessary to serialize
CSS_DYING setting here. Right?
Thanks.
Muchun
>
> /*
> - * With Xarray value set to NULL, holding the lru lock below
> - * prevents list_lru_{add,del,isolate} from touching the lru,
> - * safe to reparent.
> + * Reparent each per-node list and mark the child dead
> + * (LONG_MIN) before clearing xarray entry otherwisw a
> + * concurrent list_lru_del() may corrupt the list if it arrives
> + * after xarray clear but before reparenting as
> + * lock_list_lru_of_memcg will acquire parent's lock while the
> + * item is still on child's list.
> */
> for_each_node(i)
> memcg_reparent_list_lru_one(lru, i, &mlru->node[i], parent);
>
> + xa_erase(&lru->xa, memcg->kmemcg_id);
> +
> /*
> * Here all list_lrus corresponding to the cgroup are guaranteed
> * to remain empty, we can safely free this lru, any further
> --
> 2.52.0
>
^ permalink raw reply
* Re: [RFC PATCH v2 5/9] mm: add common locality admission for zswap large swapin
From: Fujunjie @ 2026-06-01 9:47 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=O_Jt3aCxocDoY1h5AY=-eOYnj_0saQ4rMbdfnLzPAFxw@mail.gmail.com>
On 5/30/2026 3:00 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> Fully zswap-backed ranges are safe to load as a large folio only when
>> the caller has a reason to expect the neighbouring slots to be useful.
>> Otherwise a sparse refault can turn one 4K demand fault into a 64K
>> decompression and swapcache fill.
>>
>> Add a common admission gate for zswap-backed large swapin. The common
>> layer keeps backend checks, the 64K cap, recent-refault rejection, and
>> zswap reclaim-pressure rejection. It consumes a caller-provided locality
>> order mask instead of looking at anon or shmem state directly.
>
> Can you add more documentation about these policies, both in patch
> changelog and in code? I'm pretty confused by the
> zswap_pool_reclaim_pressure heuristics, for e.g
>
You're right, this should have been documented much better, and I think the
heuristic itself needs another look.
The intent was not to block demand swapin when zswap is under pressure. It was
only meant to block speculative large zswapin. Even after locality_orders
admits an order, that is still only evidence that the neighbouring slots are
likely useful; it does not prove that the whole large folio will be consumed
soon enough to justify the extra resident memory under pressure.
In this RFC, the cost is also higher because of my late mixed-backend fallback
design. I kept the zswap entries after filling the large swapcache folio, so
that the fresh large folio could still be dropped and retried as order-0 if a
late mixed-backend race was detected. With that design, a large zswapin does
not immediately reduce the zswap pool: the uncompressed large swapcache folio
and the compressed zswap entries can exist at the same time until the swap
slots are freed.
I used zswap_pool_reclaim_pressure() as a rough signal for avoiding that extra
speculative expansion. My assumption was that a zswap pool that has reached
its limit is often correlated with memory pressure. But that is not a strict
relationship, and I did not validate this heuristic rigorously. Sorry, this
was too under-explained and probably too ad hoc in this RFC.
Your earlier point also makes me think the design is over-defensive. Once the
large swapcache folio is installed, zswap writeback should not be able to turn
one slot in the range into disk-backed state, since it first has to allocate
an order-0 swapcache folio. For v3 I will revisit the completion rule first:
drop the late mixed-race -EAGAIN path, decide whether a successful large load
should consume zswap entries like the order-0 path, and then either remove
this pressure heuristic or keep it only with clearer documentation and
specific experiments.
>>
>> Callers pass no locality evidence for now, so this patch only installs
>> the common policy hook. Later patches add anon and shmem producers.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> mm/memory.c | 2 +-
>> mm/shmem.c | 2 +-
>> mm/swap.h | 8 ++--
>> mm/swap_state.c | 118 ++++++++++++++++++++++++++++++++++++++++++++----
>> 4 files changed, 117 insertions(+), 13 deletions(-)
>>
>> diff --git a/mm/memory.c b/mm/memory.c
>> index d73a19692dea..92a82008d583 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -4849,7 +4849,7 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> if (data_race(si->flags & SWP_SYNCHRONOUS_IO))
>> folio = swapin_sync(entry, GFP_HIGHUSER_MOVABLE,
>> thp_swapin_suitable_orders(vmf) | BIT(0),
>> - vmf, NULL, 0);
>> + 0, vmf, NULL, 0);
>> else
>> folio = swapin_readahead(entry, GFP_HIGHUSER_MOVABLE, vmf);
>>
>> diff --git a/mm/shmem.c b/mm/shmem.c
>> index 56c23a7b15c7..fa99b48ed62b 100644
>> --- a/mm/shmem.c
>> +++ b/mm/shmem.c
>> @@ -2031,7 +2031,7 @@ static struct folio *shmem_swap_alloc_folio(struct inode *inode,
>>
>> again:
>> mpol = shmem_get_pgoff_policy(info, index, order, &ilx);
>> - folio = swapin_sync(entry, gfp, BIT(order), vmf, mpol, ilx);
>> + folio = swapin_sync(entry, gfp, BIT(order), 0, vmf, mpol, ilx);
>> mpol_cond_put(mpol);
>>
>> if (!IS_ERR(folio))
>> diff --git a/mm/swap.h b/mm/swap.h
>> index ea7e1f3c4410..dd35a310d06d 100644
>> --- a/mm/swap.h
>> +++ b/mm/swap.h
>> @@ -323,9 +323,10 @@ struct folio *read_swap_cache_async(swp_entry_t entry, gfp_t gfp_mask,
>> struct folio *swap_cluster_readahead(swp_entry_t entry, gfp_t flag,
>> struct mempolicy *mpol, pgoff_t ilx);
>> struct folio *swapin_readahead(swp_entry_t entry, gfp_t flag,
>> - struct vm_fault *vmf);
>> + struct vm_fault *vmf);
>> struct folio *swapin_sync(swp_entry_t entry, gfp_t flag, unsigned long orders,
>> - struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx);
>> + unsigned long locality_orders, struct vm_fault *vmf,
>> + struct mempolicy *mpol, pgoff_t ilx);
>> void swap_update_readahead(struct folio *folio, struct vm_area_struct *vma,
>> unsigned long addr);
>>
>> @@ -418,7 +419,8 @@ static inline struct folio *swapin_readahead(swp_entry_t swp, gfp_t gfp_mask,
>>
>> static inline struct folio *swapin_sync(
>> swp_entry_t entry, gfp_t flag, unsigned long orders,
>> - struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)
>> + unsigned long locality_orders, struct vm_fault *vmf,
>> + struct mempolicy *mpol, pgoff_t ilx)
>> {
>> return NULL;
>> }
>> diff --git a/mm/swap_state.c b/mm/swap_state.c
>> index f03ad4832f16..5a4ca289009a 100644
>> --- a/mm/swap_state.c
>> +++ b/mm/swap_state.c
>> @@ -21,6 +21,7 @@
>> #include <linux/migrate.h>
>> #include <linux/vmalloc.h>
>> #include <linux/huge_mm.h>
>> +#include <linux/sizes.h>
>> #include <linux/zswap.h>
>> #include <linux/shmem_fs.h>
>> #include "internal.h"
>> @@ -556,6 +557,24 @@ static struct folio *swap_cache_alloc_speculative_folio(swp_entry_t targ_entry,
>> mpol, ilx, true);
>> }
>>
>> +/*
>> + * Initial conservative cap for speculative zswap large swapin. Locality
>> + * evidence is supplied by the caller or by generic VMA hints; the common
>> + * swapin layer keeps backend safety and pressure decisions here.
>> + */
>> +#define SWAPIN_ZSWAP_MAX_SIZE SZ_64K
>> +#if PAGE_SIZE < SWAPIN_ZSWAP_MAX_SIZE
>> +#define SWAPIN_ZSWAP_MAX_ORDER \
>> + ilog2(SWAPIN_ZSWAP_MAX_SIZE / PAGE_SIZE)
>> +#else
>> +#define SWAPIN_ZSWAP_MAX_ORDER 0
>> +#endif
>> +
>> +struct zswap_admit_ctx {
>> + bool pressure_checked;
>> + bool reclaim_pressure;
>> +};
>> +
>> static bool swapin_zeromap_same(swp_entry_t entry, unsigned int nr_pages)
>> {
>> unsigned int ci_start = swp_cluster_offset(entry);
>> @@ -586,11 +605,84 @@ static bool swapin_zeromap_same(swp_entry_t entry, unsigned int nr_pages)
>> return true;
>> }
>>
>> +static bool swapin_zswap_locality(struct vm_fault *vmf, unsigned int order,
>> + unsigned long locality_orders)
>> +{
>> + struct vm_area_struct *vma = vmf ? vmf->vma : NULL;
>> +
>> + if (!order || order > MAX_PAGE_ORDER)
>> + return false;
>> +
>> + if (vma && (vma->vm_flags & VM_RAND_READ))
>> + return false;
>
> what about VM_SEQ_READ?
This helper is meant to consume locality_orders, not to produce locality
evidence itself. VM_SEQ_READ is handled by the caller-side locality producer:
the anon producer returns all candidate orders for VM_SEQ_READ, and the shmem
producer does the same for now.
I kept the check in the common helper mostly as a common veto for the readahead path where ra_orders are passed as locality_orders directly.
I think the cleaner fix is to move this into the producer side as well.
>
>> +
>> + return locality_orders & BIT(order);
>> +}
>> +
>> +static bool swapin_zswap_refaulted(swp_entry_t entry, unsigned int nr_pages)
>
> nit: this does not seem zswap-specific. Just call it
> swapin_range_refaulted or sth like that, maybe?
>
>> + for (i = 0; i < nr_pages; i++) {
>> + bool workingset;
>> + void *shadow;
>> +
>> + shadow = swap_cache_get_shadow(swp_entry(type, offset + i));
>
> This seems inefficient. Can't we just lock the swap cluster once,
> check all the shadow in the range, instead of repeatedly getting then
> dropping the swap cluster lock?
both points make sense.
The refault check is not zswap-specific, so I will rename it to something like
swapin_range_refaulted().
And yes, the current implementation is too expensive. The range is already
bounded and contiguous, so I should check the swap table under one cluster lock
instead of calling swap_cache_get_shadow() for every slot. I will rework that
in v3.
>
>> + if (!shadow)
>> + continue;
>> + if (workingset_test_recent(shadow, false, &workingset, false) &&
>> + workingset)
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> +static bool swapin_zswap_admit(swp_entry_t entry,
>> + unsigned int order, unsigned int nr_pages,
>> + struct vm_fault *vmf,
>> + unsigned long locality_orders,
>> + struct zswap_admit_ctx *ctx)
>> +{
>> + if (order > SWAPIN_ZSWAP_MAX_ORDER)
>> + return false;
>> +
>> + /*
>> + * Treat zswap-backed large swapin as speculative. The common layer
>> + * consumes caller-provided locality orders, but does not inspect
>> + * anon-specific PTE state or shmem-specific mapping state directly.
>> + */
>> + if (!swapin_zswap_locality(vmf, order, locality_orders))
>> + return false;
>> +
>> + /*
>> + * A recent workingset refault shadow in the target range means reclaim
>> + * already saw churn there. Keep the refault path narrow instead of
>> + * speculatively decompressing neighbouring slots.
>> + */
>> + if (swapin_zswap_refaulted(entry, nr_pages))
>> + return false;
>
> Hmm this depends. If it's just a refault coming from a speculative
> read (readhead or THP (z)swpin), which is then promptly discarded,
> then yeah we should backoff here. But maybe the refaulted page is
> workingset one?
>
> But yeah I guess it is better to be cautious when you are uncertain :)
>
>
>> +
>> + if (!ctx->pressure_checked) {
>> + ctx->reclaim_pressure = zswap_pool_reclaim_pressure();
>> + ctx->pressure_checked = true;
>> + }
>
> Why do we backoff if there is zswap_pool_reclaim_pressure (which only
> check if the pool is full ONCE in its lifetime)? What's the rationale
> here?
The ctx is only local to one swapin_admit_orders() call. It is initialized
before walking the candidate orders, so pressure_checked is not persistent
across faults and is not tied to the lifetime of the zswap pool.
It is also not there because zswap_pool_reclaim_pressure() is expensive. That
helper is cheap. The point was to use one consistent pressure snapshot while
evaluating all candidate orders for the same fault, instead of letting
different orders observe different pressure states.
The zswap state being read is not "full once forever". It is
hysteretic: zswap_check_limits() sets zswap_pool_reached_full when the pool
reaches the max limit, and clears it after the pool drops below the accept
threshold.
The rationale for backing off was conservative: I treated zswap pool pressure
as correlated with memory/reclaim pressure. With this RFC's current design,
large zswapin can temporarily keep both the compressed zswap entries and the
uncompressed large swapcache folio, so I wanted to avoid that speculative
expansion when the compressed pool already looks stressed.
>
>> + if (ctx->reclaim_pressure)
>> + return false;
>> +
>> + return true;
>> +}
>> +
>> static unsigned long swapin_admit_orders(swp_entry_t entry,
>> - unsigned long orders)
>> + unsigned long orders,
>> + struct vm_fault *vmf,
>> + unsigned long locality_orders)
>> {
>> unsigned long candidates = orders & ~BIT(0);
>> unsigned long admitted = orders & BIT(0);
>> + struct zswap_admit_ctx zswap_ctx = {};
>> int order;
>>
>> if (!candidates)
>> @@ -616,9 +708,14 @@ static unsigned long swapin_admit_orders(swp_entry_t entry,
>>
>> state = zswap_probe_range(range_entry, nr_pages);
>> switch (state) {
>> + case ZSWAP_RANGE_ALL_ZSWAP:
>> + admit = swapin_zswap_admit(range_entry, order,
>> + nr_pages, vmf,
>> + locality_orders,
>> + &zswap_ctx);
>> + break;
>> case ZSWAP_RANGE_MIXED:
>> break;
>> - case ZSWAP_RANGE_ALL_ZSWAP:
>> case ZSWAP_RANGE_NEVER_ENABLED:
>> case ZSWAP_RANGE_NO_ZSWAP:
>> admit = true;
>> @@ -769,8 +866,8 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
>> ret = swap_read_folio(folio, plug);
>> /*
>> * Swap readahead allocates order-0 folios. -EAGAIN is reserved for
>> - * retryable large zswap backend races and must be handled by the
>> - * synchronous common swapin path.
>> + * retryable large zswap backend races and should never escape to this
>> + * order-0 path.
>> */
>> VM_WARN_ON_ONCE(ret == -EAGAIN);
>> if (readahead) {
>> @@ -786,6 +883,7 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
>> * @entry: swap entry indicating the target slot
>> * @gfp: memory allocation flags
>> * @orders: allocation orders
>> + * @locality_orders: orders with caller-provided locality evidence
>> * @vmf: fault information
>> * @mpol: NUMA memory allocation policy to be applied
>> * @ilx: NUMA interleave index, for use only when MPOL_INTERLEAVE
>> @@ -794,16 +892,20 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
>> * existing folio in the swap cache for @entry. This initiates the IO, too,
>> * if needed. @entry is rounded down if @orders allow large allocation.
>> *
>> - * Context: Caller must ensure @entry is valid and pin the swap device with refcount.
>> + * Context: Caller must ensure @entry is valid and pin the swap device with
>> + * refcount.
>> * Return: Returns the folio on success, error code if failed.
>> */
>> -struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
>> - struct vm_fault *vmf, struct mempolicy *mpol, pgoff_t ilx)
>> +struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp,
>> + unsigned long orders,
>> + unsigned long locality_orders,
>> + struct vm_fault *vmf, struct mempolicy *mpol,
>> + pgoff_t ilx)
>> {
>> struct folio *folio;
>> int ret;
>>
>> - orders = swapin_admit_orders(entry, orders);
>> + orders = swapin_admit_orders(entry, orders, vmf, locality_orders);
>> again:
>> do {
>> folio = swap_cache_get_folio(entry);
>> --
>> 2.34.1
>>
^ permalink raw reply
* Re: [PATCH 5/5] cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()
From: Bert Karwatzki @ 2026-06-01 9:22 UTC (permalink / raw)
To: Mark Brown, Tejun Heo
Cc: Johannes Weiner, spasswolf, Michal Koutný,
Sebastian Andrzej Siewior, Petr Malat, kernel test robot,
Martin Pitt, cgroups, linux-kernel, Aishwarya.TCV
In-Reply-To: <4e986b4ed7e16547805d54b6e67d09120bc4d2f2.camel@web.de>
Am Sonntag, dem 31.05.2026 um 20:45 +0200 schrieb Bert Karwatzki:
>
> The test that hang when running
> # LTPROOT=/home/bert/ltp-install/ ./kirk --run-suite controllers
> is always cgroup_fj_function_net_prio.
> Also when bisecting this I disabled (i.e. commented out) the
> memcg_stress test in ~/ltp-install/runtest/controllers as it takes a lot of
> time (30min) and succeeds even in the version where hangs occur.
>
> Bert Karwatzki
I've done more testing and found that running the
cgroup_fj_function_net_prio test alone gives no hang, the hang
only occurs when other tests are run before it:
Suite: controllers
──────────────────
cgroup_core01: pass (0.026s)
cgroup_core02: pass (0.004s)
cgroup_core03: pass (0.005s)
cgroup: fail (2m 41s)
memcg_regression: skip (3.558s)
memcg_test_3: pass (0.112s)
memcg_failcnt: skip (0.027s)
memcg_force_empty: skip (0.016s)
memcg_limit_in_bytes: skip (0.015s)
memcg_stat_rss: skip (0.015s)
memcg_subgroup_charge: skip (0.015s)
memcg_max_usage_in_bytes: skip (0.014s)
memcg_move_charge_at_immigrate: skip (0.015s)
memcg_memsw_limit_in_bytes: skip (0.015s)
memcg_stat: skip (0.014s)
memcg_use_hierarchy: skip (0.015s)
memcg_usage_in_bytes: skip (0.014s)
memcg_control: pass (6.046s)
memcontrol01: pass (0.004s)
memcontrol02: pass (0.628s)
memcontrol03: pass (16.009s)
memcontrol04: pass (0.926s)
cgroup_fj_function_debug: skip (0.012s)
cgroup_fj_function_cpuset: skip (0.037s)
cgroup_fj_function_cpu: skip (0.055s)
cgroup_fj_function_cpuacct: pass (0.046s)
cgroup_fj_function_memory: skip (0.035s)
cgroup_fj_function_freezer: pass (0.044s)
cgroup_fj_function_devices: pass (0.067s)
cgroup_fj_function_blkio: skip (0.010s)
cgroup_fj_function_net_cls: pass (0.055s)
cgroup_fj_function_perf_event: pass (0.063s)
cgroup_fj_function_net_prio: HANG
I tried to narrow down this list and found that a hang occurs
int the net_prio test only if the perf_event test is run before it:
cgroup_fj_function_perf_event: pass (0.063s)
cgroup_fj_function_net_prio: HANG
Bert Karwatzki
^ permalink raw reply
* Re: [PATCH v5 0/9] mm: switch THP shrinker to list_lru
From: Lance Yang @ 2026-06-01 8:36 UTC (permalink / raw)
To: hannes
Cc: akpm, david, ljs, shakeel.butt, mhocko, david, roman.gushchin,
muchun.song, qi.zheng, yosry.ahmed, ziy, liam, usama.arif, kas,
vbabka, ryncsn, zaslonko, gor, baolin.wang, baohua, dev.jain,
lance.yang, npache, ryan.roberts, cgroups, linux-mm, linux-kernel
In-Reply-To: <20260527204757.2544958-1-hannes@cmpxchg.org>
Hi Johannes,
On Wed, May 27, 2026 at 04:45:07PM -0400, Johannes Weiner wrote:
>This is version 5 of switching the THP shrinker to list_lru.
>
>Core of the new version is the list_lru/set_shrinker_bit fix up front,
>which minimally affects later patches; and a rebase onto the latest
>mm-unstable - replaced alloc_swap_folio() with __swap_cache_alloc().
>
>The changes seemed small enough that *I chose to keep the review tags
>from v4*. Please shout if you object to this!
>
>Changes in v5:
>- patch 1 is a new fix for a very old, pre-existing set_shrinker_bit()
> problem in list_lru, where the bit can be set on a dying child memcg
> instead of the ancestor that actually received the item. Pointed out
> by Usama Arif and Sashiko; fix it first to make it minimally
> backportable and so the conversion is safe.
>- patches 6 and 9 adapt to that fix's new memcg-by-reference
> lock_list_lru_of_memcg() signature
>- collapse_huge_page(): propagate folio_memcg_alloc_deferred() failure
> as SCAN_ALLOC_HUGE_PAGE_FAIL instead of leaking SCAN_SUCCEED and
> falsely reporting a successful MADV_COLLAPSE (Usama Arif, Sashiko)
>- deferred_split_isolate(): fix a UAF by reading folio state before
> list_lru_isolate(); once removed, a racing folio_put() frees the
> folio via the lockless list_empty() check while we still touch its
> flags and stats (Sashiko)
>- rebased to mm-unstable of 2026-05-27, which simplifies the flatten
> prep patch (now anon-only, as alloc_swap_folio() was folded into the
> new __swap_cache_alloc()) and moves the swap-side
> folio_memcg_alloc_deferred() hook into __swap_cache_alloc(). Kairui,
> I would appreciate an eyeball on that.
>
>Changes in v4:
>- guard folio_memcg_alloc_deferred() with mem_cgroup_disabled() to fix
> NULL deref in __memcg_list_lru_alloc() when booting with
> cgroup_disable=memory (e.g., kdump capture kernel) -- reported and
> tested by Mikhail Zaslonko on s390 and x86
>- flatten if (folio) branches in alloc_swap_folio() and alloc_anon_folio()
> in a prep patch so the list_lru allocation additions are a clean minimal
> diff (Lorenzo)
>- folio_memcg_alloc_deferred() moved out of alloc_charge_folio() into the
> anon-only collapse_huge_page() path; collapse_file() shares that helper
> but its pages don't go on the THP shrinker queue (David)
>- guard folio_memcg_alloc_deferred() with order > 1; mTHPs below order-2
> can't be queued on the deferred split list (David)
>- make deferred_split_lru static, hide behind folio_memcg_alloc_deferred()
> wrapper with GFP_KERNEL (Lorenzo)
>- rename l -> lru throughout huge_memory.c (Lorenzo)
>- kdoc for folio_memcg_list_lru_alloc() (Lorenzo)
>- list_lru_lock_irq()/unlock_irq()/add_irq() irq-disabling variants;
> use list_lru_add_irq() in deferred_split_scan() (Lorenzo)
>- reorder shrinker_free() before list_lru_destroy() (Lorenzo)
>
>Changes in v3:
>- dedicated lockdep_key for irqsafe deferred_split_lru.lock (syzbot)
>- conditional list_lru ops in __folio_freeze_and_split_unmapped() (syzbot)
>- annotate runs of inscrutable false, NULL, false function arguments (David)
>- rename to folio_memcg_list_lru_alloc() (David)
>
>Changes in v2:
>- explicit rcu_read_lock() in __folio_freeze_and_split_unmapped() (Usama)
>- split out list_lru prep bits (Dave)
>
>The open-coded deferred split queue has issues. It's not NUMA-aware
>(when cgroup is enabled), and it's more complicated in the callsites
>interacting with it. Switching to list_lru fixes the NUMA problem and
>streamlines things. It also simplifies planned shrinker work.
>
>Patch 1 fixes a pre-existing list_lru bug where the shrinker bit is
>set on the caller's memcg rather than the ancestor whose sublist the
>item actually lands on after a walk-up. Standalone, backportable; the
>rest of the series depends on it.
>
>Patches 2-5 are cleanups and small refactors in list_lru code. They're
>basically independent, but make the THP shrinker conversion easier.
>
>Patch 6 extends the list_lru API to allow the caller to control the
>locking scope. The THP shrinker has private state it needs to keep
>synchronized with the LRU state.
>
>Patch 7 extends the list_lru API with a convenience helper to do
>list_lru head allocation (memcg_list_lru_alloc) when coming from a
>folio. Anon THPs are instantiated in several places, and with the
>folio reparenting patches pending, folio_memcg() access is now a more
>delicate dance. This avoids having to replicate that dance everywhere.
>
>Patch 8 flattens the alloc_anon_folio() retry loop so the next patch's
>list_lru hook lands as a clean addition rather than nested deep inside
>an if (folio) block.
>
>Patch 9 finally switches the deferred_split_queue to list_lru.
As the changelog above says, the old queue is per-memcg only, rather
than per-memcg-per-node. So reclaim on one node can still walk the whole
memcg queue and split underused THPs from other nodes in the same memcg.
But I think the new one can lose reclaim in the cgroup.memory=nokmem
case ...
With nokmem, the deferred shrinker can still run from memcg reclaim,
because it is SHRINKER_NONSLAB. But the list_lru is no longer per-memcg:
__list_lru_init() clears memcg_aware,
if (mem_cgroup_kmem_disabled())
memcg_aware = false;
so list_lru_from_memcg_idx() falls back to the shared node list:
static inline struct list_lru_one *
list_lru_from_memcg_idx(struct list_lru *lru, int nid, int idx)
{
if (list_lru_memcg_aware(lru) && idx >= 0) {
[...]
}
return &lru->node[nid].lru;
}
That makes the shrinker bit unreliable. __list_lru_add() still sets the
bit on the memcg passed in, but only when the list goes from empty to
non-empty:
bool __list_lru_add(struct list_lru *lru, struct list_lru_one *l,
struct list_head *item, int nid,
struct mem_cgroup *memcg)
{
if (list_empty(item)) {
[...]
if (!l->nr_items++)
set_shrinker_bit(memcg, nid, lru_shrinker_id(lru));
[...]
return true;
}
return false;
}
If memcg A adds the first folio, A gets the bit. If memcg B later adds a
folio to the same shared list, B does not get a bit, because the list
was already non-empty.
So in the A-first/B-later case, reclaim from B may not call the deferred
shrinker at all. The shared list is scanned from memcg reclaim only if
reclaim runs from the memcg that has the bit, such as A here, or from
global reclaim :)
Anyway, only after the shared list is emptied does the next memcg to add
a folio get to be the one with the bit, IIUC :)
Hopefully I didn't miss somthing important ...
Cheers, Lance
^ permalink raw reply
* Re: [RFC PATCH 0/5] mm, swap: Virtual Swap Space (Swap Table Edition)
From: Kairui Song @ 2026-06-01 7:34 UTC (permalink / raw)
To: Nhat Pham
Cc: kasong, Liam.Howlett, akpm, apopple, axelrasmussen, baohua,
baolin.wang, bhe, byungchul, cgroups, chengming.zhou, chrisl,
corbet, david, dev.jain, gourry, hannes, hughd, jannh,
joshua.hahnjy, lance.yang, lenb, linux-doc, linux-kernel,
linux-mm, linux-pm, lorenzo.stoakes, matthew.brost, mhocko,
muchun.song, npache, pavel, peterx, peterz, pfalcato, rafael,
rakie.kim, roman.gushchin, rppt, ryan.roberts, shakeel.butt,
shikemeng, surenb, tglx, vbabka, weixugc, ying.huang, yosry.ahmed,
yuanchu, zhengqi.arch, ziy, kernel-team, riel, haowenchao22
In-Reply-To: <20260528212955.1912856-1-nphamcs@gmail.com>
On Thu, May 28, 2026 at 02:29:24PM +0800, Nhat Pham wrote:
> Based on: mm-unstable @ 444fc9435e57 + swap-table phase IV v5 [2].
>
> I manually adapted Kairui's ghost device implementation (from [4])
> for my vswap device. I've credited him as Co-developed-by on Patch I
> since a substantial portion of the dynamic-cluster infrastructure is
> his (I did propose the idea of using xarray/radix tree for dynamic
> swap clusters allocation and management though :P).
>
> >From here on out, for simplicity, I will refer to swap table phase IV
> as "P4", and the older v6 virtual swap space implementation as "v6".
>
...
>
> This series reimplements the virtual swap space concept (see [1])
> on top of Kairui Song's swap table infrastructure, on top of [2]
> and in accordance with his proposal in [3]. The proposal's idea
> is interesting, so I decided to give it a shot myself. I'm still not
> 100% sure that this is bug-proof, but hey, it compiles, and has
> not crashed in my simple stress testing :)
>
> The prototype here is feature-complete relative to the swap-table P4
> baseline — swapout, swapin, freeing, swapoff, zswap writeback, zswap
> shrinker, memcg charging, and THP swapin all work for
> both vswap and direct-physical entries — and satisfies all three
> requirements above: no backend coupling (zswap/zero entries hold no
> physical slot), dynamic swap space (clusters allocated on demand via
> xarray, no static provisioning), and efficient backend transfer
> (in-place vtable updates, no PTE/rmap walking).
>
> II. Design
>
> With vswap, pages are assigned virtual swap entries on a ghost device
> with no backing storage. These entries are backed by zswap, zero pages,
> or (lazily) physical swap slots. Physical backing is allocated only
> when needed — on zswap writeback or reclaim writeout, after the rmap
> step.
>
> Compared to the standalone v6 implementation [1], which introduces a
> 24-byte per-entry swap descriptor and its own cluster allocator, this
> edition uses swap_table infrastructure, and share a lot of the allocator
> logic. Per-slot metadata is stored in a tag-encoded virtual_table
> (atomic_long_t, 8 bytes per slot), and physical clusters store
> Pointer-tagged rmap entries in the swap_table for reverse lookup back to
> the virtual cluster.
>
> Here are some data layout diagrams:
>
> Case 1: vswap entry (virtualized)
>
> PTE swap_cluster_info_dynamic
> vswap_entry +-------------------------+
> (swp_entry_t) ------>| swap_cluster_info (ci) |
> | +--------------------+ |
> | | swap_table | |
> | | PFN / Shadow | |
> | | memcg_table | |
> | | count,flags,order | |
> | | lock, list | |
> | +--------------------+ |
> | |
> | virtual_table |
> | +--------------------+ |
> | | NONE | |
> | | PHYS | |
> | | ZERO | |
> | | ZSWAP(entry*) | |
> | | FOLIO(folio*) | |
> | +--------------------+ |
> +-------------------------+
> |
> | PHYS resolves to
> v
> PHYSICAL CLUSTER (swap_cluster_info)
> +--------------------------+
> | swap_table per-slot: |
> | NULL - free |
> | PFN - cached folio |
> | Shadow - swapped out |
> | Pointer- vswap rmap |
> | Bad - unusable |
> | |
> | Vswap-backing slot: |
> | Pointer(C|swp_entry_t) |
> | rmap back to vswap |
> +--------------------------+
>
> Case 2: direct-mapped physical entry (no vswap)
>
> PTE PHYSICAL CLUSTER (swap_cluster_info)
> phys_entry +--------------------------+
> (swp_entry_t) ------>| swap_table per-slot: |
> | NULL - free |
> | PFN - cached folio |
> | Shadow - swapped out |
> | Bad - unusable |
> +--------------------------+
>
> struct swap_cluster_info_dynamic {
> struct swap_cluster_info ci; /* swap_table, lock, etc. */
> unsigned int index; /* position in xarray */
> struct rcu_head rcu; /* kfree_rcu deferred free */
> atomic_long_t *virtual_table; /* backend info, 8 B/slot */
> };
>
> Each vswap cluster (swap_cluster_info_dynamic) extends the classic
> swap_cluster_info struct with a virtual_table array that stores the
> backend information for each virtual swap entry in the cluster. Each
> entry is tag-encoded in the low 3 bits to indicate backend types:
>
> NONE: |----- 0000 ------|000| free / unbacked
> PHYS: |-- (type:5,off:N)|001| on a physical swapfile (shifted)
> ZERO: |----- 0000 ------|010| zero-filled page
> ZSWAP: |--- zswap_entry* |011| compressed in zswap
> FOLIO: |--- folio* ------|100| in-memory folio
Thanks for trying this approach!
For the format part, PHYS don't need that much bits I think,
so by slightly adjust the format vswap device could be share
mostly the same format with ordinary device.
For example typical modern system don't have a address space larger
than 52 bit. (Even with full 64 bits used for addressing, shift it
by 12 we get 52). Plus 5 for type, you get 57, so you can have a
marker that should work as long as it shorter than 1000000 for PHYS,
and shared for all table format since it's not in conflict with
anything. You have also use a few extra bits so a single swap space
can be 8 times larger than RAM space, and since we can help
multiple swap type I think that should be far than enough?
Then you have Shadow back at 001, and zero bit in shadow. The only
special one is Zswap, which will be 100 now, and that's exactly the
reserved pointer format in current swap table format, on seeing
si->flags & VSWAP && is_pointer(swp_tb) you know that's zswap :)
Folio / PFN can still be 010 as in the current swap table format.
Then everything seems clean and aligned, no more special handling
for vswap needed, there are detailed to sort out, but it should work.
> - Pointer-tagged swap_table on physical clusters for rmap (physical
> -> virtual) lookup.
Or reuse the PHYS format (rename it maybe) since point back to vswap
is also pointing to a si.
> III. Follow-ups:
>
> In no particular order (and most of which can be done as follow-up
> patch series rather than shoving everything in the initial landing):
>
> - More thorough stress testing is very much needed.
>
> - Performance benchmarks to make sure I don't accidentally regress
> the vswap-less case, and that the vswap's case performance is
> good. I suspect I will have to port a lot of the
> optimizations I implemented in v6 over here - some of the
> inefficiencies are inherent in any swap virtualization, and
> would require the same fix (for e.g the MRU cluster caching
> for faster cluster lookup - see [8] and [9]).
This could be imporved by per-si percpu cluster. Both YoungJun's
tiering and Baoquan's previous swap ops mentioned this is needed,
and now vswap also need that. If the vswap is also a si, then it will
make use of this too.
YoungJun posted this a few month before:
https://lore.kernel.org/linux-mm/20260131125454.3187546-5-youngjun.park@lge.com/
The concern is that some locking contention could be heavier, or maybe
that's just a hypothetical problem though.
>
> - Runtime enable/disable of the vswap device. To be honest, I don't
> know if there is a value in this. My preference is vswap can be
> optimized to the point that any overhead is negligible. Failing that,
> maybe we can come up with some simple heuristics that automatically
> decides for users?
>
> In this RFC, CONFIG_VSWAP=y means the vswap device is always created at
> boot, and CONFIG_VSWAP=n means the vswap device is never created. This
> *might* be enough just on its own.
>
> Is a runtime knob (sysfs or sysctl) worth the complexity beyond
> these heuristics? I'm not sure yet. Maintaining both cases
I checked the code and I think it's not hard to do, patch 1 already
handling the meta data dynamically, everything will still just work
even if you remove vswap at runtime. The rest of patches need adaption
but might not end up being complex, it other comments here
are considered.
For patch 2, a few routines like vswap_can_swapin_thp seems not
needed or should be moved to __swap_cache_alloc? VSWAP_FOLIO is
same as swap cache folio check, which is already covered. Same for
zero checking, and VSWAP_NONE which is same as swap count check
I think. That way we not only save a lot of code, we also no
longer need to treat vswap specially.
If you keep the format compatible with what we already have
as the earlier comment mentions, a large portion of this part
might be unneeded.
> at runtime also has overhead for checking as well, and some of the
> checks are not cheap :)
I also noticed the new introduced swap_read_folio_phys in patch 3, so
this actually can be done using Baoquan's swapops idea which is now
part of Christoph's swap batching:
https://lore.kernel.org/linux-mm/20260528124559.2566481-9-hch@lst.de/#r
That series is focusing on batching and better performance but swapops
was also proposed as a way to solve the virtual layer, makes it possible
to have vswap as one kind of swapops which is Chris talked a lot about:
https://lore.kernel.org/linux-mm/aZiFvzlBJiYBUDre@MiWiFi-R3L-srv/
Following this, we could have something like:
const struct swap_ops swap_vswap_ops = {
.submit_write = swap_vswap_submit_write,
.submit_read = swap_vswap_submit_read,
};
The move the folio_realloc_swap in swap_vswap_submit_write.
Merge of IO might be moved to lower phyiscal level for vswap.
Another gain is that the memory usage and CPU overhead will be
lower with only one layer. As I'm recently trying to offload swap
dataplane off CPU so the CPU won't touch the data at all, the
overhead will be purely by swap itself, plus some mm overhead.
Things like that and IO optimization above and could make swap
subsystem more and more performance sensitive so we have cases
that needs only one layer.
>
> - Defer per-cluster memcg_table and zeromap allocation on physical
> clusters. A physical swap cluster backing vswap entries only do
> not really need their memcg_table, but the current design forces
> us to allocate it anyway. This is a waste of memory, and is an
> overhead regression compared to my older design on the zswap-only
> case, which Johannes has pointed out multiple times (see [6]),
> and is one of the biggest reasons why I have not been satisfied
> with this approach thus far. It honestly is a bit of a
> deal-breaker...
>
> That said, I think I might be able to allocate them on demand, i.e
> only when the first direct-mapped slot is allocated on that cluster.
> That will give us the best of BOTH worlds, for both the vswap and
> directly-mapped physical swap cases. No promises, but I will try
> (if this approach is good enough for all parties).
Zero map is not really a problem when it's just a inlined bit I think.
For memcg table allocation, on demand seems a good idea, and actually
we are not far from there, I tried to generalize the
alloc-then-retry-sleep-alloc in swap_alloc_table but still not generic
enough I guess.. Good new is the allocation of the table is already
kind of ondemand, just need to split the detection of these two kind
of table.
Mean while I also remember we once discussed about splitting the
accounting for vswap / physical swap? If we went that approach we
don't need to treat memcg_table specially.
> - Widen swap_info_struct->max to unsigned long. The vswap device's
> max is currently clamped to ALIGN_DOWN(UINT_MAX, SWAPFILE_CLUSTER)
> (~16 TiB) to fit in unsigned int. 16 TiB is small for vswap,
> especially when we're getting increasingly big machines memory-wise.
This should be very easy to do, just replace unsigned int with
unsigned long, a lot of place to touch though :)
^ permalink raw reply
* [PATCH] mm/list_lru: drain before clearing xarray entry on reparent
From: Shakeel Butt @ 2026-06-01 6:34 UTC (permalink / raw)
To: Andrew Morton
Cc: Johannes Weiner, Dave Chinner, Roman Gushchin, Muchun Song,
Qi Zheng, Kairui Song, Meta kernel team, linux-mm, cgroups,
linux-kernel, Chris Mason
memcg_reparent_list_lrus() clears the dying memcg's xarray entry with
xas_store(&xas, NULL) before reparenting its per-node lists into the
parent. This opens a window where a concurrent list_lru_del() arriving
for the dying memcg sees xa_load() == NULL, walks to the parent in
lock_list_lru_of_memcg(), takes the parent's per-node lock, and calls
list_del_init() on an item still physically linked on the dying
memcg's list.
If another in-flight thread holds the dying memcg's per-node lock at
the same moment (another list_lru_del, or a list_lru_walk_one running
an isolate callback), both threads modify ->next/->prev pointers on the
same physical list under different locks. Adjacent items can corrupt
each other's links.
Fix it by reversing the order: reparent each per-node list and mark the
child's list lru dead and then clear the xarray entry. Any concurrent
list_lru op that finds the still-set xarray entry either takes the dying
memcg's per-node lock (synchronizing with the drain) or sees LONG_MIN
and walks to the parent, where the items now live.
Fixes: fb56fdf8b9a2 ("mm/list_lru: split the lock to per-cgroup scope")
Signed-off-by: Shakeel Butt <shakeel.butt@linux.dev>
Reported-by: Chris Mason <clm@fb.com>
---
mm/list_lru.c | 20 +++++++++-----------
1 file changed, 9 insertions(+), 11 deletions(-)
diff --git a/mm/list_lru.c b/mm/list_lru.c
index dd29bcf8eb5f..ae55a52307db 100644
--- a/mm/list_lru.c
+++ b/mm/list_lru.c
@@ -473,26 +473,24 @@ void memcg_reparent_list_lrus(struct mem_cgroup *memcg, struct mem_cgroup *paren
mutex_lock(&list_lrus_mutex);
list_for_each_entry(lru, &memcg_list_lrus, list) {
struct list_lru_memcg *mlru;
- XA_STATE(xas, &lru->xa, memcg->kmemcg_id);
- /*
- * Lock the Xarray to ensure no on going list_lru_memcg
- * allocation and further allocation will see css_is_dying().
- */
- xas_lock_irq(&xas);
- mlru = xas_store(&xas, NULL);
- xas_unlock_irq(&xas);
+ mlru = xa_load(&lru->xa, memcg->kmemcg_id);
if (!mlru)
continue;
/*
- * With Xarray value set to NULL, holding the lru lock below
- * prevents list_lru_{add,del,isolate} from touching the lru,
- * safe to reparent.
+ * Reparent each per-node list and mark the child dead
+ * (LONG_MIN) before clearing xarray entry otherwisw a
+ * concurrent list_lru_del() may corrupt the list if it arrives
+ * after xarray clear but before reparenting as
+ * lock_list_lru_of_memcg will acquire parent's lock while the
+ * item is still on child's list.
*/
for_each_node(i)
memcg_reparent_list_lru_one(lru, i, &mlru->node[i], parent);
+ xa_erase(&lru->xa, memcg->kmemcg_id);
+
/*
* Here all list_lrus corresponding to the cgroup are guaranteed
* to remain empty, we can safely free this lru, any further
--
2.52.0
^ permalink raw reply related
* [tj-cgroup:for-7.2] BUILD SUCCESS 6935f0496c2eb74dd3f57228a799d2db1a8a9ac8
From: kernel test robot @ 2026-06-01 6:30 UTC (permalink / raw)
To: Tejun Heo; +Cc: cgroups
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-7.2
branch HEAD: 6935f0496c2eb74dd3f57228a799d2db1a8a9ac8 docs: cgroup: Fix stale source file paths
elapsed time: 732m
configs tested: 211
configs skipped: 6
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc axs103_smp_defconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260601 clang-23
arc randconfig-002-20260601 clang-23
arc vdk_hs38_smp_defconfig gcc-15.2.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260601 clang-23
arm randconfig-002-20260601 clang-23
arm randconfig-003-20260601 clang-23
arm randconfig-004-20260601 clang-23
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001 clang-23
arm64 randconfig-001-20260601 gcc-8.5.0
arm64 randconfig-002 clang-23
arm64 randconfig-002-20260601 gcc-8.5.0
arm64 randconfig-003 clang-23
arm64 randconfig-003-20260601 gcc-8.5.0
arm64 randconfig-004 clang-23
arm64 randconfig-004-20260601 gcc-8.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001 clang-23
csky randconfig-001-20260601 gcc-8.5.0
csky randconfig-002 clang-23
csky randconfig-002-20260601 gcc-8.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260601 gcc-8.5.0
hexagon randconfig-002-20260601 gcc-8.5.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001 gcc-12
i386 buildonly-randconfig-001-20260601 gcc-12
i386 buildonly-randconfig-002 gcc-12
i386 buildonly-randconfig-002-20260601 gcc-12
i386 buildonly-randconfig-003 gcc-12
i386 buildonly-randconfig-003-20260601 gcc-12
i386 buildonly-randconfig-004 gcc-12
i386 buildonly-randconfig-004-20260601 gcc-12
i386 buildonly-randconfig-005 gcc-12
i386 buildonly-randconfig-005-20260601 gcc-12
i386 buildonly-randconfig-006 gcc-12
i386 buildonly-randconfig-006-20260601 gcc-12
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260601 gcc-14
i386 randconfig-002-20260601 gcc-14
i386 randconfig-003-20260601 gcc-14
i386 randconfig-004-20260601 gcc-14
i386 randconfig-005-20260601 gcc-14
i386 randconfig-006-20260601 gcc-14
i386 randconfig-007-20260601 gcc-14
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260601 gcc-8.5.0
loongarch randconfig-002-20260601 gcc-8.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips cavium_octeon_defconfig gcc-15.2.0
mips malta_kvm_defconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260601 gcc-8.5.0
nios2 randconfig-002-20260601 gcc-8.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001 gcc-10.5.0
parisc randconfig-001-20260601 gcc-10.5.0
parisc randconfig-002 gcc-10.5.0
parisc randconfig-002-20260601 gcc-10.5.0
parisc64 alldefconfig gcc-15.2.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc ppc64_defconfig clang-23
powerpc randconfig-001 gcc-10.5.0
powerpc randconfig-001-20260601 gcc-10.5.0
powerpc randconfig-002 gcc-10.5.0
powerpc randconfig-002-20260601 gcc-10.5.0
powerpc64 randconfig-001 gcc-10.5.0
powerpc64 randconfig-001-20260601 gcc-10.5.0
powerpc64 randconfig-002 gcc-10.5.0
powerpc64 randconfig-002-20260601 gcc-10.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001 clang-23
riscv randconfig-001-20260601 clang-23
riscv randconfig-002 clang-23
riscv randconfig-002-20260601 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001 clang-23
s390 randconfig-001-20260601 clang-23
s390 randconfig-002 clang-23
s390 randconfig-002-20260601 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh dreamcast_defconfig gcc-15.2.0
sh randconfig-001 clang-23
sh randconfig-001-20260601 clang-23
sh randconfig-002 clang-23
sh randconfig-002-20260601 clang-23
sh se7722_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-8.5.0
sparc randconfig-001-20260601 gcc-15.2.0
sparc randconfig-001-20260601 gcc-8.5.0
sparc randconfig-002 gcc-8.5.0
sparc randconfig-002-20260601 gcc-15.2.0
sparc randconfig-002-20260601 gcc-8.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-8.5.0
sparc64 randconfig-001-20260601 gcc-15.2.0
sparc64 randconfig-001-20260601 gcc-8.5.0
sparc64 randconfig-002 gcc-8.5.0
sparc64 randconfig-002-20260601 gcc-15.2.0
sparc64 randconfig-002-20260601 gcc-8.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-8.5.0
um randconfig-001-20260601 gcc-15.2.0
um randconfig-001-20260601 gcc-8.5.0
um randconfig-002 gcc-8.5.0
um randconfig-002-20260601 gcc-15.2.0
um randconfig-002-20260601 gcc-8.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260601 clang-20
x86_64 buildonly-randconfig-002-20260601 clang-20
x86_64 buildonly-randconfig-003-20260601 clang-20
x86_64 buildonly-randconfig-004-20260601 clang-20
x86_64 buildonly-randconfig-005-20260601 clang-20
x86_64 buildonly-randconfig-006-20260601 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260601 clang-20
x86_64 randconfig-002-20260601 clang-20
x86_64 randconfig-003-20260601 clang-20
x86_64 randconfig-004-20260601 clang-20
x86_64 randconfig-005-20260601 clang-20
x86_64 randconfig-006-20260601 clang-20
x86_64 randconfig-011-20260601 clang-20
x86_64 randconfig-012-20260601 clang-20
x86_64 randconfig-013-20260601 clang-20
x86_64 randconfig-014-20260601 clang-20
x86_64 randconfig-015-20260601 clang-20
x86_64 randconfig-016-20260601 clang-20
x86_64 randconfig-071-20260601 gcc-14
x86_64 randconfig-072-20260601 gcc-14
x86_64 randconfig-073-20260601 gcc-14
x86_64 randconfig-074-20260601 gcc-14
x86_64 randconfig-075-20260601 gcc-14
x86_64 randconfig-076-20260601 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-8.5.0
xtensa randconfig-001-20260601 gcc-15.2.0
xtensa randconfig-001-20260601 gcc-8.5.0
xtensa randconfig-002 gcc-8.5.0
xtensa randconfig-002-20260601 gcc-15.2.0
xtensa randconfig-002-20260601 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [tj-cgroup:for-next] BUILD SUCCESS 9e27aaab61c2c36a13008f16ca56db8e7fe20bf0
From: kernel test robot @ 2026-06-01 6:28 UTC (permalink / raw)
To: Tejun Heo; +Cc: cgroups
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-next
branch HEAD: 9e27aaab61c2c36a13008f16ca56db8e7fe20bf0 Merge branch 'for-7.2' into for-next
elapsed time: 730m
configs tested: 205
configs skipped: 6
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-23
arc axs103_smp_defconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc randconfig-001-20260601 clang-23
arc randconfig-002-20260601 clang-23
arc vdk_hs38_smp_defconfig gcc-15.2.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm randconfig-001-20260601 clang-23
arm randconfig-002-20260601 clang-23
arm randconfig-003-20260601 clang-23
arm randconfig-004-20260601 clang-23
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001 clang-23
arm64 randconfig-001-20260601 gcc-8.5.0
arm64 randconfig-002 clang-23
arm64 randconfig-002-20260601 gcc-8.5.0
arm64 randconfig-003 clang-23
arm64 randconfig-003-20260601 gcc-8.5.0
arm64 randconfig-004 clang-23
arm64 randconfig-004-20260601 gcc-8.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001 clang-23
csky randconfig-001-20260601 gcc-8.5.0
csky randconfig-002 clang-23
csky randconfig-002-20260601 gcc-8.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001-20260601 gcc-8.5.0
hexagon randconfig-002-20260601 gcc-8.5.0
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001 gcc-12
i386 buildonly-randconfig-001-20260601 gcc-12
i386 buildonly-randconfig-002 gcc-12
i386 buildonly-randconfig-002-20260601 gcc-12
i386 buildonly-randconfig-003 gcc-12
i386 buildonly-randconfig-003-20260601 gcc-12
i386 buildonly-randconfig-004 gcc-12
i386 buildonly-randconfig-004-20260601 gcc-12
i386 buildonly-randconfig-005 gcc-12
i386 buildonly-randconfig-005-20260601 gcc-12
i386 buildonly-randconfig-006 gcc-12
i386 buildonly-randconfig-006-20260601 gcc-12
i386 defconfig gcc-15.2.0
i386 randconfig-001-20260601 gcc-14
i386 randconfig-002-20260601 gcc-14
i386 randconfig-003-20260601 gcc-14
i386 randconfig-004-20260601 gcc-14
i386 randconfig-005-20260601 gcc-14
i386 randconfig-006-20260601 gcc-14
i386 randconfig-007-20260601 gcc-14
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001-20260601 gcc-8.5.0
loongarch randconfig-002-20260601 gcc-8.5.0
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips cavium_octeon_defconfig gcc-15.2.0
mips malta_kvm_defconfig gcc-15.2.0
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 defconfig clang-19
nios2 randconfig-001-20260601 gcc-8.5.0
nios2 randconfig-002-20260601 gcc-8.5.0
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001-20260601 gcc-10.5.0
parisc randconfig-002-20260601 gcc-10.5.0
parisc64 alldefconfig gcc-15.2.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc ppc64_defconfig clang-23
powerpc randconfig-001-20260601 gcc-10.5.0
powerpc randconfig-002-20260601 gcc-10.5.0
powerpc64 randconfig-001-20260601 gcc-10.5.0
powerpc64 randconfig-002-20260601 gcc-10.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001 clang-23
riscv randconfig-001-20260601 clang-23
riscv randconfig-002 clang-23
riscv randconfig-002-20260601 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001 clang-23
s390 randconfig-001-20260601 clang-23
s390 randconfig-002 clang-23
s390 randconfig-002-20260601 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allyesconfig clang-19
sh defconfig gcc-14
sh dreamcast_defconfig gcc-15.2.0
sh randconfig-001 clang-23
sh randconfig-001-20260601 clang-23
sh randconfig-002 clang-23
sh randconfig-002-20260601 clang-23
sh se7722_defconfig gcc-15.2.0
sparc allnoconfig clang-23
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-8.5.0
sparc randconfig-001-20260601 gcc-15.2.0
sparc randconfig-001-20260601 gcc-8.5.0
sparc randconfig-002 gcc-8.5.0
sparc randconfig-002-20260601 gcc-15.2.0
sparc randconfig-002-20260601 gcc-8.5.0
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-8.5.0
sparc64 randconfig-001-20260601 gcc-15.2.0
sparc64 randconfig-001-20260601 gcc-8.5.0
sparc64 randconfig-002 gcc-8.5.0
sparc64 randconfig-002-20260601 gcc-15.2.0
sparc64 randconfig-002-20260601 gcc-8.5.0
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-8.5.0
um randconfig-001-20260601 gcc-15.2.0
um randconfig-001-20260601 gcc-8.5.0
um randconfig-002 gcc-8.5.0
um randconfig-002-20260601 gcc-15.2.0
um randconfig-002-20260601 gcc-8.5.0
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001-20260601 clang-20
x86_64 buildonly-randconfig-002-20260601 clang-20
x86_64 buildonly-randconfig-003-20260601 clang-20
x86_64 buildonly-randconfig-004-20260601 clang-20
x86_64 buildonly-randconfig-005-20260601 clang-20
x86_64 buildonly-randconfig-006-20260601 clang-20
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001-20260601 clang-20
x86_64 randconfig-002-20260601 clang-20
x86_64 randconfig-003-20260601 clang-20
x86_64 randconfig-004-20260601 clang-20
x86_64 randconfig-005-20260601 clang-20
x86_64 randconfig-006-20260601 clang-20
x86_64 randconfig-011-20260601 clang-20
x86_64 randconfig-012-20260601 clang-20
x86_64 randconfig-013-20260601 clang-20
x86_64 randconfig-014-20260601 clang-20
x86_64 randconfig-015-20260601 clang-20
x86_64 randconfig-016-20260601 clang-20
x86_64 randconfig-071-20260601 gcc-14
x86_64 randconfig-072-20260601 gcc-14
x86_64 randconfig-073-20260601 gcc-14
x86_64 randconfig-074-20260601 gcc-14
x86_64 randconfig-075-20260601 gcc-14
x86_64 randconfig-076-20260601 gcc-14
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-8.5.0
xtensa randconfig-001-20260601 gcc-15.2.0
xtensa randconfig-001-20260601 gcc-8.5.0
xtensa randconfig-002 gcc-8.5.0
xtensa randconfig-002-20260601 gcc-15.2.0
xtensa randconfig-002-20260601 gcc-8.5.0
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH rdma-next v2 0/3] cgroup/rdma: add MR memory size resource tracking
From: Tao Cui @ 2026-06-01 6:08 UTC (permalink / raw)
To: yanjun.zhu, tj, hannes, mkoutny, leon, jgg
Cc: cui.tao, linux-rdma, cgroups, Tao Cui
In-Reply-To: <ea3c6ed3-5d15-436e-9fa7-2e2d8ce26147@linux.dev>
Hi Yanjun,
Thanks for the thoughtful questions. FRWR is indeed a widely used
pattern, and the interaction with mr_mem deserves clarification.
> 1. Accounting Granularity: Does mr_mem charge the maximum capacity of
> the FRWR object at its allocation time (ib_alloc_mr), or does it
> dynamically track the actual mapped bytes during the fast-reg data
> path?
In the current proposal, mr_mem is only charged for userspace MR
registrations that go through the uverbs layer (REG_MR, DM_MR,
DMABUF_MR, and the legacy ioctl path). These are the paths where a
concrete byte length is known at registration time.
FRWR MRs allocated via ib_alloc_mr() are not charged for mr_mem. The
actual registration footprint associated with an FRWR MR is not known
at allocation time: ib_alloc_mr() only specifies the maximum
scatter-gather capacity of the MR, while the mapped byte range may
change dynamically across successive ib_map_mr_sg() operations.
Supporting FRWR accounting would therefore require a separate
accounting model, since the registration footprint is established
dynamically rather than by a fixed length parameter supplied at MR
creation. This is outside the scope of the current proposal.
> 2. Kernel-space vs Userspace: FRWR pools are frequently allocated by
> kernel-space drivers (like NVMe-oF target/host). If these kernel
> threads are not bound to a specific user cgroup, will their FRWR
> allocations end up in the root cgroup, potentially bypassing the
> per-tenant limits?
The RDMA cgroup's resource control is primarily designed for userspace
consumers. Kernel-space consumers (NVMe-oF target, SRP initiator,
rtrs, iSER, etc.) allocate resources through kernel APIs
(ib_alloc_mr, ib_create_qp, etc.). These resources do not currently
participate in RDMA cgroup accounting and therefore are not subject to
per-cgroup limits.
Kernel-space FRWR pools are typically managed by the administrator
rather than subject to per-tenant limits.
This behavior is consistent with the current RDMA cgroup model, which
tracks resources associated with userspace RDMA objects. If accounting
were extended to kernel-allocated FRWR MRs, ownership semantics would
become an open question: simply charging against the current task or
the root cgroup may not accurately represent the tenant that ultimately
benefits from the resource.
> Don't you think it would be beneficial to explicitly document or
> consider the FRWR pattern in the design section, given its prevalence
> in real-world storage and networking workloads?
Agreed. I will add a note to the cover letter and commit messages
clarifying that mr_mem currently covers only userspace MR registrations
with a known length, and that kernel-space FRWR pools are out of scope
for this initial proposal. The semantic distinction between
userspace registration-length accounting and kernel-space FRWR
resource management is worth documenting explicitly.
Thanks,
Tao
^ permalink raw reply
* Re: [PATCH rdma-next v2 0/3] cgroup/rdma: add MR memory size resource tracking
From: Tao Cui @ 2026-06-01 5:37 UTC (permalink / raw)
To: Michal Koutný
Cc: cui.tao, tj, hannes, leon, jgg, linux-rdma, cgroups, Tao Cui
In-Reply-To: <ahmG_ualxJT5WU_B@localhost.localdomain>
Hi Michal,
Thanks for the review and for the reference.
> IIUC the pinned memory is regular RAM, i.e. it could be controlled
> with memcg as needed. Or is there "physical" limit of what can be
> assigned to a single device?
You are right that the pages associated with an MR are regular system
RAM. However, MR registration does not allocate new pages; it registers
existing pages that are already charged to the allocating process's
memcg.
For that reason, mr_mem is intended to represent a different resource
dimension: not "how much memory does this cgroup own", but "how much
memory may this cgroup register through a given HCA". In other words:
* memcg limits memory ownership/consumption
* mr_mem limits RDMA registration footprint
An administrator may reasonably wish to set different registration
budgets per device (for example, 1G through mlx5_0 and 4G through
mlx5_1) for the same cgroup. memcg has no notion of device-scoped
limits; it only tracks aggregate memory consumption.
This distinction is important because memory ownership and DMA
registration are not necessarily constrained by the same policy. A
tenant may remain within its memcg limit while still consuming a large
portion of a particular HCA's registration capacity. The existing RDMA
controller already provides a per-device resource control framework,
and mr_mem extends that model to cover memory registration footprint.
> Or is there "physical" limit of what can be assigned to a single device?
Yes. Real HCAs have finite resources associated with memory
registration, such as MTT/MPT capacity and related DMA translation
resources. In practice, administrators often need to prevent one tenant
from consuming a disproportionate share of a particular HCA's
registration capacity, even when sufficient system memory remains
available.
It is also worth noting that mr_mem is intentionally not an attempt to
account exact pinned pages. The accounting model is tied to MR object
lifetime and tracks registration footprint rather than dynamic physical
page state. For example, ODP MRs may have only a subset of their pages
pinned at any given time, yet still consume registration resources on
the HCA. This is why the proposal focuses on a stable,
policy-oriented registration budget rather than precise memory
ownership accounting.
> BTW, have a look at [1], it'd be good to converge to similar approach
> (the current proposal allows distinguishing whether charging should
> include or exempt memcg counting).
I've read the related dma-buf accounting work.
My understanding is that those proposals focus on allocations that
create new memory on behalf of a device, which is naturally accounted
through memcg.
RDMA MR registration is different because no new memory is allocated.
The MR object is an in-kernel registration of existing memory that has
already been accounted elsewhere. The resource being limited is
therefore the registration itself rather than the underlying memory
pages.
> Also it seems, that the dmem controller could be a one-stop solution
> for all DMA charges. Please tell me if there are any distinguishing
> factors between RDMA devices' memory and these dmem memory regions.
One distinction is that the current dmem work appears to focus on
memory resources allocated on behalf of a device, whereas mr_mem is
intended to limit host memory registered for DMA through RDMA MRs.
RDMA NICs typically do not have large device-local memory pools;
instead they provide DMA access to host RAM through memory
registration. As a result, the resource being controlled here is not
device memory consumption itself, but the registration footprint
associated with a particular HCA.
Another difference is the accounting model itself. The proposed mr_mem
accounting is tied to MR object lifetime and tracks registration
footprint rather than precise physical page usage.
My understanding is that dmem is currently integrated with the DRM/TTM
subsystem for device-local memory accounting, and there is no existing
RDMA integration today. I have not investigated what would be required
to extend that model to RDMA registration accounting.
That said, I agree that convergence would be desirable if a generic
framework can naturally express per-device DMA registration budgets.
My goal here is not necessarily to require RDMA-specific accounting,
but to address a practical resource-control problem within the existing
RDMA cgroup framework.
Thanks,
Tao
^ permalink raw reply
* Re: [PATCH v7 0/4] mm: swap: introduce swap tier infrastructure
From: YoungJun Park @ 2026-06-01 4:00 UTC (permalink / raw)
To: Andrew Morton
Cc: chrisl, linux-mm, cgroups, linux-kernel, kasong, hannes, mhocko,
roman.gushchin, shakeel.butt, muchun.song, shikemeng, nphamcs,
baoquan.he, baohua, gunho.lee, taejoon.song, hyungjun.cho,
mkoutny, baver.bae, matia.kim
In-Reply-To: <20260527133651.2ce806fa542a82eca5ff66d6@linux-foundation.org>
On Wed, May 27, 2026 at 01:36:51PM -0700, Andrew Morton wrote:
> On Wed, 27 May 2026 15:22:43 +0900 Youngjun Park <youngjun.park@lge.com> wrote:
>
> > This is v7 of the swap tier series addressing review feedback.
> > The cover letter has been simplified.
>
> One question from Sashiko. Minor, but easy to address.
> https://sashiko.dev/#/patchset/20260527062247.3440692-1-youngjun.park@lge.com
Thanks, Andrew. That is a valid concern and definitely needs to be fixed.
I will address it in the next version.
> I'm reluctant to add a new feature patchset at this time - we have a lot
> already and we're at -rc5. What do others think?
I will wait to hear others' thoughts on this.
Thanks,
Youngjun Park
^ permalink raw reply
* Re: [PATCH v7 4/4] mm: swap: filter swap allocation by memcg tier mask
From: YoungJun Park @ 2026-06-01 3:50 UTC (permalink / raw)
To: Nhat Pham
Cc: akpm, chrisl, linux-mm, cgroups, linux-kernel, kasong, hannes,
mhocko, roman.gushchin, shakeel.butt, muchun.song, shikemeng,
baoquan.he, baohua, gunho.lee, taejoon.song, hyungjun.cho,
mkoutny, baver.bae, matia.kim
In-Reply-To: <CAKEwX=N2XcMHN1jatppOk6wnmz-Shab5XMtTtzgYOzRvU_6YFw@mail.gmail.com>
On Sat, May 30, 2026 at 11:21:12AM -0700, Nhat Pham wrote:
> On Sat, May 30, 2026 at 10:51 AM Nhat Pham <nphamcs@gmail.com> wrote:
> >
> >
> > How expensive is it to add per-cpu caching for each device :(
>
> to clarify - a percpu_swap_cluster per si for every si.
>
> >
>
> ... or for each tier (assuming devices in each tier share the same
> performance characteristics, and could be used interchangeably?).
>
> Basically:
>
> struct percpu_swap_cluster {
> struct swap_info_struct *si[MAX_SWAPTIER][SWAP_NR_ORDERS];
> unsigned long offset[MAX_SWAPTIER][SWAP_NR_ORDERS];
> local_lock_t lock;
> };
>
> Seems like 4 is the default number of tier right? So the extra
> overhead is just (nr cpu) * 10 * 3 * (sizeof(unsigned long) +
> sizeof(*ptr)) or wev?
I agree. I actually considered the idea of a tier-centric cache as well.
You might remember that in the previous "per cgroup swap priority"
patchset, I implemented per-cpu caches per priority, which is essentially
the same as having them per tier.
However, as you agreed in another thread, including this optimization
right now might be a bit premature. If the core swap tier idea gets
merged, I plan to explore this optimization further as follow-up work.
Thanks
Youngjun Park
^ permalink raw reply
* Re: [PATCH v7 0/4] mm: swap: introduce swap tier infrastructure
From: YoungJun Park @ 2026-06-01 3:42 UTC (permalink / raw)
To: Nhat Pham
Cc: akpm, chrisl, linux-mm, cgroups, linux-kernel, kasong, hannes,
mhocko, roman.gushchin, shakeel.butt, muchun.song, shikemeng,
baoquan.he, baohua, gunho.lee, taejoon.song, hyungjun.cho,
mkoutny, baver.bae, matia.kim
In-Reply-To: <CAKEwX=PkiWdgNtoHberaXafQDoDngw5kycfaXeU22MnrXBoAXQ@mail.gmail.com>
On Sat, May 30, 2026 at 11:02:03AM -0700, Nhat Pham wrote:
> On Tue, May 26, 2026 at 11:23 PM Youngjun Park <youngjun.park@lge.com> wrote:
> >
> > This is v7 of the swap tier series addressing review feedback.
> > The cover letter has been simplified.
> >
> > I revisited the design (see Design Rationale). Since our use case
> > fits best with a memcg-based model, the implementation remains
> > within memcg and preserves its resource accounting semantics.
> >
> > Alternatives considered:
> >
> > 1. A separate sysfs interface under swap. (Workable. But, it would still
> > need to reference memcg paths, and fully decoupling it would add
> > swap-layer logic to manage memcgs, making it secondary option.)
> >
> > 2. Making the feature non-default.
> >
> > Other interfaces were also reviewed. Aside from sysfs and BPF,
> > the options involve trade-offs and are largely design choices.
> > BPF was excluded due to possible disablement on our embedded
> > platform, though future extension remains possible.
> >
> > Overview
> > ========
> >
> > Swap Tiers group swap devices into performance classes (e.g. NVMe,
> > HDD, Network) and allow per-memcg selection of which tiers to use.
> > This mechanism was suggested by Chris Li.
> >
> > Design Rationale
> > ================
> >
> > Swap tier selection is attached to memcg. A child cgroup may select a
> > subset of the parent's allowed tiers.
> >
> > This
> > - Preserves cgroup inheritance semantics (boundary at parent,
> > refinement at child).
> > - Reuses memcg, which already groups processes and enforces
> > hierarchical memory limits.
> > - Aligns with existing memcg swap controls (e.g. swap.max, zswap.writeback)
> > - Avoids introducing a parallel swap control hierarchy.
> >
> > Placing tier control outside memcg (e.g., via BPF, syscalls, or
> > madvise) would allow swap preference to diverge from the memcg
> > hierarchy. Integrating it into memcg keeps the swap policy
> > consistent with existing memory ownership semantics. There are
> > also real use cases built around memcg.
> >
> > In the future, this can be extended to other interfaces to cover
> > additional use cases.
> >
> > I believe a memcg-based swap control is a good starting point
> > before such extensions.
> >
> > Use Cases
> > =========
> >
> > #1: Latency separation (our primary deployment scenario)
> > [ / ]
> > |
> > +-- latency-sensitive workload (fast tier)
> > +-- background workload (slow tier)
> >
> > The parent defines the memory boundary.
> > Each workload selects a swap tier via memory.swap.tiers according to
> > latency requirements.
> >
> > This prevents latency-sensitive workloads from being swapped to
> > slow devices used by background workloads.
> >
> > #2: Per-VM swap selection (Chris Li's deployment scenario)
> > [ / ]
> > |
> > +-- [ Job on VM ] (tiers: zswap, SSD)
> > |
> > +-- [ VMM guest memory ] (tiers: SSD)
> >
> > The parent (job) has access to both zswap and SSD tiers.
> > The child (VMM guest memory) selects SSD as its swap tier via
> > memory.swap.tiers. In this deployment, swap device selection
> > happens at the child level from the parent's available set.
> >
> > #3: Tier isolation for reduced contention (hypothetical)
> > [ / ] (tiers: A, B)
> > |
> > +-- workload X (tiers: A)
> > +-- workload Y (tiers: B)
> >
> > Each child uses a different tier. Since swap paths are separated
> > per tier, synchronization overhead between the two workloads is
> > reduced.
> >
> > Future extension
> > ================
> >
> > #1: Intra-tier distribution policy:
> > Currently, swap devices with the same priority are allocated in a
> > round-robin fashion. Per-tier policy files under
> > /sys/kernel/mm/swap/tiers/ can control how devices within a tier
> > are selected (e.g. round-robin, weighted).
> >
> > #2: Inter-tier promotion and demotion:
> > Promotion and demotion apply between tiers, not within a single
> > tier. The current interface defines only tier assignment; it does
> > not yet define when or how pages move between tiers. Two triggering
> > models are possible:
> >
> > (a) User-triggered: userspace explicitly initiates migration between
> > tiers (e.g. via a new interface or existing move_pages semantics).
> > (b) Kernel-triggered: the kernel moves pages between tiers at
> > appropriate points such as reclaim or refault.
> >
> > #3: Per-VMA, per-process swap and BPF:
> > Not just for memcg based swap, possible to extend Per-VMA or per-process swap.
> > Or we can use it as BPF program.
> >
> > Experimentation
> > ===============
> >
> > Tested on our internal platform using NBD as a separate swap tier.
> > Our first production's simple usecase.
> >
> > Without tiers:
> > - No selective control over flash wear
> > - Cannot selectively assign NBD to specific applications
> >
> > Cold launch improvement (preloaded vs. baseline):
> > - App A: 13.17s -> 4.18s (68%)
> > - App B: 5.60s -> 1.12s (80%)
> > - App C: 10.25s -> 2.00s (80%)
> >
> > Performance impact with no tiers configured:
> > <1% regression in kernel build and vm-scalability benchmarks
> >
>
> Bit late to the party - working on my review backlog right now :)
>
> I see some parallels with this and memory tiering work being done. One
> future line of work could be considering how to ensure fairness when
> multiple cgroups share same tiers:
>
> https://lwn.net/Articles/1073400/
Hi Nhat,
Thanks for bringing this up. I took a quick look at the link, and I agree
with your point. We could probably use a similar proposed mechanism (e.g., setting
min/max limits per tier) to handle promotion and demotion in the future.
This also suggests that keeping the swap tier limits inside the memcg
interface is a reasonable approach. It aligns well with such future work.
If it were implemented in other layers (e.g., prctl, sysfs, or BPF), we
would likely have to revisit its integration with memcg someday anyway.
> and occupy all the space in the faster tier(s), pushing the other
> colocated tenants to the slower tier(s). We might need to figure out a
> way to ensure fairness here (while letting cgroups occupy fast swap
> backends opportunistically if there is no resources scarcity).
Agreed. Ensuring fairness will be essential when we eventually expand
the promotion and demotion mechanisms across swap tiers.
Thanks,
Youngjun
^ permalink raw reply
* Re: [RFC PATCH v2 3/9] mm/zswap: support fully zswap-backed large folio loads
From: Fujunjie @ 2026-05-31 20:03 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=Or6forBoArv1b=MZuhOuF+MTuLLZWPKgUmkBVaoBoYSQ@mail.gmail.com>
On 5/30/2026 2:25 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> zswap currently refuses large swapcache folios. That is correct for mixed
>> backend ranges, but it also prevents the common swapin path from loading a
>> range that is still fully backed by zswap.
>>
>> Teach zswap_load() to fill a locked large swapcache folio by decompressing
>> each base-page entry into the matching folio offset, then flushing the
>> folio once. A missing entry after zswap data has been seen is reported as
>> -EAGAIN so the caller can drop the speculative large folio and retry
>> order-0.
>>
>> The large load keeps the zswap entries in place. It is a clean speculative
>> fill: until the swap slots are freed, zswap remains the backing copy if
>> reclaim drops the large folio before PTEs are installed.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> mm/zswap.c | 105 ++++++++++++++++++++++++++++++++++++++++++++---------
>> 1 file changed, 87 insertions(+), 18 deletions(-)
>>
>> diff --git a/mm/zswap.c b/mm/zswap.c
>> index da5297f7bd69..94ba112a2982 100644
>> --- a/mm/zswap.c
>> +++ b/mm/zswap.c
>> @@ -15,6 +15,8 @@
>>
>> #include <linux/module.h>
>> #include <linux/cpu.h>
>> +#include <linux/mm.h>
>> +#include <linux/huge_mm.h>
>> #include <linux/highmem.h>
>> #include <linux/slab.h>
>> #include <linux/spinlock.h>
>> @@ -934,7 +936,8 @@ static bool zswap_compress(struct page *page, struct zswap_entry *entry,
>> return comp_ret == 0 && alloc_ret == 0;
>> }
>>
>> -static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
>> +static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio,
>> + unsigned int page_idx, bool flush_dcache)
>> {
>> struct zswap_pool *pool = entry->pool;
>> struct scatterlist input[2]; /* zsmalloc returns an SG list 1-2 entries */
>> @@ -952,14 +955,15 @@ static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
>>
>> WARN_ON_ONCE(input->length != PAGE_SIZE);
>>
>> - dst = kmap_local_folio(folio, 0);
>> + dst = kmap_local_folio(folio, page_idx * PAGE_SIZE);
>> memcpy_from_sglist(dst, input, 0, PAGE_SIZE);
>> dlen = PAGE_SIZE;
>> kunmap_local(dst);
>> - flush_dcache_folio(folio);
>> + if (flush_dcache)
>> + flush_dcache_folio(folio);
>> } else {
>> sg_init_table(&output, 1);
>> - sg_set_folio(&output, folio, PAGE_SIZE, 0);
>> + sg_set_folio(&output, folio, PAGE_SIZE, page_idx * PAGE_SIZE);
>> acomp_request_set_params(acomp_ctx->req, input, &output,
>> entry->length, PAGE_SIZE);
>> ret = crypto_acomp_decompress(acomp_ctx->req);
>> @@ -1042,7 +1046,7 @@ static int zswap_writeback_entry(struct zswap_entry *entry,
>> goto out;
>> }
>>
>> - if (!zswap_decompress(entry, folio)) {
>> + if (!zswap_decompress(entry, folio, 0, true)) {
>> ret = -EIO;
>> goto out;
>> }
>> @@ -1615,10 +1619,9 @@ enum zswap_range_state zswap_probe_range(swp_entry_t swp,
>> * NOT marked up-to-date, so that an IO error is emitted (e.g. do_swap_page()
>> * will SIGBUS).
>> *
>> - * -EINVAL: if the swapped out content was in zswap, but the page belongs
>> - * to a large folio, which is not supported by zswap. The folio is unlocked,
>> - * but NOT marked up-to-date, so that an IO error is emitted (e.g.
>> - * do_swap_page() will SIGBUS).
>> + * -EAGAIN: if the swapped out content belongs to a large folio, but the
>> + * range is mixed or raced with writeback. The folio remains locked so the
>> + * caller can drop the large swapcache folio and retry order-0.
>> *
>> * -ENOENT: if the swapped out content was not in zswap. The folio remains
>> * locked on return.
>> @@ -1626,9 +1629,12 @@ enum zswap_range_state zswap_probe_range(swp_entry_t swp,
>> int zswap_load(struct folio *folio)
>> {
>> swp_entry_t swp = folio->swap;
>> + unsigned int nr_pages = folio_nr_pages(folio);
>> + unsigned int type = swp_type(swp);
>> pgoff_t offset = swp_offset(swp);
>> - struct xarray *tree = swap_zswap_tree(swp);
>> + struct xarray *tree;
>> struct zswap_entry *entry;
>> + unsigned int i;
>>
>> VM_WARN_ON_ONCE(!folio_test_locked(folio));
>> VM_WARN_ON_ONCE(!folio_test_swapcache(folio));
>> @@ -1636,21 +1642,84 @@ int zswap_load(struct folio *folio)
>> if (zswap_never_enabled())
>> return -ENOENT;
>>
>> - /*
>> - * Large folios should not be swapped in while zswap is being used, as
>> - * they are not properly handled. Zswap does not properly load large
>> - * folios, and a large folio may only be partially in zswap.
>> - */
>> - if (WARN_ON_ONCE(folio_test_large(folio))) {
>> + if (folio_test_large(folio)) {
>> + struct obj_cgroup *first_objcg = NULL;
>> + bool same_objcg = true;
>> + bool saw_zswap = false;
>> + bool saw_non_zswap = false;
>> +
>> + /*
>> + * The locked large swapcache folio now covers the range and
>> + * conflicts with zswap writeback's order-0 swapcache allocation.
>> + * If the range is mixed or an entry disappears, retry order-0.
>> + */
>> + for (i = 0; i < nr_pages; i++) {
>> + tree = swap_zswap_tree(swp_entry(type, offset + i));
>> + entry = xa_load(tree, offset + i);
>> + if (!entry) {
>> + if (saw_zswap)
>> + return -EAGAIN;
>> + saw_non_zswap = true;
>> + continue;
>> + }
>
> Can we use xas_load API here instead of traversing down the tree again
> and again?
I'll rework it to use xas_load(), while handling zswap tree boundaries correctly.
>
>> + if (saw_non_zswap)
>> + return -EAGAIN;
>> +
>> + if (!saw_zswap)
>> + first_objcg = entry->objcg;
>> + else if (entry->objcg != first_objcg)
>> + same_objcg = false;
>
> Can we get different objcg at this point?
The objcg pointers can be different in principle, for example if
the range is assembled from entries that came from different per-node objcgs
of the same memcg.
But for this accounting path, count_objcg_events() ultimately charges the
event to obj_cgroup_memcg(entry->objcg). Since the large swapcache allocation
has already checked compatible swap ownership for the range, the final memcg
accounting target should be the same even if the objcg pointers differ.
I will simplify this in v3 and avoid the extra objcg equality pass.
>
>> + saw_zswap = true;
>> + }
>> + if (!saw_zswap)
>> + return -ENOENT;
>> +
>> + for (i = 0; i < nr_pages; i++) {
>> + tree = swap_zswap_tree(swp_entry(type, offset + i));
>> + entry = xa_load(tree, offset + i);
>> + if (!entry)
>> + return -EAGAIN;
>> +
>> + if (!zswap_decompress(entry, folio, i, false)) {
>> + folio_unlock(folio);
>> + return -EIO;
>> + }
>> + }
>> +
>> + flush_dcache_folio(folio);
>> + /*
>> + * Keep zswap entries until swap slots are freed. This is a clean
>> + * speculative fill; zswap remains the backing copy if reclaim
>> + * drops the large folio before PTEs are installed.
>> + */
>> + folio_mark_uptodate(folio);
>> + count_vm_events(ZSWPIN, nr_pages);
>> + count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN);
>> +
>> + if (same_objcg) {
>> + if (first_objcg)
>> + count_objcg_events(first_objcg, ZSWPIN, nr_pages);
>> + } else {
>> + for (i = 0; i < nr_pages; i++) {
>> + tree = swap_zswap_tree(swp_entry(type, offset + i));
>> + entry = xa_load(tree, offset + i);
>> + if (WARN_ON_ONCE(!entry))
>> + continue;
>> + if (entry->objcg)
>> + count_objcg_events(entry->objcg, ZSWPIN, 1);
>
> xas_load() here too?
Yes, same issue here.
>
>
>> + }
>> + }
>> +
>> folio_unlock(folio);
>> - return -EINVAL;
>> + return 0;
>> }
>
>>
>> + tree = swap_zswap_tree(swp);
>> entry = xa_load(tree, offset);
>> if (!entry)
>> return -ENOENT;
>>
>> - if (!zswap_decompress(entry, folio)) {
>> + if (!zswap_decompress(entry, folio, 0, true)) {
>> folio_unlock(folio);
>> return -EIO;
>> }
>
> I wonder how much of these two paths (order 0 and larger order) can be
> unified...
I think more of this can be unified than this version does.
I split the paths this way because I treated the large-folio load as a
speculative fill and kept the zswap entries as the backing copy. But with
your point that an installed large swapcache folio should block zswap
writeback from turning the range mixed, I should revisit that completion rule
instead of baking it into a separate path.
For the v3 version I will try to collapse the common load path. If the large-folio
case still needs different entry lifetime rules, I will make that distinction
explicit.
>
>> --
>> 2.34.1
>>
^ permalink raw reply
* Re: [PATCH 5/5] cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()
From: Bert Karwatzki @ 2026-05-31 18:45 UTC (permalink / raw)
To: Mark Brown, Tejun Heo
Cc: Johannes Weiner, spasswolf, Michal Koutný,
Sebastian Andrzej Siewior, Petr Malat, kernel test robot,
Martin Pitt, cgroups, linux-kernel, Aishwarya.TCV
In-Reply-To: <8b15e2465901b48ee63f4827c69a67ff6d0e6098.camel@web.de>
Am Sonntag, dem 31.05.2026 um 11:19 +0200 schrieb Bert Karwatzki:
> Am Freitag, dem 29.05.2026 um 22:08 +0100 schrieb Mark Brown:
> > On Fri, May 29, 2026 at 07:25:29AM -1000, Tejun Heo wrote:
> > > On Wed, May 27, 2026 at 11:45:54AM +0100, Mark Brown wrote:
> > > > On Mon, May 04, 2026 at 02:51:21PM -1000, Tejun Heo wrote:
> >
> > > > with no further output and given that this is a cgroup locking change
> > > > this does seem like a plausible commmit, though I didn't look into it in
> > > > detail. Bisect log and the list of LTP tests we're running in our test
> > > > job below. We are running multuple tests in parallel.
> >
> > > Unfortunately, I can't reproduce this in my environment. Any chance you can
> > > try testing on x86 tooa nd see whether it produces there?
> >
> > Not readily sadly, I'll see if I can figure something out. Our rootfs
> > images are based on Debian Trixie if that's relevant?
>
> Using debian unstable (sid/forky) I can at least detect a timeout when running
> the ltp controller testsuite:
>
> # LTPROOT=/home/bert/ltp-install/ ./kirk --run-suite controllers
> Host information
> Hostname: homer
> Python: 3.13.12 (main, Feb 4 2026, 15:06:39) [GCC 15.2.0]
> Directory: /tmp/kirk.root/tmp092in2yb
>
> Connecting to SUT: default
>
> Suite: controllers
> ──────────────────
> cgroup_core01: pass (0.024s)
> cgroup_core02: pass (0.004s)
> cgroup_core03: pass (0.017s)
> cgroup: skip (2m 41s)
> memcg_regression: skip (3.414s)
> memcg_test_3: pass (0.090s)
> memcg_failcnt: skip (0.019s)
> memcg_force_empty: skip (0.015s)
> memcg_limit_in_bytes: skip (0.017s)
> memcg_stat_rss: skip (0.015s)
> memcg_subgroup_charge: skip (0.015s)
> memcg_max_usage_in_bytes: skip (0.014s)
> memcg_move_charge_at_immigrate: skip (0.014s)
> memcg_memsw_limit_in_bytes: skip (0.015s)
> memcg_stat: skip (0.015s)
> memcg_use_hierarchy: skip (0.015s)
> memcg_usage_in_bytes: skip (0.014s)
> memcg_stress: pass (30m 4s)
> memcg_control: pass (6.058s)
> memcontrol01: pass (0.004s)
> memcontrol02: pass (0.636s)
> memcontrol03: pass (15.983s)
> memcontrol04: pass (0.890s)
> cgroup_fj_function_debug: skip (0.013s)
> cgroup_fj_function_cpuset: skip (0.044s)
> cgroup_fj_function_cpu: skip (0.050s)
> cgroup_fj_function_cpuacct: pass (0.052s)
> cgroup_fj_function_memory: skip (0.042s)
> cgroup_fj_function_freezer: pass (0.044s)
> cgroup_fj_function_devices: pass (0.066s)
> cgroup_fj_function_blkio: skip (0.009s)
> cgroup_fj_function_net_cls: pass (0.073s)
> cgroup_fj_function_perf_event: pass (0.072s)
>
>
> Execution time: 1h 33m 13s
>
> Disconnecting from SUT: default
>
> Target information
> ──────────────────
> Kernel: Linux 7.1.0-rc5-next-20260528-master-dirty #480 SMP PREEMPT_RT Thu May 28 19:55:12 CEST 2026
> Cmdline: BOOT_IMAGE=/boot/vmlinuz-7.1.0-rc5-next-20260528-master-dirty
> root=UUID=3d5cdc5d-1902-40bf-9e16-ca819372d350
> ro
> quiet
> Machine: unknown
> Arch: x86_64
> RAM: 63439380 kB
> Swap: 78125052 kB
> Distro: debian
>
> ────────────────────────
> TEST SUMMARY
> ────────────────────────
> Suite: controllers
> Runtime: 33m 13s
> Runs: 347
>
> Results:
> Passed: 181
> Failed: 0
> Broken: 0
> Skipped: 350
> Warnings: 0
>
> Session stopped
>
> In dmesg I get messages about task tst_cgtl hanging:
>
> [ 2212.794669] [ T346] INFO: task tst_cgctl:317896 blocked for more than 122 seconds.
> [ 2212.794674] [ T346] Not tainted 7.1.0-rc5-next-20260528-master-dirty #480
> [ 2212.794675] [ T346] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
>
> [...]
>
> [ 3318.721344] [ T346] INFO: task tst_cgctl:317896 blocked for more than 1228 seconds.
> [ 3318.721349] [ T346] Not tainted 7.1.0-rc5-next-20260528-master-dirty #480
> [ 3318.721351] [ T346] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
>
>
>
>
>
>
> On 6.19.14 the Results of this testrun is:
>
> # LTPROOT=/home/bert/ltp-install/ ./kirk --run-suite controllers
>
> [...]
>
> Target information
> ──────────────────
> Kernel: Linux 6.19.14-stable #1238 SMP PREEMPT_RT Sat May 30 17:28:29 CEST 2026
> Cmdline: BOOT_IMAGE=/boot/vmlinuz-6.19.14-stable
> root=UUID=3d5cdc5d-1902-40bf-9e16-ca819372d350
> ro
> quiet
> Machine: unknown
> Arch: x86_64
> RAM: 63436188 kB
> Swap: 78125052 kB
> Distro: debian
>
> ────────────────────────
> TEST SUMMARY
> ────────────────────────
> Suite: controllers
> Runtime: 36m 12s
> Runs: 347
>
> Results:
> Passed: 1742
> Failed: 0
> Broken: 0
> Skipped: 97
> Warnings: 0
>
> Session stopped
>
> With 6.19.14 I also get no hung tasks.
>
> On 7.0.10 the tests also work:
>
> root@homer:/mnt/data/linux-forest/kirk# LTPROOT=/home/bert/ltp-install/ ./kirk --run-suite controllers
> Host information
> Hostname: homer
> Python: 3.13.12 (main, Feb 4 2026, 15:06:39) [GCC 15.2.0]
> Directory: /tmp/kirk.root/tmpq32b09g7
>
> Connecting to SUT: default
>
> Suite: controllers
> ──────────────────
> cgroup_core01: pass (0.016s)
>
> [...]
>
> pids_9_100: pass (0.107s)
>
> Execution time: 36m 15s
>
> Disconnecting from SUT: default
>
> Target information
> ──────────────────
> Kernel: Linux 7.0.10-stable #1239 SMP PREEMPT_RT Sun May 31 00:42:41 CEST 2026
> Cmdline: BOOT_IMAGE=/boot/vmlinuz-7.0.10-stable
> root=UUID=3d5cdc5d-1902-40bf-9e16-ca819372d350
> ro
> quiet
> Machine: unknown
> Arch: x86_64
> RAM: 63435940 kB
> Swap: 78125052 kB
> Distro: debian
>
> ────────────────────────
> TEST SUMMARY
> ────────────────────────
> Suite: controllers
> Runtime: 36m 13s
> Runs: 347
>
> Results:
> Passed: 1742
> Failed: 0
> Broken: 0
> Skipped: 97
> Warnings: 0
>
> Session stopped
>
>
>
> I'm not sure if this is related to the problems on arm64, but I'll try bisecting this.
>
> Bert Karwatzki
I finished my bisectiOn (from v7.0.0 to next-20260528) and it shows
commit 1dffd95575eb ("cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()")
as first bad commit, too. During the bisection I had to apply this patch (when it's cleanly applicable)
diff --git a/fs/filesystems.c b/fs/filesystems.c
index 771fc31a69b8..712316a1e3e0 100644
--- a/fs/filesystems.c
+++ b/fs/filesystems.c
@@ -269,7 +269,7 @@ static __cold noinline int regen_filesystems_string(void)
hlist_for_each_entry_rcu(p, &file_systems, list) {
if (!(p->fs_flags & FS_REQUIRES_DEV))
newlen += strlen("nodev");
- newlen += strlen("\t") + strlen(p->name) + strlen("\n");
+ newlen += strlen("\t") + strlen(p->name) + strlen("\n");
}
spin_unlock(&file_systems_lock);
@@ -289,6 +289,7 @@ static __cold noinline int regen_filesystems_string(void)
* Did someone beat us to it?
*/
if (old && old->gen == file_systems_gen) {
+ spin_unlock(&file_systems_lock);
kfree(new);
return 0;
}
@@ -297,6 +298,7 @@ static __cold noinline int regen_filesystems_string(void)
* Did the list change in the meantime?
*/
if (gen != file_systems_gen) {
+ spin_unlock(&file_systems_lock);
kfree(new);
goto retry;
}
@@ -321,13 +323,12 @@ static __cold noinline int regen_filesystems_string(void)
* generation above and messes it up.
*/
spin_unlock(&file_systems_lock);
- if (old)
- kfree_rcu(old, rcu);
+ kfree(new);
return -EINVAL;
}
/*
- * Paired with consume fence in READ_ONCE() in filesystems_proc_show()
+ * Paired with consume fence in rcu_dereference() in filesystems_proc_show()
*/
smp_store_release(&file_systems_string, new);
spin_unlock(&file_systems_lock);
to take care of a locking issue in commit
36b3306779ea ("fs: cache the string generated by reading /proc/filesystems")
https://lore.kernel.org/all/20260520225245.2962-1-spasswolf@web.de/
The test that hang when running
# LTPROOT=/home/bert/ltp-install/ ./kirk --run-suite controllers
is always cgroup_fj_function_net_prio.
Also when bisecting this I disabled (i.e. commented out) the
memcg_stress test in ~/ltp-install/runtest/controllers as it takes a lot of
time (30min) and succeeds even in the version where hangs occur.
Bert Karwatzki
^ permalink raw reply related
* Re: [PATCH v6] cgroup/dmem: implement dmem.high soft limit via prioritized eviction
From: Tejun Heo @ 2026-05-31 17:06 UTC (permalink / raw)
To: Qiliang Yuan, Christian Koenig, Huang Rui, Matthew Auld,
Matthew Brost, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Johannes Weiner,
Michal Koutný, Natalie Vock
Cc: Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
dri-devel, linux-kernel, cgroups, linux-mm
In-Reply-To: <20260531-feature-dmem-high-v6-1-20563ecd6dc7@gmail.com>
Hello,
I don't think we want to define dmem.high (or dmem.max) in terms of a
specific reclaim mechanic. These interface files should express a
generic resource-distribution concept that stays valid regardless of
how the underlying reclaim works. As written, dmem.high comes down to
"evicted first in the high-priority eviction pass". It isn't consulted
on charge and dmem has no proactive reclaim, so the file does nothing
until a dmem.max hit elsewhere triggers eviction. That's an
implementation detail, not something I'd want to commit to in the
cgroup interface.
It also reads as a way to work around dmem's reclaim behavior rather
than a soft limit in its own right. A dmem.max hit doesn't just fail
today: the charge returns -EAGAIN and TTM already falls back to evicting
buffers and retrying before the allocation fails. So the question isn't
"max fails immediately, add reclaim via high" but which buffers reclaim
should target and when, which is a property of the max reclaim behavior.
If we work around that with a high knob whose meaning is the current
eviction order, we bake an implementation detail into the ABI and make
it harder to give dmem.high a proper soft-limit semantics later.
I'm not against a dmem soft limit. I'd rather improve the max reclaim
behavior so it makes sense in general, and then define high as a concept
on top of that, rather than the other way around.
The whole max-vs-high distinction and what a soft limit should mean has
had a lot of thought put into it on the memcg side, so adding the memcg
folks for their input.
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH v1] docs: cgroup: Fix stale source file paths
From: Tejun Heo @ 2026-05-31 16:33 UTC (permalink / raw)
To: Costa Shulyupin
Cc: Johannes Weiner, Michal Koutný, Jonathan Corbet, Shuah Khan,
Randy Dunlap, cgroups, linux-doc, linux-kernel
In-Reply-To: <20260531140045.4114289-1-costa.shul@redhat.com>
Hello,
Applied to cgroup/for-7.2.
Thanks.
--
tejun
^ permalink raw reply
* [PATCH v1] docs: cgroup: Fix stale source file paths
From: Costa Shulyupin @ 2026-05-31 14:00 UTC (permalink / raw)
To: Tejun Heo, Johannes Weiner, Michal Koutný, Jonathan Corbet,
Shuah Khan, Randy Dunlap, cgroups, linux-doc, linux-kernel
Cc: Costa Shulyupin
Update two references to files that were moved:
- kernel/cgroup.c -> kernel/cgroup/cgroup.c
- tools/cgroup/cgroup_event_listener.c ->
samples/cgroup/cgroup_event_listener.c
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Costa Shulyupin <costa.shul@redhat.com>
---
Documentation/admin-guide/cgroup-v1/cgroups.rst | 2 +-
Documentation/admin-guide/cgroup-v1/memcg_test.rst | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/cgroup-v1/cgroups.rst b/Documentation/admin-guide/cgroup-v1/cgroups.rst
index 463f98453323..e501f45ea93f 100644
--- a/Documentation/admin-guide/cgroup-v1/cgroups.rst
+++ b/Documentation/admin-guide/cgroup-v1/cgroups.rst
@@ -525,7 +525,7 @@ cgroup. It may also be taken to prevent cgroups from being
modified, but more specific locks may be more appropriate in that
situation.
-See kernel/cgroup.c for more details.
+See kernel/cgroup/cgroup.c for more details.
Subsystems can take/release the cgroup_mutex via the functions
cgroup_lock()/cgroup_unlock().
diff --git a/Documentation/admin-guide/cgroup-v1/memcg_test.rst b/Documentation/admin-guide/cgroup-v1/memcg_test.rst
index 7c7cd457cf69..ebedbc3c3f9c 100644
--- a/Documentation/admin-guide/cgroup-v1/memcg_test.rst
+++ b/Documentation/admin-guide/cgroup-v1/memcg_test.rst
@@ -321,7 +321,7 @@ Under below explanation, we assume CONFIG_SWAP=y.
----------------------
Memory controller implements memory thresholds using cgroups notification
- API. You can use tools/cgroup/cgroup_event_listener.c to test it.
+ API. You can use samples/cgroup/cgroup_event_listener.c to test it.
(Shell-A) Create cgroup and run event listener::
--
2.53.0
^ permalink raw reply related
* Re: [RFC PATCH v2 1/9] mm/zswap: expose range state for swapin policy
From: Fujunjie @ 2026-05-31 13:47 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=NUQb5b4T49dbRV0_41QYRRuLkQNUg+FVDpJiobCCCh7g@mail.gmail.com>
On 5/30/2026 2:35 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> Large folio swapin needs to know whether a candidate swap range is fully
>> backed by zswap before it can choose an order. That decision should stay
>> in common swapin code, not inside zswap.
>>
>> Export two zswap facts for that caller: a lockless range occupancy snapshot
>> and the current zswap reclaim-pressure state. The range state is
>> advisory only. Writeback or invalidation can change the backend after the
>> snapshot, so users must recheck before issuing large-folio IO.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> include/linux/zswap.h | 26 +++++++++++++++++++++++++
>> mm/zswap.c | 44 +++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 70 insertions(+)
>>
>> diff --git a/include/linux/zswap.h b/include/linux/zswap.h
>> index 30c193a1207e..8f9aee97517c 100644
>> --- a/include/linux/zswap.h
>> +++ b/include/linux/zswap.h
>> @@ -9,6 +9,18 @@ struct lruvec;
>>
>> extern atomic_long_t zswap_stored_pages;
>>
>> +/*
>> + * Advisory zswap occupancy snapshot for a swap range. This is not a complete
>> + * backend classifier; callers must recheck before depending on ALL_ZSWAP for
>> + * large-folio IO.
>> + */
>> +enum zswap_range_state {
>> + ZSWAP_RANGE_NEVER_ENABLED,
>> + ZSWAP_RANGE_NO_ZSWAP,
>> + ZSWAP_RANGE_ALL_ZSWAP,
>> + ZSWAP_RANGE_MIXED,
>> +};
>> +
>> #ifdef CONFIG_ZSWAP
>>
>> struct zswap_lruvec_state {
>> @@ -27,6 +39,9 @@ struct zswap_lruvec_state {
>> unsigned long zswap_total_pages(void);
>> bool zswap_store(struct folio *folio);
>> int zswap_load(struct folio *folio);
>> +enum zswap_range_state zswap_probe_range(swp_entry_t swp,
>> + unsigned int nr_pages);
>> +bool zswap_pool_reclaim_pressure(void);
>> void zswap_invalidate(swp_entry_t swp);
>> int zswap_swapon(int type, unsigned long nr_pages);
>> void zswap_swapoff(int type);
>> @@ -49,6 +64,17 @@ static inline int zswap_load(struct folio *folio)
>> return -ENOENT;
>> }
>>
>> +static inline enum zswap_range_state zswap_probe_range(swp_entry_t swp,
>> + unsigned int nr_pages)
>> +{
>> + return ZSWAP_RANGE_NEVER_ENABLED;
>> +}
>> +
>> +static inline bool zswap_pool_reclaim_pressure(void)
>> +{
>> + return false;
>> +}
>> +
>> static inline void zswap_invalidate(swp_entry_t swp) {}
>> static inline int zswap_swapon(int type, unsigned long nr_pages)
>> {
>> diff --git a/mm/zswap.c b/mm/zswap.c
>> index 761cd699e0a3..da5297f7bd69 100644
>> --- a/mm/zswap.c
>> +++ b/mm/zswap.c
>> @@ -506,6 +506,19 @@ unsigned long zswap_total_pages(void)
>> return total;
>> }
>>
>> +/*
>> + * Expose whether zswap reclaim pressure is active. This is a backend fact:
>> + * zswap_check_limits() sets the state once the pool reaches the hard limit and
>> + * keeps it set until the pool falls below the accept threshold.
>> + */
>> +bool zswap_pool_reclaim_pressure(void)
>> +{
>> + if (zswap_never_enabled())
>> + return false;
>> +
>> + return READ_ONCE(zswap_pool_reached_full);
>> +}
>> +
>> static bool zswap_check_limits(void)
>> {
>> unsigned long cur_pages = zswap_total_pages();
>> @@ -1559,6 +1572,37 @@ bool zswap_store(struct folio *folio)
>> return ret;
>> }
>>
>> +enum zswap_range_state zswap_probe_range(swp_entry_t swp,
>> + unsigned int nr_pages)
>> +{
>> + unsigned int type = swp_type(swp);
>> + pgoff_t offset = swp_offset(swp);
>> + bool present = false, missing = false;
>> + unsigned int i;
>> +
>> + /*
>> + * This is an advisory, lockless snapshot for common swapin admission.
>> + * Callers must recheck before depending on an all-zswap range for IO:
>> + * concurrent writeback or invalidation can change the backend state.
>> + */
>> + if (zswap_never_enabled())
>> + return ZSWAP_RANGE_NEVER_ENABLED;
>> +
>> + for (i = 0; i < nr_pages; i++) {
>> + struct xarray *tree = swap_zswap_tree(swp_entry(type, offset + i));
>> +
>> + if (xa_load(tree, offset + i))
>> + present = true;
>> + else
>> + missing = true;
>> +
>> + if (present && missing)
>> + return ZSWAP_RANGE_MIXED;
>> + }
>
> Can we use xas_load() to make this check more efficient? IIUC,
> xa_load() walks the tree every time.
>
> (We used to use a bitmap here back in frontswap days. Good times....)
Thanks for your review.
I'll switch this to xas_load() in the v3 version.
^ permalink raw reply
* Re: [RFC PATCH v2 9/9] docs: mm: update THP swapin counter descriptions
From: Fujunjie @ 2026-05-31 13:21 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=NDfJud3FM4Y+Ek3RtTtwi2aXWeDCujNxh2ReUEq-m4oA@mail.gmail.com>
On 5/30/2026 2:37 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> The THP swapin counter descriptions still describe large swapin as
>> coming only from non-zswap swap devices. Update them now that
>> zswap-backed large folio swapin can also increment swpin.
>>
>> Also describe policy and backend rejection as swpin_fallback cases,
>> since speculative zswap large swapin can intentionally fall back before
>> doing large IO.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> Documentation/admin-guide/mm/transhuge.rst | 11 ++++++-----
>> 1 file changed, 6 insertions(+), 5 deletions(-)
>>
>> diff --git a/Documentation/admin-guide/mm/transhuge.rst b/Documentation/admin-guide/mm/transhuge.rst
>> index 23f8d13c2629..59b7a0d09243 100644
>> --- a/Documentation/admin-guide/mm/transhuge.rst
>> +++ b/Documentation/admin-guide/mm/transhuge.rst
>> @@ -667,13 +667,14 @@ zswpout
>> piece without splitting.
>>
>> swpin
>> - is incremented every time a huge page is swapped in from a non-zswap
>> - swap device in one piece.
>> + is incremented every time a huge page is swapped in from swap or
>> + zswap in one piece.
>>
>> swpin_fallback
>> - is incremented if swapin fails to allocate or charge a huge page
>> - and instead falls back to using huge pages with lower orders or
>> - small pages.
>> + is incremented if swapin cannot use a huge page and instead falls
>> + back to using huge pages with lower orders or small pages. This can
>> + happen because allocation or charging fails, or because policy or
>> + backend state rejects a speculative large swapin.
>
> I think we should add separate zswpin and zswpin fallback counter for
> THP rather than overloading swpin. We already do that for zswpout vs
> swpout.
that makes sense.
^ permalink raw reply
* Re: [RFC PATCH v2 4/9] mm: admit large swapin by backend range in swapin_sync()
From: Fujunjie @ 2026-05-31 13:15 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=PvcM1u1n8TTikCAaqJN=GtgfwvnXtU2wCf=Qjp6E_Zew@mail.gmail.com>
On 5/30/2026 2:34 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> A large swapin can only read one folio when the whole range has compatible
>> backing. Mixed zswap/disk ranges must not reach large-folio IO, and zswap
>> range probes are only snapshots.
>>
>> Filter the orders passed to swap_cache_alloc_folio() in swapin_sync().
>> Uniform zeromap ranges and all-disk ranges keep the existing large swapin
>> path. Fully zswap-backed ranges may be tried. Mixed zswap/disk ranges fall
>> back before allocation.
>>
>> After a large swapcache folio is installed, recheck the zswap range and
>> drop the fresh folio if it became mixed. Also consume -EAGAIN from
>> swap_read_folio() the same way. Both cases retry order-0, where each slot
>> can resolve its current backend independently.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> mm/memcontrol-v1.c | 8 ++-
>> mm/memory.c | 31 ++++++++-
>> mm/swap_state.c | 169 ++++++++++++++++++++++++++++++++++++++++++---
>> 3 files changed, 194 insertions(+), 14 deletions(-)
>>
>> diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
>> index 765069211567..5b11b8055c66 100644
>> --- a/mm/memcontrol-v1.c
>> +++ b/mm/memcontrol-v1.c
>> @@ -682,8 +682,8 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
>> * memcg1_swapin - uncharge swap slot on swapin
>> * @folio: folio being swapped in
>> *
>> - * Call this function after successfully adding the charged
>> - * folio to swapcache.
>> + * Call this after the charged folio has been added to swapcache and the caller
>> + * is no longer going to drop it back to swapped-out state.
>> *
>> * Context: The folio has to be in swap cache and locked.
>> */
>> @@ -721,7 +721,9 @@ void memcg1_swapin(struct folio *folio)
>> id = __swap_cgroup_clear(ci, swp_cluster_offset(folio->swap),
>> nr_pages);
>> swap_cluster_unlock(ci);
>> - mem_cgroup_uncharge_swap(id, nr_pages);
>> +
>> + if (id)
>> + mem_cgroup_uncharge_swap(id, nr_pages);
>> }
>> #endif
>>
>> diff --git a/mm/memory.c b/mm/memory.c
>> index 5a365492a9a2..d73a19692dea 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -4538,6 +4538,24 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,
>> folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio));
>> }
>>
>> +static void memcg1_swapin_retry_folio(struct folio *folio,
>> + struct vm_fault *vmf)
>> +{
>> + if (!folio_test_large(folio) || !folio_test_swapcache(folio))
>> + return;
>> +
>> + if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT) {
>> + if (!folio_trylock(folio))
>> + return;
>> + } else {
>> + folio_lock(folio);
>> + }
>> +
>> + if (folio_test_large(folio) && folio_test_swapcache(folio))
>> + memcg1_swapin(folio);
>> + folio_unlock(folio);
>> +}
>> +
>> static vm_fault_t pte_marker_clear(struct vm_fault *vmf)
>> {
>> vmf->pte = pte_offset_map_lock(vmf->vma->vm_mm, vmf->pmd,
>> @@ -4857,8 +4875,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>>
>> swapcache = folio;
>> ret |= folio_lock_or_retry(folio, vmf);
>> - if (ret & VM_FAULT_RETRY)
>> + if (ret & VM_FAULT_RETRY) {
>> + memcg1_swapin_retry_folio(folio, vmf);
>> goto out_release;
>> + }
>>
>> page = folio_file_page(folio, swp_offset(entry));
>> /*
>> @@ -5067,6 +5087,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> if (unlikely(folio != swapcache)) {
>> folio_add_new_anon_rmap(folio, vma, address, RMAP_EXCLUSIVE);
>> folio_add_lru_vma(folio, vma);
>> + if (folio_test_large(swapcache))
>> + memcg1_swapin(swapcache);
>> folio_put_swap(swapcache, NULL);
>> } else if (!folio_test_anon(folio)) {
>> /*
>> @@ -5076,6 +5098,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> VM_WARN_ON_ONCE_FOLIO(folio_nr_pages(folio) != nr_pages, folio);
>> VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>> folio_add_new_anon_rmap(folio, vma, address, rmap_flags);
>> + if (folio_test_large(folio))
>> + memcg1_swapin(folio);
>> folio_put_swap(folio, NULL);
>> } else {
>> VM_WARN_ON_ONCE(nr_pages != 1 && nr_pages != folio_nr_pages(folio));
>> @@ -5132,8 +5156,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> if (vmf->pte)
>> pte_unmap_unlock(vmf->pte, vmf->ptl);
>> out_page:
>> - if (folio_test_swapcache(folio))
>> + if (folio_test_swapcache(folio)) {
>> + if (folio_test_large(folio))
>> + memcg1_swapin(folio);
>> folio_free_swap(folio);
>> + }
>> folio_unlock(folio);
>> out_release:
>> folio_put(folio);
>> diff --git a/mm/swap_state.c b/mm/swap_state.c
>> index d37097913b30..f03ad4832f16 100644
>> --- a/mm/swap_state.c
>> +++ b/mm/swap_state.c
>> @@ -21,6 +21,7 @@
>> #include <linux/migrate.h>
>> #include <linux/vmalloc.h>
>> #include <linux/huge_mm.h>
>> +#include <linux/zswap.h>
>> #include <linux/shmem_fs.h>
>> #include "internal.h"
>> #include "swap_table.h"
>> @@ -403,7 +404,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
>> static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> swp_entry_t targ_entry, gfp_t gfp,
>> unsigned int order, struct vm_fault *vmf,
>> - struct mempolicy *mpol, pgoff_t ilx)
>> + struct mempolicy *mpol, pgoff_t ilx,
>> + bool defer_memcg1_swapin)
>> {
>> int err;
>> swp_entry_t entry;
>> @@ -466,7 +468,8 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> }
>>
>> /* memsw uncharges swap when folio is added to swap cache */
>> - memcg1_swapin(folio);
>> + if (!defer_memcg1_swapin || !order)
>> + memcg1_swapin(folio);
>> if (shadow)
>> workingset_refault(folio, shadow);
>>
>> @@ -495,9 +498,12 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> * Return: Returns the folio if allocation succeeded and folio is in the swap
>> * cache. Returns error code if failed due to race, OOM or invalid arguments.
>> */
>> -struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> - unsigned long orders, struct vm_fault *vmf,
>> - struct mempolicy *mpol, pgoff_t ilx)
>> +static struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry,
>> + gfp_t gfp, unsigned long orders,
>> + struct vm_fault *vmf,
>> + struct mempolicy *mpol,
>> + pgoff_t ilx,
>> + bool defer_memcg1_swapin)
>> {
>> int order, err;
>> struct folio *ret;
>> @@ -512,7 +518,8 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>>
>> do {
>> ret = __swap_cache_alloc(ci, targ_entry, gfp, order,
>> - vmf, mpol, ilx);
>> + vmf, mpol, ilx,
>> + defer_memcg1_swapin);
>> if (!IS_ERR(ret))
>> break;
>> err = PTR_ERR(ret);
>> @@ -525,6 +532,124 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> return ret;
>> }
>>
>> +struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> + unsigned long orders, struct vm_fault *vmf,
>> + struct mempolicy *mpol, pgoff_t ilx)
>> +{
>> + return __swap_cache_alloc_folio(targ_entry, gfp, orders, vmf,
>> + mpol, ilx, false);
>> +}
>> +
>> +static struct folio *swap_cache_alloc_speculative_folio(swp_entry_t targ_entry,
>> + gfp_t gfp,
>> + unsigned long orders,
>> + struct vm_fault *vmf,
>> + struct mempolicy *mpol,
>> + pgoff_t ilx)
>> +{
>> + /*
>> + * Speculative large swapin may drop this fresh swapcache folio and
>> + * retry order-0 after backend or page-table revalidation. Keep the
>> + * cgroup v1 memsw swap owner until the caller commits the folio.
>> + */
>> + return __swap_cache_alloc_folio(targ_entry, gfp, orders, vmf,
>> + mpol, ilx, true);
>> +}
>> +
>> +static bool swapin_zeromap_same(swp_entry_t entry, unsigned int nr_pages)
>> +{
>> + unsigned int ci_start = swp_cluster_offset(entry);
>> + struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
>> + bool is_zero;
>> + unsigned int i;
>> +
>> + if (ci_start + nr_pages > SWAPFILE_CLUSTER) {
>> + VM_WARN_ON_ONCE(1);
>> + return false;
>> + }
>> +
>> + rcu_read_lock();
>> + if (!rcu_dereference(ci->table)) {
>> + rcu_read_unlock();
>> + return true;
>> + }
>> +
>> + is_zero = __swap_table_test_zero(ci, ci_start);
>> + for (i = 1; i < nr_pages; i++) {
>> + if (is_zero != __swap_table_test_zero(ci, ci_start + i)) {
>> + rcu_read_unlock();
>> + return false;
>> + }
>> + }
>> + rcu_read_unlock();
>> +
>> + return true;
>> +}
>> +
>> +static unsigned long swapin_admit_orders(swp_entry_t entry,
>> + unsigned long orders)
>> +{
>> + unsigned long candidates = orders & ~BIT(0);
>> + unsigned long admitted = orders & BIT(0);
>> + int order;
>> +
>> + if (!candidates)
>> + return orders;
>> +
>> + while (candidates) {
>> + enum zswap_range_state state;
>> + unsigned int nr_pages;
>> + swp_entry_t range_entry;
>> + bool admit = false;
>> +
>> + order = fls_long(candidates) - 1;
>> + if (order > MAX_PAGE_ORDER) {
>> + candidates &= ~BIT(order);
>> + continue;
>> + }
>> +
>> + nr_pages = 1U << order;
>> + range_entry = swp_entry(swp_type(entry),
>> + round_down(swp_offset(entry), nr_pages));
>> + if (!swapin_zeromap_same(range_entry, nr_pages))
>> + goto next;
>> +
>> + state = zswap_probe_range(range_entry, nr_pages);
>> + switch (state) {
>> + case ZSWAP_RANGE_MIXED:
>> + break;
>> + case ZSWAP_RANGE_ALL_ZSWAP:
>> + case ZSWAP_RANGE_NEVER_ENABLED:
>> + case ZSWAP_RANGE_NO_ZSWAP:
>> + admit = true;
>> + break;
>> + }
>> +
>> +next:
>> + if (admit)
>> + admitted |= BIT(order);
>> + else
>> + count_mthp_stat(order, MTHP_STAT_SWPIN_FALLBACK);
>> + candidates &= ~BIT(order);
>> + }
>> +
>> + return admitted ? admitted : BIT(0);
>> +}
>> +
>> +static bool zswap_needs_order0_retry(struct folio *folio)
>> +{
>> + if (!folio_test_large(folio))
>> + return false;
>> +
>> + /*
>> + * Admission sees only an advisory zswap snapshot. Recheck after the
>> + * large swapcache folio is installed; if the range became mixed, drop
>> + * the fresh folio before IO and let order-0 handle each slot.
>> + */
>> + return zswap_probe_range(folio->swap, folio_nr_pages(folio)) ==
>> + ZSWAP_RANGE_MIXED;
>> +}
>> +
>> /*
>> * If we are the only user, then try to free up the swap cache.
>> *
>> @@ -634,7 +759,8 @@ static struct folio *swap_cache_read_folio(swp_entry_t entry, gfp_t gfp,
>> folio = swap_cache_get_folio(entry);
>> if (folio)
>> return folio;
>> - folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL, mpol, ilx);
>> + folio = swap_cache_alloc_folio(entry, gfp, BIT(0), NULL,
>> + mpol, ilx);
>> } while (PTR_ERR(folio) == -EEXIST);
>>
>> if (IS_ERR_OR_NULL(folio))
>> @@ -677,18 +803,43 @@ struct folio *swapin_sync(swp_entry_t entry, gfp_t gfp, unsigned long orders,
>> struct folio *folio;
>> int ret;
>>
>> + orders = swapin_admit_orders(entry, orders);
>> +again:
>> do {
>> folio = swap_cache_get_folio(entry);
>> if (folio)
>> return folio;
>> - folio = swap_cache_alloc_folio(entry, gfp, orders, vmf, mpol, ilx);
>> + folio = swap_cache_alloc_speculative_folio(entry, gfp, orders,
>> + vmf, mpol, ilx);
>> } while (PTR_ERR(folio) == -EEXIST);
>>
>> if (IS_ERR(folio))
>> return folio;
>>
>> + if (zswap_needs_order0_retry(folio)) {
>> + count_mthp_stat(folio_order(folio), MTHP_STAT_SWPIN_FALLBACK);
>> + /*
>> + * The folio is newly allocated, locked, clean and not uptodate;
>> + * no data has been read into it. Removing it only restores the
>> + * swap table entries so order-0 swapin can resolve a backend
>> + * race without attempting speculative large-folio zswapin.
>> + */
>> + swap_cache_del_folio(folio);
>> + folio_unlock(folio);
>> + folio_put(folio);
>> + orders = BIT(0);
>> + goto again;
>> + }
>> +
>> ret = swap_read_folio(folio, NULL);
>> - VM_WARN_ON_ONCE(ret == -EAGAIN);
>> + if (ret == -EAGAIN) {
>
> Can this happen? After you add the entire swap range to swap cache,
> backend is locked. Zswap writeback bails out if it fails to add the
> page to swap cache.
>
> I think you can just check (zswap_probe_range or wev) before
> swap_read_folio(). If the range is still fully backed by zswap, you
> are good to go. Otherwise, bail here immediately.
>
> Then you don't need all the complexity with extending swap_read_folio
> to handle mixed range errors (for now at least).
Yes, I think you are right.
I missed that property of zswap writeback. Once the whole range is covered by
the large swapcache folio, writeback should not be able to move a subslot to
disk because it has to allocate an order-0 swapcache folio first, and that
should fail.
Sorry for adding this extra complexity. I will rework this in a more unified way for the
next version.
^ permalink raw reply
* Re: [RFC PATCH v2 6/9] mm: provide anon locality evidence for zswap large swapin
From: Fujunjie @ 2026-05-31 13:11 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=MDSwMoU-=h3NOG==-ru+qT3LeTi2_PADLWFXBB9aZZ+w@mail.gmail.com>
On 5/30/2026 3:22 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:19 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> The common zswap large-swapin policy needs locality evidence from
>> callers before it can admit a large folio. For anonymous faults, provide
>> that evidence from existing VMA hints and from the PTE young state left
>> by earlier zswap-backed large swapins.
>>
>> Keep non-faulting PTEs old when mapping a speculative all-zswap large
>> folio. A later fault can then require a dense young previous range before
>> admitting another large swapin without adding VMA state.
>
> Makes sense to me.
>
>>
>> This also removes the old zswap-enabled guard from the THP swapin
>> candidate scan. The common swapin path now classifies the backend range
>> and falls back to order-0 for mixed zswap/disk ranges or races.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> mm/memory.c | 234 +++++++++++++++++++++++++++++++++++++++++++-----
>> mm/swap.h | 6 ++
>> mm/swap_state.c | 15 ++++
>> 3 files changed, 235 insertions(+), 20 deletions(-)
>>
>> diff --git a/mm/memory.c b/mm/memory.c
>> index 92a82008d583..7bbb89632000 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -4556,6 +4556,35 @@ static void memcg1_swapin_retry_folio(struct folio *folio,
>> folio_unlock(folio);
>> }
>>
>> +static void set_swapin_ptes(struct vm_area_struct *vma,
>> + unsigned long address, pte_t *ptep, pte_t pte,
>> + unsigned int nr_pages, unsigned int fault_pte_idx,
>> + bool fault_only_young)
>> +{
>> + struct mm_struct *mm = vma->vm_mm;
>> + pte_t old_pte;
>> +
>> + if (!fault_only_young || nr_pages == 1) {
>> + set_ptes(mm, address, ptep, pte, nr_pages);
>> + return;
>> + }
>> +
>> + old_pte = pte_mkold(pte);
>> + if (fault_pte_idx)
>> + set_ptes(mm, address, ptep, old_pte, fault_pte_idx);
>> +
>> + set_pte_at(mm, address + fault_pte_idx * PAGE_SIZE,
>> + ptep + fault_pte_idx,
>> + pte_mkyoung(pte_advance_pfn(pte, fault_pte_idx)));
>
> Hmm, does this mean that without THP swapin, the faulting PTE is not
> marked young, but it is marked young if there is a THP swapin. That's
> a behavioral change right? Would this throw off other heuristics
> relying on this bit, or any justification that this is fine?
Thanks.
The intent was not to make the faulting PTE behave differently from the
normal swapin path. In do_swap_page() we first build the PTE with:
pte = mk_pte(page, vma->vm_page_prot);
and on the common architectures I checked, the normal user pgprot already
contains the accessed/young bit. For example arm64 PAGE_SHARED/PAGE_READONLY
are based on _PAGE_DEFAULT, which includes PTE_AF, and x86 user page
protections also include the accessed bit. So in practice the faulting PTE is
already young after mk_pte() there.Therefore, the default path is originally marked as young.
What I really wanted here is only to keep the speculative neighbouring PTEs
old. A large zswapin may install PTEs for pages that did not fault, and those
should not all look accessed just because mk_pte() produced a young PTE.
But, the explicit pte_mkyoung() on the faulting PTE makes this look
like THP swapin is adding a new behavior.I will try to improve it
in a way that is less ambiguous.
^ permalink raw reply
* Re: [RFC PATCH v2 4/9] mm: admit large swapin by backend range in swapin_sync()
From: Fujunjie @ 2026-05-31 12:34 UTC (permalink / raw)
To: Kairui Song
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Usama Arif, Chris Li,
Johannes Weiner, Yosry Ahmed, Nhat Pham, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAMgjq7AQwF6oNpnGTxxJWb=oyZ3dLfPL4oSNoS+eQxtuzZPgTQ@mail.gmail.com>
On 5/29/2026 10:45 PM, Kairui Song wrote:
> On Fri, May 29, 2026 at 10:43 PM Kairui Song <ryncsn@gmail.com> wrote:
>>
>> Hi Fujunjie,
>>
>> Thanks for the update, but this whole defer_memcg1_swapin thing is so
>> ugly I don't think this is the right way at all.
>>
>> If you really need this, maybe you can always defer the memcg1
>
> Oh and I'm not saying I'm against this series or the idea, I'm just
> saying this particular design of this one patch needs some improvement
> :)
Thanks for your review! I will improve the implementation.
^ permalink raw reply
* Re: [RFC PATCH v2 0/9] mm: support zswap-backed large folio swapin
From: Fujunjie @ 2026-05-31 12:32 UTC (permalink / raw)
To: Nhat Pham
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Kairui Song, Usama Arif,
Chris Li, Johannes Weiner, Yosry Ahmed, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAKEwX=PdQb2nDbFaZYuRa9_mYrMCnMEJHpxxABebKkVz+OgDHg@mail.gmail.com>
On 5/30/2026 2:06 AM, Nhat Pham wrote:
> On Fri, May 29, 2026 at 5:17 AM fujunjie <fujunjie1@qq.com> wrote:
>>
>> Hi,
>>
>> This RFC explores large-folio swapin for ranges that are still fully backed
>> by zswap.
>>
>> Large swapin is currently disabled once zswap is in the picture. Anonymous
>> faults stop considering large orders after zswap has ever been enabled,
>> shmem does the same, and zswap_load() refuses large swapcache folios. That
>> keeps mixed zswap/disk cases safe, but it also loses the dense case where
>> every slot in an aligned 64K range is still resident in zswap.
>>
>> The series keeps the policy in common swapin code:
>>
>> - zswap reports backend facts and provides the large-folio load helper.
>> - swapin_sync() filters candidate orders by backend range.
>> - all-disk and zeromap ranges keep the existing Kairui large-swapin path.
>> - mixed zswap/disk ranges stay order-0.
>> - all-zswap ranges may use a 64K folio after locality admission.
>> - anon provides locality evidence from VMA hints and PTE young density.
>> - shmem starts with explicit VMA-hint evidence only.
>> - swap readahead uses its existing VMA/cluster window as locality
>> evidence; it does not also run the anon PTE-young rule.
>>
>> The backend range probe is only a snapshot. If the backend changes after a
>> fresh large swapcache folio is allocated, the common path drops that folio
>> and falls back to order-0. zswap_load() can also return -EAGAIN for the
>> same retry path. If a late fault retry keeps the large folio in swapcache
>> instead of deleting it, the cgroup v1 memsw swap owner is committed before
>> returning.
>>
>> This is mTHP/large-folio swapin. The mappings installed by do_swap_page()
>> are still PTE mappings, not PMD mappings. The expected win is fewer faults,
>> batched PTE/rmap work, and preserving the large folio across zswapin
>> instead of rebuilding the working set as order-0 pages.
>>
>> Prior art: Usama Arif posted a related RFC on 2024-10-18:
>>
>> mm: zswap: add support for zswapin of large folios
>> https://lore.kernel.org/linux-mm/20241018105026.2521366-1-usamaarif642@gmail.com/
>>
>> This RFC keeps the same broad goal, but moves admission into common swapin
>> code. zswap does not decide the policy. Mixed zswap/disk ranges are
>> rejected before large IO, and the first cap is 64K.
>>
>> This is a rewrite of the RFC posted on 2026-05-08:
>>
>> [RFC PATCH 0/5] mm: support zswap-backed anonymous large folio swapin
>> https://lore.kernel.org/linux-mm/tencent_8B437BE4F586C162950BF71954316C1EDB05@qq.com/
>>
>> The v1 series was anonymous-only and kept too much of the policy near the
>> anon fault and zswap paths. This version is rebuilt on top of Kairui Song's
>> common swapin infrastructure. It keeps admission in common swapin code,
>> rejects mixed zswap/disk large ranges, and adds separate locality producers
>> for anon, shmem and swap readahead.
>>
>> Performance and behavior
>> ========================
>>
>> The A/B tables are 10-run measurements. Elapsed values are seconds,
>> shown as mean +/- sample standard deviation. "phase" or "refault" is the
>> measured refault subphase. "zswpin" counts zswap loads. "pswpin" counts
>> swap-ins from the real swap device; pswpin=0 means the refaults were served
>> by zswap even when a disk swap device was configured. "RFC 64K" is the mean
>> number of successful 64K swapins.
>>
>> The numbers below show where the large path is used and where it is
>> rejected.
>>
>> zram-backed zswap microbench, 64K mTHP, 8G guest:
>>
>> +-----------------+----------------+----------------+--------+--------+--------+----------+
>> | workload | base elapsed | RFC elapsed | delta | phase | zswpin | RFC 64K |
>> +-----------------+----------------+----------------+--------+--------+--------+----------+
>> | usama_1g | 11.260+/-0.235 | 10.301+/-0.140 | -8.5% | -22.2% | 1.000x | 16381.1 |
>> | nohint_seq64 | 4.398+/-0.085 | 4.025+/-0.022 | -8.5% | -21.1% | 1.000x | 6221.1 |
>> | seqhint_seq64 | 4.283+/-0.060 | 3.948+/-0.062 | -7.8% | -20.6% | 1.000x | 6223.5 |
>> | stride64_sparse | 3.095+/-0.051 | 3.086+/-0.025 | -0.3% | +5.8% | 1.002x | 1.0 |
>> | random64_sparse | 3.095+/-0.046 | 3.076+/-0.016 | -0.6% | +0.7% | 1.001x | 0.0 |
>> | random64_full | 4.423+/-0.067 | 4.405+/-0.018 | -0.4% | +0.1% | 1.000x | 0.0 |
>> +-----------------+----------------+----------------+--------+--------+--------+----------+
>>
>> The usama_1g row follows the shape of the 2024 RFC benchmark: allocate 1G,
>> fill it with compressible per-page data, reclaim it through memory.reclaim,
>> then time the full integrity-check refault. The seq64 rows use a 512M
>> target and 768M of pressure. "sparse" touches one 4K page per 64K region, while
>> "full" touches every 4K page. "seqhint" uses MADV_SEQUENTIAL; "nohint" does
>> not.
>>
>> Virtio-block swap device present, zswap enabled:
>>
>> +-----------------+---------------+---------------+--------+---------+--------+--------+---------+
>> | workload | base elapsed | RFC elapsed | delta | refault | pswpin | zswpin | RFC 64K |
>> +-----------------+---------------+---------------+--------+---------+--------+--------+---------+
>> | seq64 | 4.399+/-0.100 | 4.279+/-0.216 | -2.7% | -10.5% | 0 | 1.000x | 3110.7 |
>> | stride64_sparse | 3.103+/-0.047 | 3.119+/-0.086 | +0.5% | +6.2% | 0 | 0.999x | 0.0 |
>> | random64_sparse | 3.142+/-0.112 | 3.097+/-0.030 | -1.4% | -2.2% | 0 | 0.999x | 0.1 |
>> | random64_full | 4.473+/-0.147 | 4.445+/-0.088 | -0.6% | +0.9% | 0 | 1.000x | 0.4 |
>> +-----------------+---------------+---------------+--------+---------+--------+--------+---------+
>>
>> This run uses a real block swap device, but the refaulted data stayed in
>> zswap. It covers the all-zswap hit path with disk swap configured, not disk
>> read IO.
>>
>> Virtio-block pressure/mixed run, zswap max_pool_percent=1,
>> low-compressibility full fill:
>>
>> +-------------------------------+---------------+---------------+--------+---------+----------------+------------+---------+----------+
>> | workload | base elapsed | RFC elapsed | delta | refault | pswpin base/RFC | RFC zswpin | RFC 64K | fallback |
>> +-------------------------------+---------------+---------------+--------+---------+----------------+------------+---------+----------+
>> | seq64_full_pressure | 5.908+/-0.195 | 5.790+/-0.235 | -2.0% | +3.0% | 90258/99038 | 20327 | 0.0 | 3730 |
>> | random64_sparse_full_pressure | 5.104+/-0.069 | 5.068+/-0.090 | -0.7% | -9.1% | 6201/6196 | 1297 | 0.0 | 0 |
>> +-------------------------------+---------------+---------------+--------+---------+----------------+------------+---------+----------+
>>
>> This run reaches the disk-backed path: pswpin is non-zero in both base and
>> RFC. It is mainly fallback coverage. The RFC does not install 64K folios
>> for these disk/mixed-heavy ranges.
>
> Ok this results above look good. Basically, if we don't have spatial
> locality in access patterns, we don't do THP zswapin. Nice.
>
>>
>> Policy matrix, virtio-block swap device present:
>>
>> +------------------------------+----+------+--------+--------+-------+----------+
>> | case | pc | hint | pswpin | zswpin | zswpwb| 64K in |
>> +------------------------------+----+------+--------+--------+-------+----------+
>> | pc0_seq | 0 | none | 0 | 99559 | 0 | 0 |
>> | pc3_seq | 3 | none | 0 | 99498 | 0 | 0 |
>> | pc4_seq | 4 | none | 0 | 99512 | 0 | 3109 |
>> | pc5_seq | 5 | none | 0 | 99657 | 0 | 3113 |
>> | hint_none_random_sparse | 5 | none | 0 | 6265 | 0 | 0 |
>> | hint_random_seq | 5 | rand | 0 | 99488 | 0 | 0 |
>> | mixed_seq_full | 5 | none | 97725 | 20147 | 84 | 569 |
>> | mixed_random_sparse_full | 5 | none | 6230 | 1302 | 0 | 0 |
>> +------------------------------+----+------+--------+--------+-------+----------+
>>
>> The pc rows show the readahead-window gate. The hint_random_seq row shows
>> the explicit random hint veto. The mixed rows use a small zswap pool to
>> force disk/zswap split backing; most mixed ranges are rejected, while any
>> remaining 64K successes were all-zswap at the time of the fault.
>>
>> Kbuild pressure, zram swap, 384M memcg:
>>
>> +----------------------+----------+----------+--------+--------+----------+
>> | setup | base | RFC | delta | zswpin | RFC 64K |
>> +----------------------+----------+----------+--------+--------+----------+
>> | zram swap, 384M memcg| 2060.323 | 2047.516 | -0.6% | 0.991x | 2797 |
>> +----------------------+----------+----------+--------+--------+----------+
>>
>> This is a single-run zram pressure smoke. It did not show Kbuild
>> regression, and the RFC run installed 64K zswap-backed folios. The result
>> should not be read as a tuned-performance claim.
>>
>> Kbuild pressure, virtio-block swap device, 512M memcg:
>>
>> +-------------------------+----------+----------+--------+--------+----------+
>> | setup | base | RFC | delta | pswpin | RFC 64K |
>> +-------------------------+----------+----------+--------+--------+----------+
>> | disk swap, 512M memcg | 1420.671 | 1409.263 | -0.8% | 0 | 7497 |
>> +-------------------------+----------+----------+--------+--------+----------+
>>
>> This is a single-run pressure smoke. The disk-swap Kbuild run also stayed
>> on the all-zswap hit path, so it is pressure coverage with a disk swap device
>> present rather than proof of disk-read large swapin.
>
> Why a single-run?
I did run Kbuild a few times while debugging the series and did not see a
significant difference either way. Because of that I only kept one fresh run
with the final tree before sending the RFC, so this should be read only as a
smoke test, not as performance evidence.
For the next version I will rerun Kbuild properly with multiple fresh
iterations and report it, so it can be used as a more reliable
performance comparison instead of just smoke coverage.
>
>>
>> Shmem smoke, tmpfs huge=always, 64K shmem mTHP:
>>
>> +----------------------------+---------------+---------+-------------+----------+
>> | case | refault hint | touched | 64K shmem | 64K in |
>> +----------------------------+---------------+---------+-------------+----------+
>> | nohint_seq | none | 65536 | 4096 | 0 |
>> | seq_refault_hint | sequential | 65536 | 4096 | 4096 |
>> | random_refault_hint_sparse | random | 4096 | 4096 | 0 |
>> +----------------------------+---------------+---------+-------------+----------+
>>
>> That matches the current shmem producer: explicit sequential refault hints
>> allow large zswap swapin; no hint and random hints do not.
>>
>> What this RFC does not establish
>> ================================
>>
>> The 64K cap is deliberate, but it is not tuned. The anon PTE-young rule is
>> only anon evidence. Shmem has the framework and explicit VMA hints in this
>> RFC, not a page-cache locality producer. For larger orders, the anon
>> producer should probably use bounded sampling instead of walking every PTE
>> in a 1M or larger candidate range. The mixed-backend tests cover fallback
>> behavior, but this series does not add mixed zswap/disk large IO.
>
> The mixed IO can be deferred, but I think we should figure out a rule
> to extend this hint to arbitrarily sized ranges, and preferrably shmem
> too.
That makes sense.
The current 64K cap was intentionally conservative, but the locality rule is
too tied to that size. For v3 I will look at making the admission rule
order-independent, probably with bounded sampling rather than walking every
PTE for larger ranges.
For shmem, this RFC only uses explicit VMA hints, so it does not yet have a
real page-cache locality producer. I will think through how to add a shmem
producer with similar semantics, so the rule is not anon-only.
Thanks,
Fujunjie
^ permalink raw reply
* Re: [RFC PATCH v2 4/9] mm: admit large swapin by backend range in swapin_sync()
From: Fujunjie @ 2026-05-31 12:21 UTC (permalink / raw)
To: Kairui Song
Cc: Andrew Morton, linux-mm, Alexandre Ghiti, Usama Arif, Chris Li,
Johannes Weiner, Yosry Ahmed, Nhat Pham, David Hildenbrand,
Hugh Dickins, Roman Gushchin, Shakeel Butt, linux-kernel, cgroups
In-Reply-To: <CAMgjq7AA_1esgtA8VyxaBLWBBRM12bCBpxO2Jch5OESBZSg--A@mail.gmail.com>
On 5/29/2026 10:43 PM, Kairui Song wrote:
> On Fri, May 29, 2026 at 8:26 PM fujunjie <fujunjie1@qq.com> wrote:
>>
>> A large swapin can only read one folio when the whole range has compatible
>> backing. Mixed zswap/disk ranges must not reach large-folio IO, and zswap
>> range probes are only snapshots.
>>
>> Filter the orders passed to swap_cache_alloc_folio() in swapin_sync().
>> Uniform zeromap ranges and all-disk ranges keep the existing large swapin
>> path. Fully zswap-backed ranges may be tried. Mixed zswap/disk ranges fall
>> back before allocation.
>>
>> After a large swapcache folio is installed, recheck the zswap range and
>> drop the fresh folio if it became mixed. Also consume -EAGAIN from
>> swap_read_folio() the same way. Both cases retry order-0, where each slot
>> can resolve its current backend independently.
>>
>> Signed-off-by: fujunjie <fujunjie1@qq.com>
>> ---
>> mm/memcontrol-v1.c | 8 ++-
>> mm/memory.c | 31 ++++++++-
>> mm/swap_state.c | 169 ++++++++++++++++++++++++++++++++++++++++++---
>> 3 files changed, 194 insertions(+), 14 deletions(-)
>>
>> diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
>> index 765069211567..5b11b8055c66 100644
>> --- a/mm/memcontrol-v1.c
>> +++ b/mm/memcontrol-v1.c
>> @@ -682,8 +682,8 @@ void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
>> * memcg1_swapin - uncharge swap slot on swapin
>> * @folio: folio being swapped in
>> *
>> - * Call this function after successfully adding the charged
>> - * folio to swapcache.
>> + * Call this after the charged folio has been added to swapcache and the caller
>> + * is no longer going to drop it back to swapped-out state.
>> *
>> * Context: The folio has to be in swap cache and locked.
>> */
>> @@ -721,7 +721,9 @@ void memcg1_swapin(struct folio *folio)
>> id = __swap_cgroup_clear(ci, swp_cluster_offset(folio->swap),
>> nr_pages);
>> swap_cluster_unlock(ci);
>> - mem_cgroup_uncharge_swap(id, nr_pages);
>> +
>> + if (id)
>> + mem_cgroup_uncharge_swap(id, nr_pages);
>> }
>> #endif
>>
>> diff --git a/mm/memory.c b/mm/memory.c
>> index 5a365492a9a2..d73a19692dea 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -4538,6 +4538,24 @@ static inline bool should_try_to_free_swap(struct swap_info_struct *si,
>> folio_ref_count(folio) == (extra_refs + folio_nr_pages(folio));
>> }
>>
>> +static void memcg1_swapin_retry_folio(struct folio *folio,
>> + struct vm_fault *vmf)
>> +{
>> + if (!folio_test_large(folio) || !folio_test_swapcache(folio))
>> + return;
>> +
>> + if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT) {
>> + if (!folio_trylock(folio))
>> + return;
>> + } else {
>> + folio_lock(folio);
>> + }
>> +
>> + if (folio_test_large(folio) && folio_test_swapcache(folio))
>> + memcg1_swapin(folio);
>> + folio_unlock(folio);
>> +}
>> +
>> static vm_fault_t pte_marker_clear(struct vm_fault *vmf)
>> {
>> vmf->pte = pte_offset_map_lock(vmf->vma->vm_mm, vmf->pmd,
>> @@ -4857,8 +4875,10 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>>
>> swapcache = folio;
>> ret |= folio_lock_or_retry(folio, vmf);
>> - if (ret & VM_FAULT_RETRY)
>> + if (ret & VM_FAULT_RETRY) {
>> + memcg1_swapin_retry_folio(folio, vmf);
>> goto out_release;
>> + }
>>
>> page = folio_file_page(folio, swp_offset(entry));
>> /*
>> @@ -5067,6 +5087,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> if (unlikely(folio != swapcache)) {
>> folio_add_new_anon_rmap(folio, vma, address, RMAP_EXCLUSIVE);
>> folio_add_lru_vma(folio, vma);
>> + if (folio_test_large(swapcache))
>> + memcg1_swapin(swapcache);
>> folio_put_swap(swapcache, NULL);
>> } else if (!folio_test_anon(folio)) {
>> /*
>> @@ -5076,6 +5098,8 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> VM_WARN_ON_ONCE_FOLIO(folio_nr_pages(folio) != nr_pages, folio);
>> VM_WARN_ON_ONCE_FOLIO(folio_mapped(folio), folio);
>> folio_add_new_anon_rmap(folio, vma, address, rmap_flags);
>> + if (folio_test_large(folio))
>> + memcg1_swapin(folio);
>> folio_put_swap(folio, NULL);
>> } else {
>> VM_WARN_ON_ONCE(nr_pages != 1 && nr_pages != folio_nr_pages(folio));
>> @@ -5132,8 +5156,11 @@ vm_fault_t do_swap_page(struct vm_fault *vmf)
>> if (vmf->pte)
>> pte_unmap_unlock(vmf->pte, vmf->ptl);
>> out_page:
>> - if (folio_test_swapcache(folio))
>> + if (folio_test_swapcache(folio)) {
>> + if (folio_test_large(folio))
>> + memcg1_swapin(folio);
>> folio_free_swap(folio);
>> + }
>> folio_unlock(folio);
>> out_release:
>> folio_put(folio);
>> diff --git a/mm/swap_state.c b/mm/swap_state.c
>> index d37097913b30..f03ad4832f16 100644
>> --- a/mm/swap_state.c
>> +++ b/mm/swap_state.c
>> @@ -21,6 +21,7 @@
>> #include <linux/migrate.h>
>> #include <linux/vmalloc.h>
>> #include <linux/huge_mm.h>
>> +#include <linux/zswap.h>
>> #include <linux/shmem_fs.h>
>> #include "internal.h"
>> #include "swap_table.h"
>> @@ -403,7 +404,8 @@ void __swap_cache_replace_folio(struct swap_cluster_info *ci,
>> static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> swp_entry_t targ_entry, gfp_t gfp,
>> unsigned int order, struct vm_fault *vmf,
>> - struct mempolicy *mpol, pgoff_t ilx)
>> + struct mempolicy *mpol, pgoff_t ilx,
>> + bool defer_memcg1_swapin)
>
> Hi Fujunjie,
>
> Thanks for the update, but this whole defer_memcg1_swapin thing is so
> ugly I don't think this is the right way at all.
>
> If you really need this, maybe you can always defer the memcg1
> uncharge, I don't see why we need to treat large folio differently.
> This charge doesn't effect the memory pressure, the reason we uncharge
> memcg1's swap counter is to avoid long pinning swap cache holding the
> swap cache of a cgroup so the cgroup will no longer be able to swap
> out more folios. Deferring it won't hurt.
Yes, I think you are right.
I added defer_memcg1_swapin because I was still treating the freshly
allocated large swapcache folio as something that might be dropped after it
was installed, so I tried to avoid clearing the cgroup v1 swap owner too
early.
Nhat Pham also pointed out that this is probably the wrong model. Once the whole
range is covered by the large swapcache folio, zswap writeback should not be
able to turn one subslot into disk-backed state, since it has to allocate an
order-0 swapcache folio first and that should fail.
So the deferred memcg1 handling is likely self-inflicted complexity. I'll
drop this flag and rework the mixed-backend check so we fail the current
order before we need this late abort path. If any memcg1 timing issue remains
after that, I'll try to handle it with a uniform rule rather than a
large-folio-specific flag.
>
>> {
>> int err;
>> swp_entry_t entry;
>> @@ -466,7 +468,8 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> }
>>
>> /* memsw uncharges swap when folio is added to swap cache */
>> - memcg1_swapin(folio);
>> + if (!defer_memcg1_swapin || !order)
>> + memcg1_swapin(folio);
>> if (shadow)
>> workingset_refault(folio, shadow);
>>
>> @@ -495,9 +498,12 @@ static struct folio *__swap_cache_alloc(struct swap_cluster_info *ci,
>> * Return: Returns the folio if allocation succeeded and folio is in the swap
>> * cache. Returns error code if failed due to race, OOM or invalid arguments.
>> */
>> -struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> - unsigned long orders, struct vm_fault *vmf,
>> - struct mempolicy *mpol, pgoff_t ilx)
>> +static struct folio *__swap_cache_alloc_folio(swp_entry_t targ_entry,
>> + gfp_t gfp, unsigned long orders,
>> + struct vm_fault *vmf,
>> + struct mempolicy *mpol,
>> + pgoff_t ilx,
>> + bool defer_memcg1_swapin)
>> {
>> int order, err;
>> struct folio *ret;
>> @@ -512,7 +518,8 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>>
>> do {
>> ret = __swap_cache_alloc(ci, targ_entry, gfp, order,
>> - vmf, mpol, ilx);
>> + vmf, mpol, ilx,
>> + defer_memcg1_swapin);
>> if (!IS_ERR(ret))
>> break;
>> err = PTR_ERR(ret);
>> @@ -525,6 +532,124 @@ struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> return ret;
>> }
>>
>> +struct folio *swap_cache_alloc_folio(swp_entry_t targ_entry, gfp_t gfp,
>> + unsigned long orders, struct vm_fault *vmf,
>> + struct mempolicy *mpol, pgoff_t ilx)
>> +{
>> + return __swap_cache_alloc_folio(targ_entry, gfp, orders, vmf,
>> + mpol, ilx, false);
>> +}
>> +
>> +static struct folio *swap_cache_alloc_speculative_folio(swp_entry_t targ_entry,
>> + gfp_t gfp,
>> + unsigned long orders,
>> + struct vm_fault *vmf,
>> + struct mempolicy *mpol,
>> + pgoff_t ilx)
>> +{
>> + /*
>> + * Speculative large swapin may drop this fresh swapcache folio and
>> + * retry order-0 after backend or page-table revalidation. Keep the
>> + * cgroup v1 memsw swap owner until the caller commits the folio.
>> + */
>> + return __swap_cache_alloc_folio(targ_entry, gfp, orders, vmf,
>> + mpol, ilx, true);
>> +}
>> +
>> +static bool swapin_zeromap_same(swp_entry_t entry, unsigned int nr_pages)
>> +{
>> + unsigned int ci_start = swp_cluster_offset(entry);
>> + struct swap_cluster_info *ci = __swap_entry_to_cluster(entry);
>> + bool is_zero;
>> + unsigned int i;
>> +
>> + if (ci_start + nr_pages > SWAPFILE_CLUSTER) {
>> + VM_WARN_ON_ONCE(1);
>> + return false;
>> + }
>> +
>> + rcu_read_lock();
>> + if (!rcu_dereference(ci->table)) {
>> + rcu_read_unlock();
>> + return true;
>> + }
>> +
>> + is_zero = __swap_table_test_zero(ci, ci_start);
>> + for (i = 1; i < nr_pages; i++) {
>> + if (is_zero != __swap_table_test_zero(ci, ci_start + i)) {
>> + rcu_read_unlock();
>> + return false;
>> + }
>> + }
>> + rcu_read_unlock();
>> +
>> + return true;
>> +}
>> +
>> +static unsigned long swapin_admit_orders(swp_entry_t entry,
>> + unsigned long orders)
>
> And this swapin_admit_orders chunk doesn't look good either...、
Yes, this helper is doing too much.
I wanted to keep mixed zswap/disk ranges away from large-folio IO, but this
ended up mixing policy with range feasibility checks.
>
>> +{
>> + unsigned long candidates = orders & ~BIT(0);
>> + unsigned long admitted = orders & BIT(0);
>> + int order;
>> +
>> + if (!candidates)
>> + return orders;
>> +
>> + while (candidates) {
>> + enum zswap_range_state state;
>> + unsigned int nr_pages;
>> + swp_entry_t range_entry;
>> + bool admit = false;
>> +
>> + order = fls_long(candidates) - 1;
>> + if (order > MAX_PAGE_ORDER) {
>> + candidates &= ~BIT(order);
>> + continue;
>> + }
>> +
>> + nr_pages = 1U << order;
>> + range_entry = swp_entry(swp_type(entry),
>> + round_down(swp_offset(entry), nr_pages));
>> + if (!swapin_zeromap_same(range_entry, nr_pages))
>> + goto next;
>
> I think you don't need to test zeromap at all? __swap_cache_alloc
> handles that already.
I am sorry for missed that this is already covered by __swap_cache_add_check().
I'll drop the explicit zeromap scan in v3 version.
>
>> +
>> + state = zswap_probe_range(range_entry, nr_pages);
>
> If you just move the zswap_probe_range into __swap_cache_alloc and do
> fallback there (or maybe you can shrink the order faster), then this
> two new helpers are all redundant.
>
>> + switch (state) {
>> + case ZSWAP_RANGE_MIXED:
>> + break;
>> + case ZSWAP_RANGE_ALL_ZSWAP:
>> + case ZSWAP_RANGE_NEVER_ENABLED:
>> + case ZSWAP_RANGE_NO_ZSWAP:
>> + admit = true;
>> + break;
>> + }
>> +
>> +next:
>> + if (admit)
>> + admitted |= BIT(order);
>> + else
>> + count_mthp_stat(order, MTHP_STAT_SWPIN_FALLBACK);
>> + candidates &= ~BIT(order);
>> + }
>> +
>> + return admitted ? admitted : BIT(0);
>> +}
>> +
>> +static bool zswap_needs_order0_retry(struct folio *folio)
>> +{
>> + if (!folio_test_large(folio))
>> + return false;
>> +
>> + /*
>> + * Admission sees only an advisory zswap snapshot. Recheck after the
>> + * large swapcache folio is installed; if the range became mixed, drop
>> + * the fresh folio before IO and let order-0 handle each slot.
>> + */
>> + return zswap_probe_range(folio->swap, folio_nr_pages(folio)) ==
>> + ZSWAP_RANGE_MIXED;
>> +}
>> +
>
> Again, I think you can just probe the suitable size in
> __swap_cache_alloc directly, that way, we avoid the diverge of sync /
> non-sync device, and avoid the whole chunk making the code much
> simplier too, just like what we are alreadying doing for zero map in
> __swap_cache_alloc, or am I over simpliying it?Thanks for your review! I will try it.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox