linux/mm/swap.h
Alexandre Ghiti 4154a1cf2f mm: swap: move LRU insertion out of the swap cache allocator
Patch series "mm: zswap: free cold writeback folios promptly", v6.

When zswap writes an entry back, it allocates an order-0 swap cache folio,
decompresses into it, and issues the write. The folio is cold by
construction, yet today it is left on the LRU for page reclaim to find and
free later. That wastes a reclaim scan and keeps cold memory resident
longer than necessary.

Rather than implement this in zswap, extend the existing dropbehind
mechanism to swap cache folios and have zswap opt into it (Yosry). A
PG_dropbehind folio is already dropped from its cache once writeback
completes instead of being left for reclaim; for a swap cache folio that
"drop" is removing it from the swap cache.

  Patch 1 - move LRU insertion out of the swap cache allocator into its
            callers, so zswap writeback can allocate off the LRU.

  Patch 2 - drop dropbehind swap cache folios on writeback completion.

  Patch 3 - zswap allocates its writeback folio off the LRU and marks it
            dropbehind, opting into the mechanism above.


This patch (of 3):

This is a preparatory patch.

swap_cache_alloc_folio() adds the new folio to the LRU itself, which
leaves its callers no way to act on the folio before it becomes visible
to reclaim.  Two users need exactly that:

 - moving the refault evaluation out of the swap cache folio allocation
   requires it to happen before folio_add_lru(): that consumes PG_active
   to file the folio on the inactive or the active list, and under MGLRU
   it also reads PG_workingset to pick the generation.  Setting either
   flag afterwards does not move the folio;

 - zswap writeback dropbehind needs the buffer folio to stay off the LRU
   entirely, as the per-CPU LRU batch would hold a reference on it and
   keep remove_mapping() from freeing it once writeback completes.

Defer the LRU insertion to the callers and rename the helper to
__swap_cache_alloc_folio(): each caller adds the folio right after the
allocation, so there is no functional change intended.

Link: https://lore.kernel.org/20260921151306.625134-1-alex@ghiti.fr
Link: https://lore.kernel.org/20260921151306.625134-2-alex@ghiti.fr
Signed-off-by: Alexandre Ghiti <alex@ghiti.fr>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Suggested-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Kairui Song <kasong@tencent.com>
Reviewed-by: Nhat Pham <nphamcs@gmail.com>
Reviewed-by: Kunwu Chan <kunwu.chan@gmail.com>
Acked-by: Usama Arif <usama.arif@linux.dev>
Reviewed-by: Barry Song <baohua@kernel.org>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Axel Rasmussen <axelrasmussen@google.com>
Cc: Baoquan He <baoquan.he@linux.dev>
Cc: Chengming Zhou <chengming.zhou@linux.dev>
Cc: Chis Li <chrisl@kernel.org> (Google)
Cc: Christian Brauner (Amutable) <brauner@kernel.org>
Cc: "David Hildenbrand (arm)" <david@kernel.org>
Cc: Jan Kara <jack@suse.cz>
Cc: Johannes Weiner <hannes@cmpxchg.org>
Cc: Kemeng Shi <shikemeng@huaweicloud.com>
Cc: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Cc: Matthew Wilcox <willy@infradead.org>
Cc: Michal Hocko <mhocko@kernel.org>
Cc: Qi Zheng <qi.zheng@linux.dev>
Cc: Shakeel Butt <shakeel.butt@linux.dev>
Cc: Tal Zussman <tz2294@columbia.edu>
Cc: Wei Xu <weixugc@google.com>
Cc: Yosry Ahmed <yosry@kernel.org>
Cc: Youngjun Park <youngjun.park@lge.com>
Cc: Yuanchu Xie <yuanchu@google.com>
2026-10-12 21:26:21 -07:00

473 lines
14 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _MM_SWAP_H
#define _MM_SWAP_H
#include <linux/atomic.h> /* for atomic_long_t */
#include <linux/mm.h> /* for PAGE_SHIFT */
#include <linux/memcontrol.h> /* for mem_cgroup_swappiness() */
#include <linux/swap.h> /* for MAX_SWAPFILES_SHIFT, struct swap_info_struct */
struct mempolicy;
struct swap_iocb;
struct swap_memcg_table;
struct swap_io_ctx;
#if defined(MAX_POSSIBLE_PHYSMEM_BITS)
#define SWAP_CACHE_PFN_BITS (MAX_POSSIBLE_PHYSMEM_BITS - PAGE_SHIFT)
#elif defined(MAX_PHYSMEM_BITS)
#define SWAP_CACHE_PFN_BITS (MAX_PHYSMEM_BITS - PAGE_SHIFT)
#else
#define SWAP_CACHE_PFN_BITS (BITS_PER_LONG - PAGE_SHIFT)
#endif
/* Swap table marker, 0x1 means shadow, 0x2 means PFN (SWP_TB_PFN_MARK) */
#define SWAP_CACHE_PFN_MARK_BITS 2
/* At least 2 bits are needed to distinguish SWP_TB_COUNT_MAX, 1 and 0 */
#define SWAP_COUNT_MIN_BITS 2
/* If there are enough bits besides PFN and marker, store zero flag inline */
#define SWAP_TABLE_HAS_ZEROFLAG ((BITS_PER_LONG - SWAP_CACHE_PFN_MARK_BITS - \
SWAP_CACHE_PFN_BITS) > SWAP_COUNT_MIN_BITS)
#ifdef CONFIG_THP_SWAP
#define SWAPFILE_CLUSTER HPAGE_PMD_NR
#define swap_entry_order(order) (order)
#else
#define SWAPFILE_CLUSTER 256
#define swap_entry_order(order) 0
#endif
extern struct swap_info_struct *swap_info[];
/*
* We use this to track usage of a cluster. A cluster is a block of swap disk
* space with SWAPFILE_CLUSTER pages long and naturally aligns in disk. All
* free clusters are organized into a list. We fetch an entry from the list to
* get a free cluster.
*
* The flags field determines if a cluster is free. This is
* protected by cluster lock.
*/
struct swap_cluster_info {
spinlock_t lock; /*
* Protect swap_cluster_info fields
* other than list, and swap_info_struct->swap_map
* elements corresponding to the swap cluster.
*/
u16 count;
u8 flags;
u8 order;
atomic_long_t __rcu *table; /* Swap table entries, see mm/swap_table.h */
unsigned int *extend_table; /* For large swap count, protected by ci->lock */
#ifdef CONFIG_MEMCG
struct swap_memcg_table *memcg_table; /* Swap table entries' cgroup record */
#endif
#if !SWAP_TABLE_HAS_ZEROFLAG
unsigned long *zero_bitmap;
#endif
struct list_head list;
};
/* All on-list cluster must have a non-zero flag. */
enum swap_cluster_flags {
CLUSTER_FLAG_NONE = 0, /* For temporary off-list cluster */
CLUSTER_FLAG_FREE,
CLUSTER_FLAG_NONFULL,
CLUSTER_FLAG_FRAG,
/* Clusters with flags above are allocatable */
CLUSTER_FLAG_USABLE = CLUSTER_FLAG_FRAG,
CLUSTER_FLAG_FULL,
CLUSTER_FLAG_DISCARD,
CLUSTER_FLAG_MAX,
};
extern int vm_swappiness;
static inline int mem_cgroup_swappiness(const struct mem_cgroup *memcg)
{
#ifdef CONFIG_MEMCG_V1
if (!cgroup_subsys_on_dfl(memory_cgrp_subsys) &&
!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
return READ_ONCE(memcg->swappiness);
#endif
return READ_ONCE(vm_swappiness);
}
#ifdef CONFIG_SWAP
#include <linux/swapops.h> /* for swp_offset */
#include <linux/blk_types.h> /* for bio_end_io_t */
static inline unsigned int swp_cluster_offset(swp_entry_t entry)
{
return swp_offset(entry) % SWAPFILE_CLUSTER;
}
/*
* Callers of all helpers below must ensure the entry, type, or offset is
* valid, and protect the swap device with reference count or locks.
*/
static inline struct swap_info_struct *__swap_type_to_info(int type)
{
struct swap_info_struct *si;
si = READ_ONCE(swap_info[type]); /* rcu_dereference() */
VM_WARN_ON_ONCE(percpu_ref_is_zero(&si->users)); /* race with swapoff */
return si;
}
static inline struct swap_info_struct *__swap_entry_to_info(swp_entry_t entry)
{
return __swap_type_to_info(swp_type(entry));
}
static inline struct swap_cluster_info *__swap_offset_to_cluster(
struct swap_info_struct *si, pgoff_t offset)
{
VM_WARN_ON_ONCE(percpu_ref_is_zero(&si->users)); /* race with swapoff */
VM_WARN_ON_ONCE(offset >= roundup(si->max, SWAPFILE_CLUSTER));
return &si->cluster_info[offset / SWAPFILE_CLUSTER];
}
static inline struct swap_cluster_info *__swap_entry_to_cluster(swp_entry_t entry)
{
return __swap_offset_to_cluster(__swap_entry_to_info(entry),
swp_offset(entry));
}
static __always_inline struct swap_cluster_info *__swap_cluster_lock(
struct swap_info_struct *si, unsigned long offset, bool irq)
{
struct swap_cluster_info *ci = __swap_offset_to_cluster(si, offset);
/*
* Nothing modifies swap cache in an IRQ context. All access to
* swap cache is wrapped by swap_cache_* helpers, and swap cache
* writeback is handled outside of IRQs. Swapin or swapout never
* occurs in IRQ, and neither does in-place split or replace.
*
* Besides, modifying swap cache requires synchronization with
* swap_map, which was never IRQ safe.
*/
VM_WARN_ON_ONCE(!in_task());
VM_WARN_ON_ONCE(percpu_ref_is_zero(&si->users)); /* race with swapoff */
if (irq)
spin_lock_irq(&ci->lock);
else
spin_lock(&ci->lock);
return ci;
}
/**
* swap_cluster_lock - Lock and return the swap cluster of given offset.
* @si: swap device the cluster belongs to.
* @offset: the swap entry offset, pointing to a valid slot.
*
* Context: The caller must ensure the offset is in the valid range and
* protect the swap device with reference count or locks.
*/
static inline struct swap_cluster_info *swap_cluster_lock(
struct swap_info_struct *si, unsigned long offset)
{
return __swap_cluster_lock(si, offset, false);
}
static inline struct swap_cluster_info *__swap_cluster_get_and_lock(
const struct folio *folio, bool irq)
{
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
return __swap_cluster_lock(__swap_entry_to_info(folio->swap),
swp_offset(folio->swap), irq);
}
/*
* swap_cluster_get_and_lock - Locks the cluster that holds a folio's entries.
* @folio: The folio.
*
* This locks and returns the swap cluster that contains a folio's swap
* entries. The swap entries of a folio are always in one single cluster.
* The folio has to be locked so its swap entries won't change and the
* cluster won't be freed.
*
* Context: Caller must ensure the folio is locked and in the swap cache.
* Return: Pointer to the swap cluster.
*/
static inline struct swap_cluster_info *swap_cluster_get_and_lock(
const struct folio *folio)
{
return __swap_cluster_get_and_lock(folio, false);
}
/*
* swap_cluster_get_and_lock_irq - Locks the cluster that holds a folio's entries.
* @folio: The folio.
*
* Same as swap_cluster_get_and_lock but also disable IRQ.
*
* Context: Caller must ensure the folio is locked and in the swap cache.
* Return: Pointer to the swap cluster.
*/
static inline struct swap_cluster_info *swap_cluster_get_and_lock_irq(
const struct folio *folio)
{
return __swap_cluster_get_and_lock(folio, true);
}
static inline void swap_cluster_unlock(struct swap_cluster_info *ci)
{
spin_unlock(&ci->lock);
}
static inline void swap_cluster_unlock_irq(struct swap_cluster_info *ci)
{
spin_unlock_irq(&ci->lock);
}
extern int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp);
/*
* Below are the core routines for doing swap for a folio.
* All helpers requires the folio to be locked, and a locked folio
* in the swap cache pins the swap entries / slots allocated to the
* folio, swap relies heavily on the swap cache and folio lock for
* synchronization.
*
* folio_alloc_swap(): the entry point for a folio to be swapped
* out. It allocates swap slots and pins the slots with swap cache.
* The slots start with a swap count of zero. The slots are pinned
* by swap cache reference which doesn't contribute to swap count.
*
* folio_dup_swap(): increases the swap count of a folio, usually
* during it gets unmapped and a swap entry is installed to replace
* it (e.g., swap entry in page table). A swap slot with swap
* count == 0 can only be increased by this helper.
*
* folio_put_swap(): does the opposite thing of folio_dup_swap().
*/
int folio_alloc_swap(struct folio *folio);
int folio_dup_swap(struct folio *folio, struct page *page);
void folio_put_swap(struct folio *folio, struct page *page);
/* For internal use */
extern void __swap_cluster_free_entries(struct swap_info_struct *si,
struct swap_cluster_info *ci,
unsigned int ci_off, unsigned int nr_pages);
/* linux/mm/page_io.c */
int sio_pool_init(void);
void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio);
void swap_read_submit(struct swap_io_ctx *ctx);
void swap_write_submit(struct swap_io_ctx *ctx);
int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio);
void __swap_writeout(struct swap_io_ctx *ctx, struct folio *folio);
/* linux/mm/swap_state.c */
extern struct address_space swap_space __read_mostly;
static inline struct address_space *swap_address_space(swp_entry_t entry)
{
return &swap_space;
}
/*
* Return the swap device position of the swap entry.
*/
static inline loff_t swap_dev_pos(swp_entry_t entry)
{
return ((loff_t)swp_offset(entry)) << PAGE_SHIFT;
}
/**
* folio_matches_swap_entry - Check if a folio matches a given swap entry.
* @folio: The folio.
* @entry: The swap entry to check against.
*
* Context: The caller should have the folio locked to ensure it's stable
* and nothing will move it in or out of the swap cache.
* Return: true or false.
*/
static inline bool folio_matches_swap_entry(const struct folio *folio,
swp_entry_t entry)
{
swp_entry_t folio_entry = folio->swap;
long nr_pages = folio_nr_pages(folio);
VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
if (!folio_test_swapcache(folio))
return false;
VM_WARN_ON_ONCE_FOLIO(!IS_ALIGNED(folio_entry.val, nr_pages), folio);
return folio_entry.val == round_down(entry.val, nr_pages);
}
/*
* All swap cache helpers below require the caller to ensure the swap entries
* used are valid and stabilize the device by any of the following ways:
* - Hold a reference by get_swap_device(): this ensures a single entry is
* valid and increases the swap device's refcount.
* - Locking a folio in the swap cache: this ensures the folio's swap entries
* are valid and pinned, also implies reference to the device.
* - Locking anything referencing the swap entry: e.g. PTL that protects
* swap entries in the page table, similar to locking swap cache folio.
* - See the comment of get_swap_device() for more complex usage.
*/
bool swap_cache_has_folio(swp_entry_t entry);
struct folio *swap_cache_get_folio(swp_entry_t entry);
void *swap_cache_get_shadow(swp_entry_t entry);
void swap_cache_del_folio(struct folio *folio);
struct folio *__swap_cache_alloc_folio(swp_entry_t target_entry, gfp_t gfp_mask,
unsigned long orders, struct vm_fault *vmf,
struct mempolicy *mpol, pgoff_t ilx);
/* Below helpers require the caller to lock and pass in the swap cluster. */
void __swap_cache_add_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry);
void __swap_cache_del_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry, void *shadow,
bool swapout);
void __swap_cache_replace_folio(struct swap_cluster_info *ci,
struct folio *old, struct folio *new);
void show_swap_cache_info(void);
struct folio *read_swap_cache_async(struct swap_io_ctx *ctx, swp_entry_t entry,
gfp_t gfp_mask, struct vm_area_struct *vma, unsigned long addr);
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 folio *swapin_sync(swp_entry_t entry, gfp_t flag, unsigned long 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);
#else /* CONFIG_SWAP */
static inline struct swap_cluster_info *swap_cluster_get_and_lock(
struct folio *folio)
{
return NULL;
}
static inline struct swap_cluster_info *swap_cluster_get_and_lock_irq(
struct folio *folio)
{
return NULL;
}
static inline void swap_cluster_unlock(struct swap_cluster_info *ci)
{
}
static inline void swap_cluster_unlock_irq(struct swap_cluster_info *ci)
{
}
static inline struct swap_info_struct *__swap_entry_to_info(swp_entry_t entry)
{
return NULL;
}
static inline int folio_alloc_swap(struct folio *folio)
{
return -EINVAL;
}
static inline int folio_dup_swap(struct folio *folio, struct page *page)
{
return -EINVAL;
}
static inline void folio_put_swap(struct folio *folio, struct page *page)
{
}
static inline void swap_read_folio(struct swap_io_ctx *ctx, struct folio *folio)
{
}
static inline void swap_write_submit(struct swap_io_ctx *ctx)
{
}
static inline struct address_space *swap_address_space(swp_entry_t entry)
{
return NULL;
}
static inline bool folio_matches_swap_entry(const struct folio *folio, swp_entry_t entry)
{
return false;
}
static inline void show_swap_cache_info(void)
{
}
static inline struct folio *swap_cluster_readahead(swp_entry_t entry,
gfp_t gfp_mask, struct mempolicy *mpol, pgoff_t ilx)
{
return NULL;
}
static inline struct folio *swapin_readahead(swp_entry_t swp, gfp_t gfp_mask,
struct vm_fault *vmf)
{
return NULL;
}
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)
{
return NULL;
}
static inline void swap_update_readahead(struct folio *folio,
struct vm_area_struct *vma, unsigned long addr)
{
}
static inline int swap_writeout(struct swap_io_ctx *ctx, struct folio *folio)
{
return 0;
}
static inline int swap_retry_table_alloc(swp_entry_t entry, gfp_t gfp)
{
return -EINVAL;
}
static inline bool swap_cache_has_folio(swp_entry_t entry)
{
return false;
}
static inline struct folio *swap_cache_get_folio(swp_entry_t entry)
{
return NULL;
}
static inline void *swap_cache_get_shadow(swp_entry_t entry)
{
return NULL;
}
static inline void swap_cache_del_folio(struct folio *folio)
{
}
static inline void __swap_cache_del_folio(struct swap_cluster_info *ci,
struct folio *folio, swp_entry_t entry, void *shadow,
bool swapout)
{
}
static inline void __swap_cache_replace_folio(struct swap_cluster_info *ci,
struct folio *old, struct folio *new)
{
}
#endif /* CONFIG_SWAP */
extern const struct swap_ops swap_bdev_ops;
int shmem_writeout(struct swap_io_ctx *ctx, struct folio *folio,
struct list_head *folio_list);
#endif /* _MM_SWAP_H */