* [PATCH v5 0/2] erofs: share the superblock for file-backed mounts
@ 2026-08-11 7:10 Giuseppe Scrivano
2026-08-11 7:10 ` [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it Giuseppe Scrivano
2026-08-11 7:10 ` [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts Giuseppe Scrivano
0 siblings, 2 replies; 7+ messages in thread
From: Giuseppe Scrivano @ 2026-08-11 7:10 UTC (permalink / raw)
To: linux-erofs; +Cc: xiang, linux-fsdevel, amir73il, gscrivan, brauner
This series adds a "superblock_share" mount option for file-backed EROFS
mounts. When set, mounting the same backing file (identified by inode and
fsoffset) with compatible options reuses the existing superblock instead
of creating a new one, so the page cache and inode cache are shared across
all mounts of the image. Without the option the behaviour is unchanged.
v4: https://lore.kernel.org/linux-fsdevel/20260804060455.2928514-1-gscrivan@redhat.com/
v3: https://lore.kernel.org/linux-fsdevel/20260801075446.2508805-1-gscrivan@redhat.com/
v2: https://lore.kernel.org/linux-fsdevel/20260731160901.2276832-1-gscrivan@redhat.com/
v1: https://lore.kernel.org/linux-fsdevel/20260730124120.1501126-1-gscrivan@redhat.com/
Needs: https://lore.kernel.org/linux-fsdevel/20260804055949.2927896-1-gscrivan@redhat.com/
Giuseppe Scrivano (2):
fs: rename vfs_get_super() to get_tree_super() and export it
erofs: reuse superblock for file-backed mounts
Documentation/filesystems/erofs.rst | 8 ++++
fs/erofs/internal.h | 1 +
fs/erofs/super.c | 58 +++++++++++++++++++++++++++--
fs/super.c | 19 ++++++++--
include/linux/fs_context.h | 4 ++
5 files changed, 83 insertions(+), 7 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it
2026-08-11 7:10 [PATCH v5 0/2] erofs: share the superblock for file-backed mounts Giuseppe Scrivano
@ 2026-08-11 7:10 ` Giuseppe Scrivano
2026-08-11 15:44 ` Gao Xiang
2026-08-11 7:10 ` [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts Giuseppe Scrivano
1 sibling, 1 reply; 7+ messages in thread
From: Giuseppe Scrivano @ 2026-08-11 7:10 UTC (permalink / raw)
To: linux-erofs; +Cc: xiang, linux-fsdevel, amir73il, gscrivan, brauner
vfs_get_super() already implements the common get_tree pattern of
looking up an existing superblock via a test callback and initialising
a new one with fill_super otherwise, but it is private to fs/super.c
and only reachable through the get_tree_nodev()/get_tree_single()/
get_tree_keyed() wrappers, none of which let a filesystem supply its
own test callback.
Rename it to get_tree_super() and export it so filesystems that need a
custom superblock matching policy can reuse it directly instead of
open-coding sget_fc() + fill_super(). No functional change.
This is a preparatory fix for the next patch.
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
fs/super.c | 19 +++++++++++++++----
include/linux/fs_context.h | 4 ++++
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/fs/super.c b/fs/super.c
index a8fd61136aaf..84b878317364 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -1251,7 +1251,17 @@ static int test_single_super(struct super_block *s, struct fs_context *fc)
return 1;
}
-static int vfs_get_super(struct fs_context *fc,
+/**
+ * get_tree_super - Get a superblock, optionally sharing an existing one
+ * @fc: The filesystem context holding the parameters
+ * @test: Comparison function to find a matching existing superblock, or NULL
+ * @fill_super: Helper to initialise a new superblock
+ *
+ * If @test is non-NULL and matches an existing superblock, that superblock is
+ * reused; otherwise a new anonymous superblock is created and initialised with
+ * @fill_super. Passing NULL for @test always creates a new superblock.
+ */
+int get_tree_super(struct fs_context *fc,
int (*test)(struct super_block *, struct fs_context *),
int (*fill_super)(struct super_block *sb,
struct fs_context *fc))
@@ -1278,12 +1288,13 @@ static int vfs_get_super(struct fs_context *fc,
deactivate_locked_super(sb);
return err;
}
+EXPORT_SYMBOL(get_tree_super);
int get_tree_nodev(struct fs_context *fc,
int (*fill_super)(struct super_block *sb,
struct fs_context *fc))
{
- return vfs_get_super(fc, NULL, fill_super);
+ return get_tree_super(fc, NULL, fill_super);
}
EXPORT_SYMBOL(get_tree_nodev);
@@ -1291,7 +1302,7 @@ int get_tree_single(struct fs_context *fc,
int (*fill_super)(struct super_block *sb,
struct fs_context *fc))
{
- return vfs_get_super(fc, test_single_super, fill_super);
+ return get_tree_super(fc, test_single_super, fill_super);
}
EXPORT_SYMBOL(get_tree_single);
@@ -1301,7 +1312,7 @@ int get_tree_keyed(struct fs_context *fc,
void *key)
{
fc->s_fs_info = key;
- return vfs_get_super(fc, test_keyed_super, fill_super);
+ return get_tree_super(fc, test_keyed_super, fill_super);
}
EXPORT_SYMBOL(get_tree_keyed);
diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h
index 0d6c8a6d7be2..c920aba5177c 100644
--- a/include/linux/fs_context.h
+++ b/include/linux/fs_context.h
@@ -150,6 +150,10 @@ extern int vfs_parse_fs_param_source(struct fs_context *fc,
struct fs_parameter *param);
extern void fc_drop_locked(struct fs_context *fc);
+extern int get_tree_super(struct fs_context *fc,
+ int (*test)(struct super_block *, struct fs_context *),
+ int (*fill_super)(struct super_block *sb,
+ struct fs_context *fc));
extern int get_tree_nodev(struct fs_context *fc,
int (*fill_super)(struct super_block *sb,
struct fs_context *fc));
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts
2026-08-11 7:10 [PATCH v5 0/2] erofs: share the superblock for file-backed mounts Giuseppe Scrivano
2026-08-11 7:10 ` [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it Giuseppe Scrivano
@ 2026-08-11 7:10 ` Giuseppe Scrivano
2026-08-11 15:31 ` Gao Xiang
1 sibling, 1 reply; 7+ messages in thread
From: Giuseppe Scrivano @ 2026-08-11 7:10 UTC (permalink / raw)
To: linux-erofs; +Cc: xiang, linux-fsdevel, amir73il, gscrivan, brauner
When the same file-backed image is mounted multiple times (via path or
fd) with the superblock_share mount option, reuse the existing
superblock instead of creating a new one. This allows multiple mounts
of the same image to share in-kernel data structures more efficiently.
The backing file is identified by its inode and fsoffset. If mount
options conflict or extra devices are used, a separate superblock
is created transparently as a fallback. Remount is not allowed on
shared superblocks.
Tested by mounting a 177M Fedora EROFS image 20 times with full
traversal:
#!/bin/sh
IMG=${1:-/root/fedora.erofs}
OPTS=${2:+-o $2}
N=20
DIR=$(mktemp -d)
trap "umount $DIR/m* 2>/dev/null; rm -rf $DIR" EXIT
sync; echo 3 > /proc/sys/vm/drop_caches
INODES_BEFORE=$(grep erofs_inode /proc/slabinfo | awk '{print $2}')
MEM_BEFORE=$(grep ^Slab: /proc/meminfo | awk '{print $2}')
for i in $(seq 1 $N); do
mkdir $DIR/m$i
mount -t erofs $OPTS "$IMG" $DIR/m$i
find $DIR/m$i > /dev/null
done
echo "Superblocks: $(grep $DIR /proc/self/mountinfo | \
awk '{print $3}' | sort -u | wc -l)"
echo "erofs_inode delta: +$(( $(grep erofs_inode /proc/slabinfo | \
awk '{print $2}') - INODES_BEFORE ))"
echo "Slab delta: +$(( $(grep ^Slab: /proc/meminfo | \
awk '{print $2}') - MEM_BEFORE )) kB"
without superblock_share:
# time ./test.sh /root/fedora.erofs
Superblocks: 20
erofs_inode delta: +45864
Slab delta: +37448 kB
real 0m2.311s
user 0m0.222s
sys 0m1.998s
with superblock_share:
# time ./test.sh /root/fedora.erofs superblock_share
Superblocks: 1
erofs_inode delta: +2044
Slab delta: +284 kB
real 0m0.679s
user 0m0.157s
sys 0m0.469s
The time difference shows that sharing the superblock also benefits
the page cache and inode cache, as subsequent mounts of the same image
avoid re-reading the backing file. This is particularly useful for
container hosts running multiple containers from the same base image.
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
---
Documentation/filesystems/erofs.rst | 8 ++++
fs/erofs/internal.h | 1 +
fs/erofs/super.c | 58 +++++++++++++++++++++++++++--
3 files changed, 64 insertions(+), 3 deletions(-)
diff --git a/Documentation/filesystems/erofs.rst b/Documentation/filesystems/erofs.rst
index d301d9ac946a..49c4e7dbc5d6 100644
--- a/Documentation/filesystems/erofs.rst
+++ b/Documentation/filesystems/erofs.rst
@@ -139,6 +139,9 @@ inode_share Enable inode page sharing for this filesystem. Inodes wi
page cache.
source=%s (For file-backed mounts) Specify the backing image as a path
or as an already-opened file descriptor.
+superblock_share (For file-backed mounts) Share the superblock when the same
+ backing file is mounted more than once with compatible
+ options. Remount is not allowed on shared superblocks.
=================== =========================================================
File-backed mounts
@@ -156,6 +159,11 @@ Only regular files are accepted as backing files; to mount an image that
resides on a block device, use the traditional block device mount path
instead.
+When superblock_share is specified and the same backing file (identified
+by inode and fsoffset) is mounted more than once with compatible mount
+options, the kernel reuses the existing superblock instead of creating a
+new one. Remount is not allowed on shared superblocks.
+
Sysfs Entries
=============
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 580f8d9f14e7..8d65cac35702 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -156,6 +156,7 @@ struct erofs_sb_info {
#define EROFS_MOUNT_DAX_NEVER 0x00000080
#define EROFS_MOUNT_DIRECT_IO 0x00000100
#define EROFS_MOUNT_INODE_SHARE 0x00000200
+#define EROFS_MOUNT_SUPERBLOCK_SHARE 0x00000400
#define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
#define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index d40961248a49..72d654ce49b7 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -386,7 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
enum {
Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
- Opt_source,
+ Opt_source, Opt_superblock_share,
};
static const struct constant_table erofs_param_cache_strategy[] = {
@@ -415,6 +415,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = {
fsparam_u64("fsoffset", Opt_fsoffset),
fsparam_flag("inode_share", Opt_inode_share),
fsparam_file_or_string("source", Opt_source),
+ fsparam_flag("superblock_share", Opt_superblock_share),
{}
};
@@ -560,6 +561,12 @@ static int erofs_fc_parse_param(struct fs_context *fc,
else
set_opt(&sbi->opt, INODE_SHARE);
break;
+ case Opt_superblock_share:
+ if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE))
+ errorfc(fc, "%s option not supported", erofs_fs_parameters[opt].name);
+ else
+ set_opt(&sbi->opt, SUPERBLOCK_SHARE);
+ break;
case Opt_source:
return erofs_fc_parse_source(fc, param);
}
@@ -663,6 +670,17 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
errorfc(fc, "FSDAX is not allowed when inode_share is on");
return -EINVAL;
}
+ if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
+ test_opt(&sbi->opt, INODE_SHARE)) {
+ errorfc(fc, "superblock_share is not allowed when inode_share is on");
+ return -EINVAL;
+ }
+ /* Extra devices are not yet supported with superblock sharing. */
+ if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
+ sbi->devs->extra_devices) {
+ errorfc(fc, "superblock_share does not support extra devices");
+ return -EINVAL;
+ }
sbi->blkszbits = PAGE_SHIFT;
if (!sb->s_bdev) {
@@ -788,6 +806,36 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
return 0;
}
+static int erofs_fc_test_file_super(struct super_block *sb,
+ struct fs_context *fc)
+{
+ /*
+ * The assumption is that the function is used only with a sbi that
+ * has SUPERBLOCK_SHARE set, so that the memcmp later ensures
+ * new_sbi also is shareable.
+ */
+ struct erofs_sb_info *sbi = EROFS_SB(sb);
+ struct erofs_sb_info *new_sbi = fc->s_fs_info;
+
+ if (sb->s_iflags & SB_I_RETIRED)
+ return 0;
+ if (!sbi->dif0.file || !new_sbi->dif0.file)
+ return 0;
+ return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) &&
+ sbi->dif0.fsoff == new_sbi->dif0.fsoff &&
+ !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt));
+}
+
+static int erofs_fc_get_tree_file(struct fs_context *fc)
+{
+ struct erofs_sb_info *sbi = fc->s_fs_info;
+
+ if (!test_opt(&sbi->opt, SUPERBLOCK_SHARE))
+ return get_tree_nodev(fc, erofs_fc_fill_super);
+
+ return get_tree_super(fc, erofs_fc_test_file_super, erofs_fc_fill_super);
+}
+
static int erofs_fc_get_tree(struct fs_context *fc)
{
struct erofs_sb_info *sbi = fc->s_fs_info;
@@ -803,7 +851,7 @@ static int erofs_fc_get_tree(struct fs_context *fc)
errorfc(fc, "source is unsupported");
return -EINVAL;
}
- return get_tree_nodev(fc, erofs_fc_fill_super);
+ return erofs_fc_get_tree_file(fc);
}
ret = get_tree_bdev_flags(fc, erofs_fc_fill_super,
@@ -821,7 +869,7 @@ static int erofs_fc_get_tree(struct fs_context *fc)
if (S_ISREG(file_inode(sbi->dif0.file)->i_mode) &&
sbi->dif0.file->f_mapping->a_ops->read_folio)
- return get_tree_nodev(fc, erofs_fc_fill_super);
+ return erofs_fc_get_tree_file(fc);
}
return ret;
}
@@ -834,6 +882,10 @@ static int erofs_fc_reconfigure(struct fs_context *fc)
DBG_BUGON(!sb_rdonly(sb));
+ /* Shared superblocks must not be reconfigured. */
+ if (test_opt(&sbi->opt, SUPERBLOCK_SHARE))
+ return -EBUSY;
+
if (new_sbi->domain_id)
erofs_info(sb, "ignoring reconfiguration for domain_id.");
--
2.55.0
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts
2026-08-11 7:10 ` [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts Giuseppe Scrivano
@ 2026-08-11 15:31 ` Gao Xiang
2026-08-11 15:57 ` Giuseppe Scrivano
0 siblings, 1 reply; 7+ messages in thread
From: Gao Xiang @ 2026-08-11 15:31 UTC (permalink / raw)
To: Giuseppe Scrivano; +Cc: linux-erofs, xiang, linux-fsdevel, amir73il, brauner
Hi Giuseppe,
On Tue, Aug 11, 2026 at 09:10:51AM +0200, Giuseppe Scrivano wrote:
> When the same file-backed image is mounted multiple times (via path or
> fd) with the superblock_share mount option, reuse the existing
> superblock instead of creating a new one. This allows multiple mounts
> of the same image to share in-kernel data structures more efficiently.
>
> The backing file is identified by its inode and fsoffset. If mount
> options conflict or extra devices are used, a separate superblock
> is created transparently as a fallback. Remount is not allowed on
> shared superblocks.
>
> Tested by mounting a 177M Fedora EROFS image 20 times with full
> traversal:
>
> #!/bin/sh
> IMG=${1:-/root/fedora.erofs}
> OPTS=${2:+-o $2}
> N=20
> DIR=$(mktemp -d)
> trap "umount $DIR/m* 2>/dev/null; rm -rf $DIR" EXIT
>
> sync; echo 3 > /proc/sys/vm/drop_caches
> INODES_BEFORE=$(grep erofs_inode /proc/slabinfo | awk '{print $2}')
> MEM_BEFORE=$(grep ^Slab: /proc/meminfo | awk '{print $2}')
>
> for i in $(seq 1 $N); do
> mkdir $DIR/m$i
> mount -t erofs $OPTS "$IMG" $DIR/m$i
> find $DIR/m$i > /dev/null
> done
>
> echo "Superblocks: $(grep $DIR /proc/self/mountinfo | \
> awk '{print $3}' | sort -u | wc -l)"
> echo "erofs_inode delta: +$(( $(grep erofs_inode /proc/slabinfo | \
> awk '{print $2}') - INODES_BEFORE ))"
> echo "Slab delta: +$(( $(grep ^Slab: /proc/meminfo | \
> awk '{print $2}') - MEM_BEFORE )) kB"
>
> without superblock_share:
>
> # time ./test.sh /root/fedora.erofs
> Superblocks: 20
> erofs_inode delta: +45864
> Slab delta: +37448 kB
> real 0m2.311s
> user 0m0.222s
> sys 0m1.998s
>
> with superblock_share:
>
> # time ./test.sh /root/fedora.erofs superblock_share
> Superblocks: 1
> erofs_inode delta: +2044
> Slab delta: +284 kB
> real 0m0.679s
> user 0m0.157s
> sys 0m0.469s
>
> The time difference shows that sharing the superblock also benefits
> the page cache and inode cache, as subsequent mounts of the same image
> avoid re-reading the backing file. This is particularly useful for
> container hosts running multiple containers from the same base image.
>
> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
Thanks for the patch.
It generally looks good to me. Just some minor nits:
> ---
> Documentation/filesystems/erofs.rst | 8 ++++
> fs/erofs/internal.h | 1 +
> fs/erofs/super.c | 58 +++++++++++++++++++++++++++--
> 3 files changed, 64 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/filesystems/erofs.rst b/Documentation/filesystems/erofs.rst
> index d301d9ac946a..49c4e7dbc5d6 100644
> --- a/Documentation/filesystems/erofs.rst
> +++ b/Documentation/filesystems/erofs.rst
> @@ -139,6 +139,9 @@ inode_share Enable inode page sharing for this filesystem. Inodes wi
> page cache.
> source=%s (For file-backed mounts) Specify the backing image as a path
> or as an already-opened file descriptor.
> +superblock_share (For file-backed mounts) Share the superblock when the same
> + backing file is mounted more than once with compatible
> + options. Remount is not allowed on shared superblocks.
> =================== =========================================================
>
> File-backed mounts
> @@ -156,6 +159,11 @@ Only regular files are accepted as backing files; to mount an image that
> resides on a block device, use the traditional block device mount path
> instead.
>
> +When superblock_share is specified and the same backing file (identified
> +by inode and fsoffset) is mounted more than once with compatible mount
> +options, the kernel reuses the existing superblock instead of creating a
When superblock_share is specified, multiple mounts of the same backing
file at the same fsoffset with compatible mount options can share a single
superblock. Remount is now allowed on shared superblocks.
> +new one. Remount is not allowed on shared superblocks.
> +
> Sysfs Entries
> =============
>
> diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
> index 580f8d9f14e7..8d65cac35702 100644
> --- a/fs/erofs/internal.h
> +++ b/fs/erofs/internal.h
> @@ -156,6 +156,7 @@ struct erofs_sb_info {
> #define EROFS_MOUNT_DAX_NEVER 0x00000080
> #define EROFS_MOUNT_DIRECT_IO 0x00000100
> #define EROFS_MOUNT_INODE_SHARE 0x00000200
> +#define EROFS_MOUNT_SUPERBLOCK_SHARE 0x00000400
>
> #define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
> #define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
> index d40961248a49..72d654ce49b7 100644
> --- a/fs/erofs/super.c
> +++ b/fs/erofs/super.c
> @@ -386,7 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
> enum {
> Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
> Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
> - Opt_source,
> + Opt_source, Opt_superblock_share,
> };
>
> static const struct constant_table erofs_param_cache_strategy[] = {
> @@ -415,6 +415,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = {
> fsparam_u64("fsoffset", Opt_fsoffset),
> fsparam_flag("inode_share", Opt_inode_share),
> fsparam_file_or_string("source", Opt_source),
> + fsparam_flag("superblock_share", Opt_superblock_share),
> {}
> };
>
> @@ -560,6 +561,12 @@ static int erofs_fc_parse_param(struct fs_context *fc,
> else
> set_opt(&sbi->opt, INODE_SHARE);
> break;
> + case Opt_superblock_share:
> + if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE))
> + errorfc(fc, "%s option not supported", erofs_fs_parameters[opt].name);
> + else
> + set_opt(&sbi->opt, SUPERBLOCK_SHARE);
> + break;
> case Opt_source:
> return erofs_fc_parse_source(fc, param);
> }
> @@ -663,6 +670,17 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
> errorfc(fc, "FSDAX is not allowed when inode_share is on");
> return -EINVAL;
> }
> + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
> + test_opt(&sbi->opt, INODE_SHARE)) {
> + errorfc(fc, "superblock_share is not allowed when inode_share is on");
> + return -EINVAL;
> + }
> + /* Extra devices are not yet supported with superblock sharing. */
Okay, if the comment is added here, I don't think it's helpful since the
error message indicates the same.
> + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
> + sbi->devs->extra_devices) {
> + errorfc(fc, "superblock_share does not support extra devices");
> + return -EINVAL;
> + }
>
> sbi->blkszbits = PAGE_SHIFT;
> if (!sb->s_bdev) {
> @@ -788,6 +806,36 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
> return 0;
> }
>
Maybe just move the comment below here:
/*
* The function is used only with a sbi that has SUPERBLOCK_SHARE set,
* so that the memcmp later ensures new_sbi is also shareable.
*/
> +static int erofs_fc_test_file_super(struct super_block *sb,
Can we just rename it as erofs_sb_share_test_super()?
> + struct fs_context *fc)
> +{
> + /*
> + * The assumption is that the function is used only with a sbi that
> + * has SUPERBLOCK_SHARE set, so that the memcmp later ensures
> + * new_sbi also is shareable.
> + */
> + struct erofs_sb_info *sbi = EROFS_SB(sb);
> + struct erofs_sb_info *new_sbi = fc->s_fs_info;
> +
> + if (sb->s_iflags & SB_I_RETIRED)
> + return 0;
> + if (!sbi->dif0.file || !new_sbi->dif0.file)
> + return 0;
> + return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) &&
> + sbi->dif0.fsoff == new_sbi->dif0.fsoff &&
> + !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt));
> +}
> +
> +static int erofs_fc_get_tree_file(struct fs_context *fc)
Could we just rename it as erofs_file_get_tree()?
Thanks,
Gao Xiang
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it
2026-08-11 7:10 ` [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it Giuseppe Scrivano
@ 2026-08-11 15:44 ` Gao Xiang
2026-08-11 15:59 ` Giuseppe Scrivano
0 siblings, 1 reply; 7+ messages in thread
From: Gao Xiang @ 2026-08-11 15:44 UTC (permalink / raw)
To: Giuseppe Scrivano
Cc: linux-erofs, xiang, linux-fsdevel, amir73il, brauner,
Alexander Viro, Jan Kara
(+ cc vfs maintainers)
Hi,
On Tue, Aug 11, 2026 at 09:10:50AM +0200, Giuseppe Scrivano wrote:
> vfs_get_super() already implements the common get_tree pattern of
> looking up an existing superblock via a test callback and initialising
> a new one with fill_super otherwise, but it is private to fs/super.c
> and only reachable through the get_tree_nodev()/get_tree_single()/
> get_tree_keyed() wrappers, none of which let a filesystem supply its
> own test callback.
>
> Rename it to get_tree_super() and export it so filesystems that need a
> custom superblock matching policy can reuse it directly instead of
> open-coding sget_fc() + fill_super(). No functional change.
>
> This is a preparatory fix for the next patch.
>
> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
With the new vfs_get_super() helper, it seems much cleaner compared
to the previous versions.
Since this series interacts with the other erofs ongoing patches.
So I hope at least [PATCH 2/2] can be routed into the erofs tree to
avoid unnecessary conflict resolving.
Maybe the simplistic way is vfs maintainers can ack this vfs patch so
that both patches can go through erofs tree for the next cycle
directly.
> ---
...
> +/**
> + * get_tree_super - Get a superblock, optionally sharing an existing one
> + * @fc: The filesystem context holding the parameters
> + * @test: Comparison function to find a matching existing superblock, or NULL
> + * @fill_super: Helper to initialise a new superblock
> + *
> + * If @test is non-NULL and matches an existing superblock, that superblock is
> + * reused; otherwise a new anonymous superblock is created and initialised with
> + * @fill_super. Passing NULL for @test always creates a new superblock.
> + */
> +int get_tree_super(struct fs_context *fc,
> int (*test)(struct super_block *, struct fs_context *),
> int (*fill_super)(struct super_block *sb,
> struct fs_context *fc))
> @@ -1278,12 +1288,13 @@ static int vfs_get_super(struct fs_context *fc,
> deactivate_locked_super(sb);
> return err;
> }
> +EXPORT_SYMBOL(get_tree_super);
btw, some people prefer EXPORT_SYMBOL_GPL() for this kind of helpers.
Thanks,
Gao Xiang
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts
2026-08-11 15:31 ` Gao Xiang
@ 2026-08-11 15:57 ` Giuseppe Scrivano
0 siblings, 0 replies; 7+ messages in thread
From: Giuseppe Scrivano @ 2026-08-11 15:57 UTC (permalink / raw)
To: linux-erofs; +Cc: xiang, linux-fsdevel, amir73il, brauner
Gao Xiang <xiang@kernel.org> writes:
> Hi Giuseppe,
>
> On Tue, Aug 11, 2026 at 09:10:51AM +0200, Giuseppe Scrivano wrote:
>> When the same file-backed image is mounted multiple times (via path or
>> fd) with the superblock_share mount option, reuse the existing
>> superblock instead of creating a new one. This allows multiple mounts
>> of the same image to share in-kernel data structures more efficiently.
>>
>> The backing file is identified by its inode and fsoffset. If mount
>> options conflict or extra devices are used, a separate superblock
>> is created transparently as a fallback. Remount is not allowed on
>> shared superblocks.
>>
>> Tested by mounting a 177M Fedora EROFS image 20 times with full
>> traversal:
>>
>> #!/bin/sh
>> IMG=${1:-/root/fedora.erofs}
>> OPTS=${2:+-o $2}
>> N=20
>> DIR=$(mktemp -d)
>> trap "umount $DIR/m* 2>/dev/null; rm -rf $DIR" EXIT
>>
>> sync; echo 3 > /proc/sys/vm/drop_caches
>> INODES_BEFORE=$(grep erofs_inode /proc/slabinfo | awk '{print $2}')
>> MEM_BEFORE=$(grep ^Slab: /proc/meminfo | awk '{print $2}')
>>
>> for i in $(seq 1 $N); do
>> mkdir $DIR/m$i
>> mount -t erofs $OPTS "$IMG" $DIR/m$i
>> find $DIR/m$i > /dev/null
>> done
>>
>> echo "Superblocks: $(grep $DIR /proc/self/mountinfo | \
>> awk '{print $3}' | sort -u | wc -l)"
>> echo "erofs_inode delta: +$(( $(grep erofs_inode /proc/slabinfo | \
>> awk '{print $2}') - INODES_BEFORE ))"
>> echo "Slab delta: +$(( $(grep ^Slab: /proc/meminfo | \
>> awk '{print $2}') - MEM_BEFORE )) kB"
>>
>> without superblock_share:
>>
>> # time ./test.sh /root/fedora.erofs
>> Superblocks: 20
>> erofs_inode delta: +45864
>> Slab delta: +37448 kB
>> real 0m2.311s
>> user 0m0.222s
>> sys 0m1.998s
>>
>> with superblock_share:
>>
>> # time ./test.sh /root/fedora.erofs superblock_share
>> Superblocks: 1
>> erofs_inode delta: +2044
>> Slab delta: +284 kB
>> real 0m0.679s
>> user 0m0.157s
>> sys 0m0.469s
>>
>> The time difference shows that sharing the superblock also benefits
>> the page cache and inode cache, as subsequent mounts of the same image
>> avoid re-reading the backing file. This is particularly useful for
>> container hosts running multiple containers from the same base image.
>>
>> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
>
> Thanks for the patch.
>
> It generally looks good to me. Just some minor nits:
>
>> ---
>> Documentation/filesystems/erofs.rst | 8 ++++
>> fs/erofs/internal.h | 1 +
>> fs/erofs/super.c | 58 +++++++++++++++++++++++++++--
>> 3 files changed, 64 insertions(+), 3 deletions(-)
>>
>> diff --git a/Documentation/filesystems/erofs.rst b/Documentation/filesystems/erofs.rst
>> index d301d9ac946a..49c4e7dbc5d6 100644
>> --- a/Documentation/filesystems/erofs.rst
>> +++ b/Documentation/filesystems/erofs.rst
>> @@ -139,6 +139,9 @@ inode_share Enable inode page sharing for this filesystem. Inodes wi
>> page cache.
>> source=%s (For file-backed mounts) Specify the backing image as a path
>> or as an already-opened file descriptor.
>> +superblock_share (For file-backed mounts) Share the superblock when the same
>> + backing file is mounted more than once with compatible
>> + options. Remount is not allowed on shared superblocks.
>> =================== =========================================================
>>
>> File-backed mounts
>> @@ -156,6 +159,11 @@ Only regular files are accepted as backing files; to mount an image that
>> resides on a block device, use the traditional block device mount path
>> instead.
>>
>> +When superblock_share is specified and the same backing file (identified
>> +by inode and fsoffset) is mounted more than once with compatible mount
>> +options, the kernel reuses the existing superblock instead of creating a
>
> When superblock_share is specified, multiple mounts of the same backing
> file at the same fsoffset with compatible mount options can share a single
> superblock. Remount is now allowed on shared superblocks.
>
>> +new one. Remount is not allowed on shared superblocks.
>> +
>> Sysfs Entries
>> =============
>>
>> diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
>> index 580f8d9f14e7..8d65cac35702 100644
>> --- a/fs/erofs/internal.h
>> +++ b/fs/erofs/internal.h
>> @@ -156,6 +156,7 @@ struct erofs_sb_info {
>> #define EROFS_MOUNT_DAX_NEVER 0x00000080
>> #define EROFS_MOUNT_DIRECT_IO 0x00000100
>> #define EROFS_MOUNT_INODE_SHARE 0x00000200
>> +#define EROFS_MOUNT_SUPERBLOCK_SHARE 0x00000400
>>
>> #define clear_opt(opt, option) ((opt)->mount_opt &= ~EROFS_MOUNT_##option)
>> #define set_opt(opt, option) ((opt)->mount_opt |= EROFS_MOUNT_##option)
>> diff --git a/fs/erofs/super.c b/fs/erofs/super.c
>> index d40961248a49..72d654ce49b7 100644
>> --- a/fs/erofs/super.c
>> +++ b/fs/erofs/super.c
>> @@ -386,7 +386,7 @@ static void erofs_default_options(struct erofs_sb_info *sbi)
>> enum {
>> Opt_user_xattr, Opt_acl, Opt_cache_strategy, Opt_dax, Opt_dax_enum,
>> Opt_device, Opt_domain_id, Opt_directio, Opt_fsoffset, Opt_inode_share,
>> - Opt_source,
>> + Opt_source, Opt_superblock_share,
>> };
>>
>> static const struct constant_table erofs_param_cache_strategy[] = {
>> @@ -415,6 +415,7 @@ static const struct fs_parameter_spec erofs_fs_parameters[] = {
>> fsparam_u64("fsoffset", Opt_fsoffset),
>> fsparam_flag("inode_share", Opt_inode_share),
>> fsparam_file_or_string("source", Opt_source),
>> + fsparam_flag("superblock_share", Opt_superblock_share),
>> {}
>> };
>>
>> @@ -560,6 +561,12 @@ static int erofs_fc_parse_param(struct fs_context *fc,
>> else
>> set_opt(&sbi->opt, INODE_SHARE);
>> break;
>> + case Opt_superblock_share:
>> + if (!IS_ENABLED(CONFIG_EROFS_FS_BACKED_BY_FILE))
>> + errorfc(fc, "%s option not supported", erofs_fs_parameters[opt].name);
>> + else
>> + set_opt(&sbi->opt, SUPERBLOCK_SHARE);
>> + break;
>> case Opt_source:
>> return erofs_fc_parse_source(fc, param);
>> }
>> @@ -663,6 +670,17 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
>> errorfc(fc, "FSDAX is not allowed when inode_share is on");
>> return -EINVAL;
>> }
>> + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
>> + test_opt(&sbi->opt, INODE_SHARE)) {
>> + errorfc(fc, "superblock_share is not allowed when inode_share is on");
>> + return -EINVAL;
>> + }
>> + /* Extra devices are not yet supported with superblock sharing. */
>
> Okay, if the comment is added here, I don't think it's helpful since the
> error message indicates the same.
>
>> + if (test_opt(&sbi->opt, SUPERBLOCK_SHARE) &&
>> + sbi->devs->extra_devices) {
>> + errorfc(fc, "superblock_share does not support extra devices");
>> + return -EINVAL;
>> + }
>>
>> sbi->blkszbits = PAGE_SHIFT;
>> if (!sb->s_bdev) {
>> @@ -788,6 +806,36 @@ static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
>> return 0;
>> }
>>
>
> Maybe just move the comment below here:
>
> /*
> * The function is used only with a sbi that has SUPERBLOCK_SHARE set,
> * so that the memcmp later ensures new_sbi is also shareable.
> */
>
>> +static int erofs_fc_test_file_super(struct super_block *sb,
>
> Can we just rename it as erofs_sb_share_test_super()?
>
>> + struct fs_context *fc)
>> +{
>> + /*
>> + * The assumption is that the function is used only with a sbi that
>> + * has SUPERBLOCK_SHARE set, so that the memcmp later ensures
>> + * new_sbi also is shareable.
>> + */
>> + struct erofs_sb_info *sbi = EROFS_SB(sb);
>> + struct erofs_sb_info *new_sbi = fc->s_fs_info;
>> +
>> + if (sb->s_iflags & SB_I_RETIRED)
>> + return 0;
>> + if (!sbi->dif0.file || !new_sbi->dif0.file)
>> + return 0;
>> + return file_inode(sbi->dif0.file) == file_inode(new_sbi->dif0.file) &&
>> + sbi->dif0.fsoff == new_sbi->dif0.fsoff &&
>> + !memcmp(&sbi->opt, &new_sbi->opt, sizeof(sbi->opt));
>> +}
>> +
>> +static int erofs_fc_get_tree_file(struct fs_context *fc)
>
> Could we just rename it as erofs_file_get_tree()?
sure, thanks for the review. I'll send another version later
today/tomorrow if there are no other comments.
Regards,
Giuseppe
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it
2026-08-11 15:44 ` Gao Xiang
@ 2026-08-11 15:59 ` Giuseppe Scrivano
0 siblings, 0 replies; 7+ messages in thread
From: Giuseppe Scrivano @ 2026-08-11 15:59 UTC (permalink / raw)
To: linux-erofs
Cc: xiang, linux-fsdevel, amir73il, brauner, Alexander Viro, Jan Kara
Gao Xiang <xiang@kernel.org> writes:
> (+ cc vfs maintainers)
>
> Hi,
>
> On Tue, Aug 11, 2026 at 09:10:50AM +0200, Giuseppe Scrivano wrote:
>> vfs_get_super() already implements the common get_tree pattern of
>> looking up an existing superblock via a test callback and initialising
>> a new one with fill_super otherwise, but it is private to fs/super.c
>> and only reachable through the get_tree_nodev()/get_tree_single()/
>> get_tree_keyed() wrappers, none of which let a filesystem supply its
>> own test callback.
>>
>> Rename it to get_tree_super() and export it so filesystems that need a
>> custom superblock matching policy can reuse it directly instead of
>> open-coding sget_fc() + fill_super(). No functional change.
>>
>> This is a preparatory fix for the next patch.
>>
>> Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
>
> With the new vfs_get_super() helper, it seems much cleaner compared
> to the previous versions.
>
> Since this series interacts with the other erofs ongoing patches.
> So I hope at least [PATCH 2/2] can be routed into the erofs tree to
> avoid unnecessary conflict resolving.
>
> Maybe the simplistic way is vfs maintainers can ack this vfs patch so
> that both patches can go through erofs tree for the next cycle
> directly.
>
>
>> ---
>
> ...
>
>> +/**
>> + * get_tree_super - Get a superblock, optionally sharing an existing one
>> + * @fc: The filesystem context holding the parameters
>> + * @test: Comparison function to find a matching existing superblock, or NULL
>> + * @fill_super: Helper to initialise a new superblock
>> + *
>> + * If @test is non-NULL and matches an existing superblock, that superblock is
>> + * reused; otherwise a new anonymous superblock is created and initialised with
>> + * @fill_super. Passing NULL for @test always creates a new superblock.
>> + */
>> +int get_tree_super(struct fs_context *fc,
>> int (*test)(struct super_block *, struct fs_context *),
>> int (*fill_super)(struct super_block *sb,
>> struct fs_context *fc))
>> @@ -1278,12 +1288,13 @@ static int vfs_get_super(struct fs_context *fc,
>> deactivate_locked_super(sb);
>> return err;
>> }
>> +EXPORT_SYMBOL(get_tree_super);
>
> btw, some people prefer EXPORT_SYMBOL_GPL() for this kind of helpers.
I am fine with this change, but the rest of the file is using
EXPORT_SYMBOL though, that is why I've used it.
Regards,
Giuseppe
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-08-11 15:59 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-11 7:10 [PATCH v5 0/2] erofs: share the superblock for file-backed mounts Giuseppe Scrivano
2026-08-11 7:10 ` [PATCH v5 1/2] fs: rename vfs_get_super() to get_tree_super() and export it Giuseppe Scrivano
2026-08-11 15:44 ` Gao Xiang
2026-08-11 15:59 ` Giuseppe Scrivano
2026-08-11 7:10 ` [PATCH v5 2/2] erofs: reuse superblock for file-backed mounts Giuseppe Scrivano
2026-08-11 15:31 ` Gao Xiang
2026-08-11 15:57 ` Giuseppe Scrivano
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.