[PATCH 2/6] mm, swap: do not charge zswap-backed xswap entries

From: Baoquan He

Date: Fri Oct 02 2026 - 21:12:29 EST


From: Nhat Pham <nphamcs@xxxxxxxxx>

An xswap entry occupies no physical swap space: its data lives in zswap
until a backend is taken. It was nevertheless charged against
memcg->swap at allocation, as if it did.

Record the memcg at allocation but do not charge. The charge is taken
when the entry acquires physical backing, and released with it.

memory.swap.current therefore counts only on-disk swap usage, not
zswap-backed xswap entries. A cgroup can reclaim its anon memory with
memory.swap.max set to 0, provided zswap is allowed for it.

Suggested-by: Johannes Weiner <hannes@xxxxxxxxxxx>
Signed-off-by: Nhat Pham <nphamcs@xxxxxxxxx>
Signed-off-by: Baoquan He <hebaoquan@xxxxxxxxxx>
---
mm/swapfile.c | 23 +++++++++++++++++------
1 file changed, 17 insertions(+), 6 deletions(-)

diff --git a/mm/swapfile.c b/mm/swapfile.c
index 8e52ace44de8..0310b474f08d 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -2147,7 +2147,12 @@ int folio_alloc_swap(struct folio *folio)
* The owner is taken here, then charged and recorded.
*/
if (mem_cgroup_swap_get(folio, &swap_id)) {
- if (unlikely(mem_cgroup_swap_charge(swap_id, size))) {
+ /*
+ * An xswap entry has no physical swap yet, so only record the
+ * owner here. Its backing is charged when one is taken.
+ */
+ if (!(__swap_entry_to_info(folio->swap)->flags & SWP_XSWAP) &&
+ unlikely(mem_cgroup_swap_charge(swap_id, size))) {
mem_cgroup_swap_put(swap_id, size);
swap_cache_del_folio(folio);
goto failed;
@@ -2298,10 +2303,15 @@ struct swap_info_struct *get_swap_device(swp_entry_t entry)
return ERR_PTR(-EIO);
}

-/* Uncharge a batch of freed swap slots and drop their owner refs. */
-static void memcg_swap_free(unsigned short id, unsigned int nr)
+/*
+ * An xswap entry was not charged at allocation, so there is nothing to
+ * uncharge here and only the owner refs go. Its backing, if any, was
+ * uncharged when it was released.
+ */
+static void memcg_swap_free(unsigned short id, unsigned int nr, bool is_xswap)
{
- mem_cgroup_swap_uncharge(id, nr);
+ if (!is_xswap)
+ mem_cgroup_swap_uncharge(id, nr);
mem_cgroup_swap_put(id, nr);
}

@@ -2318,6 +2328,7 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
unsigned int ci_off = ci_start, ci_end = ci_start + nr_pages;
unsigned long ci_head = cluster_offset(si, ci);
unsigned int batch_off = ci_off;
+ bool is_xswap = si->flags & SWP_XSWAP;

VM_WARN_ON(ci->count < nr_pages);

@@ -2347,14 +2358,14 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
id_cur = __swap_cgroup_clear(ci, ci_off, 1);
if (batch_id != id_cur) {
if (batch_id)
- memcg_swap_free(batch_id, ci_off - batch_off);
+ memcg_swap_free(batch_id, ci_off - batch_off, is_xswap);
batch_id = id_cur;
batch_off = ci_off;
}
} while (++ci_off < ci_end);

if (batch_id)
- memcg_swap_free(batch_id, ci_off - batch_off);
+ memcg_swap_free(batch_id, ci_off - batch_off, is_xswap);

swap_range_free(si, ci_head + ci_start, nr_pages);
swap_cluster_assert_empty(ci, ci_start, nr_pages, false);
--
2.54.0