2019-07-31 23:57:31 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2018-07-26 20:21:59 +08:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2018 HUAWEI, Inc.
|
2020-07-13 21:09:44 +08:00
|
|
|
* https://www.huawei.com/
|
2018-07-26 20:21:59 +08:00
|
|
|
*/
|
|
|
|
#include "internal.h"
|
|
|
|
|
2024-04-02 18:00:36 +08:00
|
|
|
struct z_erofs_gbuf {
|
|
|
|
spinlock_t lock;
|
|
|
|
void *ptr;
|
|
|
|
struct page **pages;
|
|
|
|
unsigned int nrpages;
|
|
|
|
};
|
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
static struct z_erofs_gbuf *z_erofs_gbufpool, *z_erofs_rsvbuf;
|
|
|
|
static unsigned int z_erofs_gbuf_count, z_erofs_gbuf_nrpages,
|
|
|
|
z_erofs_rsv_nrpages;
|
2024-04-02 18:00:36 +08:00
|
|
|
|
|
|
|
module_param_named(global_buffers, z_erofs_gbuf_count, uint, 0444);
|
2024-04-02 21:15:23 +08:00
|
|
|
module_param_named(reserved_pages, z_erofs_rsv_nrpages, uint, 0444);
|
2024-04-02 18:00:36 +08:00
|
|
|
|
2024-04-01 21:55:50 +08:00
|
|
|
static atomic_long_t erofs_global_shrink_cnt; /* for all mounted instances */
|
|
|
|
/* protected by 'erofs_sb_list_lock' */
|
|
|
|
static unsigned int shrinker_run_no;
|
|
|
|
|
|
|
|
/* protects the mounted 'erofs_sb_list' */
|
|
|
|
static DEFINE_SPINLOCK(erofs_sb_list_lock);
|
|
|
|
static LIST_HEAD(erofs_sb_list);
|
|
|
|
static struct shrinker *erofs_shrinker_info;
|
|
|
|
|
2024-04-02 18:00:36 +08:00
|
|
|
static unsigned int z_erofs_gbuf_id(void)
|
|
|
|
{
|
|
|
|
return raw_smp_processor_id() % z_erofs_gbuf_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *z_erofs_get_gbuf(unsigned int requiredpages)
|
|
|
|
__acquires(gbuf->lock)
|
|
|
|
{
|
|
|
|
struct z_erofs_gbuf *gbuf;
|
|
|
|
|
|
|
|
gbuf = &z_erofs_gbufpool[z_erofs_gbuf_id()];
|
|
|
|
spin_lock(&gbuf->lock);
|
|
|
|
/* check if the buffer is too small */
|
|
|
|
if (requiredpages > gbuf->nrpages) {
|
|
|
|
spin_unlock(&gbuf->lock);
|
|
|
|
/* (for sparse checker) pretend gbuf->lock is still taken */
|
|
|
|
__acquire(gbuf->lock);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return gbuf->ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
void z_erofs_put_gbuf(void *ptr) __releases(gbuf->lock)
|
|
|
|
{
|
|
|
|
struct z_erofs_gbuf *gbuf;
|
|
|
|
|
|
|
|
gbuf = &z_erofs_gbufpool[z_erofs_gbuf_id()];
|
|
|
|
DBG_BUGON(gbuf->ptr != ptr);
|
|
|
|
spin_unlock(&gbuf->lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
int z_erofs_gbuf_growsize(unsigned int nrpages)
|
|
|
|
{
|
|
|
|
static DEFINE_MUTEX(gbuf_resize_mutex);
|
2024-04-02 17:27:57 +08:00
|
|
|
struct page **tmp_pages = NULL;
|
|
|
|
struct z_erofs_gbuf *gbuf;
|
|
|
|
void *ptr, *old_ptr;
|
|
|
|
int last, i, j;
|
2024-04-02 18:00:36 +08:00
|
|
|
|
|
|
|
mutex_lock(&gbuf_resize_mutex);
|
|
|
|
/* avoid shrinking gbufs, since no idea how many fses rely on */
|
2024-04-02 17:27:57 +08:00
|
|
|
if (nrpages <= z_erofs_gbuf_nrpages) {
|
|
|
|
mutex_unlock(&gbuf_resize_mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
2024-04-02 18:00:36 +08:00
|
|
|
|
|
|
|
for (i = 0; i < z_erofs_gbuf_count; ++i) {
|
2024-04-02 17:27:57 +08:00
|
|
|
gbuf = &z_erofs_gbufpool[i];
|
2024-04-02 18:00:36 +08:00
|
|
|
tmp_pages = kcalloc(nrpages, sizeof(*tmp_pages), GFP_KERNEL);
|
|
|
|
if (!tmp_pages)
|
2024-04-02 17:27:57 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
for (j = 0; j < gbuf->nrpages; ++j)
|
|
|
|
tmp_pages[j] = gbuf->pages[j];
|
|
|
|
do {
|
|
|
|
last = j;
|
|
|
|
j = alloc_pages_bulk_array(GFP_KERNEL, nrpages,
|
|
|
|
tmp_pages);
|
|
|
|
if (last == j)
|
|
|
|
goto out;
|
|
|
|
} while (j != nrpages);
|
|
|
|
|
2024-04-02 18:00:36 +08:00
|
|
|
ptr = vmap(tmp_pages, nrpages, VM_MAP, PAGE_KERNEL);
|
|
|
|
if (!ptr)
|
2024-04-02 17:27:57 +08:00
|
|
|
goto out;
|
2024-04-02 18:00:36 +08:00
|
|
|
|
|
|
|
spin_lock(&gbuf->lock);
|
2024-04-02 17:27:57 +08:00
|
|
|
kfree(gbuf->pages);
|
|
|
|
gbuf->pages = tmp_pages;
|
2024-04-02 18:00:36 +08:00
|
|
|
old_ptr = gbuf->ptr;
|
|
|
|
gbuf->ptr = ptr;
|
|
|
|
gbuf->nrpages = nrpages;
|
|
|
|
spin_unlock(&gbuf->lock);
|
|
|
|
if (old_ptr)
|
|
|
|
vunmap(old_ptr);
|
|
|
|
}
|
|
|
|
z_erofs_gbuf_nrpages = nrpages;
|
|
|
|
out:
|
2024-04-02 17:27:57 +08:00
|
|
|
if (i < z_erofs_gbuf_count && tmp_pages) {
|
|
|
|
for (j = 0; j < nrpages; ++j)
|
|
|
|
if (tmp_pages[j] && tmp_pages[j] != gbuf->pages[j])
|
|
|
|
__free_page(tmp_pages[j]);
|
|
|
|
kfree(tmp_pages);
|
|
|
|
}
|
2024-04-02 18:00:36 +08:00
|
|
|
mutex_unlock(&gbuf_resize_mutex);
|
2024-04-02 17:27:57 +08:00
|
|
|
return i < z_erofs_gbuf_count ? -ENOMEM : 0;
|
2024-04-02 18:00:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int __init z_erofs_gbuf_init(void)
|
|
|
|
{
|
2024-04-02 21:15:23 +08:00
|
|
|
unsigned int i, total = num_possible_cpus();
|
2024-04-02 18:00:36 +08:00
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
if (z_erofs_gbuf_count)
|
|
|
|
total = min(z_erofs_gbuf_count, total);
|
|
|
|
z_erofs_gbuf_count = total;
|
2024-04-02 18:00:36 +08:00
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
/* The last (special) global buffer is the reserved buffer */
|
|
|
|
total += !!z_erofs_rsv_nrpages;
|
|
|
|
|
|
|
|
z_erofs_gbufpool = kcalloc(total, sizeof(*z_erofs_gbufpool),
|
|
|
|
GFP_KERNEL);
|
2024-04-02 18:00:36 +08:00
|
|
|
if (!z_erofs_gbufpool)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
if (z_erofs_rsv_nrpages) {
|
|
|
|
z_erofs_rsvbuf = &z_erofs_gbufpool[total - 1];
|
|
|
|
z_erofs_rsvbuf->pages = kcalloc(z_erofs_rsv_nrpages,
|
|
|
|
sizeof(*z_erofs_rsvbuf->pages), GFP_KERNEL);
|
|
|
|
if (!z_erofs_rsvbuf->pages) {
|
|
|
|
z_erofs_rsvbuf = NULL;
|
|
|
|
z_erofs_rsv_nrpages = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (i = 0; i < total; ++i)
|
2024-04-02 18:00:36 +08:00
|
|
|
spin_lock_init(&z_erofs_gbufpool[i].lock);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void z_erofs_gbuf_exit(void)
|
|
|
|
{
|
2024-06-25 06:02:05 +08:00
|
|
|
int i, j;
|
2024-04-02 18:00:36 +08:00
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
for (i = 0; i < z_erofs_gbuf_count + (!!z_erofs_rsvbuf); ++i) {
|
2024-04-02 18:00:36 +08:00
|
|
|
struct z_erofs_gbuf *gbuf = &z_erofs_gbufpool[i];
|
|
|
|
|
|
|
|
if (gbuf->ptr) {
|
|
|
|
vunmap(gbuf->ptr);
|
|
|
|
gbuf->ptr = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!gbuf->pages)
|
|
|
|
continue;
|
|
|
|
|
2024-06-25 06:02:05 +08:00
|
|
|
for (j = 0; j < gbuf->nrpages; ++j)
|
|
|
|
if (gbuf->pages[j])
|
|
|
|
put_page(gbuf->pages[j]);
|
2024-04-02 18:00:36 +08:00
|
|
|
kfree(gbuf->pages);
|
|
|
|
gbuf->pages = NULL;
|
|
|
|
}
|
|
|
|
kfree(z_erofs_gbufpool);
|
|
|
|
}
|
|
|
|
|
2024-04-02 21:15:23 +08:00
|
|
|
struct page *__erofs_allocpage(struct page **pagepool, gfp_t gfp, bool tryrsv)
|
2018-07-26 20:21:59 +08:00
|
|
|
{
|
2021-10-22 17:01:20 +08:00
|
|
|
struct page *page = *pagepool;
|
2018-07-26 20:21:59 +08:00
|
|
|
|
2021-10-22 17:01:20 +08:00
|
|
|
if (page) {
|
|
|
|
*pagepool = (struct page *)page_private(page);
|
2024-04-02 21:15:23 +08:00
|
|
|
} else if (tryrsv && z_erofs_rsvbuf && z_erofs_rsvbuf->nrpages) {
|
|
|
|
spin_lock(&z_erofs_rsvbuf->lock);
|
|
|
|
if (z_erofs_rsvbuf->nrpages)
|
|
|
|
page = z_erofs_rsvbuf->pages[--z_erofs_rsvbuf->nrpages];
|
|
|
|
spin_unlock(&z_erofs_rsvbuf->lock);
|
2018-07-26 20:21:59 +08:00
|
|
|
}
|
2024-04-02 21:15:23 +08:00
|
|
|
if (!page)
|
|
|
|
page = alloc_page(gfp);
|
|
|
|
DBG_BUGON(page && page_ref_count(page) != 1);
|
|
|
|
return page;
|
2018-07-26 20:21:59 +08:00
|
|
|
}
|
|
|
|
|
2021-10-22 17:01:20 +08:00
|
|
|
void erofs_release_pages(struct page **pagepool)
|
|
|
|
{
|
|
|
|
while (*pagepool) {
|
|
|
|
struct page *page = *pagepool;
|
|
|
|
|
|
|
|
*pagepool = (struct page *)page_private(page);
|
2024-04-02 21:15:23 +08:00
|
|
|
/* try to fill reserved global pool first */
|
|
|
|
if (z_erofs_rsvbuf && z_erofs_rsvbuf->nrpages <
|
|
|
|
z_erofs_rsv_nrpages) {
|
|
|
|
spin_lock(&z_erofs_rsvbuf->lock);
|
|
|
|
if (z_erofs_rsvbuf->nrpages < z_erofs_rsv_nrpages) {
|
|
|
|
z_erofs_rsvbuf->pages[z_erofs_rsvbuf->nrpages++]
|
|
|
|
= page;
|
|
|
|
spin_unlock(&z_erofs_rsvbuf->lock);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
spin_unlock(&z_erofs_rsvbuf->lock);
|
|
|
|
}
|
2021-10-22 17:01:20 +08:00
|
|
|
put_page(page);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
static bool erofs_workgroup_get(struct erofs_workgroup *grp)
|
2019-01-16 16:59:53 +08:00
|
|
|
{
|
2023-05-29 20:37:27 +08:00
|
|
|
if (lockref_get_not_zero(&grp->lockref))
|
|
|
|
return true;
|
2019-01-16 16:59:53 +08:00
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
spin_lock(&grp->lockref.lock);
|
|
|
|
if (__lockref_is_dead(&grp->lockref)) {
|
|
|
|
spin_unlock(&grp->lockref.lock);
|
|
|
|
return false;
|
|
|
|
}
|
2019-01-16 16:59:53 +08:00
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
if (!grp->lockref.count++)
|
2019-01-16 21:10:10 +08:00
|
|
|
atomic_long_dec(&erofs_global_shrink_cnt);
|
2023-05-29 20:37:27 +08:00
|
|
|
spin_unlock(&grp->lockref.lock);
|
|
|
|
return true;
|
2019-01-16 16:59:53 +08:00
|
|
|
}
|
2018-07-26 20:22:05 +08:00
|
|
|
|
2019-01-16 21:10:10 +08:00
|
|
|
struct erofs_workgroup *erofs_find_workgroup(struct super_block *sb,
|
2020-01-02 20:01:16 +08:00
|
|
|
pgoff_t index)
|
2018-07-26 20:22:05 +08:00
|
|
|
{
|
|
|
|
struct erofs_sb_info *sbi = EROFS_SB(sb);
|
|
|
|
struct erofs_workgroup *grp;
|
|
|
|
|
|
|
|
repeat:
|
|
|
|
rcu_read_lock();
|
2020-02-20 10:46:42 +08:00
|
|
|
grp = xa_load(&sbi->managed_pslots, index);
|
2019-03-22 10:38:16 +08:00
|
|
|
if (grp) {
|
2023-05-29 20:37:27 +08:00
|
|
|
if (!erofs_workgroup_get(grp)) {
|
2018-07-26 20:22:05 +08:00
|
|
|
/* prefer to relax rcu read side */
|
|
|
|
rcu_read_unlock();
|
|
|
|
goto repeat;
|
|
|
|
}
|
|
|
|
|
2018-12-11 15:17:50 +08:00
|
|
|
DBG_BUGON(index != grp->index);
|
2018-07-26 20:22:05 +08:00
|
|
|
}
|
|
|
|
rcu_read_unlock();
|
|
|
|
return grp;
|
|
|
|
}
|
|
|
|
|
2020-02-20 10:46:42 +08:00
|
|
|
struct erofs_workgroup *erofs_insert_workgroup(struct super_block *sb,
|
|
|
|
struct erofs_workgroup *grp)
|
2018-07-26 20:22:05 +08:00
|
|
|
{
|
2020-02-20 10:46:42 +08:00
|
|
|
struct erofs_sb_info *const sbi = EROFS_SB(sb);
|
|
|
|
struct erofs_workgroup *pre;
|
2018-07-26 20:22:05 +08:00
|
|
|
|
2023-10-31 14:05:24 +08:00
|
|
|
DBG_BUGON(grp->lockref.count < 1);
|
2020-02-20 10:46:42 +08:00
|
|
|
repeat:
|
|
|
|
xa_lock(&sbi->managed_pslots);
|
|
|
|
pre = __xa_cmpxchg(&sbi->managed_pslots, grp->index,
|
2024-01-24 11:19:45 +08:00
|
|
|
NULL, grp, GFP_KERNEL);
|
2020-02-20 10:46:42 +08:00
|
|
|
if (pre) {
|
|
|
|
if (xa_is_err(pre)) {
|
|
|
|
pre = ERR_PTR(xa_err(pre));
|
2023-05-29 20:37:27 +08:00
|
|
|
} else if (!erofs_workgroup_get(pre)) {
|
2020-02-20 10:46:42 +08:00
|
|
|
/* try to legitimize the current in-tree one */
|
|
|
|
xa_unlock(&sbi->managed_pslots);
|
|
|
|
cond_resched();
|
|
|
|
goto repeat;
|
|
|
|
}
|
|
|
|
grp = pre;
|
|
|
|
}
|
|
|
|
xa_unlock(&sbi->managed_pslots);
|
|
|
|
return grp;
|
2018-07-26 20:22:05 +08:00
|
|
|
}
|
|
|
|
|
2018-11-23 01:16:00 +08:00
|
|
|
static void __erofs_workgroup_free(struct erofs_workgroup *grp)
|
|
|
|
{
|
|
|
|
atomic_long_dec(&erofs_global_shrink_cnt);
|
|
|
|
erofs_workgroup_free_rcu(grp);
|
|
|
|
}
|
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
void erofs_workgroup_put(struct erofs_workgroup *grp)
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
{
|
2023-05-29 20:37:27 +08:00
|
|
|
if (lockref_put_or_lock(&grp->lockref))
|
|
|
|
return;
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
DBG_BUGON(__lockref_is_dead(&grp->lockref));
|
|
|
|
if (grp->lockref.count == 1)
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
atomic_long_inc(&erofs_global_shrink_cnt);
|
2023-05-29 20:37:27 +08:00
|
|
|
--grp->lockref.count;
|
|
|
|
spin_unlock(&grp->lockref.lock);
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
}
|
|
|
|
|
2019-01-08 19:31:47 +08:00
|
|
|
static bool erofs_try_to_release_workgroup(struct erofs_sb_info *sbi,
|
2019-10-08 20:56:13 +08:00
|
|
|
struct erofs_workgroup *grp)
|
2018-11-23 01:16:00 +08:00
|
|
|
{
|
2023-05-29 20:37:27 +08:00
|
|
|
int free = false;
|
|
|
|
|
|
|
|
spin_lock(&grp->lockref.lock);
|
|
|
|
if (grp->lockref.count)
|
|
|
|
goto out;
|
2018-11-23 01:16:00 +08:00
|
|
|
|
|
|
|
/*
|
2023-05-29 20:37:27 +08:00
|
|
|
* Note that all cached pages should be detached before deleted from
|
|
|
|
* the XArray. Otherwise some cached pages could be still attached to
|
|
|
|
* the orphan old workgroup when the new one is available in the tree.
|
2018-11-23 01:16:00 +08:00
|
|
|
*/
|
2024-03-05 17:14:48 +08:00
|
|
|
if (erofs_try_to_free_all_cached_folios(sbi, grp))
|
2023-05-29 20:37:27 +08:00
|
|
|
goto out;
|
2018-11-23 01:16:00 +08:00
|
|
|
|
|
|
|
/*
|
2019-07-31 23:57:50 +08:00
|
|
|
* It's impossible to fail after the workgroup is freezed,
|
2018-11-23 01:16:00 +08:00
|
|
|
* however in order to avoid some race conditions, add a
|
|
|
|
* DBG_BUGON to observe this in advance.
|
|
|
|
*/
|
2021-11-18 21:58:44 +08:00
|
|
|
DBG_BUGON(__xa_erase(&sbi->managed_pslots, grp->index) != grp);
|
2018-11-23 01:16:00 +08:00
|
|
|
|
2023-05-29 20:37:27 +08:00
|
|
|
lockref_mark_dead(&grp->lockref);
|
|
|
|
free = true;
|
|
|
|
out:
|
|
|
|
spin_unlock(&grp->lockref.lock);
|
|
|
|
if (free)
|
|
|
|
__erofs_workgroup_free(grp);
|
|
|
|
return free;
|
2018-11-23 01:16:00 +08:00
|
|
|
}
|
|
|
|
|
2019-07-31 23:57:39 +08:00
|
|
|
static unsigned long erofs_shrink_workstation(struct erofs_sb_info *sbi,
|
2019-10-08 20:56:13 +08:00
|
|
|
unsigned long nr_shrink)
|
2018-07-26 20:22:05 +08:00
|
|
|
{
|
2020-02-20 10:46:42 +08:00
|
|
|
struct erofs_workgroup *grp;
|
2018-09-11 03:41:14 +08:00
|
|
|
unsigned int freed = 0;
|
2020-02-20 10:46:42 +08:00
|
|
|
unsigned long index;
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
|
2021-11-18 21:58:44 +08:00
|
|
|
xa_lock(&sbi->managed_pslots);
|
2020-02-20 10:46:42 +08:00
|
|
|
xa_for_each(&sbi->managed_pslots, index, grp) {
|
2018-11-23 01:16:00 +08:00
|
|
|
/* try to shrink each valid workgroup */
|
2019-10-08 20:56:13 +08:00
|
|
|
if (!erofs_try_to_release_workgroup(sbi, grp))
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
continue;
|
2021-11-18 21:58:44 +08:00
|
|
|
xa_unlock(&sbi->managed_pslots);
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
|
|
|
|
++freed;
|
2019-08-30 00:38:27 +08:00
|
|
|
if (!--nr_shrink)
|
2021-11-18 21:58:44 +08:00
|
|
|
return freed;
|
|
|
|
xa_lock(&sbi->managed_pslots);
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
}
|
2021-11-18 21:58:44 +08:00
|
|
|
xa_unlock(&sbi->managed_pslots);
|
staging: erofs: introduce VLE decompression support
This patch introduces the basic in-place VLE decompression
implementation for the erofs file system.
Compared with fixed-sized input compression, it implements
what we call 'the variable-length extent compression' which
specifies the same output size for each compression block
to make the full use of IO bandwidth (which means almost
all data from block device can be directly used for decomp-
ression), improve the real (rather than just via data caching,
which costs more memory) random read and keep the relatively
lower compression ratios (it saves more storage space than
fixed-sized input compression which is also configured with
the same input block size), as illustrated below:
|--- variable-length extent ---|------ VLE ------|--- VLE ---|
/> clusterofs /> clusterofs /> clusterofs /> clusterofs
++---|-------++-----------++---------|-++-----------++-|---------++-|
...|| | || || | || || | || | ... original data
++---|-------++-----------++---------|-++-----------++-|---------++-|
++->cluster<-++->cluster<-++->cluster<-++->cluster<-++->cluster<-++
size size size size size
\ / / /
\ / / /
\ / / /
++-----------++-----------++-----------++
... || || || || ... compressed clusters
++-----------++-----------++-----------++
++->cluster<-++->cluster<-++->cluster<-++
size size size
The main point of 'in-place' refers to the decompression mode:
Instead of allocating independent compressed pages and data
structures, it reuses the allocated file cache pages at most
to store its compressed data and the corresponding pagevec in
a time-sharing approach by default, which will be useful for
low memory scenario.
In the end, unlike the other filesystems with (de)compression
support using a relatively large compression block size, which
reads and decompresses >= 128KB at once, and gains a more
good-looking random read (In fact it collects small random reads
into large sequential reads and caches all decompressed data
in memory, but it is unacceptable especially for embedded devices
with limited memory, and it is not the real random read), we
select a universal small-sized 4KB compressed cluster, which is
the smallest page size for most architectures, and all compressed
clusters can be read and decompressed independently, which ensures
random read number for all use cases.
Signed-off-by: Gao Xiang <gaoxiang25@huawei.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-07-26 20:22:06 +08:00
|
|
|
return freed;
|
2018-07-26 20:22:05 +08:00
|
|
|
}
|
|
|
|
|
2019-07-31 23:57:39 +08:00
|
|
|
void erofs_shrinker_register(struct super_block *sb)
|
2018-07-26 20:22:03 +08:00
|
|
|
{
|
2018-07-26 20:22:04 +08:00
|
|
|
struct erofs_sb_info *sbi = EROFS_SB(sb);
|
|
|
|
|
|
|
|
mutex_init(&sbi->umount_mutex);
|
|
|
|
|
|
|
|
spin_lock(&erofs_sb_list_lock);
|
|
|
|
list_add(&sbi->list, &erofs_sb_list);
|
|
|
|
spin_unlock(&erofs_sb_list_lock);
|
2018-07-26 20:22:03 +08:00
|
|
|
}
|
|
|
|
|
2019-07-31 23:57:39 +08:00
|
|
|
void erofs_shrinker_unregister(struct super_block *sb)
|
2018-07-26 20:22:03 +08:00
|
|
|
{
|
2019-07-31 23:57:39 +08:00
|
|
|
struct erofs_sb_info *const sbi = EROFS_SB(sb);
|
|
|
|
|
|
|
|
mutex_lock(&sbi->umount_mutex);
|
2019-10-08 20:56:13 +08:00
|
|
|
/* clean up all remaining workgroups in memory */
|
|
|
|
erofs_shrink_workstation(sbi, ~0UL);
|
2019-07-31 23:57:39 +08:00
|
|
|
|
2018-07-26 20:22:04 +08:00
|
|
|
spin_lock(&erofs_sb_list_lock);
|
2019-07-31 23:57:39 +08:00
|
|
|
list_del(&sbi->list);
|
2018-07-26 20:22:04 +08:00
|
|
|
spin_unlock(&erofs_sb_list_lock);
|
2019-07-31 23:57:39 +08:00
|
|
|
mutex_unlock(&sbi->umount_mutex);
|
2018-07-26 20:22:04 +08:00
|
|
|
}
|
|
|
|
|
2019-01-16 16:59:55 +08:00
|
|
|
static unsigned long erofs_shrink_count(struct shrinker *shrink,
|
|
|
|
struct shrink_control *sc)
|
2018-07-26 20:22:04 +08:00
|
|
|
{
|
|
|
|
return atomic_long_read(&erofs_global_shrink_cnt);
|
|
|
|
}
|
|
|
|
|
2019-01-16 16:59:55 +08:00
|
|
|
static unsigned long erofs_shrink_scan(struct shrinker *shrink,
|
|
|
|
struct shrink_control *sc)
|
2018-07-26 20:22:04 +08:00
|
|
|
{
|
|
|
|
struct erofs_sb_info *sbi;
|
|
|
|
struct list_head *p;
|
|
|
|
|
|
|
|
unsigned long nr = sc->nr_to_scan;
|
|
|
|
unsigned int run_no;
|
|
|
|
unsigned long freed = 0;
|
|
|
|
|
|
|
|
spin_lock(&erofs_sb_list_lock);
|
2019-07-31 23:57:50 +08:00
|
|
|
do {
|
2018-07-26 20:22:04 +08:00
|
|
|
run_no = ++shrinker_run_no;
|
2019-07-31 23:57:50 +08:00
|
|
|
} while (run_no == 0);
|
2018-07-26 20:22:04 +08:00
|
|
|
|
|
|
|
/* Iterate over all mounted superblocks and try to shrink them */
|
|
|
|
p = erofs_sb_list.next;
|
|
|
|
while (p != &erofs_sb_list) {
|
|
|
|
sbi = list_entry(p, struct erofs_sb_info, list);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We move the ones we do to the end of the list, so we stop
|
|
|
|
* when we see one we have already done.
|
|
|
|
*/
|
|
|
|
if (sbi->shrinker_run_no == run_no)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!mutex_trylock(&sbi->umount_mutex)) {
|
|
|
|
p = p->next;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
spin_unlock(&erofs_sb_list_lock);
|
|
|
|
sbi->shrinker_run_no = run_no;
|
|
|
|
|
2020-02-26 16:10:06 +08:00
|
|
|
freed += erofs_shrink_workstation(sbi, nr - freed);
|
2018-07-26 20:22:04 +08:00
|
|
|
|
|
|
|
spin_lock(&erofs_sb_list_lock);
|
|
|
|
/* Get the next list element before we move this one */
|
|
|
|
p = p->next;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Move this one to the end of the list to provide some
|
|
|
|
* fairness.
|
|
|
|
*/
|
|
|
|
list_move_tail(&sbi->list, &erofs_sb_list);
|
|
|
|
mutex_unlock(&sbi->umount_mutex);
|
|
|
|
|
|
|
|
if (freed >= nr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
spin_unlock(&erofs_sb_list_lock);
|
|
|
|
return freed;
|
2018-07-26 20:22:03 +08:00
|
|
|
}
|
|
|
|
|
2019-07-31 23:57:39 +08:00
|
|
|
int __init erofs_init_shrinker(void)
|
|
|
|
{
|
2023-09-11 17:44:05 +08:00
|
|
|
erofs_shrinker_info = shrinker_alloc(0, "erofs-shrinker");
|
|
|
|
if (!erofs_shrinker_info)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
erofs_shrinker_info->count_objects = erofs_shrink_count;
|
|
|
|
erofs_shrinker_info->scan_objects = erofs_shrink_scan;
|
|
|
|
shrinker_register(erofs_shrinker_info);
|
|
|
|
return 0;
|
2019-07-31 23:57:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void erofs_exit_shrinker(void)
|
|
|
|
{
|
2023-09-11 17:44:05 +08:00
|
|
|
shrinker_free(erofs_shrinker_info);
|
2019-07-31 23:57:39 +08:00
|
|
|
}
|