Add a per-device knob for the xswap size ceiling, si->nr_clusters. Grow and shrink both work without it; a cap below the mapped range only makes shrink target the cap directly. /sys/kernel/mm/xswap/type/limit read/write, in pages Reading reports the usable size, si->pages, in 4 KiB pages. Writing sets a new ceiling, rounded up to SWAPFILE_CLUSTER (the device's growth granularity). Each device gets its own kobject directory under /sys/kernel/mm/xswap/. Signed-off-by: Baoquan He --- include/linux/swap.h | 4 + mm/swapfile.c | 184 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 179 insertions(+), 9 deletions(-) diff --git a/include/linux/swap.h b/include/linux/swap.h index 6307d8e8f7ed..c76c801c34ea 100644 --- a/include/linux/swap.h +++ b/include/linux/swap.h @@ -16,6 +16,8 @@ #include #include +struct kobject; + #define SWAP_FLAG_PREFER 0x8000 /* set if swap priority specified */ #define SWAP_FLAG_PRIO_MASK 0x7fff #define SWAP_FLAG_DISCARD 0x10000 /* enable discard for swap */ @@ -251,7 +253,9 @@ struct swap_info_struct { #ifdef CONFIG_XSWAP struct vm_struct *cluster_vm; /* VM_SPARSE area for cluster_info */ unsigned long nr_clusters_max;/* total clusters in the xswap address space */ + unsigned long nr_clusters; /* growth ceiling, set by type/limit */ unsigned long nr_clusters_mapped; /* currently mapped cluster count */ + struct kobject *xswap_dev_kobj; /* sysfs: /sys/kernel/mm/xswap/type/ */ struct work_struct xswap_shrink_work; /* deferred shrink trigger */ struct mutex xswap_lock; /* serialize map/unmap operations */ #endif diff --git a/mm/swapfile.c b/mm/swapfile.c index c2ca0a9ac625..fd3bc83f6644 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -50,8 +50,13 @@ #include "swap_table.h" #include "internal.h" #include "swap.h" + #define DEF_SWAP_PRIO -1 +static DEFINE_SPINLOCK(swap_lock); + +static long swap_usage_in_pages(struct swap_info_struct *si); + #ifdef CONFIG_XSWAP /* * xswap: dynamically grow and shrink the cluster_info array via a @@ -158,6 +163,142 @@ static inline void xswap_sysfs_init(void) { } #endif /* CONFIG_SYSFS */ + +#ifdef CONFIG_SYSFS +/* + * Per-device directory: /sys/kernel/mm/xswap/type/limit (rw). + * It is the runtime size ceiling of the device, in pages (rounded up to + * SWAPFILE_CLUSTER, the device's growth granularity). Lowering it below + * the current usage only stops further growth down to the used size and + * triggers shrink of the free tail. + */ +struct xswap_sysfs_dev { + struct kobject kobj; + struct swap_info_struct *si; +}; + +static ssize_t xswap_limit_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct swap_info_struct *si = + container_of(kobj, struct xswap_sysfs_dev, kobj)->si; + + return sysfs_emit(buf, "%u\n", READ_ONCE(si->pages)); +} + +static ssize_t xswap_limit_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct swap_info_struct *si = + container_of(kobj, struct xswap_sysfs_dev, kobj)->si; + unsigned long val, clusters, new_pages; + int err; + + err = kstrtoul(buf, 0, &val); + if (err) + return err; + + spin_lock(&swap_lock); + /* Reject writes once swapoff has cleared SWP_WRITEOK. */ + if (!(si->flags & SWP_WRITEOK)) { + spin_unlock(&swap_lock); + return -ENODEV; + } + + clusters = DIV_ROUND_UP(val, SWAPFILE_CLUSTER); + if (clusters > si->nr_clusters_max) + clusters = si->nr_clusters_max; + + spin_lock(&si->lock); + si->nr_clusters = clusters; + spin_unlock(&si->lock); + + new_pages = min_t(unsigned long, clusters * SWAPFILE_CLUSTER, si->max); + if (new_pages) + new_pages--; + + if (new_pages < swap_usage_in_pages(si)) + new_pages = swap_usage_in_pages(si); + if (new_pages != si->pages) { + long delta = (long)new_pages - (long)si->pages; + + si->pages = new_pages; + atomic_long_add(delta, &nr_swap_pages); + total_swap_pages += delta; + } + spin_unlock(&swap_lock); + + /* Lowering the ceiling may free tail clusters. */ + xswap_try_shrink(si); + + return count; +} + +static struct kobj_attribute xswap_limit_attr = + __ATTR(limit, 0644, xswap_limit_show, xswap_limit_store); + +static void xswap_dev_release(struct kobject *kobj) +{ + kfree(container_of(kobj, struct xswap_sysfs_dev, kobj)); +} + +static const struct kobj_type xswap_dev_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .release = xswap_dev_release, +}; + +static int xswap_dev_kobj_add(struct swap_info_struct *si) +{ + struct xswap_sysfs_dev *dev; + int err; + + if (!xswap_kobj) + return 0; + + dev = kzalloc_obj(*dev, GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->si = si; + + err = kobject_init_and_add(&dev->kobj, &xswap_dev_ktype, xswap_kobj, + "type%d", si->type); + if (err) { + kobject_put(&dev->kobj); + return err; + } + + err = sysfs_create_file(&dev->kobj, &xswap_limit_attr.attr); + if (err) { + kobject_del(&dev->kobj); + kobject_put(&dev->kobj); + return err; + } + si->xswap_dev_kobj = &dev->kobj; + return 0; +} + +static void xswap_dev_kobj_del(struct swap_info_struct *si) +{ + struct kobject *kobj = si->xswap_dev_kobj; + + if (!kobj) + return; + si->xswap_dev_kobj = NULL; + sysfs_remove_file(kobj, &xswap_limit_attr.attr); + kobject_del(kobj); + kobject_put(kobj); +} +#else /* !CONFIG_SYSFS */ +static inline int xswap_dev_kobj_add(struct swap_info_struct *si) +{ + return 0; +} + +static inline void xswap_dev_kobj_del(struct swap_info_struct *si) +{ +} +#endif /* CONFIG_SYSFS */ #else /* !CONFIG_XSWAP */ static inline void xswap_sysfs_init(void) { @@ -178,7 +319,6 @@ static void move_cluster(struct swap_info_struct *si, * * Also protects swap_active_head total_swap_pages, and the SWP_WRITEOK flag. */ -static DEFINE_SPINLOCK(swap_lock); static unsigned int nr_swapfiles; atomic_long_t nr_swap_pages; atomic_t nr_real_swapfiles; @@ -3274,6 +3414,7 @@ static void free_swap_cluster_info(struct swap_info_struct *si) #ifdef CONFIG_XSWAP if (si->flags & SWP_XSWAP) { + xswap_dev_kobj_del(si); cancel_work_sync(&si->xswap_shrink_work); /* Unmap all mapped clusters and free the VM_SPARSE area */ if (si->nr_clusters_mapped > 0) @@ -4057,8 +4198,9 @@ static void xswap_shrink_work_fn(struct work_struct *work) */ static void xswap_try_shrink(struct swap_info_struct *si) { + unsigned long nr_mapped, nr_ceiling, nr_tail, nr_unmap; + unsigned long start_idx, slack, min, i; struct swap_cluster_info *ci; - unsigned long nr_mapped, nr_tail, nr_unmap, start_idx, i; if (!(si->flags & SWP_XSWAP)) return; @@ -4066,18 +4208,34 @@ static void xswap_try_shrink(struct swap_info_struct *si) mutex_lock(&si->xswap_lock); nr_mapped = READ_ONCE(si->nr_clusters_mapped); + nr_ceiling = READ_ONCE(si->nr_clusters); + if (nr_mapped <= 1) /* keep cluster 0 */ goto out_unlock; /* - * Reclaim on our own, but only once the mapped range is at most - * half in use: growth is demand driven, so reclaiming on a smaller - * dip would only map the same clusters again, and every unmap costs - * an RCU grace period. + * A cap below the mapped range is reason enough to reclaim on its + * own; otherwise only once the mapped range is at most half in use, + * because growth is demand driven and reclaiming on a smaller dip + * would only map the same clusters again, at the price of an RCU + * grace period per unmap. */ - if (atomic_long_read(&si->inuse_pages) * 2 > nr_mapped * SWAPFILE_CLUSTER) + if (nr_ceiling >= nr_mapped && + atomic_long_read(&si->inuse_pages) * 2 > nr_mapped * SWAPFILE_CLUSTER) goto out_unlock; + /* + * Keep one chunk of free tail as slack so that the next allocation + * does not grow the range back immediately - unless a cap asks for + * the whole tail. + */ + slack = XSWAP_SHRINK_SLACK; + min = XSWAP_SHRINK_MIN; + if (nr_ceiling < nr_mapped) { + slack = 0; + min = XSWAP_GROW_CLUSTERS; + } + /* * Count the free clusters at the tail of the mapped range. Scanned, * not tracked: the count must be exact to size the unmap, and an @@ -4091,10 +4249,13 @@ static void xswap_try_shrink(struct swap_info_struct *si) break; nr_tail++; } - if (nr_tail < XSWAP_SHRINK_SLACK + XSWAP_SHRINK_MIN) + if (nr_tail < slack + min) goto out_unlock; - nr_unmap = rounddown(nr_tail - XSWAP_SHRINK_SLACK, XSWAP_GROW_CLUSTERS); + nr_unmap = rounddown(nr_tail - slack, XSWAP_GROW_CLUSTERS); + if (nr_ceiling < nr_mapped) + nr_unmap = min(nr_unmap, rounddown(nr_mapped - nr_ceiling, + XSWAP_GROW_CLUSTERS)); if (!nr_unmap) goto out_unlock; start_idx = nr_mapped - nr_unmap; @@ -4164,6 +4325,8 @@ static int setup_swap_clusters_info(struct swap_info_struct *si, cluster_info = vm->addr; si->cluster_vm = vm; si->nr_clusters_max = nr_clusters; + /* No cap unless type/limit is written. */ + si->nr_clusters = nr_clusters; si->cluster_info = cluster_info; /* Must be initialized before xswap_map_clusters() locks it. */ @@ -4205,6 +4368,9 @@ static int setup_swap_clusters_info(struct swap_info_struct *si, } INIT_WORK(&si->xswap_shrink_work, xswap_shrink_work_fn); + if (xswap_dev_kobj_add(si)) + pr_warn("xswap: failed to add sysfs interface for type %d\n", + si->type); return 0; err_unmap: -- 2.54.0