Quelle zswap.c
Sprache: C
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* zswap . c - zswap driver file
*
* zswap is a cache that takes pages that are in the process
* of being swapped out and attempts to compress and store them in a
* RAM - based memory pool . This can result in a significant I / O reduction on
* the swap device and , in the case where decompressing from RAM is faster
* than reading from the swap device , can also improve workload performance .
*
* Copyright ( C ) 2012 Seth Jennings < sjenning @ linux . vnet . ibm . com >
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <linux/swap.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
#include <linux/mempolicy.h>
#include <linux/mempool.h>
#include <linux/zpool.h>
#include <crypto/acompress.h>
#include <linux/zswap.h>
#include <linux/mm_types.h>
#include <linux/page-flags.h>
#include <linux/swapops.h>
#include <linux/writeback.h>
#include <linux/pagemap.h>
#include <linux/workqueue.h>
#include <linux/list_lru.h>
#include "swap.h"
#include "internal.h"
/*********************************
* statistics
**********************************/
/* The number of compressed pages currently stored in zswap */
atomic_long_t zswap_stored_pages = ATOMIC_LONG_INIT(0 );
/*
* The statistics below are not protected from concurrent access for
* performance reasons so they may not be a 100 % accurate . However ,
* they do provide useful information on roughly how many times a
* certain event is occurring .
*/
/* Pool limit was hit (see zswap_max_pool_percent) */
static u64 zswap_pool_limit_hit;
/* Pages written back when pool limit was reached */
static u64 zswap_written_back_pages;
/* Store failed due to a reclaim failure after pool limit was reached */
static u64 zswap_reject_reclaim_fail;
/* Store failed due to compression algorithm failure */
static u64 zswap_reject_compress_fail;
/* Compressed page was too big for the allocator to (optimally) store */
static u64 zswap_reject_compress_poor;
/* Load or writeback failed due to decompression failure */
static u64 zswap_decompress_fail;
/* Store failed because underlying allocator could not get memory */
static u64 zswap_reject_alloc_fail;
/* Store failed because the entry metadata could not be allocated (rare) */
static u64 zswap_reject_kmemcache_fail;
/* Shrinker work queue */
static struct workqueue_struct *shrink_wq;
/* Pool limit was hit, we need to calm down */
static bool zswap_pool_reached_full;
/*********************************
* tunables
**********************************/
#define ZSWAP_PARAM_UNSET ""
static int zswap_setup(void );
/* Enable/disable zswap */
static DEFINE_STATIC_KEY_MAYBE(CONFIG_ZSWAP_DEFAULT_ON, zswap_ever_enabled);
static bool zswap_enabled = IS_ENABLED(CONFIG_ZSWAP_DEFAULT_ON);
static int zswap_enabled_param_set(const char *,
const struct kernel_param *);
static const struct kernel_param_ops zswap_enabled_param_ops = {
.set = zswap_enabled_param_set,
.get = param_get_bool,
};
module_param_cb(enabled, &zswap_enabled_param_ops, &zswap_enabled, 0644 );
/* Crypto compressor to use */
static char *zswap_compressor = CONFIG_ZSWAP_COMPRESSOR_DEFAULT;
static int zswap_compressor_param_set(const char *,
const struct kernel_param *);
static const struct kernel_param_ops zswap_compressor_param_ops = {
.set = zswap_compressor_param_set,
.get = param_get_charp,
.free = param_free_charp,
};
module_param_cb(compressor, &zswap_compressor_param_ops,
&zswap_compressor, 0644 );
/* Compressed storage zpool to use */
static char *zswap_zpool_type = CONFIG_ZSWAP_ZPOOL_DEFAULT;
static int zswap_zpool_param_set(const char *, const struct kernel_param *);
static const struct kernel_param_ops zswap_zpool_param_ops = {
.set = zswap_zpool_param_set,
.get = param_get_charp,
.free = param_free_charp,
};
module_param_cb(zpool, &zswap_zpool_param_ops, &zswap_zpool_type, 0644 );
/* The maximum percentage of memory that the compressed pool can occupy */
static unsigned int zswap_max_pool_percent = 20 ;
module_param_named(max_pool_percent, zswap_max_pool_percent, uint, 0644 );
/* The threshold for accepting new pages after the max_pool_percent was hit */
static unsigned int zswap_accept_thr_percent = 90 ; /* of max pool size */
module_param_named(accept_threshold_percent, zswap_accept_thr_percent,
uint, 0644 );
/* Enable/disable memory pressure-based shrinker. */
static bool zswap_shrinker_enabled = IS_ENABLED(
CONFIG_ZSWAP_SHRINKER_DEFAULT_ON);
module_param_named(shrinker_enabled, zswap_shrinker_enabled, bool , 0644 );
bool zswap_is_enabled(void )
{
return zswap_enabled;
}
bool zswap_never_enabled(void )
{
return !static_branch_maybe(CONFIG_ZSWAP_DEFAULT_ON, &zswap_ever_enabled);
}
/*********************************
* data structures
**********************************/
struct crypto_acomp_ctx {
struct crypto_acomp *acomp;
struct acomp_req *req;
struct crypto_wait wait;
u8 *buffer;
struct mutex mutex;
bool is_sleepable;
};
/*
* The lock ordering is zswap_tree . lock - > zswap_pool . lru_lock .
* The only case where lru_lock is not acquired while holding tree . lock is
* when a zswap_entry is taken off the lru for writeback , in that case it
* needs to be verified that it ' s still valid in the tree .
*/
struct zswap_pool {
struct zpool *zpool;
struct crypto_acomp_ctx __percpu *acomp_ctx;
struct percpu_ref ref;
struct list_head list;
struct work_struct release_work;
struct hlist_node node;
char tfm_name[CRYPTO_MAX_ALG_NAME];
};
/* Global LRU lists shared by all zswap pools. */
static struct list_lru zswap_list_lru;
/* The lock protects zswap_next_shrink updates. */
static DEFINE_SPINLOCK(zswap_shrink_lock);
static struct mem_cgroup *zswap_next_shrink;
static struct work_struct zswap_shrink_work;
static struct shrinker *zswap_shrinker;
/*
* struct zswap_entry
*
* This structure contains the metadata for tracking a single compressed
* page within zswap .
*
* swpentry - associated swap entry , the offset indexes into the red - black tree
* length - the length in bytes of the compressed page data . Needed during
* decompression .
* referenced - true if the entry recently entered the zswap pool . Unset by the
* writeback logic . The entry is only reclaimed by the writeback
* logic if referenced is unset . See comments in the shrinker
* section for context .
* pool - the zswap_pool the entry ' s data is in
* handle - zpool allocation handle that stores the compressed page data
* objcg - the obj_cgroup that the compressed memory is charged to
* lru - handle to the pool ' s lru used to evict pages .
*/
struct zswap_entry {
swp_entry_t swpentry;
unsigned int length;
bool referenced;
struct zswap_pool *pool;
unsigned long handle;
struct obj_cgroup *objcg;
struct list_head lru;
};
static struct xarray *zswap_trees[MAX_SWAPFILES];
static unsigned int nr_zswap_trees[MAX_SWAPFILES];
/* RCU-protected iteration */
static LIST_HEAD(zswap_pools);
/* protects zswap_pools list modification */
static DEFINE_SPINLOCK(zswap_pools_lock);
/* pool counter to provide unique names to zpool */
static atomic_t zswap_pools_count = ATOMIC_INIT(0 );
enum zswap_init_type {
ZSWAP_UNINIT,
ZSWAP_INIT_SUCCEED,
ZSWAP_INIT_FAILED
};
static enum zswap_init_type zswap_init_state;
/* used to ensure the integrity of initialization */
static DEFINE_MUTEX(zswap_init_lock);
/* init completed, but couldn't create the initial pool */
static bool zswap_has_pool;
/*********************************
* helpers and fwd declarations
**********************************/
static inline struct xarray *swap_zswap_tree(swp_entry_t swp)
{
return &zswap_trees[swp_type(swp)][swp_offset(swp)
>> SWAP_ADDRESS_SPACE_SHIFT];
}
#define zswap_pool_debug(msg, p) \
pr_debug("%s pool %s/%s\n" , msg, (p)->tfm_name, \
zpool_get_type((p)->zpool))
/*********************************
* pool functions
**********************************/
static void __zswap_pool_empty(struct percpu_ref *ref);
static struct zswap_pool *zswap_pool_create(char *type, char *compressor)
{
struct zswap_pool *pool;
char name[38 ]; /* 'zswap' + 32 char (max) num + \0 */
gfp_t gfp = __GFP_NORETRY | __GFP_NOWARN | __GFP_KSWAPD_RECLAIM;
int ret, cpu;
if (!zswap_has_pool) {
/* if either are unset, pool initialization failed, and we
* need both params to be set correctly before trying to
* create a pool .
*/
if (!strcmp(type, ZSWAP_PARAM_UNSET))
return NULL;
if (!strcmp(compressor, ZSWAP_PARAM_UNSET))
return NULL;
}
pool = kzalloc(sizeof (*pool), GFP_KERNEL);
if (!pool)
return NULL;
/* unique name for each pool specifically required by zsmalloc */
snprintf(name, 38 , "zswap%x" , atomic_inc_return(&zswap_pools_count));
pool->zpool = zpool_create_pool(type, name, gfp);
if (!pool->zpool) {
pr_err("%s zpool not available\n" , type);
goto error;
}
pr_debug("using %s zpool\n" , zpool_get_type(pool->zpool));
strscpy(pool->tfm_name, compressor, sizeof (pool->tfm_name));
pool->acomp_ctx = alloc_percpu(*pool->acomp_ctx);
if (!pool->acomp_ctx) {
pr_err("percpu alloc failed\n" );
goto error;
}
for_each_possible_cpu(cpu)
mutex_init(&per_cpu_ptr(pool->acomp_ctx, cpu)->mutex);
ret = cpuhp_state_add_instance(CPUHP_MM_ZSWP_POOL_PREPARE,
&pool->node);
if (ret)
goto error;
/* being the current pool takes 1 ref; this func expects the
* caller to always add the new pool as the current pool
*/
ret = percpu_ref_init(&pool->ref, __zswap_pool_empty,
PERCPU_REF_ALLOW_REINIT, GFP_KERNEL);
if (ret)
goto ref_fail;
INIT_LIST_HEAD(&pool->list);
zswap_pool_debug("created" , pool);
return pool;
ref_fail:
cpuhp_state_remove_instance(CPUHP_MM_ZSWP_POOL_PREPARE, &pool->node);
error:
if (pool->acomp_ctx)
free_percpu(pool->acomp_ctx);
if (pool->zpool)
zpool_destroy_pool(pool->zpool);
kfree(pool);
return NULL;
}
static struct zswap_pool *__zswap_pool_create_fallback(void )
{
bool has_comp, has_zpool;
has_comp = crypto_has_acomp(zswap_compressor, 0 , 0 );
if (!has_comp && strcmp(zswap_compressor,
CONFIG_ZSWAP_COMPRESSOR_DEFAULT)) {
pr_err("compressor %s not available, using default %s\n" ,
zswap_compressor, CONFIG_ZSWAP_COMPRESSOR_DEFAULT);
param_free_charp(&zswap_compressor);
zswap_compressor = CONFIG_ZSWAP_COMPRESSOR_DEFAULT;
has_comp = crypto_has_acomp(zswap_compressor, 0 , 0 );
}
if (!has_comp) {
pr_err("default compressor %s not available\n" ,
zswap_compressor);
param_free_charp(&zswap_compressor);
zswap_compressor = ZSWAP_PARAM_UNSET;
}
has_zpool = zpool_has_pool(zswap_zpool_type);
if (!has_zpool && strcmp(zswap_zpool_type,
CONFIG_ZSWAP_ZPOOL_DEFAULT)) {
pr_err("zpool %s not available, using default %s\n" ,
zswap_zpool_type, CONFIG_ZSWAP_ZPOOL_DEFAULT);
param_free_charp(&zswap_zpool_type);
zswap_zpool_type = CONFIG_ZSWAP_ZPOOL_DEFAULT;
has_zpool = zpool_has_pool(zswap_zpool_type);
}
if (!has_zpool) {
pr_err("default zpool %s not available\n" ,
zswap_zpool_type);
param_free_charp(&zswap_zpool_type);
zswap_zpool_type = ZSWAP_PARAM_UNSET;
}
if (!has_comp || !has_zpool)
return NULL;
return zswap_pool_create(zswap_zpool_type, zswap_compressor);
}
static void zswap_pool_destroy(struct zswap_pool *pool)
{
zswap_pool_debug("destroying" , pool);
cpuhp_state_remove_instance(CPUHP_MM_ZSWP_POOL_PREPARE, &pool->node);
free_percpu(pool->acomp_ctx);
zpool_destroy_pool(pool->zpool);
kfree(pool);
}
static void __zswap_pool_release(struct work_struct *work)
{
struct zswap_pool *pool = container_of(work, typeof(*pool),
release_work);
synchronize_rcu();
/* nobody should have been able to get a ref... */
WARN_ON(!percpu_ref_is_zero(&pool->ref));
percpu_ref_exit(&pool->ref);
/* pool is now off zswap_pools list and has no references. */
zswap_pool_destroy(pool);
}
static struct zswap_pool *zswap_pool_current(void );
static void __zswap_pool_empty(struct percpu_ref *ref)
{
struct zswap_pool *pool;
pool = container_of(ref, typeof(*pool), ref);
spin_lock_bh(&zswap_pools_lock);
WARN_ON(pool == zswap_pool_current());
list_del_rcu(&pool->list);
INIT_WORK(&pool->release_work, __zswap_pool_release);
schedule_work(&pool->release_work);
spin_unlock_bh(&zswap_pools_lock);
}
static int __must_check zswap_pool_tryget(struct zswap_pool *pool)
{
if (!pool)
return 0 ;
return percpu_ref_tryget(&pool->ref);
}
/* The caller must already have a reference. */
static void zswap_pool_get(struct zswap_pool *pool)
{
percpu_ref_get(&pool->ref);
}
static void zswap_pool_put(struct zswap_pool *pool)
{
percpu_ref_put(&pool->ref);
}
static struct zswap_pool *__zswap_pool_current(void )
{
struct zswap_pool *pool;
pool = list_first_or_null_rcu(&zswap_pools, typeof(*pool), list);
WARN_ONCE(!pool && zswap_has_pool,
"%s: no page storage pool!\n" , __func__);
return pool;
}
static struct zswap_pool *zswap_pool_current(void )
{
assert_spin_locked(&zswap_pools_lock);
return __zswap_pool_current();
}
static struct zswap_pool *zswap_pool_current_get(void )
{
struct zswap_pool *pool;
rcu_read_lock();
pool = __zswap_pool_current();
if (!zswap_pool_tryget(pool))
pool = NULL;
rcu_read_unlock();
return pool;
}
/* type and compressor must be null-terminated */
static struct zswap_pool *zswap_pool_find_get(char *type, char *compressor)
{
struct zswap_pool *pool;
assert_spin_locked(&zswap_pools_lock);
list_for_each_entry_rcu(pool, &zswap_pools, list) {
if (strcmp(pool->tfm_name, compressor))
continue ;
if (strcmp(zpool_get_type(pool->zpool), type))
continue ;
/* if we can't get it, it's about to be destroyed */
if (!zswap_pool_tryget(pool))
continue ;
return pool;
}
return NULL;
}
static unsigned long zswap_max_pages(void )
{
return totalram_pages() * zswap_max_pool_percent / 100 ;
}
static unsigned long zswap_accept_thr_pages(void )
{
return zswap_max_pages() * zswap_accept_thr_percent / 100 ;
}
unsigned long zswap_total_pages(void )
{
struct zswap_pool *pool;
unsigned long total = 0 ;
rcu_read_lock();
list_for_each_entry_rcu(pool, &zswap_pools, list)
total += zpool_get_total_pages(pool->zpool);
rcu_read_unlock();
return total;
}
static bool zswap_check_limits(void )
{
unsigned long cur_pages = zswap_total_pages();
unsigned long max_pages = zswap_max_pages();
if (cur_pages >= max_pages) {
zswap_pool_limit_hit++;
zswap_pool_reached_full = true ;
} else if (zswap_pool_reached_full &&
cur_pages <= zswap_accept_thr_pages()) {
zswap_pool_reached_full = false ;
}
return zswap_pool_reached_full;
}
/*********************************
* param callbacks
**********************************/
static bool zswap_pool_changed(const char *s, const struct kernel_param *kp)
{
/* no change required */
if (!strcmp(s, *(char **)kp->arg) && zswap_has_pool)
return false ;
return true ;
}
/* val must be a null-terminated string */
static int __zswap_param_set(const char *val, const struct kernel_param *kp,
char *type, char *compressor)
{
struct zswap_pool *pool, *put_pool = NULL;
char *s = strstrip((char *)val);
int ret = 0 ;
bool new_pool = false ;
mutex_lock(&zswap_init_lock);
switch (zswap_init_state) {
case ZSWAP_UNINIT:
/* if this is load-time (pre-init) param setting,
* don ' t create a pool ; that ' s done during init .
*/
ret = param_set_charp(s, kp);
break ;
case ZSWAP_INIT_SUCCEED:
new_pool = zswap_pool_changed(s, kp);
break ;
case ZSWAP_INIT_FAILED:
pr_err("can't set param, initialization failed\n" );
ret = -ENODEV;
}
mutex_unlock(&zswap_init_lock);
/* no need to create a new pool, return directly */
if (!new_pool)
return ret;
if (!type) {
if (!zpool_has_pool(s)) {
pr_err("zpool %s not available\n" , s);
return -ENOENT;
}
type = s;
} else if (!compressor) {
if (!crypto_has_acomp(s, 0 , 0 )) {
pr_err("compressor %s not available\n" , s);
return -ENOENT;
}
compressor = s;
} else {
WARN_ON(1 );
return -EINVAL;
}
spin_lock_bh(&zswap_pools_lock);
pool = zswap_pool_find_get(type, compressor);
if (pool) {
zswap_pool_debug("using existing" , pool);
WARN_ON(pool == zswap_pool_current());
list_del_rcu(&pool->list);
}
spin_unlock_bh(&zswap_pools_lock);
if (!pool)
pool = zswap_pool_create(type, compressor);
else {
/*
* Restore the initial ref dropped by percpu_ref_kill ( )
* when the pool was decommissioned and switch it again
* to percpu mode .
*/
percpu_ref_resurrect(&pool->ref);
/* Drop the ref from zswap_pool_find_get(). */
zswap_pool_put(pool);
}
if (pool)
ret = param_set_charp(s, kp);
else
ret = -EINVAL;
spin_lock_bh(&zswap_pools_lock);
if (!ret) {
put_pool = zswap_pool_current();
list_add_rcu(&pool->list, &zswap_pools);
zswap_has_pool = true ;
} else if (pool) {
/* add the possibly pre-existing pool to the end of the pools
* list ; if it ' s new ( and empty ) then it ' ll be removed and
* destroyed by the put after we drop the lock
*/
list_add_tail_rcu(&pool->list, &zswap_pools);
put_pool = pool;
}
spin_unlock_bh(&zswap_pools_lock);
if (!zswap_has_pool && !pool) {
/* if initial pool creation failed, and this pool creation also
* failed , maybe both compressor and zpool params were bad .
* Allow changing this param , so pool creation will succeed
* when the other param is changed . We already verified this
* param is ok in the zpool_has_pool ( ) or crypto_has_acomp ( )
* checks above .
*/
ret = param_set_charp(s, kp);
}
/* drop the ref from either the old current pool,
* or the new pool we failed to add
*/
if (put_pool)
percpu_ref_kill(&put_pool->ref);
return ret;
}
static int zswap_compressor_param_set(const char *val,
const struct kernel_param *kp)
{
return __zswap_param_set(val, kp, zswap_zpool_type, NULL);
}
static int zswap_zpool_param_set(const char *val,
const struct kernel_param *kp)
{
return __zswap_param_set(val, kp, NULL, zswap_compressor);
}
static int zswap_enabled_param_set(const char *val,
const struct kernel_param *kp)
{
int ret = -ENODEV;
/* if this is load-time (pre-init) param setting, only set param. */
if (system_state != SYSTEM_RUNNING)
return param_set_bool(val, kp);
mutex_lock(&zswap_init_lock);
switch (zswap_init_state) {
case ZSWAP_UNINIT:
if (zswap_setup())
break ;
fallthrough;
case ZSWAP_INIT_SUCCEED:
if (!zswap_has_pool)
pr_err("can't enable, no pool configured\n" );
else
ret = param_set_bool(val, kp);
break ;
case ZSWAP_INIT_FAILED:
pr_err("can't enable, initialization failed\n" );
}
mutex_unlock(&zswap_init_lock);
return ret;
}
/*********************************
* lru functions
**********************************/
/* should be called under RCU */
#ifdef CONFIG_MEMCG
static inline struct mem_cgroup *mem_cgroup_from_entry(struct zswap_entry *entry)
{
return entry->objcg ? obj_cgroup_memcg(entry->objcg) : NULL;
}
#else
static inline struct mem_cgroup *mem_cgroup_from_entry(struct zswap_entry *entry)
{
return NULL;
}
#endif
static inline int entry_to_nid(struct zswap_entry *entry)
{
return page_to_nid(virt_to_page(entry));
}
static void zswap_lru_add(struct list_lru *list_lru, struct zswap_entry *entry)
{
int nid = entry_to_nid(entry);
struct mem_cgroup *memcg;
/*
* Note that it is safe to use rcu_read_lock ( ) here , even in the face of
* concurrent memcg offlining :
*
* 1 . list_lru_add ( ) is called before list_lru_one is dead . The
* new entry will be reparented to memcg ' s parent ' s list_lru .
* 2 . list_lru_add ( ) is called after list_lru_one is dead . The
* new entry will be added directly to memcg ' s parent ' s list_lru .
*
* Similar reasoning holds for list_lru_del ( ) .
*/
rcu_read_lock();
memcg = mem_cgroup_from_entry(entry);
/* will always succeed */
list_lru_add(list_lru, &entry->lru, nid, memcg);
rcu_read_unlock();
}
static void zswap_lru_del(struct list_lru *list_lru, struct zswap_entry *entry)
{
int nid = entry_to_nid(entry);
struct mem_cgroup *memcg;
rcu_read_lock();
memcg = mem_cgroup_from_entry(entry);
/* will always succeed */
list_lru_del(list_lru, &entry->lru, nid, memcg);
rcu_read_unlock();
}
void zswap_lruvec_state_init(struct lruvec *lruvec)
{
atomic_long_set(&lruvec->zswap_lruvec_state.nr_disk_swapins, 0 );
}
void zswap_folio_swapin(struct folio *folio)
{
struct lruvec *lruvec;
if (folio) {
lruvec = folio_lruvec(folio);
atomic_long_inc(&lruvec->zswap_lruvec_state.nr_disk_swapins);
}
}
/*
* This function should be called when a memcg is being offlined .
*
* Since the global shrinker shrink_worker ( ) may hold a reference
* of the memcg , we must check and release the reference in
* zswap_next_shrink .
*
* shrink_worker ( ) must handle the case where this function releases
* the reference of memcg being shrunk .
*/
void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg)
{
/* lock out zswap shrinker walking memcg tree */
spin_lock(&zswap_shrink_lock);
if (zswap_next_shrink == memcg) {
do {
zswap_next_shrink = mem_cgroup_iter(NULL, zswap_next_shrink, NULL);
} while (zswap_next_shrink && !mem_cgroup_online(zswap_next_shrink));
}
spin_unlock(&zswap_shrink_lock);
}
/*********************************
* zswap entry functions
**********************************/
static struct kmem_cache *zswap_entry_cache;
static struct zswap_entry *zswap_entry_cache_alloc(gfp_t gfp, int nid)
{
struct zswap_entry *entry;
entry = kmem_cache_alloc_node(zswap_entry_cache, gfp, nid);
if (!entry)
return NULL;
return entry;
}
static void zswap_entry_cache_free(struct zswap_entry *entry)
{
kmem_cache_free(zswap_entry_cache, entry);
}
/*
* Carries out the common pattern of freeing and entry ' s zpool allocation ,
* freeing the entry itself , and decrementing the number of stored pages .
*/
static void zswap_entry_free(struct zswap_entry *entry)
{
zswap_lru_del(&zswap_list_lru, entry);
zpool_free(entry->pool->zpool, entry->handle);
zswap_pool_put(entry->pool);
if (entry->objcg) {
obj_cgroup_uncharge_zswap(entry->objcg, entry->length);
obj_cgroup_put(entry->objcg);
}
zswap_entry_cache_free(entry);
atomic_long_dec(&zswap_stored_pages);
}
/*********************************
* compressed storage functions
**********************************/
static int zswap_cpu_comp_prepare(unsigned int cpu, struct hlist_node *node)
{
struct zswap_pool *pool = hlist_entry(node, struct zswap_pool, node);
struct crypto_acomp_ctx *acomp_ctx = per_cpu_ptr(pool->acomp_ctx, cpu);
struct crypto_acomp *acomp = NULL;
struct acomp_req *req = NULL;
u8 *buffer = NULL;
int ret;
buffer = kmalloc_node(PAGE_SIZE * 2 , GFP_KERNEL, cpu_to_node(cpu));
if (!buffer) {
ret = -ENOMEM;
goto fail;
}
acomp = crypto_alloc_acomp_node(pool->tfm_name, 0 , 0 , cpu_to_node(cpu));
if (IS_ERR(acomp)) {
pr_err("could not alloc crypto acomp %s : %ld\n" ,
pool->tfm_name, PTR_ERR(acomp));
ret = PTR_ERR(acomp);
goto fail;
}
req = acomp_request_alloc(acomp);
if (!req) {
pr_err("could not alloc crypto acomp_request %s\n" ,
pool->tfm_name);
ret = -ENOMEM;
goto fail;
}
/*
* Only hold the mutex after completing allocations , otherwise we may
* recurse into zswap through reclaim and attempt to hold the mutex
* again resulting in a deadlock .
*/
mutex_lock(&acomp_ctx->mutex);
crypto_init_wait(&acomp_ctx->wait);
/*
* if the backend of acomp is async zip , crypto_req_done ( ) will wakeup
* crypto_wait_req ( ) ; if the backend of acomp is scomp , the callback
* won ' t be called , crypto_wait_req ( ) will return without blocking .
*/
acomp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
crypto_req_done, &acomp_ctx->wait);
acomp_ctx->buffer = buffer;
acomp_ctx->acomp = acomp;
acomp_ctx->is_sleepable = acomp_is_async(acomp);
acomp_ctx->req = req;
mutex_unlock(&acomp_ctx->mutex);
return 0 ;
fail:
if (acomp)
crypto_free_acomp(acomp);
kfree(buffer);
return ret;
}
static int zswap_cpu_comp_dead(unsigned int cpu, struct hlist_node *node)
{
struct zswap_pool *pool = hlist_entry(node, struct zswap_pool, node);
struct crypto_acomp_ctx *acomp_ctx = per_cpu_ptr(pool->acomp_ctx, cpu);
struct acomp_req *req;
struct crypto_acomp *acomp;
u8 *buffer;
if (IS_ERR_OR_NULL(acomp_ctx))
return 0 ;
mutex_lock(&acomp_ctx->mutex);
req = acomp_ctx->req;
acomp = acomp_ctx->acomp;
buffer = acomp_ctx->buffer;
acomp_ctx->req = NULL;
acomp_ctx->acomp = NULL;
acomp_ctx->buffer = NULL;
mutex_unlock(&acomp_ctx->mutex);
/*
* Do the actual freeing after releasing the mutex to avoid subtle
* locking dependencies causing deadlocks .
*/
if (!IS_ERR_OR_NULL(req))
acomp_request_free(req);
if (!IS_ERR_OR_NULL(acomp))
crypto_free_acomp(acomp);
kfree(buffer);
return 0 ;
}
static struct crypto_acomp_ctx *acomp_ctx_get_cpu_lock(struct zswap_pool *pool)
{
struct crypto_acomp_ctx *acomp_ctx;
for (;;) {
acomp_ctx = raw_cpu_ptr(pool->acomp_ctx);
mutex_lock(&acomp_ctx->mutex);
if (likely(acomp_ctx->req))
return acomp_ctx;
/*
* It is possible that we were migrated to a different CPU after
* getting the per - CPU ctx but before the mutex was acquired . If
* the old CPU got offlined , zswap_cpu_comp_dead ( ) could have
* already freed ctx - > req ( among other things ) and set it to
* NULL . Just try again on the new CPU that we ended up on .
*/
mutex_unlock(&acomp_ctx->mutex);
}
}
static void acomp_ctx_put_unlock(struct crypto_acomp_ctx *acomp_ctx)
{
mutex_unlock(&acomp_ctx->mutex);
}
static bool zswap_compress(struct page *page, struct zswap_entry *entry,
struct zswap_pool *pool)
{
struct crypto_acomp_ctx *acomp_ctx;
struct scatterlist input, output;
int comp_ret = 0 , alloc_ret = 0 ;
unsigned int dlen = PAGE_SIZE;
unsigned long handle;
struct zpool *zpool;
gfp_t gfp;
u8 *dst;
acomp_ctx = acomp_ctx_get_cpu_lock(pool);
dst = acomp_ctx->buffer;
sg_init_table(&input, 1 );
sg_set_page(&input, page, PAGE_SIZE, 0 );
/*
* We need PAGE_SIZE * 2 here since there maybe over - compression case ,
* and hardware - accelerators may won ' t check the dst buffer size , so
* giving the dst buffer with enough length to avoid buffer overflow .
*/
sg_init_one(&output, dst, PAGE_SIZE * 2 );
acomp_request_set_params(acomp_ctx->req, &input, &output, PAGE_SIZE, dlen);
/*
* it maybe looks a little bit silly that we send an asynchronous request ,
* then wait for its completion synchronously . This makes the process look
* synchronous in fact .
* Theoretically , acomp supports users send multiple acomp requests in one
* acomp instance , then get those requests done simultaneously . but in this
* case , zswap actually does store and load page by page , there is no
* existing method to send the second page before the first page is done
* in one thread doing zwap .
* but in different threads running on different cpu , we have different
* acomp instance , so multiple threads can do ( de ) compression in parallel .
*/
comp_ret = crypto_wait_req(crypto_acomp_compress(acomp_ctx->req), &acomp_ctx->wait);
dlen = acomp_ctx->req->dlen;
if (comp_ret)
goto unlock;
zpool = pool->zpool;
gfp = GFP_NOWAIT | __GFP_NORETRY | __GFP_HIGHMEM | __GFP_MOVABLE;
alloc_ret = zpool_malloc(zpool, dlen, gfp, &handle, page_to_nid(page));
if (alloc_ret)
goto unlock;
zpool_obj_write(zpool, handle, dst, dlen);
entry->handle = handle;
entry->length = dlen;
unlock:
if (comp_ret == -ENOSPC || alloc_ret == -ENOSPC)
zswap_reject_compress_poor++;
else if (comp_ret)
zswap_reject_compress_fail++;
else if (alloc_ret)
zswap_reject_alloc_fail++;
acomp_ctx_put_unlock(acomp_ctx);
return comp_ret == 0 && alloc_ret == 0 ;
}
static bool zswap_decompress(struct zswap_entry *entry, struct folio *folio)
{
struct zpool *zpool = entry->pool->zpool;
struct scatterlist input, output;
struct crypto_acomp_ctx *acomp_ctx;
int decomp_ret, dlen;
u8 *src, *obj;
acomp_ctx = acomp_ctx_get_cpu_lock(entry->pool);
obj = zpool_obj_read_begin(zpool, entry->handle, acomp_ctx->buffer);
/*
* zpool_obj_read_begin ( ) might return a kmap address of highmem when
* acomp_ctx - > buffer is not used . However , sg_init_one ( ) does not
* handle highmem addresses , so copy the object to acomp_ctx - > buffer .
*/
if (virt_addr_valid(obj)) {
src = obj;
} else {
WARN_ON_ONCE(obj == acomp_ctx->buffer);
memcpy(acomp_ctx->buffer, obj, entry->length);
src = acomp_ctx->buffer;
}
sg_init_one(&input, src, entry->length);
sg_init_table(&output, 1 );
sg_set_folio(&output, folio, PAGE_SIZE, 0 );
acomp_request_set_params(acomp_ctx->req, &input, &output, entry->length, PAGE_SIZE);
decomp_ret = crypto_wait_req(crypto_acomp_decompress(acomp_ctx->req), &acomp_ctx->wait);
dlen = acomp_ctx->req->dlen;
zpool_obj_read_end(zpool, entry->handle, obj);
acomp_ctx_put_unlock(acomp_ctx);
if (!decomp_ret && dlen == PAGE_SIZE)
return true ;
zswap_decompress_fail++;
pr_alert_ratelimited("Decompression error from zswap (%d:%lu %s %u->%d)\n" ,
swp_type(entry->swpentry),
swp_offset(entry->swpentry),
entry->pool->tfm_name, entry->length, dlen);
return false ;
}
/*********************************
* writeback code
**********************************/
/*
* Attempts to free an entry by adding a folio to the swap cache ,
* decompressing the entry data into the folio , and issuing a
* bio write to write the folio back to the swap device .
*
* This can be thought of as a " resumed writeback " of the folio
* to the swap device . We are basically resuming the same swap
* writeback path that was intercepted with the zswap_store ( )
* in the first place . After the folio has been decompressed into
* the swap cache , the compressed version stored by zswap can be
* freed .
*/
static int zswap_writeback_entry(struct zswap_entry *entry,
swp_entry_t swpentry)
{
struct xarray *tree;
pgoff_t offset = swp_offset(swpentry);
struct folio *folio;
struct mempolicy *mpol;
bool folio_was_allocated;
struct swap_info_struct *si;
int ret = 0 ;
/* try to allocate swap cache folio */
si = get_swap_device(swpentry);
if (!si)
return -EEXIST;
mpol = get_task_policy(current);
folio = __read_swap_cache_async(swpentry, GFP_KERNEL, mpol,
NO_INTERLEAVE_INDEX, &folio_was_allocated, true );
put_swap_device(si);
if (!folio)
return -ENOMEM;
/*
* Found an existing folio , we raced with swapin or concurrent
* shrinker . We generally writeback cold folios from zswap , and
* swapin means the folio just became hot , so skip this folio .
* For unlikely concurrent shrinker case , it will be unlinked
* and freed when invalidated by the concurrent shrinker anyway .
*/
if (!folio_was_allocated) {
ret = -EEXIST;
goto out;
}
/*
* folio is locked , and the swapcache is now secured against
* concurrent swapping to and from the slot , and concurrent
* swapoff so we can safely dereference the zswap tree here .
* Verify that the swap entry hasn ' t been invalidated and recycled
* behind our backs , to avoid overwriting a new swap folio with
* old compressed data . Only when this is successful can the entry
* be dereferenced .
*/
tree = swap_zswap_tree(swpentry);
if (entry != xa_load(tree, offset)) {
ret = -ENOMEM;
goto out;
}
if (!zswap_decompress(entry, folio)) {
ret = -EIO;
goto out;
}
xa_erase(tree, offset);
count_vm_event(ZSWPWB);
if (entry->objcg)
count_objcg_events(entry->objcg, ZSWPWB, 1 );
zswap_entry_free(entry);
/* folio is up to date */
folio_mark_uptodate(folio);
/* move it to the tail of the inactive list after end_writeback */
folio_set_reclaim(folio);
/* start writeback */
__swap_writepage(folio, NULL);
out:
if (ret && ret != -EEXIST) {
delete_from_swap_cache(folio);
folio_unlock(folio);
}
folio_put(folio);
return ret;
}
/*********************************
* shrinker functions
**********************************/
/*
* The dynamic shrinker is modulated by the following factors :
*
* 1 . Each zswap entry has a referenced bit , which the shrinker unsets ( giving
* the entry a second chance ) before rotating it in the LRU list . If the
* entry is considered again by the shrinker , with its referenced bit unset ,
* it is written back . The writeback rate as a result is dynamically
* adjusted by the pool activities - if the pool is dominated by new entries
* ( i . e lots of recent zswapouts ) , these entries will be protected and
* the writeback rate will slow down . On the other hand , if the pool has a
* lot of stagnant entries , these entries will be reclaimed immediately ,
* effectively increasing the writeback rate .
*
* 2 . Swapins counter : If we observe swapins , it is a sign that we are
* overshrinking and should slow down . We maintain a swapins counter , which
* is consumed and subtract from the number of eligible objects on the LRU
* in zswap_shrinker_count ( ) .
*
* 3 . Compression ratio . The better the workload compresses , the less gains we
* can expect from writeback . We scale down the number of objects available
* for reclaim by this ratio .
*/
static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_one *l,
void *arg)
{
struct zswap_entry *entry = container_of(item, struct zswap_entry, lru);
bool *encountered_page_in_swapcache = (bool *)arg;
swp_entry_t swpentry;
enum lru_status ret = LRU_REMOVED_RETRY;
int writeback_result;
/*
* Second chance algorithm : if the entry has its referenced bit set , give it
* a second chance . Only clear the referenced bit and rotate it in the
* zswap ' s LRU list .
*/
if (entry->referenced) {
entry->referenced = false ;
return LRU_ROTATE;
}
/*
* As soon as we drop the LRU lock , the entry can be freed by
* a concurrent invalidation . This means the following :
*
* 1 . We extract the swp_entry_t to the stack , allowing
* zswap_writeback_entry ( ) to pin the swap entry and
* then validate the zwap entry against that swap entry ' s
* tree using pointer value comparison . Only when that
* is successful can the entry be dereferenced .
*
* 2 . Usually , objects are taken off the LRU for reclaim . In
* this case this isn ' t possible , because if reclaim fails
* for whatever reason , we have no means of knowing if the
* entry is alive to put it back on the LRU .
*
* So rotate it before dropping the lock . If the entry is
* written back or invalidated , the free path will unlink
* it . For failures , rotation is the right thing as well .
*
* Temporary failures , where the same entry should be tried
* again immediately , almost never happen for this shrinker .
* We don ' t do any trylocking ; - ENOMEM comes closest ,
* but that ' s extremely rare and doesn ' t happen spuriously
* either . Don ' t bother distinguishing this case .
*/
list_move_tail(item, &l->list);
/*
* Once the lru lock is dropped , the entry might get freed . The
* swpentry is copied to the stack , and entry isn ' t deref ' d again
* until the entry is verified to still be alive in the tree .
*/
swpentry = entry->swpentry;
/*
* It ' s safe to drop the lock here because we return either
* LRU_REMOVED_RETRY , LRU_RETRY or LRU_STOP .
*/
spin_unlock(&l->lock);
writeback_result = zswap_writeback_entry(entry, swpentry);
if (writeback_result) {
zswap_reject_reclaim_fail++;
ret = LRU_RETRY;
/*
* Encountering a page already in swap cache is a sign that we are shrinking
* into the warmer region . We should terminate shrinking ( if we ' re in the dynamic
* shrinker context ) .
*/
if (writeback_result == -EEXIST && encountered_page_in_swapcache) {
ret = LRU_STOP;
*encountered_page_in_swapcache = true ;
}
} else {
zswap_written_back_pages++;
}
return ret;
}
static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
struct shrink_control *sc)
{
unsigned long shrink_ret;
bool encountered_page_in_swapcache = false ;
if (!zswap_shrinker_enabled ||
!mem_cgroup_zswap_writeback_enabled(sc->memcg)) {
sc->nr_scanned = 0 ;
return SHRINK_STOP;
}
shrink_ret = list_lru_shrink_walk(&zswap_list_lru, sc, &shrink_memcg_cb,
&encountered_page_in_swapcache);
if (encountered_page_in_swapcache)
return SHRINK_STOP;
return shrink_ret ? shrink_ret : SHRINK_STOP;
}
static unsigned long zswap_shrinker_count(struct shrinker *shrinker,
struct shrink_control *sc)
{
struct mem_cgroup *memcg = sc->memcg;
struct lruvec *lruvec = mem_cgroup_lruvec(memcg, NODE_DATA(sc->nid));
atomic_long_t *nr_disk_swapins =
&lruvec->zswap_lruvec_state.nr_disk_swapins;
unsigned long nr_backing, nr_stored, nr_freeable, nr_disk_swapins_cur,
nr_remain;
if (!zswap_shrinker_enabled || !mem_cgroup_zswap_writeback_enabled(memcg))
return 0 ;
/*
* The shrinker resumes swap writeback , which will enter block
* and may enter fs . XXX : Harmonize with vmscan . c _ _ GFP_FS
* rules ( may_enter_fs ( ) ) , which apply on a per - folio basis .
*/
if (!gfp_has_io_fs(sc->gfp_mask))
return 0 ;
/*
* For memcg , use the cgroup - wide ZSWAP stats since we don ' t
* have them per - node and thus per - lruvec . Careful if memcg is
* runtime - disabled : we can get sc - > memcg = = NULL , which is ok
* for the lruvec , but not for memcg_page_state ( ) .
*
* Without memcg , use the zswap pool - wide metrics .
*/
if (!mem_cgroup_disabled()) {
mem_cgroup_flush_stats(memcg);
nr_backing = memcg_page_state(memcg, MEMCG_ZSWAP_B) >> PAGE_SHIFT;
nr_stored = memcg_page_state(memcg, MEMCG_ZSWAPPED);
} else {
nr_backing = zswap_total_pages();
nr_stored = atomic_long_read(&zswap_stored_pages);
}
if (!nr_stored)
return 0 ;
nr_freeable = list_lru_shrink_count(&zswap_list_lru, sc);
if (!nr_freeable)
return 0 ;
/*
* Subtract from the lru size the number of pages that are recently swapped
* in from disk . The idea is that had we protect the zswap ' s LRU by this
* amount of pages , these disk swapins would not have happened .
*/
nr_disk_swapins_cur = atomic_long_read(nr_disk_swapins);
do {
if (nr_freeable >= nr_disk_swapins_cur)
nr_remain = 0 ;
else
nr_remain = nr_disk_swapins_cur - nr_freeable;
} while (!atomic_long_try_cmpxchg(
nr_disk_swapins, &nr_disk_swapins_cur, nr_remain));
nr_freeable -= nr_disk_swapins_cur - nr_remain;
if (!nr_freeable)
return 0 ;
/*
* Scale the number of freeable pages by the memory saving factor .
* This ensures that the better zswap compresses memory , the fewer
* pages we will evict to swap ( as it will otherwise incur IO for
* relatively small memory saving ) .
*/
return mult_frac(nr_freeable, nr_backing, nr_stored);
}
static struct shrinker *zswap_alloc_shrinker(void )
{
struct shrinker *shrinker;
shrinker =
shrinker_alloc(SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE, "mm-zswap" );
if (!shrinker)
return NULL;
shrinker->scan_objects = zswap_shrinker_scan;
shrinker->count_objects = zswap_shrinker_count;
shrinker->batch = 0 ;
shrinker->seeks = DEFAULT_SEEKS;
return shrinker;
}
static int shrink_memcg(struct mem_cgroup *memcg)
{
int nid, shrunk = 0 , scanned = 0 ;
if (!mem_cgroup_zswap_writeback_enabled(memcg))
return -ENOENT;
/*
* Skip zombies because their LRUs are reparented and we would be
* reclaiming from the parent instead of the dead memcg .
*/
if (memcg && !mem_cgroup_online(memcg))
return -ENOENT;
for_each_node_state(nid, N_NORMAL_MEMORY) {
unsigned long nr_to_walk = 1 ;
shrunk += list_lru_walk_one(&zswap_list_lru, nid, memcg,
&shrink_memcg_cb, NULL, &nr_to_walk);
scanned += 1 - nr_to_walk;
}
if (!scanned)
return -ENOENT;
return shrunk ? 0 : -EAGAIN;
}
static void shrink_worker(struct work_struct *w)
{
struct mem_cgroup *memcg;
int ret, failures = 0 , attempts = 0 ;
unsigned long thr;
/* Reclaim down to the accept threshold */
thr = zswap_accept_thr_pages();
/*
* Global reclaim will select cgroup in a round - robin fashion from all
* online memcgs , but memcgs that have no pages in zswap and
* writeback - disabled memcgs ( memory . zswap . writeback = 0 ) are not
* candidates for shrinking .
*
* Shrinking will be aborted if we encounter the following
* MAX_RECLAIM_RETRIES times :
* - No writeback - candidate memcgs found in a memcg tree walk .
* - Shrinking a writeback - candidate memcg failed .
*
* We save iteration cursor memcg into zswap_next_shrink ,
* which can be modified by the offline memcg cleaner
* zswap_memcg_offline_cleanup ( ) .
*
* Since the offline cleaner is called only once , we cannot leave an
* offline memcg reference in zswap_next_shrink .
* We can rely on the cleaner only if we get online memcg under lock .
*
* If we get an offline memcg , we cannot determine if the cleaner has
* already been called or will be called later . We must put back the
* reference before returning from this function . Otherwise , the
* offline memcg left in zswap_next_shrink will hold the reference
* until the next run of shrink_worker ( ) .
*/
do {
/*
* Start shrinking from the next memcg after zswap_next_shrink .
* When the offline cleaner has already advanced the cursor ,
* advancing the cursor here overlooks one memcg , but this
* should be negligibly rare .
*
* If we get an online memcg , keep the extra reference in case
* the original one obtained by mem_cgroup_iter ( ) is dropped by
* zswap_memcg_offline_cleanup ( ) while we are shrinking the
* memcg .
*/
spin_lock(&zswap_shrink_lock);
do {
memcg = mem_cgroup_iter(NULL, zswap_next_shrink, NULL);
zswap_next_shrink = memcg;
} while (memcg && !mem_cgroup_tryget_online(memcg));
spin_unlock(&zswap_shrink_lock);
if (!memcg) {
/*
* Continue shrinking without incrementing failures if
* we found candidate memcgs in the last tree walk .
*/
if (!attempts && ++failures == MAX_RECLAIM_RETRIES)
break ;
attempts = 0 ;
goto resched;
}
ret = shrink_memcg(memcg);
/* drop the extra reference */
mem_cgroup_put(memcg);
/*
* There are no writeback - candidate pages in the memcg .
* This is not an issue as long as we can find another memcg
* with pages in zswap . Skip this without incrementing attempts
* and failures .
*/
if (ret == -ENOENT)
continue ;
++attempts;
if (ret && ++failures == MAX_RECLAIM_RETRIES)
break ;
resched:
cond_resched();
} while (zswap_total_pages() > thr);
}
/*********************************
* main API
**********************************/
static bool zswap_store_page(struct page *page,
struct obj_cgroup *objcg,
struct zswap_pool *pool)
{
swp_entry_t page_swpentry = page_swap_entry(page);
struct zswap_entry *entry, *old;
/* allocate entry */
entry = zswap_entry_cache_alloc(GFP_KERNEL, page_to_nid(page));
if (!entry) {
zswap_reject_kmemcache_fail++;
return false ;
}
if (!zswap_compress(page, entry, pool))
goto compress_failed;
old = xa_store(swap_zswap_tree(page_swpentry),
swp_offset(page_swpentry),
entry, GFP_KERNEL);
if (xa_is_err(old)) {
int err = xa_err(old);
WARN_ONCE(err != -ENOMEM, "unexpected xarray error: %d\n" , err);
zswap_reject_alloc_fail++;
goto store_failed;
}
/*
* We may have had an existing entry that became stale when
* the folio was redirtied and now the new version is being
* swapped out . Get rid of the old .
*/
if (old)
zswap_entry_free(old);
/*
* The entry is successfully compressed and stored in the tree , there is
* no further possibility of failure . Grab refs to the pool and objcg ,
* charge zswap memory , and increment zswap_stored_pages .
* The opposite actions will be performed by zswap_entry_free ( )
* when the entry is removed from the tree .
*/
zswap_pool_get(pool);
if (objcg) {
obj_cgroup_get(objcg);
obj_cgroup_charge_zswap(objcg, entry->length);
}
atomic_long_inc(&zswap_stored_pages);
/*
* We finish initializing the entry while it ' s already in xarray .
* This is safe because :
*
* 1 . Concurrent stores and invalidations are excluded by folio lock .
*
* 2 . Writeback is excluded by the entry not being on the LRU yet .
* The publishing order matters to prevent writeback from seeing
* an incoherent entry .
*/
entry->pool = pool;
entry->swpentry = page_swpentry;
entry->objcg = objcg;
entry->referenced = true ;
if (entry->length) {
INIT_LIST_HEAD(&entry->lru);
zswap_lru_add(&zswap_list_lru, entry);
}
return true ;
store_failed:
zpool_free(pool->zpool, entry->handle);
compress_failed:
zswap_entry_cache_free(entry);
return false ;
}
bool zswap_store(struct folio *folio)
{
long nr_pages = folio_nr_pages(folio);
swp_entry_t swp = folio->swap;
struct obj_cgroup *objcg = NULL;
struct mem_cgroup *memcg = NULL;
struct zswap_pool *pool;
bool ret = false ;
long index;
VM_WARN_ON_ONCE(!folio_test_locked(folio));
VM_WARN_ON_ONCE(!folio_test_swapcache(folio));
if (!zswap_enabled)
goto check_old;
objcg = get_obj_cgroup_from_folio(folio);
if (objcg && !obj_cgroup_may_zswap(objcg)) {
memcg = get_mem_cgroup_from_objcg(objcg);
if (shrink_memcg(memcg)) {
mem_cgroup_put(memcg);
goto put_objcg;
}
mem_cgroup_put(memcg);
}
if (zswap_check_limits())
goto put_objcg;
pool = zswap_pool_current_get();
if (!pool)
goto put_objcg;
if (objcg) {
memcg = get_mem_cgroup_from_objcg(objcg);
if (memcg_list_lru_alloc(memcg, &zswap_list_lru, GFP_KERNEL)) {
mem_cgroup_put(memcg);
goto put_pool;
}
mem_cgroup_put(memcg);
}
for (index = 0 ; index < nr_pages; ++index) {
struct page *page = folio_page(folio, index);
if (!zswap_store_page(page, objcg, pool))
goto put_pool;
}
if (objcg)
count_objcg_events(objcg, ZSWPOUT, nr_pages);
count_vm_events(ZSWPOUT, nr_pages);
ret = true ;
put_pool:
zswap_pool_put(pool);
put_objcg:
obj_cgroup_put(objcg);
if (!ret && zswap_pool_reached_full)
queue_work(shrink_wq, &zswap_shrink_work);
check_old:
/*
* If the zswap store fails or zswap is disabled , we must invalidate
* the possibly stale entries which were previously stored at the
* offsets corresponding to each page of the folio . Otherwise ,
* writeback could overwrite the new data in the swapfile .
*/
if (!ret) {
unsigned type = swp_type(swp);
pgoff_t offset = swp_offset(swp);
struct zswap_entry *entry;
struct xarray *tree;
for (index = 0 ; index < nr_pages; ++index) {
tree = swap_zswap_tree(swp_entry(type, offset + index));
entry = xa_erase(tree, offset + index);
if (entry)
zswap_entry_free(entry);
}
}
return ret;
}
/**
* zswap_load ( ) - load a folio from zswap
* @ folio : folio to load
*
* Return : 0 on success , with the folio unlocked and marked up - to - date , or one
* of the following error codes :
*
* - EIO : if the swapped out content was in zswap , but could not be loaded
* into the page due to a decompression failure . The folio is unlocked , but
* 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 ) .
*
* - ENOENT : if the swapped out content was not in zswap . The folio remains
* locked on return .
*/
int zswap_load(struct folio *folio)
{
swp_entry_t swp = folio->swap;
pgoff_t offset = swp_offset(swp);
bool swapcache = folio_test_swapcache(folio);
struct xarray *tree = swap_zswap_tree(swp);
struct zswap_entry *entry;
VM_WARN_ON_ONCE(!folio_test_locked(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))) {
folio_unlock(folio);
return -EINVAL;
}
entry = xa_load(tree, offset);
if (!entry)
return -ENOENT;
if (!zswap_decompress(entry, folio)) {
folio_unlock(folio);
return -EIO;
}
folio_mark_uptodate(folio);
count_vm_event(ZSWPIN);
if (entry->objcg)
count_objcg_events(entry->objcg, ZSWPIN, 1 );
/*
* When reading into the swapcache , invalidate our entry . The
* swapcache can be the authoritative owner of the page and
* its mappings , and the pressure that results from having two
* in - memory copies outweighs any benefits of caching the
* compression work .
*
* ( Most swapins go through the swapcache . The notable
* exception is the singleton fault on SWP_SYNCHRONOUS_IO
* files , which reads into a private page and may free it if
* the fault fails . We remain the primary owner of the entry . )
*/
if (swapcache) {
folio_mark_dirty(folio);
xa_erase(tree, offset);
zswap_entry_free(entry);
}
folio_unlock(folio);
return 0 ;
}
void zswap_invalidate(swp_entry_t swp)
{
pgoff_t offset = swp_offset(swp);
struct xarray *tree = swap_zswap_tree(swp);
struct zswap_entry *entry;
if (xa_empty(tree))
return ;
entry = xa_erase(tree, offset);
if (entry)
zswap_entry_free(entry);
}
int zswap_swapon(int type, unsigned long nr_pages)
{
struct xarray *trees, *tree;
unsigned int nr, i;
nr = DIV_ROUND_UP(nr_pages, SWAP_ADDRESS_SPACE_PAGES);
trees = kvcalloc(nr, sizeof (*tree), GFP_KERNEL);
if (!trees) {
pr_err("alloc failed, zswap disabled for swap type %d\n" , type);
return -ENOMEM;
}
for (i = 0 ; i < nr; i++)
xa_init(trees + i);
nr_zswap_trees[type] = nr;
zswap_trees[type] = trees;
return 0 ;
}
void zswap_swapoff(int type)
{
struct xarray *trees = zswap_trees[type];
unsigned int i;
if (!trees)
return ;
/* try_to_unuse() invalidated all the entries already */
for (i = 0 ; i < nr_zswap_trees[type]; i++)
WARN_ON_ONCE(!xa_empty(trees + i));
kvfree(trees);
nr_zswap_trees[type] = 0 ;
zswap_trees[type] = NULL;
}
/*********************************
* debugfs functions
**********************************/
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
static struct dentry *zswap_debugfs_root;
static int debugfs_get_total_size(void *data, u64 *val)
{
*val = zswap_total_pages() * PAGE_SIZE;
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(total_size_fops, debugfs_get_total_size, NULL, "%llu\n" );
static int debugfs_get_stored_pages(void *data, u64 *val)
{
*val = atomic_long_read(&zswap_stored_pages);
return 0 ;
}
DEFINE_DEBUGFS_ATTRIBUTE(stored_pages_fops, debugfs_get_stored_pages, NULL, "%llu\n" );
static int zswap_debugfs_init(void )
{
if (!debugfs_initialized())
return -ENODEV;
zswap_debugfs_root = debugfs_create_dir("zswap" , NULL);
debugfs_create_u64("pool_limit_hit" , 0444 ,
zswap_debugfs_root, &zswap_pool_limit_hit);
debugfs_create_u64("reject_reclaim_fail" , 0444 ,
zswap_debugfs_root, &zswap_reject_reclaim_fail);
debugfs_create_u64("reject_alloc_fail" , 0444 ,
zswap_debugfs_root, &zswap_reject_alloc_fail);
debugfs_create_u64("reject_kmemcache_fail" , 0444 ,
zswap_debugfs_root, &zswap_reject_kmemcache_fail);
debugfs_create_u64("reject_compress_fail" , 0444 ,
zswap_debugfs_root, &zswap_reject_compress_fail);
debugfs_create_u64("reject_compress_poor" , 0444 ,
zswap_debugfs_root, &zswap_reject_compress_poor);
debugfs_create_u64("decompress_fail" , 0444 ,
zswap_debugfs_root, &zswap_decompress_fail);
debugfs_create_u64("written_back_pages" , 0444 ,
zswap_debugfs_root, &zswap_written_back_pages);
debugfs_create_file("pool_total_size" , 0444 ,
zswap_debugfs_root, NULL, &total_size_fops);
debugfs_create_file("stored_pages" , 0444 ,
zswap_debugfs_root, NULL, &stored_pages_fops);
return 0 ;
}
#else
static int zswap_debugfs_init(void )
{
return 0 ;
}
#endif
/*********************************
* module init and exit
**********************************/
static int zswap_setup(void )
{
struct zswap_pool *pool;
int ret;
zswap_entry_cache = KMEM_CACHE(zswap_entry, 0 );
if (!zswap_entry_cache) {
pr_err("entry cache creation failed\n" );
goto cache_fail;
}
ret = cpuhp_setup_state_multi(CPUHP_MM_ZSWP_POOL_PREPARE,
"mm/zswap_pool:prepare" ,
zswap_cpu_comp_prepare,
zswap_cpu_comp_dead);
if (ret)
goto hp_fail;
shrink_wq = alloc_workqueue("zswap-shrink" ,
WQ_UNBOUND|WQ_MEM_RECLAIM, 1 );
if (!shrink_wq)
goto shrink_wq_fail;
zswap_shrinker = zswap_alloc_shrinker();
if (!zswap_shrinker)
goto shrinker_fail;
if (list_lru_init_memcg(&zswap_list_lru, zswap_shrinker))
goto lru_fail;
shrinker_register(zswap_shrinker);
INIT_WORK(&zswap_shrink_work, shrink_worker);
pool = __zswap_pool_create_fallback();
if (pool) {
pr_info("loaded using pool %s/%s\n" , pool->tfm_name,
zpool_get_type(pool->zpool));
list_add(&pool->list, &zswap_pools);
zswap_has_pool = true ;
static_branch_enable(&zswap_ever_enabled);
} else {
pr_err("pool creation failed\n" );
zswap_enabled = false ;
}
if (zswap_debugfs_init())
pr_warn("debugfs initialization failed\n" );
zswap_init_state = ZSWAP_INIT_SUCCEED;
return 0 ;
lru_fail:
shrinker_free(zswap_shrinker);
shrinker_fail:
destroy_workqueue(shrink_wq);
shrink_wq_fail:
cpuhp_remove_multi_state(CPUHP_MM_ZSWP_POOL_PREPARE);
hp_fail:
kmem_cache_destroy(zswap_entry_cache);
cache_fail:
/* if built-in, we aren't unloaded on failure; don't allow use */
zswap_init_state = ZSWAP_INIT_FAILED;
zswap_enabled = false ;
return -ENOMEM;
}
static int __init zswap_init(void )
{
if (!zswap_enabled)
return 0 ;
return zswap_setup();
}
/* must be late so crypto has time to come up */
late_initcall(zswap_init);
MODULE_AUTHOR("Seth Jennings <sjennings@variantweb.net>" );
MODULE_DESCRIPTION("Compressed cache for swap pages" );
Messung V0.5 in Prozent C=95 H=90 G=92
¤ Dauer der Verarbeitung: 0.52 Sekunden
(vorverarbeitet am 2026-09-28)
¤
*© Formatika GbR, Deutschland
2026-10-09
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126438
#Domains=1867298