* [PATCH RFC v7 0/7] erofs: inode page cache share feature
@ 2025-10-21 10:48 Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 1/7] erofs: move `struct erofs_anon_fs_type` to super.c Hongbo Li
` (7 more replies)
0 siblings, 8 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
Enabling page cahe sharing in container scenarios has become increasingly
crucial, as it can significantly reduce memory usage. In previous efforts,
Hongzhen has done substantial work to push this feature into the EROFS
mainline. Due to other commitments, he hasn't been able to continue his
work recently, and I'm very pleased to build upon his work and continue
to refine this implementation.
This is a forward-port of Hongzhen's original erofs shared pagecache
posted a half yeas ago at (the latest):
https://lore.kernel.org/all/20250301145002.2420830-1-hongzhen@linux.alibaba.com/T/#u
In addition to the forward-port, I have also fixed a couple bugs and
some minor cleanup during the migration.
Notes: Currently, only compilation tests and basic function have been
verified. Validation for the shared page cache feature is pending until
the erofs-utils tool is complete.
(A recap of Hongzhen's original cover letter is below, edited slightly
for this serise:)
Background
==============
Currently, reading files with different paths (or names) but the same
content can consume multiple copies of the page cache, even if the
content of these caches is identical. For example, reading identical
files (e.g., *.so files) from two different minor versions of container
images can result in multiple copies of the same page cache, since
different containers have different mount points. Therefore, sharing
the page cache for files with the same content can save memory.
Proposal
==============
1. determining file identity
----------------------------
First, a way needs to be found to check whether the content of two files
is the same. Here, the xattr values associated with the file
fingerprints are assessed for consistency. When creating the EROFS
image, users can specify the name of the xattr for file fingerprints,
and the corresponding name will be stored in the packfile. The on-disk
`ishare_key_start` indicates the offset of the xattr's name within the
packfile:
```
struct erofs_super_block {
__le32 build_time; /* seconds added to epoch for mkfs time */
__le64 rootnid_8b; /* (48BIT on) nid of root directory */
- __le64 reserved2;
+ __le32 ishare_key_start; /* start of ishare key */
+ __le32 reserved2;
__le64 metabox_nid; /* (METABOX on) nid of the metabox inode */
__le64 reserved3; /* [align to extslot 1] */
};
```
For example, users can specify the first long prefix as the name for the
file fingerprint as follows:
```
mkfs.erofs --ishare_key=trusted.erofs.fingerprint erofs.img ./dir
```
In this way, `trusted.erofs.fingerprint` serves as the name of the xattr
for the file fingerprint. The relevant patches for erofs-utils will be
released later.
At the same time, for security reasons, this patch series only shares
files within the same domain, which is achieved by adding
"-o domain_id=xxxx" during the mounting process:
```
mount -t erofs -o domain_id=xxx erofs.img /mnt
```
If no domain ID is specified, it will fall back to the non-page cache
sharing mode.
2. whose page cache is shared?
------------------------------
2.1. share the page cache of inode_A or inode_B
-----------------------------------------------
For example, we can share the page cache of inode_A, referred to as
PGCache_A. When reading file B, we read the contents from PGCache_A to
achieve memory savings. Furthermore, if we need to read another file C
with the same content, we will still read from PGCache_A. In this way,
we fulfill multiple read requests with just a single page cache.
2.2. share the de-duplicated inode's page cache
-----------------------------------------------
Unlike in 2.1, we allocate an internal deduplicated inode and use its
page cache as shared. Reads for files with identical content will
ultimately be routed to the page cache of the deduplicated inode. In
this way, a single page cache satisfies multiple read requests for
different files with the same contents.
2.3. discussion of the two solutions
-----------------------------------------------
Although the solution in 2.1 allows for page cache sharing, it has
inherent drawbacks. The creation and destruction of inode nodes in the
file system mean that when inode_A is destroyed, PGCache_A is also
released. Consequently, if we need to read the file content afterward,
we must retrieve the data from the disk again. This conflicts with the
design philosophy of page cache (caching contents from the disk).
Therefore, I choose to implement the solution in 2.2, which is to
allocate an internal deduplicated inode and use its page cache as
shared.
3. Implementation
==================
3.1. file open & close
----------------------
When the file is opened, the ->private_data field of file A or file B is
set to point to an internal deduplicated file. When the actual read
occurs, the page cache of this deduplicated file will be accessed.
When the file is opened, if the corresponding erofs inode is newly
created, then perform the following actions:
1. add the erofs inode to the backing list of the deduplicated inode;
2. increase the reference count of the deduplicated inode.
The purpose of step 1 above is to ensure that when a real I/O operation
occurs, the deduplicated inode can locate one of the disk devices
(as the deduplicated inode itself is not bound to a specific device).
Step 2 is for managing the lifecycle of the deduplicated inode.
When the erofs inode is destroyed, the opposite actions mentioned above
will be taken.
3.2. file reading
-----------------
Assuming the deduplication inode's page cache is PGCache_dedup, there
are two possible scenarios when reading a file:
1) the content being read is already present in PGCache_dedup;
2) the content being read is not present in PGCache_dedup.
In the second scenario, it involves the iomap operation to read from the
disk.
3.2.1. reading existing data in PGCache_dedup
-------------------------------------------
In this case, the overall read flowchart is as follows (take ksys_read()
for example):
ksys_read
│
│
▼
...
│
│
▼
erofs_ishare_file_read_iter (switch to backing deduplicated file)
│
│
▼
read PGCache_dedup & return
At this point, the content in PGCache_dedup will be read directly and
returned.
3.2.2 reading non-existent content in PGCache_dedup
---------------------------------------------------
In this case, disk I/O operations will be involved. Taking the reading
of an uncompressed file as an example, here is the reading process:
ksys_read
│
│
▼
...
│
│
▼
erofs_ishare_file_read_iter (switch to backing deduplicated file)
│
│
▼
... (allocate pages)
│
│
▼
erofs_read_folio/erofs_readahead
│
│
▼
... (iomap)
│
│
▼
erofs_iomap_begin
│
│
▼
...
Iomap and the layers below will involve disk I/O operations. As
described in 3.1, the deduplicated inode itself is not bound to a
specific device. The deduplicated inode will select an erofs inode from
the backing list (by default, the first one) to complete the
corresponding iomap operation.
3.2.3 optimized inode selection
-------------------------------
The inode selection method described in 3.2.2 may select an "inactive"
inode. An inactive inode indicates that there may have been no read
operations on the inode's device for a long time, and there is a high
likelihood that the device may be unmounted. In this case, unmounting
the device may experience a slight delay due to other read requests
being routed to that device. Therefore, we need to select some "active"
inodes for the iomap operation.
To achieve optimized inode selection, an additional `processing` list
has been added. At the beginning of erofs_{read_folio,readahead}(), the
corresponding erofs inode will be added to the `processing` list
(because they are active). And it is removed at the end of
erofs_{read_folio,readahead}(). In erofs_iomap_begin(), the selected
erofs inode's count is incremented, and in erofs_iomap_end(), the count
is decremented.
In this way, even after the erofs inode is removed from the `processing`
list, the increment in the reference count can ensure the integrity of
the data reading process. This is somewhat similar to RCU (not exactly
the same, but similar).
3.3. release page cache
-----------------------
Similar to overlayfs, when dropping the page cache via .fadvise, erofs
locates the deduplicated file and applies vfs_fadvise to that specific
file.
Effect
==================
I conducted experiments on two aspects across two different minor
versions of container images:
1. reading all files in two different minor versions of container images
2. run workloads or use the default entrypoint within the containers^[1]
Below is the memory usage for reading all files in two different minor
versions of container images:
+-------------------+------------------+-------------+---------------+
| Image | Page Cache Share | Memory (MB) | Memory |
| | | | Reduction (%) |
+-------------------+------------------+-------------+---------------+
| | No | 241 | - |
| redis +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 163 | 33% |
+-------------------+------------------+-------------+---------------+
| | No | 872 | - |
| postgres +------------------+-------------+---------------+
| 16.1 & 16.2 | Yes | 630 | 28% |
+-------------------+------------------+-------------+---------------+
| | No | 2771 | - |
| tensorflow +------------------+-------------+---------------+
| 2.11.0 & 2.11.1 | Yes | 2340 | 16% |
+-------------------+------------------+-------------+---------------+
| | No | 926 | - |
| mysql +------------------+-------------+---------------+
| 8.0.11 & 8.0.12 | Yes | 735 | 21% |
+-------------------+------------------+-------------+---------------+
| | No | 390 | - |
| nginx +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 219 | 44% |
+-------------------+------------------+-------------+---------------+
| tomcat | No | 924 | - |
| 10.1.25 & 10.1.26 +------------------+-------------+---------------+
| | Yes | 474 | 49% |
+-------------------+------------------+-------------+---------------+
Additionally, the table below shows the runtime memory usage of the
container:
+-------------------+------------------+-------------+---------------+
| Image | Page Cache Share | Memory (MB) | Memory |
| | | | Reduction (%) |
+-------------------+------------------+-------------+---------------+
| | No | 34.9 | - |
| redis +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 33.6 | 4% |
+-------------------+------------------+-------------+---------------+
| | No | 149.1 | - |
| postgres +------------------+-------------+---------------+
| 16.1 & 16.2 | Yes | 95 | 37% |
+-------------------+------------------+-------------+---------------+
| | No | 1027.9 | - |
| tensorflow +------------------+-------------+---------------+
| 2.11.0 & 2.11.1 | Yes | 934.3 | 10% |
+-------------------+------------------+-------------+---------------+
| | No | 155.0 | - |
| mysql +------------------+-------------+---------------+
| 8.0.11 & 8.0.12 | Yes | 139.1 | 11% |
+-------------------+------------------+-------------+---------------+
| | No | 25.4 | - |
| nginx +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 18.8 | 26% |
+-------------------+------------------+-------------+---------------+
| tomcat | No | 186 | - |
| 10.1.25 & 10.1.26 +------------------+-------------+---------------+
| | Yes | 99 | 47% |
+-------------------+------------------+-------------+---------------+
It can be observed that when reading all the files in the image, the
reduced memory usage varies from 16% to 49%, depending on the specific
image. Additionally, the container's runtime memory usage reduction
ranges from 4% to 47%.
[1] Below are the workload for these images:
- redis: redis-benchmark
- postgres: sysbench
- tensorflow: app.py of tensorflow.python.platform
- mysql: sysbench
- nginx: wrk
- tomcat: default entrypoint
The patch in this version has made the following changes compared to
the previous versionv(patch v5):
- support user-defined fingerprint name;
- support domain-specific page cache share;
- adjusted the code style;
- adjustments in code implementation, etc.
v5: https://lore.kernel.org/all/20250105151208.3797385-1-hongzhen@linux.alibaba.com/
v4: https://lore.kernel.org/all/20240902110620.2202586-1-hongzhen@linux.alibaba.com/
v3: https://lore.kernel.org/all/20240828111959.3677011-1-hongzhen@linux.alibaba.com/
v2: https://lore.kernel.org/all/20240731080704.678259-1-hongzhen@linux.alibaba.com/
v1: https://lore.kernel.org/all/20240722065355.1396365-1-hongzhen@linux.alibaba.com/
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 1/7] erofs: move `struct erofs_anon_fs_type` to super.c
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 2/7] erofs: support user-defined fingerprint name Hongbo Li
` (6 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
Move the `struct erofs_anon_fs_type` to the super.c and
expose it in preparation for the upcoming page cache share
feature.
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/fscache.c | 13 -------------
fs/erofs/internal.h | 2 ++
fs/erofs/super.c | 13 +++++++++++++
3 files changed, 15 insertions(+), 13 deletions(-)
diff --git a/fs/erofs/fscache.c b/fs/erofs/fscache.c
index 362acf828279..2d1683479fc0 100644
--- a/fs/erofs/fscache.c
+++ b/fs/erofs/fscache.c
@@ -3,7 +3,6 @@
* Copyright (C) 2022, Alibaba Cloud
* Copyright (C) 2022, Bytedance Inc. All rights reserved.
*/
-#include <linux/pseudo_fs.h>
#include <linux/fscache.h>
#include "internal.h"
@@ -13,18 +12,6 @@ static LIST_HEAD(erofs_domain_list);
static LIST_HEAD(erofs_domain_cookies_list);
static struct vfsmount *erofs_pseudo_mnt;
-static int erofs_anon_init_fs_context(struct fs_context *fc)
-{
- return init_pseudo(fc, EROFS_SUPER_MAGIC) ? 0 : -ENOMEM;
-}
-
-static struct file_system_type erofs_anon_fs_type = {
- .owner = THIS_MODULE,
- .name = "pseudo_erofs",
- .init_fs_context = erofs_anon_init_fs_context,
- .kill_sb = kill_anon_super,
-};
-
struct erofs_fscache_io {
struct netfs_cache_resources cres;
struct iov_iter iter;
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index f7f622836198..eac067446140 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -409,6 +409,8 @@ extern const struct file_operations erofs_dir_fops;
extern const struct iomap_ops z_erofs_iomap_report_ops;
+extern struct file_system_type erofs_anon_fs_type;
+
/* flags for erofs_fscache_register_cookie() */
#define EROFS_REG_COOKIE_SHARE 0x0001
#define EROFS_REG_COOKIE_NEED_NOEXIST 0x0002
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index f3f8d8c066e4..f9d8f978bf81 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -11,6 +11,7 @@
#include <linux/fs_parser.h>
#include <linux/exportfs.h>
#include <linux/backing-dev.h>
+#include <linux/pseudo_fs.h>
#include "xattr.h"
#define CREATE_TRACE_POINTS
@@ -920,6 +921,18 @@ static struct file_system_type erofs_fs_type = {
};
MODULE_ALIAS_FS("erofs");
+static int erofs_anon_init_fs_context(struct fs_context *fc)
+{
+ return init_pseudo(fc, EROFS_SUPER_MAGIC) ? 0 : -ENOMEM;
+}
+
+struct file_system_type erofs_anon_fs_type = {
+ .owner = THIS_MODULE,
+ .name = "pseudo_erofs",
+ .init_fs_context = erofs_anon_init_fs_context,
+ .kill_sb = kill_anon_super,
+};
+
static int __init erofs_module_init(void)
{
int err;
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 2/7] erofs: support user-defined fingerprint name
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 1/7] erofs: move `struct erofs_anon_fs_type` to super.c Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 3/7] erofs: support domain-specific page cache share Hongbo Li
` (5 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
When creating the EROFS image, users can specify the fingerprint name.
This is to prepare for the upcoming inode page cache share.
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port, minor fixes and cleanup]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/Kconfig | 10 ++++++++++
fs/erofs/erofs_fs.h | 6 ++++--
fs/erofs/internal.h | 6 ++++++
fs/erofs/super.c | 3 ++-
fs/erofs/xattr.c | 47 +++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/xattr.h | 6 ++++++
6 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/fs/erofs/Kconfig b/fs/erofs/Kconfig
index d81f3318417d..ef30cb452244 100644
--- a/fs/erofs/Kconfig
+++ b/fs/erofs/Kconfig
@@ -194,3 +194,13 @@ config EROFS_FS_PCPU_KTHREAD_HIPRI
at higher priority.
If unsure, say N.
+
+config EROFS_FS_INODE_SHARE
+ bool "EROFS inode page cache share support"
+ depends on EROFS_FS && EROFS_FS_XATTR
+ default n
+ help
+ This permits EROFS to share page cache for files with same
+ fingerprints.
+
+ If unsure, say N.
\ No newline at end of file
diff --git a/fs/erofs/erofs_fs.h b/fs/erofs/erofs_fs.h
index 3d5738f80072..f6ea8c35db45 100644
--- a/fs/erofs/erofs_fs.h
+++ b/fs/erofs/erofs_fs.h
@@ -35,8 +35,9 @@
#define EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES 0x00000040
#define EROFS_FEATURE_INCOMPAT_48BIT 0x00000080
#define EROFS_FEATURE_INCOMPAT_METABOX 0x00000100
+#define EROFS_FEATURE_INCOMPAT_ISHARE_KEY 0x00000200
#define EROFS_ALL_FEATURE_INCOMPAT \
- ((EROFS_FEATURE_INCOMPAT_METABOX << 1) - 1)
+ ((EROFS_FEATURE_INCOMPAT_ISHARE_KEY << 1) - 1)
#define EROFS_SB_EXTSLOT_SIZE 16
@@ -86,7 +87,8 @@ struct erofs_super_block {
__u8 reserved[3];
__le32 build_time; /* seconds added to epoch for mkfs time */
__le64 rootnid_8b; /* (48BIT on) nid of root directory */
- __le64 reserved2;
+ __le32 ishare_key_start; /* start of ishare key */
+ __le32 reserved2;
__le64 metabox_nid; /* (METABOX on) nid of the metabox inode */
__le64 reserved3; /* [align to extslot 1] */
};
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index eac067446140..245b9e3897bc 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -167,6 +167,11 @@ struct erofs_sb_info {
struct erofs_domain *domain;
char *fsid;
char *domain_id;
+
+ /* inode page cache share support */
+ u32 ishare_key_start;
+ int ishare_key_idx;
+ char *ishare_key;
};
#define EROFS_SB(sb) ((struct erofs_sb_info *)(sb)->s_fs_info)
@@ -232,6 +237,7 @@ EROFS_FEATURE_FUNCS(dedupe, incompat, INCOMPAT_DEDUPE)
EROFS_FEATURE_FUNCS(xattr_prefixes, incompat, INCOMPAT_XATTR_PREFIXES)
EROFS_FEATURE_FUNCS(48bit, incompat, INCOMPAT_48BIT)
EROFS_FEATURE_FUNCS(metabox, incompat, INCOMPAT_METABOX)
+EROFS_FEATURE_FUNCS(ishare_key, incompat, INCOMPAT_ISHARE_KEY)
EROFS_FEATURE_FUNCS(sb_chksum, compat, COMPAT_SB_CHKSUM)
EROFS_FEATURE_FUNCS(xattr_filter, compat, COMPAT_XATTR_FILTER)
EROFS_FEATURE_FUNCS(shared_ea_in_metabox, compat, COMPAT_SHARED_EA_IN_METABOX)
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index f9d8f978bf81..283449024996 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -339,7 +339,7 @@ static int erofs_read_superblock(struct super_block *sb)
return -EFSCORRUPTED; /* self-loop detection */
}
sbi->inos = le64_to_cpu(dsb->inos);
-
+ sbi->ishare_key_start = le32_to_cpu(dsb->ishare_key_start);
sbi->epoch = (s64)le64_to_cpu(dsb->epoch);
sbi->fixed_nsec = le32_to_cpu(dsb->fixed_nsec);
super_set_uuid(sb, (void *)dsb->uuid, sizeof(dsb->uuid));
@@ -738,6 +738,7 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
if (err)
return err;
+ erofs_xattr_set_ishare_key(sb);
erofs_set_sysfs_name(sb);
err = erofs_register_sysfs(sb);
if (err)
diff --git a/fs/erofs/xattr.c b/fs/erofs/xattr.c
index 396536d9a862..6610c007ee4c 100644
--- a/fs/erofs/xattr.c
+++ b/fs/erofs/xattr.c
@@ -564,3 +564,50 @@ struct posix_acl *erofs_get_acl(struct inode *inode, int type, bool rcu)
return acl;
}
#endif
+
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+void erofs_xattr_set_ishare_key(struct super_block *sb)
+{
+ struct erofs_sb_info *sbi = EROFS_SB(sb);
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ struct xattr_handler const *handler;
+ erofs_off_t pos;
+ char *key;
+ int len, i;
+ void *ptr;
+
+ if (!erofs_sb_has_fragments(sbi) || !erofs_sb_has_ishare_key(sbi) ||
+ !sbi->packed_inode)
+ return;
+
+ buf.mapping = sbi->packed_inode->i_mapping;
+ pos = sbi->ishare_key_start << 2;
+ (void)erofs_init_metabuf(&buf, sb, false);
+ ptr = erofs_read_metadata(sb, &buf, &pos, &len);
+
+ if (IS_ERR(ptr))
+ goto out;
+
+ for (i = 0; ARRAY_SIZE(erofs_xattr_handlers) - 1; i++) {
+ handler = erofs_xattr_handlers[i];
+ if (!handler)
+ break;
+ if (!memcmp(handler->prefix, ptr, strlen(handler->prefix)))
+ break;
+ }
+
+ if (!handler)
+ goto out;
+
+ len -= strlen(handler->prefix);
+ key = kzalloc(len + 1, GFP_KERNEL);
+ if (!key)
+ goto out;
+
+ memcpy(key, ptr + strlen(handler->prefix), len);
+ sbi->ishare_key = key;
+ sbi->ishare_key_idx = handler->flags;
+out:
+ erofs_put_metabuf(&buf);
+}
+#endif
diff --git a/fs/erofs/xattr.h b/fs/erofs/xattr.h
index 6317caa8413e..99c4674cfd51 100644
--- a/fs/erofs/xattr.h
+++ b/fs/erofs/xattr.h
@@ -67,4 +67,10 @@ struct posix_acl *erofs_get_acl(struct inode *inode, int type, bool rcu);
#define erofs_get_acl (NULL)
#endif
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+void erofs_xattr_set_ishare_key(struct super_block *sb);
+#else
+static inline void erofs_xattr_set_ishare_key(struct super_block *sb) {}
+#endif
+
#endif
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 3/7] erofs: support domain-specific page cache share
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 1/7] erofs: move `struct erofs_anon_fs_type` to super.c Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 2/7] erofs: support user-defined fingerprint name Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 4/7] erofs: introduce the page cache share feature Hongbo Li
` (4 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
Only files in the same domain will share the page cache. Also modify
the sysfs related content in preparation for the upcoming page cache
share feature.
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port, minor fixes]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/super.c | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 283449024996..93cc24542405 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -515,6 +515,8 @@ static int erofs_fc_parse_param(struct fs_context *fc,
if (!sbi->fsid)
return -ENOMEM;
break;
+#endif
+#if defined(CONFIG_EROFS_FS_ONDEMAND) || defined(CONFIG_EROFS_FS_INODE_SHARE)
case Opt_domain_id:
kfree(sbi->domain_id);
sbi->domain_id = kstrdup(param->string, GFP_KERNEL);
@@ -615,7 +617,7 @@ static void erofs_set_sysfs_name(struct super_block *sb)
{
struct erofs_sb_info *sbi = EROFS_SB(sb);
- if (sbi->domain_id)
+ if (sbi->domain_id && !sbi->ishare_key)
super_set_sysfs_name_generic(sb, "%s,%s", sbi->domain_id,
sbi->fsid);
else if (sbi->fsid)
@@ -1032,6 +1034,8 @@ static int erofs_show_options(struct seq_file *seq, struct dentry *root)
#ifdef CONFIG_EROFS_FS_ONDEMAND
if (sbi->fsid)
seq_printf(seq, ",fsid=%s", sbi->fsid);
+#endif
+#if defined(CONFIG_EROFS_FS_ONDEMAND) || defined(CONFIG_EROFS_FS_INODE_SHARE)
if (sbi->domain_id)
seq_printf(seq, ",domain_id=%s", sbi->domain_id);
#endif
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 4/7] erofs: introduce the page cache share feature
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
` (2 preceding siblings ...)
2025-10-21 10:48 ` [PATCH RFC v7 3/7] erofs: support domain-specific page cache share Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share Hongbo Li
` (3 subsequent siblings)
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
Currently, reading files with different paths (or names) but the same
content will consume multiple copies of the page cache, even if the
content of these page caches is the same. For example, reading
identical files (e.g., *.so files) from two different minor versions of
container images will cost multiple copies of the same page cache,
since different containers have different mount points. Therefore,
sharing the page cache for files with the same content can save memory.
This introduces the page cache share feature in erofs. It allocate a
deduplicated inode and use its page cache as shared. Reads for files
with identical content will ultimately be routed to the page cache of
the deduplicated inode. In this way, a single page cache satisfies
multiple read requests for different files with the same contents.
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port, minor fixes and cleanup]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/Makefile | 1 +
fs/erofs/internal.h | 25 +++++
fs/erofs/ishare.c | 236 ++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/ishare.h | 28 ++++++
fs/erofs/super.c | 30 +++++-
fs/erofs/xattr.c | 14 +--
6 files changed, 326 insertions(+), 8 deletions(-)
create mode 100644 fs/erofs/ishare.c
create mode 100644 fs/erofs/ishare.h
diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index 549abc424763..102a23bf5dec 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -10,3 +10,4 @@ erofs-$(CONFIG_EROFS_FS_ZIP_ZSTD) += decompressor_zstd.o
erofs-$(CONFIG_EROFS_FS_ZIP_ACCEL) += decompressor_crypto.o
erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
+erofs-$(CONFIG_EROFS_FS_INODE_SHARE) += ishare.o
\ No newline at end of file
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 245b9e3897bc..158bda6ba784 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -199,6 +199,14 @@ static inline bool erofs_is_fscache_mode(struct super_block *sb)
!erofs_is_fileio_mode(EROFS_SB(sb)) && !sb->s_bdev;
}
+extern struct file_system_type erofs_anon_fs_type;
+
+static inline bool erofs_is_ishare_inode(struct inode *inode)
+{
+ return !erofs_is_fscache_mode(inode->i_sb) &&
+ inode->i_sb->s_type == &erofs_anon_fs_type;
+}
+
enum {
EROFS_ZIP_CACHE_DISABLED,
EROFS_ZIP_CACHE_READAHEAD,
@@ -306,6 +314,22 @@ struct erofs_inode {
};
#endif /* CONFIG_EROFS_FS_ZIP */
};
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+ union {
+ /* internal dedup inode */
+ struct {
+ char *fingerprint;
+ spinlock_t lock;
+ /* all backing inodes */
+ struct list_head backing_head;
+ };
+
+ struct {
+ struct inode *ishare;
+ struct list_head backing_link;
+ };
+ };
+#endif
/* the corresponding vfs inode */
struct inode vfs_inode;
};
@@ -412,6 +436,7 @@ extern const struct inode_operations erofs_dir_iops;
extern const struct file_operations erofs_file_fops;
extern const struct file_operations erofs_dir_fops;
+extern const struct file_operations erofs_ishare_fops;
extern const struct iomap_ops z_erofs_iomap_report_ops;
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
new file mode 100644
index 000000000000..910b732bf8e7
--- /dev/null
+++ b/fs/erofs/ishare.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024, Alibaba Cloud
+ */
+#include <linux/xxhash.h>
+#include <linux/refcount.h>
+#include <linux/mount.h>
+#include <linux/mutex.h>
+#include <linux/ramfs.h>
+#include "ishare.h"
+#include "internal.h"
+#include "xattr.h"
+
+static DEFINE_MUTEX(erofs_ishare_lock);
+static struct vfsmount *erofs_ishare_mnt;
+static refcount_t erofs_ishare_supers;
+
+int erofs_ishare_init(struct super_block *sb)
+{
+ struct vfsmount *mnt = NULL;
+ struct erofs_sb_info *sbi = EROFS_SB(sb);
+
+ if (!sbi->ishare_key)
+ return 0;
+
+ mutex_lock(&erofs_ishare_lock);
+ if (erofs_ishare_mnt) {
+ refcount_inc(&erofs_ishare_supers);
+ } else {
+ mnt = kern_mount(&erofs_anon_fs_type);
+ if (!IS_ERR(mnt)) {
+ erofs_ishare_mnt = mnt;
+ refcount_set(&erofs_ishare_supers, 1);
+ }
+ }
+ mutex_unlock(&erofs_ishare_lock);
+ return IS_ERR(mnt) ? PTR_ERR(mnt) : 0;
+}
+
+void erofs_ishare_exit(struct super_block *sb)
+{
+ struct erofs_sb_info *sbi = EROFS_SB(sb);
+ struct vfsmount *tmp;
+
+ if (!sbi->ishare_key || !erofs_ishare_mnt)
+ return;
+
+ mutex_lock(&erofs_ishare_lock);
+ if (refcount_dec_and_test(&erofs_ishare_supers)) {
+ tmp = erofs_ishare_mnt;
+ erofs_ishare_mnt = NULL;
+ mutex_unlock(&erofs_ishare_lock);
+ kern_unmount(tmp);
+ mutex_lock(&erofs_ishare_lock);
+ }
+ mutex_unlock(&erofs_ishare_lock);
+ kfree(sbi->ishare_key);
+ sbi->ishare_key = NULL;
+}
+
+static int erofs_ishare_iget5_eq(struct inode *inode, void *data)
+{
+ struct erofs_inode *vi = EROFS_I(inode);
+
+ return vi->fingerprint && memcmp(vi->fingerprint, data,
+ sizeof(size_t) + *(size_t *)data) == 0;
+}
+
+static int erofs_ishare_iget5_set(struct inode *inode, void *data)
+{
+ struct erofs_inode *vi = EROFS_I(inode);
+
+ vi->fingerprint = data;
+ INIT_LIST_HEAD(&vi->backing_head);
+ spin_lock_init(&vi->lock);
+ return 0;
+}
+
+bool erofs_ishare_fill_inode(struct inode *inode)
+{
+ struct erofs_inode *vi = EROFS_I(inode);
+ struct erofs_sb_info *sbi = EROFS_SB(inode->i_sb);
+ struct inode *idedup;
+ /*
+ * fingerprint layout:
+ * fingerprint length + fingerprint content (xattr_value + domain_id)
+ */
+ char *ishare_key = sbi->ishare_key, *fingerprint;
+ ssize_t ishare_vlen;
+ unsigned long hash;
+ int key_idx;
+
+ if (!sbi->domain_id || !ishare_key)
+ return false;
+
+ key_idx = sbi->ishare_key_idx;
+ ishare_vlen = erofs_getxattr(inode, key_idx, ishare_key, NULL, 0);
+ if (ishare_vlen <= 0 || ishare_vlen > (1 << sbi->blkszbits))
+ return false;
+
+ fingerprint = kmalloc(sizeof(ssize_t) + ishare_vlen +
+ strlen(sbi->domain_id), GFP_KERNEL);
+ if (!fingerprint)
+ return false;
+
+ *(ssize_t *)fingerprint = ishare_vlen + strlen(sbi->domain_id);
+ if (ishare_vlen != erofs_getxattr(inode, key_idx, ishare_key,
+ fingerprint + sizeof(ssize_t),
+ ishare_vlen)) {
+ kfree(fingerprint);
+ return false;
+ }
+
+ memcpy(fingerprint + sizeof(ssize_t) + ishare_vlen,
+ sbi->domain_id, strlen(sbi->domain_id));
+ hash = xxh32(fingerprint + sizeof(ssize_t),
+ ishare_vlen + strlen(sbi->domain_id), hash);
+ idedup = iget5_locked(erofs_ishare_mnt->mnt_sb, hash,
+ erofs_ishare_iget5_eq, erofs_ishare_iget5_set,
+ fingerprint);
+ if (!idedup) {
+ kfree(fingerprint);
+ return false;
+ }
+
+ INIT_LIST_HEAD(&vi->backing_link);
+ vi->ishare = idedup;
+ spin_lock(&EROFS_I(idedup)->lock);
+ list_add(&vi->backing_link, &EROFS_I(idedup)->backing_head);
+ spin_unlock(&EROFS_I(idedup)->lock);
+
+ if (!(idedup->i_state & I_NEW)) {
+ kfree(fingerprint);
+ return true;
+ }
+
+ if (erofs_inode_is_data_compressed(vi->datalayout))
+ idedup->i_mapping->a_ops = &z_erofs_aops;
+ else
+ idedup->i_mapping->a_ops = &erofs_aops;
+ idedup->i_mode = vi->vfs_inode.i_mode;
+ i_size_write(idedup, vi->vfs_inode.i_size);
+ unlock_new_inode(idedup);
+ return true;
+}
+
+void erofs_ishare_free_inode(struct inode *inode)
+{
+ struct erofs_inode *vi = EROFS_I(inode);
+ struct inode *idedup = vi->ishare;
+
+ if (!idedup)
+ return;
+
+ spin_lock(&EROFS_I(idedup)->lock);
+ list_del(&vi->backing_link);
+ spin_unlock(&EROFS_I(idedup)->lock);
+ iput(idedup);
+ vi->ishare = NULL;
+}
+
+static int erofs_ishare_file_open(struct inode *inode, struct file *file)
+{
+ struct file *realfile;
+ struct inode *dedup;
+
+ dedup = EROFS_I(inode)->ishare;
+ if (!dedup)
+ return -EINVAL;
+
+ realfile = alloc_file_pseudo(dedup, erofs_ishare_mnt, "erofs_ishare_file",
+ O_RDONLY, &erofs_file_fops);
+ if (IS_ERR(realfile))
+ return PTR_ERR(realfile);
+
+ file_ra_state_init(&realfile->f_ra, file->f_mapping);
+ realfile->private_data = EROFS_I(inode);
+ file->private_data = realfile;
+ return 0;
+}
+
+static int erofs_ishare_file_release(struct inode *inode, struct file *file)
+{
+ struct file *realfile = file->private_data;
+
+ if (!realfile)
+ return -EINVAL;
+ fput(realfile);
+ realfile->private_data = NULL;
+ return 0;
+}
+
+static ssize_t erofs_ishare_file_read_iter(struct kiocb *iocb,
+ struct iov_iter *to)
+{
+ struct file *realfile = iocb->ki_filp->private_data;
+ struct inode *inode = file_inode(iocb->ki_filp);
+ struct kiocb dedup_iocb;
+ ssize_t nread;
+
+ if (!realfile)
+ return -EINVAL;
+ if (!iov_iter_count(to))
+ return 0;
+
+ /* fallback to the original file in DAX or DIRECT mode */
+ if (IS_DAX(inode) || (iocb->ki_flags & IOCB_DIRECT))
+ realfile = iocb->ki_filp;
+
+ kiocb_clone(&dedup_iocb, iocb, realfile);
+ nread = filemap_read(&dedup_iocb, to, 0);
+ iocb->ki_pos = dedup_iocb.ki_pos;
+ touch_atime(&iocb->ki_filp->f_path);
+ return nread;
+}
+
+static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct file *realfile = file->private_data;
+
+ if (!realfile)
+ return -EINVAL;
+
+ vma_set_file(vma, realfile);
+ return generic_file_readonly_mmap(file, vma);
+}
+
+const struct file_operations erofs_ishare_fops = {
+ .open = erofs_ishare_file_open,
+ .llseek = generic_file_llseek,
+ .read_iter = erofs_ishare_file_read_iter,
+ .mmap = erofs_ishare_mmap,
+ .release = erofs_ishare_file_release,
+ .get_unmapped_area = thp_get_unmapped_area,
+ .splice_read = filemap_splice_read,
+};
diff --git a/fs/erofs/ishare.h b/fs/erofs/ishare.h
new file mode 100644
index 000000000000..54f2251c8179
--- /dev/null
+++ b/fs/erofs/ishare.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2024, Alibaba Cloud
+ */
+#ifndef __EROFS_ISHARE_H
+#define __EROFS_ISHARE_H
+
+#include <linux/fs.h>
+#include <linux/spinlock.h>
+#include "internal.h"
+
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+
+int erofs_ishare_init(struct super_block *sb);
+void erofs_ishare_exit(struct super_block *sb);
+bool erofs_ishare_fill_inode(struct inode *inode);
+void erofs_ishare_free_inode(struct inode *inode);
+
+#else
+
+static inline int erofs_ishare_init(struct super_block *sb) { return 0; }
+static inline void erofs_ishare_exit(struct super_block *sb) {}
+static inline bool erofs_ishare_fill_inode(struct inode *inode) { return false; }
+static inline void erofs_ishare_free_inode(struct inode *inode) {}
+
+#endif // CONFIG_EROFS_FS_INODE_SHARE
+
+#endif
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 93cc24542405..f067633c0072 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -13,6 +13,7 @@
#include <linux/backing-dev.h>
#include <linux/pseudo_fs.h>
#include "xattr.h"
+#include "ishare.h"
#define CREATE_TRACE_POINTS
#include <trace/events/erofs.h>
@@ -77,10 +78,25 @@ static struct inode *erofs_alloc_inode(struct super_block *sb)
return &vi->vfs_inode;
}
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+static void erofs_free_dedup_inode(struct erofs_inode *vi)
+{
+ kfree(vi->fingerprint);
+ kmem_cache_free(erofs_inode_cachep, vi);
+};
+#else
+static void erofs_free_dedup_inode(struct erofs_inode *vi)
+{}
+#endif
+
static void erofs_free_inode(struct inode *inode)
{
struct erofs_inode *vi = EROFS_I(inode);
+ if (erofs_is_ishare_inode(inode)) {
+ erofs_free_dedup_inode(vi);
+ return;
+ }
if (inode->i_op == &erofs_fast_symlink_iops)
kfree(inode->i_link);
kfree(vi->xattr_shared_xattrs);
@@ -924,9 +940,21 @@ static struct file_system_type erofs_fs_type = {
};
MODULE_ALIAS_FS("erofs");
+static const struct super_operations erofs_anon_sops = {
+ .statfs = simple_statfs,
+ .alloc_inode = erofs_alloc_inode,
+ .free_inode = erofs_free_inode,
+};
+
static int erofs_anon_init_fs_context(struct fs_context *fc)
{
- return init_pseudo(fc, EROFS_SUPER_MAGIC) ? 0 : -ENOMEM;
+ struct pseudo_fs_context *ctx;
+
+ ctx = init_pseudo(fc, EROFS_SUPER_MAGIC);
+ if (ctx)
+ ctx->ops = &erofs_anon_sops;
+
+ return ctx ? 0 : -ENOMEM;
}
struct file_system_type erofs_anon_fs_type = {
diff --git a/fs/erofs/xattr.c b/fs/erofs/xattr.c
index 6610c007ee4c..4b39db939135 100644
--- a/fs/erofs/xattr.c
+++ b/fs/erofs/xattr.c
@@ -571,6 +571,7 @@ void erofs_xattr_set_ishare_key(struct super_block *sb)
struct erofs_sb_info *sbi = EROFS_SB(sb);
struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
struct xattr_handler const *handler;
+ bool found = false;
erofs_off_t pos;
char *key;
int len, i;
@@ -582,28 +583,27 @@ void erofs_xattr_set_ishare_key(struct super_block *sb)
buf.mapping = sbi->packed_inode->i_mapping;
pos = sbi->ishare_key_start << 2;
- (void)erofs_init_metabuf(&buf, sb, false);
ptr = erofs_read_metadata(sb, &buf, &pos, &len);
if (IS_ERR(ptr))
goto out;
- for (i = 0; ARRAY_SIZE(erofs_xattr_handlers) - 1; i++) {
+ for (i = 0; ARRAY_SIZE(erofs_xattr_handlers); i++) {
handler = erofs_xattr_handlers[i];
if (!handler)
+ continue;
+ if (!memcmp(handler->prefix, ptr, strlen(handler->prefix))) {
+ found = true;
break;
- if (!memcmp(handler->prefix, ptr, strlen(handler->prefix)))
- break;
+ }
}
- if (!handler)
+ if (!found)
goto out;
-
len -= strlen(handler->prefix);
key = kzalloc(len + 1, GFP_KERNEL);
if (!key)
goto out;
-
memcpy(key, ptr + strlen(handler->prefix), len);
sbi->ishare_key = key;
sbi->ishare_key_idx = handler->flags;
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
` (3 preceding siblings ...)
2025-10-21 10:48 ` [PATCH RFC v7 4/7] erofs: introduce the page cache share feature Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 13:01 ` Gao Xiang
2025-10-21 10:48 ` [PATCH RFC v7 6/7] erofs: support compressed " Hongbo Li
` (2 subsequent siblings)
7 siblings, 1 reply; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
This patch adds inode page cache sharing functionality for unencoded
files.
I conducted experiments in the container environment. Below is the
memory usage for reading all files in two different minor versions
of container images:
+-------------------+------------------+-------------+---------------+
| Image | Page Cache Share | Memory (MB) | Memory |
| | | | Reduction (%) |
+-------------------+------------------+-------------+---------------+
| | No | 241 | - |
| redis +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 163 | 33% |
+-------------------+------------------+-------------+---------------+
| | No | 872 | - |
| postgres +------------------+-------------+---------------+
| 16.1 & 16.2 | Yes | 630 | 28% |
+-------------------+------------------+-------------+---------------+
| | No | 2771 | - |
| tensorflow +------------------+-------------+---------------+
| 2.11.0 & 2.11.1 | Yes | 2340 | 16% |
+-------------------+------------------+-------------+---------------+
| | No | 926 | - |
| mysql +------------------+-------------+---------------+
| 8.0.11 & 8.0.12 | Yes | 735 | 21% |
+-------------------+------------------+-------------+---------------+
| | No | 390 | - |
| nginx +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 219 | 44% |
+-------------------+------------------+-------------+---------------+
| tomcat | No | 924 | - |
| 10.1.25 & 10.1.26 +------------------+-------------+---------------+
| | Yes | 474 | 49% |
+-------------------+------------------+-------------+---------------+
Additionally, the table below shows the runtime memory usage of the
container:
+-------------------+------------------+-------------+---------------+
| Image | Page Cache Share | Memory (MB) | Memory |
| | | | Reduction (%) |
+-------------------+------------------+-------------+---------------+
| | No | 35 | - |
| redis +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 28 | 20% |
+-------------------+------------------+-------------+---------------+
| | No | 149 | - |
| postgres +------------------+-------------+---------------+
| 16.1 & 16.2 | Yes | 95 | 37% |
+-------------------+------------------+-------------+---------------+
| | No | 1028 | - |
| tensorflow +------------------+-------------+---------------+
| 2.11.0 & 2.11.1 | Yes | 930 | 10% |
+-------------------+------------------+-------------+---------------+
| | No | 155 | - |
| mysql +------------------+-------------+---------------+
| 8.0.11 & 8.0.12 | Yes | 132 | 15% |
+-------------------+------------------+-------------+---------------+
| | No | 25 | - |
| nginx +------------------+-------------+---------------+
| 7.2.4 & 7.2.5 | Yes | 20 | 20% |
+-------------------+------------------+-------------+---------------+
| tomcat | No | 186 | - |
| 10.1.25 & 10.1.26 +------------------+-------------+---------------+
| | Yes | 98 | 48% |
+-------------------+------------------+-------------+---------------+
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port, minor fixes and cleanup]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/data.c | 81 ++++++++++++++++++++++++++++++++++++++-----
fs/erofs/inode.c | 5 +++
fs/erofs/internal.h | 4 +++
fs/erofs/ishare.c | 83 +++++++++++++++++++++++++++++++++++++++++++++
fs/erofs/ishare.h | 18 ++++++++++
fs/erofs/super.c | 7 ++++
6 files changed, 190 insertions(+), 8 deletions(-)
diff --git a/fs/erofs/data.c b/fs/erofs/data.c
index 8ca29962a3dd..438d43c959aa 100644
--- a/fs/erofs/data.c
+++ b/fs/erofs/data.c
@@ -5,6 +5,7 @@
* Copyright (C) 2021, Alibaba Cloud
*/
#include "internal.h"
+#include "ishare.h"
#include <linux/sched/mm.h>
#include <trace/events/erofs.h>
@@ -266,25 +267,55 @@ void erofs_onlinefolio_end(struct folio *folio, int err, bool dirty)
folio_end_read(folio, !(v & BIT(EROFS_ONLINEFOLIO_EIO)));
}
+struct erofs_iomap {
+ void *base;
+ struct inode *realinode;
+};
+
static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
int ret;
- struct super_block *sb = inode->i_sb;
+ struct super_block *sb;
struct erofs_map_blocks map;
struct erofs_map_dev mdev;
+ struct inode *realinode = inode;
+ struct erofs_iomap *erofs_iomap;
+ bool is_ishare = erofs_is_ishare_inode(inode);
+
+ if (is_ishare) {
+ if (!iomap->private) {
+ erofs_iomap = kzalloc(sizeof(*erofs_iomap),
+ GFP_KERNEL);
+ if (!erofs_iomap)
+ return -ENOMEM;
+ erofs_iomap->realinode = erofs_ishare_iget(inode);
+ if (!erofs_iomap->realinode) {
+ kfree(erofs_iomap);
+ return -EINVAL;
+ }
+ iomap->private = erofs_iomap;
+ }
+ erofs_iomap = iomap->private;
+ realinode = erofs_iomap->realinode;
+ }
+ sb = realinode->i_sb;
map.m_la = offset;
map.m_llen = length;
- ret = erofs_map_blocks(inode, &map);
+ ret = erofs_map_blocks(realinode, &map);
if (ret < 0)
return ret;
iomap->offset = map.m_la;
iomap->length = map.m_llen;
iomap->flags = 0;
- iomap->private = NULL;
iomap->addr = IOMAP_NULL_ADDR;
+
+ if (is_ishare)
+ erofs_iomap->base = NULL;
+ else
+ iomap->private = NULL;
if (!(map.m_flags & EROFS_MAP_MAPPED)) {
iomap->type = IOMAP_HOLE;
return 0;
@@ -318,7 +349,10 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
if (IS_ERR(ptr))
return PTR_ERR(ptr);
iomap->inline_data = ptr;
- iomap->private = buf.base;
+ if (is_ishare)
+ erofs_iomap->base = buf.base;
+ else
+ iomap->private = buf.base;
} else {
iomap->type = IOMAP_MAPPED;
}
@@ -328,7 +362,17 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
static int erofs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
- void *ptr = iomap->private;
+ struct erofs_iomap *erofs_iomap;
+ bool is_ishare;
+ void *ptr;
+
+ is_ishare = erofs_is_ishare_inode(inode);
+ if (is_ishare) {
+ erofs_iomap = iomap->private;
+ ptr = erofs_iomap->base;
+ } else {
+ ptr = iomap->private;
+ }
if (ptr) {
struct erofs_buf buf = {
@@ -341,6 +385,12 @@ static int erofs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
} else {
DBG_BUGON(iomap->type == IOMAP_INLINE);
}
+
+ if (is_ishare) {
+ erofs_ishare_iput(erofs_iomap->realinode);
+ kfree(erofs_iomap);
+ iomap->private = NULL;
+ }
return written;
}
@@ -369,17 +419,32 @@ int erofs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
*/
static int erofs_read_folio(struct file *file, struct folio *folio)
{
+ struct erofs_read_ctx rdctx = {
+ .file = file,
+ .inode = folio_inode(folio),
+ };
+ int ret;
+
+ erofs_read_begin(&rdctx);
+ ret = iomap_read_folio(folio, &erofs_iomap_ops);
+ erofs_read_end(&rdctx);
trace_erofs_read_folio(folio, true);
- return iomap_read_folio(folio, &erofs_iomap_ops);
+ return ret;
}
static void erofs_readahead(struct readahead_control *rac)
{
+ struct erofs_read_ctx rdctx = {
+ .file = rac->file,
+ .inode = rac->mapping->host,
+ };
+
+ erofs_read_begin(&rdctx);
+ iomap_readahead(rac, &erofs_iomap_ops);
+ erofs_read_end(&rdctx);
trace_erofs_readahead(rac->mapping->host, readahead_index(rac),
readahead_count(rac), true);
-
- return iomap_readahead(rac, &erofs_iomap_ops);
}
static sector_t erofs_bmap(struct address_space *mapping, sector_t block)
diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c
index cb780c095d28..fe45e6c18f8e 100644
--- a/fs/erofs/inode.c
+++ b/fs/erofs/inode.c
@@ -5,6 +5,7 @@
* Copyright (C) 2021, Alibaba Cloud
*/
#include "xattr.h"
+#include "ishare.h"
#include <linux/compat.h>
#include <trace/events/erofs.h>
@@ -215,6 +216,10 @@ static int erofs_fill_inode(struct inode *inode)
case S_IFREG:
inode->i_op = &erofs_generic_iops;
inode->i_fop = &erofs_file_fops;
+#ifdef CONFIG_EROFS_FS_INODE_SHARE
+ if (erofs_ishare_fill_inode(inode))
+ inode->i_fop = &erofs_ishare_fops;
+#endif
break;
case S_IFDIR:
inode->i_op = &erofs_dir_iops;
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 158bda6ba784..9ce6e5753978 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -322,11 +322,15 @@ struct erofs_inode {
spinlock_t lock;
/* all backing inodes */
struct list_head backing_head;
+ /* processing list */
+ struct list_head processing_head;
};
struct {
struct inode *ishare;
struct list_head backing_link;
+ struct list_head processing_link;
+ atomic_t processing_count;
};
};
#endif
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index 910b732bf8e7..73432b13bf75 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -72,6 +72,7 @@ static int erofs_ishare_iget5_set(struct inode *inode, void *data)
vi->fingerprint = data;
INIT_LIST_HEAD(&vi->backing_head);
+ INIT_LIST_HEAD(&vi->processing_head);
spin_lock_init(&vi->lock);
return 0;
}
@@ -124,7 +125,9 @@ bool erofs_ishare_fill_inode(struct inode *inode)
}
INIT_LIST_HEAD(&vi->backing_link);
+ INIT_LIST_HEAD(&vi->processing_link);
vi->ishare = idedup;
+
spin_lock(&EROFS_I(idedup)->lock);
list_add(&vi->backing_link, &EROFS_I(idedup)->backing_head);
spin_unlock(&EROFS_I(idedup)->lock);
@@ -234,3 +237,83 @@ const struct file_operations erofs_ishare_fops = {
.get_unmapped_area = thp_get_unmapped_area,
.splice_read = filemap_splice_read,
};
+
+void erofs_read_begin(struct erofs_read_ctx *rdctx)
+{
+ struct erofs_inode *vi, *vi_dedup;
+
+ if (!rdctx->file || !erofs_is_ishare_inode(rdctx->inode))
+ return;
+
+ vi = rdctx->file->private_data;
+ vi_dedup = EROFS_I(file_inode(rdctx->file));
+
+ spin_lock(&vi_dedup->lock);
+ if (!list_empty(&vi->processing_link)) {
+ atomic_inc(&vi->processing_count);
+ } else {
+ list_add(&vi->processing_link,
+ &vi_dedup->processing_head);
+ atomic_set(&vi->processing_count, 1);
+ }
+ spin_unlock(&vi_dedup->lock);
+}
+
+void erofs_read_end(struct erofs_read_ctx *rdctx)
+{
+ struct erofs_inode *vi, *vi_dedup;
+
+ if (!rdctx->file || !erofs_is_ishare_inode(rdctx->inode))
+ return;
+
+ vi = rdctx->file->private_data;
+ vi_dedup = EROFS_I(file_inode(rdctx->file));
+
+ spin_lock(&vi_dedup->lock);
+ if (atomic_dec_and_test(&vi->processing_count))
+ list_del_init(&vi->processing_link);
+ spin_unlock(&vi_dedup->lock);
+}
+
+/*
+ * erofs_ishare_iget - find the backing inode.
+ */
+struct inode *erofs_ishare_iget(struct inode *inode)
+{
+ struct erofs_inode *vi, *vi_dedup;
+ struct inode *realinode;
+
+ if (!erofs_is_ishare_inode(inode))
+ return igrab(inode);
+
+ vi_dedup = EROFS_I(inode);
+ spin_lock(&vi_dedup->lock);
+ /* try processing inodes first */
+ if (!list_empty(&vi_dedup->processing_head)) {
+ list_for_each_entry(vi, &vi_dedup->processing_head,
+ processing_link) {
+ realinode = igrab(&vi->vfs_inode);
+ if (realinode) {
+ spin_unlock(&vi_dedup->lock);
+ return realinode;
+ }
+ }
+ }
+
+ /* fall back to all backing inodes */
+ DBG_BUGON(list_empty(&vi_dedup->backing_head));
+ list_for_each_entry(vi, &vi_dedup->backing_head, backing_link) {
+ realinode = igrab(&vi->vfs_inode);
+ if (realinode)
+ break;
+ }
+ spin_unlock(&vi_dedup->lock);
+
+ DBG_BUGON(!realinode);
+ return realinode;
+}
+
+void erofs_ishare_iput(struct inode *realinode)
+{
+ iput(realinode);
+}
diff --git a/fs/erofs/ishare.h b/fs/erofs/ishare.h
index 54f2251c8179..b85fa240507b 100644
--- a/fs/erofs/ishare.h
+++ b/fs/erofs/ishare.h
@@ -9,6 +9,11 @@
#include <linux/spinlock.h>
#include "internal.h"
+struct erofs_read_ctx {
+ struct file *file; /* may be NULL */
+ struct inode *inode;
+};
+
#ifdef CONFIG_EROFS_FS_INODE_SHARE
int erofs_ishare_init(struct super_block *sb);
@@ -16,6 +21,13 @@ void erofs_ishare_exit(struct super_block *sb);
bool erofs_ishare_fill_inode(struct inode *inode);
void erofs_ishare_free_inode(struct inode *inode);
+/* read/readahead */
+void erofs_read_begin(struct erofs_read_ctx *rdctx);
+void erofs_read_end(struct erofs_read_ctx *rdctx);
+
+struct inode *erofs_ishare_iget(struct inode *inode);
+void erofs_ishare_iput(struct inode *realinode);
+
#else
static inline int erofs_ishare_init(struct super_block *sb) { return 0; }
@@ -23,6 +35,12 @@ static inline void erofs_ishare_exit(struct super_block *sb) {}
static inline bool erofs_ishare_fill_inode(struct inode *inode) { return false; }
static inline void erofs_ishare_free_inode(struct inode *inode) {}
+static inline void erofs_read_begin(struct erofs_read_ctx *rdctx) {}
+static inline void erofs_read_end(struct erofs_read_ctx *rdctx) {}
+
+static inline struct inode *erofs_ishare_iget(struct inode *inode) { return inode; }
+static inline void erofs_ishare_iput(struct inode *realinode) {}
+
#endif // CONFIG_EROFS_FS_INODE_SHARE
#endif
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index f067633c0072..cba3da383558 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -97,6 +97,7 @@ static void erofs_free_inode(struct inode *inode)
erofs_free_dedup_inode(vi);
return;
}
+ erofs_ishare_free_inode(inode);
if (inode->i_op == &erofs_fast_symlink_iops)
kfree(inode->i_link);
kfree(vi->xattr_shared_xattrs);
@@ -762,6 +763,10 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
if (err)
return err;
+ err = erofs_ishare_init(sb);
+ if (err)
+ return err;
+
sbi->dir_ra_bytes = EROFS_DIR_RA_BYTES;
erofs_info(sb, "mounted with root inode @ nid %llu.", sbi->root_nid);
return 0;
@@ -911,6 +916,7 @@ static void erofs_kill_sb(struct super_block *sb)
kill_anon_super(sb);
else
kill_block_super(sb);
+
erofs_drop_internal_inodes(sbi);
fs_put_dax(sbi->dif0.dax_dev, NULL);
erofs_fscache_unregister_fs(sb);
@@ -922,6 +928,7 @@ static void erofs_put_super(struct super_block *sb)
{
struct erofs_sb_info *const sbi = EROFS_SB(sb);
+ erofs_ishare_exit(sb);
erofs_unregister_sysfs(sb);
erofs_shrinker_unregister(sb);
erofs_xattr_prefixes_cleanup(sb);
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 6/7] erofs: support compressed inodes for page cache share
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
` (4 preceding siblings ...)
2025-10-21 10:48 ` [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 7/7] erofs: implement .fadvise " Hongbo Li
2025-10-21 13:04 ` [PATCH RFC v7 0/7] erofs: inode page cache share feature Gao Xiang
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
This patch adds page cache sharing functionality for compressed inodes.
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/zdata.c | 56 +++++++++++++++++++++++++++++++++++++++---------
1 file changed, 46 insertions(+), 10 deletions(-)
diff --git a/fs/erofs/zdata.c b/fs/erofs/zdata.c
index bc80cfe482f7..e76421de86cb 100644
--- a/fs/erofs/zdata.c
+++ b/fs/erofs/zdata.c
@@ -5,6 +5,7 @@
* Copyright (C) 2022 Alibaba Cloud
*/
#include "compress.h"
+#include "ishare.h"
#include <linux/psi.h>
#include <linux/cpuhotplug.h>
#include <trace/events/erofs.h>
@@ -493,7 +494,7 @@ enum z_erofs_pclustermode {
};
struct z_erofs_frontend {
- struct inode *const inode;
+ struct inode *inode;
struct erofs_map_blocks map;
struct z_erofs_bvec_iter biter;
@@ -1870,10 +1871,24 @@ static void z_erofs_pcluster_readmore(struct z_erofs_frontend *f,
static int z_erofs_read_folio(struct file *file, struct folio *folio)
{
- struct inode *const inode = folio->mapping->host;
- Z_EROFS_DEFINE_FRONTEND(f, inode, folio_pos(folio));
+ struct inode *const inode = folio->mapping->host, *realinode;
+ Z_EROFS_DEFINE_FRONTEND(f, NULL, folio_pos(folio));
+ struct erofs_read_ctx rdctx = {
+ .file = file,
+ .inode = inode,
+ };
int err;
+ trace_erofs_read_folio(folio, false);
+
+ erofs_read_begin(&rdctx);
+
+ if (erofs_is_ishare_inode(inode))
+ realinode = erofs_ishare_iget(inode);
+ else
+ realinode = inode;
+
+ f.inode = realinode;
trace_erofs_read_folio(folio, false);
z_erofs_pcluster_readmore(&f, NULL, true);
err = z_erofs_scan_folio(&f, folio, false);
@@ -1883,23 +1898,39 @@ static int z_erofs_read_folio(struct file *file, struct folio *folio)
/* if some pclusters are ready, need submit them anyway */
err = z_erofs_runqueue(&f, 0) ?: err;
if (err && err != -EINTR)
- erofs_err(inode->i_sb, "read error %d @ %lu of nid %llu",
- err, folio->index, EROFS_I(inode)->nid);
+ erofs_err(realinode->i_sb, "read error %d @ %lu of nid %llu",
+ err, folio->index, EROFS_I(realinode)->nid);
erofs_put_metabuf(&f.map.buf);
erofs_release_pages(&f.pagepool);
+
+ if (erofs_is_ishare_inode(inode))
+ erofs_ishare_iput(realinode);
+
+ erofs_read_end(&rdctx);
return err;
}
static void z_erofs_readahead(struct readahead_control *rac)
{
- struct inode *const inode = rac->mapping->host;
- Z_EROFS_DEFINE_FRONTEND(f, inode, readahead_pos(rac));
+ struct inode *const inode = rac->mapping->host, *realinode;
+ Z_EROFS_DEFINE_FRONTEND(f, NULL, readahead_pos(rac));
unsigned int nrpages = readahead_count(rac);
struct folio *head = NULL, *folio;
+ struct erofs_read_ctx rdctx = {
+ .file = rac->file,
+ .inode = inode,
+ };
int err;
- trace_erofs_readahead(inode, readahead_index(rac), nrpages, false);
+ erofs_read_begin(&rdctx);
+ if (erofs_is_ishare_inode(inode))
+ realinode = erofs_ishare_iget(inode);
+ else
+ realinode = inode;
+
+ f.inode = realinode;
+ trace_erofs_readahead(realinode, readahead_index(rac), nrpages, false);
z_erofs_pcluster_readmore(&f, rac, true);
while ((folio = readahead_folio(rac))) {
folio->private = head;
@@ -1913,8 +1944,8 @@ static void z_erofs_readahead(struct readahead_control *rac)
err = z_erofs_scan_folio(&f, folio, true);
if (err && err != -EINTR)
- erofs_err(inode->i_sb, "readahead error at folio %lu @ nid %llu",
- folio->index, EROFS_I(inode)->nid);
+ erofs_err(realinode->i_sb, "readahead error at folio %lu @ nid %llu",
+ folio->index, EROFS_I(realinode)->nid);
}
z_erofs_pcluster_readmore(&f, rac, false);
z_erofs_pcluster_end(&f);
@@ -1922,6 +1953,11 @@ static void z_erofs_readahead(struct readahead_control *rac)
(void)z_erofs_runqueue(&f, nrpages);
erofs_put_metabuf(&f.map.buf);
erofs_release_pages(&f.pagepool);
+
+ if (erofs_is_ishare_inode(inode))
+ erofs_ishare_iput(realinode);
+
+ erofs_read_end(&rdctx);
}
const struct address_space_operations z_erofs_aops = {
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH RFC v7 7/7] erofs: implement .fadvise for page cache share
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
` (5 preceding siblings ...)
2025-10-21 10:48 ` [PATCH RFC v7 6/7] erofs: support compressed " Hongbo Li
@ 2025-10-21 10:48 ` Hongbo Li
2025-10-21 13:04 ` [PATCH RFC v7 0/7] erofs: inode page cache share feature Gao Xiang
7 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-21 10:48 UTC (permalink / raw)
To: hsiangkao, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel, lihongbo22
From: Hongzhen Luo <hongzhen@linux.alibaba.com>
This patch implements the .fadvise interface for page cache share.
Similar to overlayfs, it drops those clean, unused pages through
vfs_fadvise().
Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
[hongbo: forward port, minor cleanup]
Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
---
fs/erofs/ishare.c | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
index 73432b13bf75..b067f0e02d6c 100644
--- a/fs/erofs/ishare.c
+++ b/fs/erofs/ishare.c
@@ -228,6 +228,16 @@ static int erofs_ishare_mmap(struct file *file, struct vm_area_struct *vma)
return generic_file_readonly_mmap(file, vma);
}
+static int erofs_ishare_fadvice(struct file *file, loff_t offset,
+ loff_t len, int advice)
+{
+ struct file *realfile = file->private_data;
+
+ if (!realfile)
+ return -EINVAL;
+ return vfs_fadvise(realfile, offset, len, advice);
+}
+
const struct file_operations erofs_ishare_fops = {
.open = erofs_ishare_file_open,
.llseek = generic_file_llseek,
@@ -236,6 +246,7 @@ const struct file_operations erofs_ishare_fops = {
.release = erofs_ishare_file_release,
.get_unmapped_area = thp_get_unmapped_area,
.splice_read = filemap_splice_read,
+ .fadvise = erofs_ishare_fadvice,
};
void erofs_read_begin(struct erofs_read_ctx *rdctx)
--
2.22.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share
2025-10-21 10:48 ` [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share Hongbo Li
@ 2025-10-21 13:01 ` Gao Xiang
0 siblings, 0 replies; 11+ messages in thread
From: Gao Xiang @ 2025-10-21 13:01 UTC (permalink / raw)
To: Hongbo Li, brauner; +Cc: linux-erofs, linux-kernel, Chao Yu, Hongzhen Luo
On 2025/10/21 18:48, Hongbo Li wrote:
> From: Hongzhen Luo <hongzhen@linux.alibaba.com>
>
> This patch adds inode page cache sharing functionality for unencoded
> files.
>
> I conducted experiments in the container environment. Below is the
> memory usage for reading all files in two different minor versions
> of container images:
>
> +-------------------+------------------+-------------+---------------+
> | Image | Page Cache Share | Memory (MB) | Memory |
> | | | | Reduction (%) |
> +-------------------+------------------+-------------+---------------+
> | | No | 241 | - |
> | redis +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 163 | 33% |
> +-------------------+------------------+-------------+---------------+
> | | No | 872 | - |
> | postgres +------------------+-------------+---------------+
> | 16.1 & 16.2 | Yes | 630 | 28% |
> +-------------------+------------------+-------------+---------------+
> | | No | 2771 | - |
> | tensorflow +------------------+-------------+---------------+
> | 2.11.0 & 2.11.1 | Yes | 2340 | 16% |
> +-------------------+------------------+-------------+---------------+
> | | No | 926 | - |
> | mysql +------------------+-------------+---------------+
> | 8.0.11 & 8.0.12 | Yes | 735 | 21% |
> +-------------------+------------------+-------------+---------------+
> | | No | 390 | - |
> | nginx +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 219 | 44% |
> +-------------------+------------------+-------------+---------------+
> | tomcat | No | 924 | - |
> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
> | | Yes | 474 | 49% |
> +-------------------+------------------+-------------+---------------+
>
> Additionally, the table below shows the runtime memory usage of the
> container:
>
> +-------------------+------------------+-------------+---------------+
> | Image | Page Cache Share | Memory (MB) | Memory |
> | | | | Reduction (%) |
> +-------------------+------------------+-------------+---------------+
> | | No | 35 | - |
> | redis +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 28 | 20% |
> +-------------------+------------------+-------------+---------------+
> | | No | 149 | - |
> | postgres +------------------+-------------+---------------+
> | 16.1 & 16.2 | Yes | 95 | 37% |
> +-------------------+------------------+-------------+---------------+
> | | No | 1028 | - |
> | tensorflow +------------------+-------------+---------------+
> | 2.11.0 & 2.11.1 | Yes | 930 | 10% |
> +-------------------+------------------+-------------+---------------+
> | | No | 155 | - |
> | mysql +------------------+-------------+---------------+
> | 8.0.11 & 8.0.12 | Yes | 132 | 15% |
> +-------------------+------------------+-------------+---------------+
> | | No | 25 | - |
> | nginx +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 20 | 20% |
> +-------------------+------------------+-------------+---------------+
> | tomcat | No | 186 | - |
> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
> | | Yes | 98 | 48% |
> +-------------------+------------------+-------------+---------------+
>
> Signed-off-by: Hongzhen Luo <hongzhen@linux.alibaba.com>
> [hongbo: forward port, minor fixes and cleanup]
> Signed-off-by: Hongbo Li <lihongbo22@huawei.com>
> ---
> fs/erofs/data.c | 81 ++++++++++++++++++++++++++++++++++++++-----
> fs/erofs/inode.c | 5 +++
> fs/erofs/internal.h | 4 +++
> fs/erofs/ishare.c | 83 +++++++++++++++++++++++++++++++++++++++++++++
> fs/erofs/ishare.h | 18 ++++++++++
> fs/erofs/super.c | 7 ++++
> 6 files changed, 190 insertions(+), 8 deletions(-)
>
> diff --git a/fs/erofs/data.c b/fs/erofs/data.c
> index 8ca29962a3dd..438d43c959aa 100644
> --- a/fs/erofs/data.c
> +++ b/fs/erofs/data.c
> @@ -5,6 +5,7 @@
> * Copyright (C) 2021, Alibaba Cloud
> */
> #include "internal.h"
> +#include "ishare.h"
> #include <linux/sched/mm.h>
> #include <trace/events/erofs.h>
>
> @@ -266,25 +267,55 @@ void erofs_onlinefolio_end(struct folio *folio, int err, bool dirty)
> folio_end_read(folio, !(v & BIT(EROFS_ONLINEFOLIO_EIO)));
> }
>
> +struct erofs_iomap {
> + void *base;
> + struct inode *realinode;
> +};
> +
> static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
> {
> int ret;
> - struct super_block *sb = inode->i_sb;
> + struct super_block *sb;
> struct erofs_map_blocks map;
> struct erofs_map_dev mdev;
> + struct inode *realinode = inode;
> + struct erofs_iomap *erofs_iomap;
> + bool is_ishare = erofs_is_ishare_inode(inode);
> +
> + if (is_ishare) {
> + if (!iomap->private) {
I tend to pass in `iomap->private` and allocate on disk
as I mentioned in
https://lore.kernel.org/r/20250829235627.4053234-14-joannelkoong@gmail.com
to avoid unnecessary kzalloc.
Thanks,
Gao Xiang
> + erofs_iomap = kzalloc(sizeof(*erofs_iomap),
> + GFP_KERNEL);
> + if (!erofs_iomap)
> + return -ENOMEM;
> + erofs_iomap->realinode = erofs_ishare_iget(inode);
> + if (!erofs_iomap->realinode) {
> + kfree(erofs_iomap);
> + return -EINVAL;
> + }
> + iomap->private = erofs_iomap;
> + }
> + erofs_iomap = iomap->private;
> + realinode = erofs_iomap->realinode;
> + }
>
> + sb = realinode->i_sb;
> map.m_la = offset;
> map.m_llen = length;
> - ret = erofs_map_blocks(inode, &map);
> + ret = erofs_map_blocks(realinode, &map);
> if (ret < 0)
> return ret;
>
> iomap->offset = map.m_la;
> iomap->length = map.m_llen;
> iomap->flags = 0;
> - iomap->private = NULL;
> iomap->addr = IOMAP_NULL_ADDR;
> +
> + if (is_ishare)
> + erofs_iomap->base = NULL;
> + else
> + iomap->private = NULL;
> if (!(map.m_flags & EROFS_MAP_MAPPED)) {
> iomap->type = IOMAP_HOLE;
> return 0;
> @@ -318,7 +349,10 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> if (IS_ERR(ptr))
> return PTR_ERR(ptr);
> iomap->inline_data = ptr;
> - iomap->private = buf.base;
> + if (is_ishare)
> + erofs_iomap->base = buf.base;
> + else
> + iomap->private = buf.base;
> } else {
> iomap->type = IOMAP_MAPPED;
> }
> @@ -328,7 +362,17 @@ static int erofs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
> static int erofs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
> ssize_t written, unsigned int flags, struct iomap *iomap)
> {
> - void *ptr = iomap->private;
> + struct erofs_iomap *erofs_iomap;
> + bool is_ishare;
> + void *ptr;
> +
> + is_ishare = erofs_is_ishare_inode(inode);
> + if (is_ishare) {
> + erofs_iomap = iomap->private;
> + ptr = erofs_iomap->base;
> + } else {
> + ptr = iomap->private;
> + }
>
> if (ptr) {
> struct erofs_buf buf = {
> @@ -341,6 +385,12 @@ static int erofs_iomap_end(struct inode *inode, loff_t pos, loff_t length,
> } else {
> DBG_BUGON(iomap->type == IOMAP_INLINE);
> }
> +
> + if (is_ishare) {
> + erofs_ishare_iput(erofs_iomap->realinode);
> + kfree(erofs_iomap);
> + iomap->private = NULL;
> + }
> return written;
> }
>
> @@ -369,17 +419,32 @@ int erofs_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
> */
> static int erofs_read_folio(struct file *file, struct folio *folio)
> {
> + struct erofs_read_ctx rdctx = {
> + .file = file,
> + .inode = folio_inode(folio),
> + };
> + int ret;
> +
> + erofs_read_begin(&rdctx);
> + ret = iomap_read_folio(folio, &erofs_iomap_ops);
> + erofs_read_end(&rdctx);
> trace_erofs_read_folio(folio, true);
>
> - return iomap_read_folio(folio, &erofs_iomap_ops);
> + return ret;
> }
>
> static void erofs_readahead(struct readahead_control *rac)
> {
> + struct erofs_read_ctx rdctx = {
> + .file = rac->file,
> + .inode = rac->mapping->host,
> + };
> +
> + erofs_read_begin(&rdctx);
> + iomap_readahead(rac, &erofs_iomap_ops);
> + erofs_read_end(&rdctx);
> trace_erofs_readahead(rac->mapping->host, readahead_index(rac),
> readahead_count(rac), true);
> -
> - return iomap_readahead(rac, &erofs_iomap_ops);
> }
>
> static sector_t erofs_bmap(struct address_space *mapping, sector_t block)
> diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c
> index cb780c095d28..fe45e6c18f8e 100644
> --- a/fs/erofs/inode.c
> +++ b/fs/erofs/inode.c
> @@ -5,6 +5,7 @@
> * Copyright (C) 2021, Alibaba Cloud
> */
> #include "xattr.h"
> +#include "ishare.h"
> #include <linux/compat.h>
> #include <trace/events/erofs.h>
>
> @@ -215,6 +216,10 @@ static int erofs_fill_inode(struct inode *inode)
> case S_IFREG:
> inode->i_op = &erofs_generic_iops;
> inode->i_fop = &erofs_file_fops;
> +#ifdef CONFIG_EROFS_FS_INODE_SHARE
> + if (erofs_ishare_fill_inode(inode))
> + inode->i_fop = &erofs_ishare_fops;
> +#endif
> break;
> case S_IFDIR:
> inode->i_op = &erofs_dir_iops;
> diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
> index 158bda6ba784..9ce6e5753978 100644
> --- a/fs/erofs/internal.h
> +++ b/fs/erofs/internal.h
> @@ -322,11 +322,15 @@ struct erofs_inode {
> spinlock_t lock;
> /* all backing inodes */
> struct list_head backing_head;
> + /* processing list */
> + struct list_head processing_head;
> };
>
> struct {
> struct inode *ishare;
> struct list_head backing_link;
> + struct list_head processing_link;
> + atomic_t processing_count;
> };
> };
> #endif
> diff --git a/fs/erofs/ishare.c b/fs/erofs/ishare.c
> index 910b732bf8e7..73432b13bf75 100644
> --- a/fs/erofs/ishare.c
> +++ b/fs/erofs/ishare.c
> @@ -72,6 +72,7 @@ static int erofs_ishare_iget5_set(struct inode *inode, void *data)
>
> vi->fingerprint = data;
> INIT_LIST_HEAD(&vi->backing_head);
> + INIT_LIST_HEAD(&vi->processing_head);
> spin_lock_init(&vi->lock);
> return 0;
> }
> @@ -124,7 +125,9 @@ bool erofs_ishare_fill_inode(struct inode *inode)
> }
>
> INIT_LIST_HEAD(&vi->backing_link);
> + INIT_LIST_HEAD(&vi->processing_link);
> vi->ishare = idedup;
> +
> spin_lock(&EROFS_I(idedup)->lock);
> list_add(&vi->backing_link, &EROFS_I(idedup)->backing_head);
> spin_unlock(&EROFS_I(idedup)->lock);
> @@ -234,3 +237,83 @@ const struct file_operations erofs_ishare_fops = {
> .get_unmapped_area = thp_get_unmapped_area,
> .splice_read = filemap_splice_read,
> };
> +
> +void erofs_read_begin(struct erofs_read_ctx *rdctx)
> +{
> + struct erofs_inode *vi, *vi_dedup;
> +
> + if (!rdctx->file || !erofs_is_ishare_inode(rdctx->inode))
> + return;
> +
> + vi = rdctx->file->private_data;
> + vi_dedup = EROFS_I(file_inode(rdctx->file));
> +
> + spin_lock(&vi_dedup->lock);
> + if (!list_empty(&vi->processing_link)) {
> + atomic_inc(&vi->processing_count);
> + } else {
> + list_add(&vi->processing_link,
> + &vi_dedup->processing_head);
> + atomic_set(&vi->processing_count, 1);
> + }
> + spin_unlock(&vi_dedup->lock);
> +}
> +
> +void erofs_read_end(struct erofs_read_ctx *rdctx)
> +{
> + struct erofs_inode *vi, *vi_dedup;
> +
> + if (!rdctx->file || !erofs_is_ishare_inode(rdctx->inode))
> + return;
> +
> + vi = rdctx->file->private_data;
> + vi_dedup = EROFS_I(file_inode(rdctx->file));
> +
> + spin_lock(&vi_dedup->lock);
> + if (atomic_dec_and_test(&vi->processing_count))
> + list_del_init(&vi->processing_link);
> + spin_unlock(&vi_dedup->lock);
> +}
> +
> +/*
> + * erofs_ishare_iget - find the backing inode.
> + */
> +struct inode *erofs_ishare_iget(struct inode *inode)
> +{
> + struct erofs_inode *vi, *vi_dedup;
> + struct inode *realinode;
> +
> + if (!erofs_is_ishare_inode(inode))
> + return igrab(inode);
> +
> + vi_dedup = EROFS_I(inode);
> + spin_lock(&vi_dedup->lock);
> + /* try processing inodes first */
> + if (!list_empty(&vi_dedup->processing_head)) {
> + list_for_each_entry(vi, &vi_dedup->processing_head,
> + processing_link) {
> + realinode = igrab(&vi->vfs_inode);
> + if (realinode) {
> + spin_unlock(&vi_dedup->lock);
> + return realinode;
> + }
> + }
> + }
> +
> + /* fall back to all backing inodes */
> + DBG_BUGON(list_empty(&vi_dedup->backing_head));
> + list_for_each_entry(vi, &vi_dedup->backing_head, backing_link) {
> + realinode = igrab(&vi->vfs_inode);
> + if (realinode)
> + break;
> + }
> + spin_unlock(&vi_dedup->lock);
> +
> + DBG_BUGON(!realinode);
> + return realinode;
> +}
> +
> +void erofs_ishare_iput(struct inode *realinode)
> +{
> + iput(realinode);
> +}
> diff --git a/fs/erofs/ishare.h b/fs/erofs/ishare.h
> index 54f2251c8179..b85fa240507b 100644
> --- a/fs/erofs/ishare.h
> +++ b/fs/erofs/ishare.h
> @@ -9,6 +9,11 @@
> #include <linux/spinlock.h>
> #include "internal.h"
>
> +struct erofs_read_ctx {
> + struct file *file; /* may be NULL */
> + struct inode *inode;
> +};
> +
> #ifdef CONFIG_EROFS_FS_INODE_SHARE
>
> int erofs_ishare_init(struct super_block *sb);
> @@ -16,6 +21,13 @@ void erofs_ishare_exit(struct super_block *sb);
> bool erofs_ishare_fill_inode(struct inode *inode);
> void erofs_ishare_free_inode(struct inode *inode);
>
> +/* read/readahead */
> +void erofs_read_begin(struct erofs_read_ctx *rdctx);
> +void erofs_read_end(struct erofs_read_ctx *rdctx);
> +
> +struct inode *erofs_ishare_iget(struct inode *inode);
> +void erofs_ishare_iput(struct inode *realinode);
> +
> #else
>
> static inline int erofs_ishare_init(struct super_block *sb) { return 0; }
> @@ -23,6 +35,12 @@ static inline void erofs_ishare_exit(struct super_block *sb) {}
> static inline bool erofs_ishare_fill_inode(struct inode *inode) { return false; }
> static inline void erofs_ishare_free_inode(struct inode *inode) {}
>
> +static inline void erofs_read_begin(struct erofs_read_ctx *rdctx) {}
> +static inline void erofs_read_end(struct erofs_read_ctx *rdctx) {}
> +
> +static inline struct inode *erofs_ishare_iget(struct inode *inode) { return inode; }
> +static inline void erofs_ishare_iput(struct inode *realinode) {}
> +
> #endif // CONFIG_EROFS_FS_INODE_SHARE
>
> #endif
> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
> index f067633c0072..cba3da383558 100644
> --- a/fs/erofs/super.c
> +++ b/fs/erofs/super.c
> @@ -97,6 +97,7 @@ static void erofs_free_inode(struct inode *inode)
> erofs_free_dedup_inode(vi);
> return;
> }
> + erofs_ishare_free_inode(inode);
> if (inode->i_op == &erofs_fast_symlink_iops)
> kfree(inode->i_link);
> kfree(vi->xattr_shared_xattrs);
> @@ -762,6 +763,10 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
> if (err)
> return err;
>
> + err = erofs_ishare_init(sb);
> + if (err)
> + return err;
> +
> sbi->dir_ra_bytes = EROFS_DIR_RA_BYTES;
> erofs_info(sb, "mounted with root inode @ nid %llu.", sbi->root_nid);
> return 0;
> @@ -911,6 +916,7 @@ static void erofs_kill_sb(struct super_block *sb)
> kill_anon_super(sb);
> else
> kill_block_super(sb);
> +
> erofs_drop_internal_inodes(sbi);
> fs_put_dax(sbi->dif0.dax_dev, NULL);
> erofs_fscache_unregister_fs(sb);
> @@ -922,6 +928,7 @@ static void erofs_put_super(struct super_block *sb)
> {
> struct erofs_sb_info *const sbi = EROFS_SB(sb);
>
> + erofs_ishare_exit(sb);
> erofs_unregister_sysfs(sb);
> erofs_shrinker_unregister(sb);
> erofs_xattr_prefixes_cleanup(sb);
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC v7 0/7] erofs: inode page cache share feature
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
` (6 preceding siblings ...)
2025-10-21 10:48 ` [PATCH RFC v7 7/7] erofs: implement .fadvise " Hongbo Li
@ 2025-10-21 13:04 ` Gao Xiang
2025-10-29 12:58 ` Hongbo Li
7 siblings, 1 reply; 11+ messages in thread
From: Gao Xiang @ 2025-10-21 13:04 UTC (permalink / raw)
To: Hongbo Li, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel
Hi Hongbo,
On 2025/10/21 18:48, Hongbo Li wrote:
> Enabling page cahe sharing in container scenarios has become increasingly
> crucial, as it can significantly reduce memory usage. In previous efforts,
> Hongzhen has done substantial work to push this feature into the EROFS
> mainline. Due to other commitments, he hasn't been able to continue his
> work recently, and I'm very pleased to build upon his work and continue
> to refine this implementation.
>
> This is a forward-port of Hongzhen's original erofs shared pagecache
> posted a half yeas ago at (the latest):
> https://lore.kernel.org/all/20250301145002.2420830-1-hongzhen@linux.alibaba.com/T/#u
>
> In addition to the forward-port, I have also fixed a couple bugs and
> some minor cleanup during the migration.
>
> Notes: Currently, only compilation tests and basic function have been
> verified. Validation for the shared page cache feature is pending until
> the erofs-utils tool is complete.
>
> (A recap of Hongzhen's original cover letter is below, edited slightly
> for this serise:)
I'm still left behind of this (currently heavily working on erofs-utils
and containerd), but could we have a workable erofs-utils implementation
first?
Also, Amir's previous suggestion needs to be resolved too..
https://lore.kernel.org/r/CAOQ4uxjFcw7+w4jfjRKZRDitaXmgK1WhFbidPUFjXFt_6Kew5A@mail.gmail.com
Finally, thanks for remaining Hongzhen's email (but he was already
left, thanks for remaining our credits).
Thanks,
Gao Xiang
>
> Background
> ==============
> Currently, reading files with different paths (or names) but the same
> content can consume multiple copies of the page cache, even if the
> content of these caches is identical. For example, reading identical
> files (e.g., *.so files) from two different minor versions of container
> images can result in multiple copies of the same page cache, since
> different containers have different mount points. Therefore, sharing
> the page cache for files with the same content can save memory.
>
> Proposal
> ==============
>
> 1. determining file identity
> ----------------------------
> First, a way needs to be found to check whether the content of two files
> is the same. Here, the xattr values associated with the file
> fingerprints are assessed for consistency. When creating the EROFS
> image, users can specify the name of the xattr for file fingerprints,
> and the corresponding name will be stored in the packfile. The on-disk
> `ishare_key_start` indicates the offset of the xattr's name within the
> packfile:
>
> ```
> struct erofs_super_block {
> __le32 build_time; /* seconds added to epoch for mkfs time */
> __le64 rootnid_8b; /* (48BIT on) nid of root directory */
> - __le64 reserved2;
> + __le32 ishare_key_start; /* start of ishare key */
> + __le32 reserved2;
> __le64 metabox_nid; /* (METABOX on) nid of the metabox inode */
> __le64 reserved3; /* [align to extslot 1] */
> };
> ```
>
> For example, users can specify the first long prefix as the name for the
> file fingerprint as follows:
>
> ```
> mkfs.erofs --ishare_key=trusted.erofs.fingerprint erofs.img ./dir
> ```
>
> In this way, `trusted.erofs.fingerprint` serves as the name of the xattr
> for the file fingerprint. The relevant patches for erofs-utils will be
> released later.
>
> At the same time, for security reasons, this patch series only shares
> files within the same domain, which is achieved by adding
> "-o domain_id=xxxx" during the mounting process:
>
> ```
> mount -t erofs -o domain_id=xxx erofs.img /mnt
> ```
>
> If no domain ID is specified, it will fall back to the non-page cache
> sharing mode.
>
> 2. whose page cache is shared?
> ------------------------------
>
> 2.1. share the page cache of inode_A or inode_B
> -----------------------------------------------
> For example, we can share the page cache of inode_A, referred to as
> PGCache_A. When reading file B, we read the contents from PGCache_A to
> achieve memory savings. Furthermore, if we need to read another file C
> with the same content, we will still read from PGCache_A. In this way,
> we fulfill multiple read requests with just a single page cache.
>
> 2.2. share the de-duplicated inode's page cache
> -----------------------------------------------
> Unlike in 2.1, we allocate an internal deduplicated inode and use its
> page cache as shared. Reads for files with identical content will
> ultimately be routed to the page cache of the deduplicated inode. In
> this way, a single page cache satisfies multiple read requests for
> different files with the same contents.
>
> 2.3. discussion of the two solutions
> -----------------------------------------------
> Although the solution in 2.1 allows for page cache sharing, it has
> inherent drawbacks. The creation and destruction of inode nodes in the
> file system mean that when inode_A is destroyed, PGCache_A is also
> released. Consequently, if we need to read the file content afterward,
> we must retrieve the data from the disk again. This conflicts with the
> design philosophy of page cache (caching contents from the disk).
>
> Therefore, I choose to implement the solution in 2.2, which is to
> allocate an internal deduplicated inode and use its page cache as
> shared.
>
> 3. Implementation
> ==================
>
> 3.1. file open & close
> ----------------------
> When the file is opened, the ->private_data field of file A or file B is
> set to point to an internal deduplicated file. When the actual read
> occurs, the page cache of this deduplicated file will be accessed.
>
> When the file is opened, if the corresponding erofs inode is newly
> created, then perform the following actions:
> 1. add the erofs inode to the backing list of the deduplicated inode;
> 2. increase the reference count of the deduplicated inode.
>
> The purpose of step 1 above is to ensure that when a real I/O operation
> occurs, the deduplicated inode can locate one of the disk devices
> (as the deduplicated inode itself is not bound to a specific device).
> Step 2 is for managing the lifecycle of the deduplicated inode.
>
> When the erofs inode is destroyed, the opposite actions mentioned above
> will be taken.
>
> 3.2. file reading
> -----------------
> Assuming the deduplication inode's page cache is PGCache_dedup, there
> are two possible scenarios when reading a file:
> 1) the content being read is already present in PGCache_dedup;
> 2) the content being read is not present in PGCache_dedup.
>
> In the second scenario, it involves the iomap operation to read from the
> disk.
>
> 3.2.1. reading existing data in PGCache_dedup
> -------------------------------------------
> In this case, the overall read flowchart is as follows (take ksys_read()
> for example):
>
> ksys_read
> │
> │
> ▼
> ...
> │
> │
> ▼
> erofs_ishare_file_read_iter (switch to backing deduplicated file)
> │
> │
> ▼
>
> read PGCache_dedup & return
>
> At this point, the content in PGCache_dedup will be read directly and
> returned.
>
> 3.2.2 reading non-existent content in PGCache_dedup
> ---------------------------------------------------
> In this case, disk I/O operations will be involved. Taking the reading
> of an uncompressed file as an example, here is the reading process:
>
> ksys_read
> │
> │
> ▼
> ...
> │
> │
> ▼
> erofs_ishare_file_read_iter (switch to backing deduplicated file)
> │
> │
> ▼
> ... (allocate pages)
> │
> │
> ▼
> erofs_read_folio/erofs_readahead
> │
> │
> ▼
> ... (iomap)
> │
> │
> ▼
> erofs_iomap_begin
> │
> │
> ▼
> ...
>
> Iomap and the layers below will involve disk I/O operations. As
> described in 3.1, the deduplicated inode itself is not bound to a
> specific device. The deduplicated inode will select an erofs inode from
> the backing list (by default, the first one) to complete the
> corresponding iomap operation.
>
> 3.2.3 optimized inode selection
> -------------------------------
> The inode selection method described in 3.2.2 may select an "inactive"
> inode. An inactive inode indicates that there may have been no read
> operations on the inode's device for a long time, and there is a high
> likelihood that the device may be unmounted. In this case, unmounting
> the device may experience a slight delay due to other read requests
> being routed to that device. Therefore, we need to select some "active"
> inodes for the iomap operation.
>
> To achieve optimized inode selection, an additional `processing` list
> has been added. At the beginning of erofs_{read_folio,readahead}(), the
> corresponding erofs inode will be added to the `processing` list
> (because they are active). And it is removed at the end of
> erofs_{read_folio,readahead}(). In erofs_iomap_begin(), the selected
> erofs inode's count is incremented, and in erofs_iomap_end(), the count
> is decremented.
>
> In this way, even after the erofs inode is removed from the `processing`
> list, the increment in the reference count can ensure the integrity of
> the data reading process. This is somewhat similar to RCU (not exactly
> the same, but similar).
>
> 3.3. release page cache
> -----------------------
> Similar to overlayfs, when dropping the page cache via .fadvise, erofs
> locates the deduplicated file and applies vfs_fadvise to that specific
> file.
>
> Effect
> ==================
> I conducted experiments on two aspects across two different minor
> versions of container images:
>
> 1. reading all files in two different minor versions of container images
>
> 2. run workloads or use the default entrypoint within the containers^[1]
>
> Below is the memory usage for reading all files in two different minor
> versions of container images:
>
> +-------------------+------------------+-------------+---------------+
> | Image | Page Cache Share | Memory (MB) | Memory |
> | | | | Reduction (%) |
> +-------------------+------------------+-------------+---------------+
> | | No | 241 | - |
> | redis +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 163 | 33% |
> +-------------------+------------------+-------------+---------------+
> | | No | 872 | - |
> | postgres +------------------+-------------+---------------+
> | 16.1 & 16.2 | Yes | 630 | 28% |
> +-------------------+------------------+-------------+---------------+
> | | No | 2771 | - |
> | tensorflow +------------------+-------------+---------------+
> | 2.11.0 & 2.11.1 | Yes | 2340 | 16% |
> +-------------------+------------------+-------------+---------------+
> | | No | 926 | - |
> | mysql +------------------+-------------+---------------+
> | 8.0.11 & 8.0.12 | Yes | 735 | 21% |
> +-------------------+------------------+-------------+---------------+
> | | No | 390 | - |
> | nginx +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 219 | 44% |
> +-------------------+------------------+-------------+---------------+
> | tomcat | No | 924 | - |
> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
> | | Yes | 474 | 49% |
> +-------------------+------------------+-------------+---------------+
>
> Additionally, the table below shows the runtime memory usage of the
> container:
>
> +-------------------+------------------+-------------+---------------+
> | Image | Page Cache Share | Memory (MB) | Memory |
> | | | | Reduction (%) |
> +-------------------+------------------+-------------+---------------+
> | | No | 34.9 | - |
> | redis +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 33.6 | 4% |
> +-------------------+------------------+-------------+---------------+
> | | No | 149.1 | - |
> | postgres +------------------+-------------+---------------+
> | 16.1 & 16.2 | Yes | 95 | 37% |
> +-------------------+------------------+-------------+---------------+
> | | No | 1027.9 | - |
> | tensorflow +------------------+-------------+---------------+
> | 2.11.0 & 2.11.1 | Yes | 934.3 | 10% |
> +-------------------+------------------+-------------+---------------+
> | | No | 155.0 | - |
> | mysql +------------------+-------------+---------------+
> | 8.0.11 & 8.0.12 | Yes | 139.1 | 11% |
> +-------------------+------------------+-------------+---------------+
> | | No | 25.4 | - |
> | nginx +------------------+-------------+---------------+
> | 7.2.4 & 7.2.5 | Yes | 18.8 | 26% |
> +-------------------+------------------+-------------+---------------+
> | tomcat | No | 186 | - |
> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
> | | Yes | 99 | 47% |
> +-------------------+------------------+-------------+---------------+
>
> It can be observed that when reading all the files in the image, the
> reduced memory usage varies from 16% to 49%, depending on the specific
> image. Additionally, the container's runtime memory usage reduction
> ranges from 4% to 47%.
>
> [1] Below are the workload for these images:
> - redis: redis-benchmark
> - postgres: sysbench
> - tensorflow: app.py of tensorflow.python.platform
> - mysql: sysbench
> - nginx: wrk
> - tomcat: default entrypoint
>
> The patch in this version has made the following changes compared to
> the previous versionv(patch v5):
>
> - support user-defined fingerprint name;
> - support domain-specific page cache share;
> - adjusted the code style;
> - adjustments in code implementation, etc.
>
> v5: https://lore.kernel.org/all/20250105151208.3797385-1-hongzhen@linux.alibaba.com/
> v4: https://lore.kernel.org/all/20240902110620.2202586-1-hongzhen@linux.alibaba.com/
> v3: https://lore.kernel.org/all/20240828111959.3677011-1-hongzhen@linux.alibaba.com/
> v2: https://lore.kernel.org/all/20240731080704.678259-1-hongzhen@linux.alibaba.com/
> v1: https://lore.kernel.org/all/20240722065355.1396365-1-hongzhen@linux.alibaba.com/
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH RFC v7 0/7] erofs: inode page cache share feature
2025-10-21 13:04 ` [PATCH RFC v7 0/7] erofs: inode page cache share feature Gao Xiang
@ 2025-10-29 12:58 ` Hongbo Li
0 siblings, 0 replies; 11+ messages in thread
From: Hongbo Li @ 2025-10-29 12:58 UTC (permalink / raw)
To: Gao Xiang, chao, brauner, hongzhen; +Cc: linux-erofs, linux-kernel
Hi Xiang,
On 2025/10/21 21:04, Gao Xiang wrote:
> Hi Hongbo,
>
> On 2025/10/21 18:48, Hongbo Li wrote:
>> Enabling page cahe sharing in container scenarios has become increasingly
>> crucial, as it can significantly reduce memory usage. In previous
>> efforts,
>> Hongzhen has done substantial work to push this feature into the EROFS
>> mainline. Due to other commitments, he hasn't been able to continue his
>> work recently, and I'm very pleased to build upon his work and continue
>> to refine this implementation.
>>
>> This is a forward-port of Hongzhen's original erofs shared pagecache
>> posted a half yeas ago at (the latest):
>> https://lore.kernel.org/all/20250301145002.2420830-1-hongzhen@linux.alibaba.com/T/#u
>>
>> In addition to the forward-port, I have also fixed a couple bugs and
>> some minor cleanup during the migration.
>>
>> Notes: Currently, only compilation tests and basic function have been
>> verified. Validation for the shared page cache feature is pending until
>> the erofs-utils tool is complete.
>>
>> (A recap of Hongzhen's original cover letter is below, edited slightly
>> for this serise:)
>
> I'm still left behind of this (currently heavily working on erofs-utils
> and containerd), but could we have a workable erofs-utils implementation
> first?
>
Understood, I will implement a simple debug version first and will send
the revised code later to address the noted issues.
Thanks,
Hongbo
> Also, Amir's previous suggestion needs to be resolved too..
> https://lore.kernel.org/r/CAOQ4uxjFcw7+w4jfjRKZRDitaXmgK1WhFbidPUFjXFt_6Kew5A@mail.gmail.com
>
> Finally, thanks for remaining Hongzhen's email (but he was already
> left, thanks for remaining our credits).
>
> Thanks,
> Gao Xiang
>
>
>>
>> Background
>> ==============
>> Currently, reading files with different paths (or names) but the same
>> content can consume multiple copies of the page cache, even if the
>> content of these caches is identical. For example, reading identical
>> files (e.g., *.so files) from two different minor versions of container
>> images can result in multiple copies of the same page cache, since
>> different containers have different mount points. Therefore, sharing
>> the page cache for files with the same content can save memory.
>>
>> Proposal
>> ==============
>>
>> 1. determining file identity
>> ----------------------------
>> First, a way needs to be found to check whether the content of two files
>> is the same. Here, the xattr values associated with the file
>> fingerprints are assessed for consistency. When creating the EROFS
>> image, users can specify the name of the xattr for file fingerprints,
>> and the corresponding name will be stored in the packfile. The on-disk
>> `ishare_key_start` indicates the offset of the xattr's name within the
>> packfile:
>>
>> ```
>> struct erofs_super_block {
>> __le32 build_time; /* seconds added to epoch for mkfs time */
>> __le64 rootnid_8b; /* (48BIT on) nid of root directory */
>> - __le64 reserved2;
>> + __le32 ishare_key_start; /* start of ishare key */
>> + __le32 reserved2;
>> __le64 metabox_nid; /* (METABOX on) nid of the metabox inode */
>> __le64 reserved3; /* [align to extslot 1] */
>> };
>> ```
>>
>> For example, users can specify the first long prefix as the name for the
>> file fingerprint as follows:
>>
>> ```
>> mkfs.erofs --ishare_key=trusted.erofs.fingerprint erofs.img ./dir
>> ```
>>
>> In this way, `trusted.erofs.fingerprint` serves as the name of the xattr
>> for the file fingerprint. The relevant patches for erofs-utils will be
>> released later.
>>
>> At the same time, for security reasons, this patch series only shares
>> files within the same domain, which is achieved by adding
>> "-o domain_id=xxxx" during the mounting process:
>>
>> ```
>> mount -t erofs -o domain_id=xxx erofs.img /mnt
>> ```
>>
>> If no domain ID is specified, it will fall back to the non-page cache
>> sharing mode.
>>
>> 2. whose page cache is shared?
>> ------------------------------
>>
>> 2.1. share the page cache of inode_A or inode_B
>> -----------------------------------------------
>> For example, we can share the page cache of inode_A, referred to as
>> PGCache_A. When reading file B, we read the contents from PGCache_A to
>> achieve memory savings. Furthermore, if we need to read another file C
>> with the same content, we will still read from PGCache_A. In this way,
>> we fulfill multiple read requests with just a single page cache.
>>
>> 2.2. share the de-duplicated inode's page cache
>> -----------------------------------------------
>> Unlike in 2.1, we allocate an internal deduplicated inode and use its
>> page cache as shared. Reads for files with identical content will
>> ultimately be routed to the page cache of the deduplicated inode. In
>> this way, a single page cache satisfies multiple read requests for
>> different files with the same contents.
>>
>> 2.3. discussion of the two solutions
>> -----------------------------------------------
>> Although the solution in 2.1 allows for page cache sharing, it has
>> inherent drawbacks. The creation and destruction of inode nodes in the
>> file system mean that when inode_A is destroyed, PGCache_A is also
>> released. Consequently, if we need to read the file content afterward,
>> we must retrieve the data from the disk again. This conflicts with the
>> design philosophy of page cache (caching contents from the disk).
>>
>> Therefore, I choose to implement the solution in 2.2, which is to
>> allocate an internal deduplicated inode and use its page cache as
>> shared.
>>
>> 3. Implementation
>> ==================
>>
>> 3.1. file open & close
>> ----------------------
>> When the file is opened, the ->private_data field of file A or file B is
>> set to point to an internal deduplicated file. When the actual read
>> occurs, the page cache of this deduplicated file will be accessed.
>>
>> When the file is opened, if the corresponding erofs inode is newly
>> created, then perform the following actions:
>> 1. add the erofs inode to the backing list of the deduplicated inode;
>> 2. increase the reference count of the deduplicated inode.
>>
>> The purpose of step 1 above is to ensure that when a real I/O operation
>> occurs, the deduplicated inode can locate one of the disk devices
>> (as the deduplicated inode itself is not bound to a specific device).
>> Step 2 is for managing the lifecycle of the deduplicated inode.
>>
>> When the erofs inode is destroyed, the opposite actions mentioned above
>> will be taken.
>>
>> 3.2. file reading
>> -----------------
>> Assuming the deduplication inode's page cache is PGCache_dedup, there
>> are two possible scenarios when reading a file:
>> 1) the content being read is already present in PGCache_dedup;
>> 2) the content being read is not present in PGCache_dedup.
>>
>> In the second scenario, it involves the iomap operation to read from the
>> disk.
>>
>> 3.2.1. reading existing data in PGCache_dedup
>> -------------------------------------------
>> In this case, the overall read flowchart is as follows (take ksys_read()
>> for example):
>>
>> ksys_read
>> │
>> │
>> ▼
>> ...
>> │
>> │
>> ▼
>> erofs_ishare_file_read_iter (switch to backing deduplicated file)
>> │
>> │
>> ▼
>>
>> read PGCache_dedup & return
>>
>> At this point, the content in PGCache_dedup will be read directly and
>> returned.
>>
>> 3.2.2 reading non-existent content in PGCache_dedup
>> ---------------------------------------------------
>> In this case, disk I/O operations will be involved. Taking the reading
>> of an uncompressed file as an example, here is the reading process:
>>
>> ksys_read
>> │
>> │
>> ▼
>> ...
>> │
>> │
>> ▼
>> erofs_ishare_file_read_iter (switch to backing deduplicated file)
>> │
>> │
>> ▼
>> ... (allocate pages)
>> │
>> │
>> ▼
>> erofs_read_folio/erofs_readahead
>> │
>> │
>> ▼
>> ... (iomap)
>> │
>> │
>> ▼
>> erofs_iomap_begin
>> │
>> │
>> ▼
>> ...
>>
>> Iomap and the layers below will involve disk I/O operations. As
>> described in 3.1, the deduplicated inode itself is not bound to a
>> specific device. The deduplicated inode will select an erofs inode from
>> the backing list (by default, the first one) to complete the
>> corresponding iomap operation.
>>
>> 3.2.3 optimized inode selection
>> -------------------------------
>> The inode selection method described in 3.2.2 may select an "inactive"
>> inode. An inactive inode indicates that there may have been no read
>> operations on the inode's device for a long time, and there is a high
>> likelihood that the device may be unmounted. In this case, unmounting
>> the device may experience a slight delay due to other read requests
>> being routed to that device. Therefore, we need to select some "active"
>> inodes for the iomap operation.
>>
>> To achieve optimized inode selection, an additional `processing` list
>> has been added. At the beginning of erofs_{read_folio,readahead}(), the
>> corresponding erofs inode will be added to the `processing` list
>> (because they are active). And it is removed at the end of
>> erofs_{read_folio,readahead}(). In erofs_iomap_begin(), the selected
>> erofs inode's count is incremented, and in erofs_iomap_end(), the count
>> is decremented.
>>
>> In this way, even after the erofs inode is removed from the `processing`
>> list, the increment in the reference count can ensure the integrity of
>> the data reading process. This is somewhat similar to RCU (not exactly
>> the same, but similar).
>>
>> 3.3. release page cache
>> -----------------------
>> Similar to overlayfs, when dropping the page cache via .fadvise, erofs
>> locates the deduplicated file and applies vfs_fadvise to that specific
>> file.
>>
>> Effect
>> ==================
>> I conducted experiments on two aspects across two different minor
>> versions of container images:
>>
>> 1. reading all files in two different minor versions of container images
>>
>> 2. run workloads or use the default entrypoint within the containers^[1]
>>
>> Below is the memory usage for reading all files in two different minor
>> versions of container images:
>>
>> +-------------------+------------------+-------------+---------------+
>> | Image | Page Cache Share | Memory (MB) | Memory |
>> | | | | Reduction (%) |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 241 | - |
>> | redis +------------------+-------------+---------------+
>> | 7.2.4 & 7.2.5 | Yes | 163 | 33% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 872 | - |
>> | postgres +------------------+-------------+---------------+
>> | 16.1 & 16.2 | Yes | 630 | 28% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 2771 | - |
>> | tensorflow +------------------+-------------+---------------+
>> | 2.11.0 & 2.11.1 | Yes | 2340 | 16% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 926 | - |
>> | mysql +------------------+-------------+---------------+
>> | 8.0.11 & 8.0.12 | Yes | 735 | 21% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 390 | - |
>> | nginx +------------------+-------------+---------------+
>> | 7.2.4 & 7.2.5 | Yes | 219 | 44% |
>> +-------------------+------------------+-------------+---------------+
>> | tomcat | No | 924 | - |
>> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
>> | | Yes | 474 | 49% |
>> +-------------------+------------------+-------------+---------------+
>>
>> Additionally, the table below shows the runtime memory usage of the
>> container:
>>
>> +-------------------+------------------+-------------+---------------+
>> | Image | Page Cache Share | Memory (MB) | Memory |
>> | | | | Reduction (%) |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 34.9 | - |
>> | redis +------------------+-------------+---------------+
>> | 7.2.4 & 7.2.5 | Yes | 33.6 | 4% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 149.1 | - |
>> | postgres +------------------+-------------+---------------+
>> | 16.1 & 16.2 | Yes | 95 | 37% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 1027.9 | - |
>> | tensorflow +------------------+-------------+---------------+
>> | 2.11.0 & 2.11.1 | Yes | 934.3 | 10% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 155.0 | - |
>> | mysql +------------------+-------------+---------------+
>> | 8.0.11 & 8.0.12 | Yes | 139.1 | 11% |
>> +-------------------+------------------+-------------+---------------+
>> | | No | 25.4 | - |
>> | nginx +------------------+-------------+---------------+
>> | 7.2.4 & 7.2.5 | Yes | 18.8 | 26% |
>> +-------------------+------------------+-------------+---------------+
>> | tomcat | No | 186 | - |
>> | 10.1.25 & 10.1.26 +------------------+-------------+---------------+
>> | | Yes | 99 | 47% |
>> +-------------------+------------------+-------------+---------------+
>>
>> It can be observed that when reading all the files in the image, the
>> reduced memory usage varies from 16% to 49%, depending on the specific
>> image. Additionally, the container's runtime memory usage reduction
>> ranges from 4% to 47%.
>>
>> [1] Below are the workload for these images:
>> - redis: redis-benchmark
>> - postgres: sysbench
>> - tensorflow: app.py of tensorflow.python.platform
>> - mysql: sysbench
>> - nginx: wrk
>> - tomcat: default entrypoint
>>
>> The patch in this version has made the following changes compared to
>> the previous versionv(patch v5):
>>
>> - support user-defined fingerprint name;
>> - support domain-specific page cache share;
>> - adjusted the code style;
>> - adjustments in code implementation, etc.
>>
>> v5:
>> https://lore.kernel.org/all/20250105151208.3797385-1-hongzhen@linux.alibaba.com/
>> v4:
>> https://lore.kernel.org/all/20240902110620.2202586-1-hongzhen@linux.alibaba.com/
>> v3:
>> https://lore.kernel.org/all/20240828111959.3677011-1-hongzhen@linux.alibaba.com/
>> v2:
>> https://lore.kernel.org/all/20240731080704.678259-1-hongzhen@linux.alibaba.com/
>> v1:
>> https://lore.kernel.org/all/20240722065355.1396365-1-hongzhen@linux.alibaba.com/
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-10-29 12:58 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-21 10:48 [PATCH RFC v7 0/7] erofs: inode page cache share feature Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 1/7] erofs: move `struct erofs_anon_fs_type` to super.c Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 2/7] erofs: support user-defined fingerprint name Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 3/7] erofs: support domain-specific page cache share Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 4/7] erofs: introduce the page cache share feature Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 5/7] erofs: support unencoded inodes for page cache share Hongbo Li
2025-10-21 13:01 ` Gao Xiang
2025-10-21 10:48 ` [PATCH RFC v7 6/7] erofs: support compressed " Hongbo Li
2025-10-21 10:48 ` [PATCH RFC v7 7/7] erofs: implement .fadvise " Hongbo Li
2025-10-21 13:04 ` [PATCH RFC v7 0/7] erofs: inode page cache share feature Gao Xiang
2025-10-29 12:58 ` Hongbo Li
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®