* [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl
@ 2026-07-03 9:28 Vadlakonda Swetha
2026-07-06 21:10 ` Joanne Koong
0 siblings, 1 reply; 3+ messages in thread
From: Vadlakonda Swetha @ 2026-07-03 9:28 UTC (permalink / raw)
To: Miklos Szeredi; +Cc: fuse-devel, linux-fsdevel, linux-kernel, Vadlakonda Swetha
Expose `folio_max_order` and `folio_min_order` entries under the FUSE
control filesystem (`/sys/fs/fuse/connections/<connection_id>/`).
Motivation:
For fuse based systems like GCSFuse (Google Cloud Storage Fuse), large
folios gives a significant boost to the read performance.
- Without large folios, single file read perf is capped at 3GiB/s
- With large folios, we are able to achieve 12GiB/s, 1MB blocksize
- Writes:2GiB/s with large folios vs 1.6GiB/s without folios for 1MB bs
We understand that subfolio dirty page tracking is not available for
writeback cache with large folios. However this is not a blocker for
GCSFuse and similar cloud/network-based filesystems for the following
reasons:
1. We generally recommend applications to use larger io sizes (>1MB)
for writes because of the latency involved with the network calls.
2. Random writes & small overwrites are also not recommended for cloud
based filesystems.
3. Beneficial when write back cache is explicitly disabled. Incase of
GCSFuse, write back cache is disabled for different other reasons.
Providing these controls allow individual filesystems to explicitly opt
into large folios once they evaluate their specific workload patterns.
Why sysfs (/sys/fs/fuse/connections/):
Exposing the folio order range via `fusectl` provides a simple interface
that avoids needing to bump the FUSE UAPI protocol version or update
libfuse, while allowing dynamic tuning of active FUSE mounts.
Testing:
- Verified folio order range assignment on regular inodes.
- Range validation checks (min > max and order > MAX_PAGECACHE_ORDER)
correctly return -EINVAL.
- Added automated selftest to tools/testing/selftests/filesystems/fuse/.
Signed-off-by: Vadlakonda Swetha <swethav0411@gmail.com>
---
fs/fuse/control.c | 109 +++++++++++++-
fs/fuse/fuse_i.h | 6 +
fs/fuse/inode.c | 13 ++
tools/testing/selftests/filesystems/fuse/fusectl_test.c | 33 +++++
4 files changed, 160 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/control.c b/fs/fuse/control.c
index 21ffde596..49144a04d 100644
--- a/fs/fuse/control.c
+++ b/fs/fuse/control.c
@@ -11,6 +11,7 @@
#include <linux/module.h>
#include <linux/fs_context.h>
#include <linux/namei.h>
+#include <linux/pagemap.h>
#define FUSE_CTL_SUPER_MAGIC 0x65735543
@@ -173,6 +174,94 @@ static ssize_t fuse_conn_congestion_threshold_write(struct file *file,
return ret;
}
+static ssize_t fuse_conn_folio_max_order_read(struct file *file,
+ char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ struct fuse_conn *fc;
+ unsigned int val;
+
+ fc = fuse_ctl_file_conn_get(file);
+ if (!fc)
+ return 0;
+
+ val = READ_ONCE(fc->folio_max_order);
+ fuse_conn_put(fc);
+
+ return fuse_conn_limit_read(file, buf, len, ppos, val);
+}
+
+static ssize_t fuse_conn_folio_max_order_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int val = 0;
+ struct fuse_conn *fc;
+ ssize_t ret;
+
+ ret = fuse_conn_limit_write(file, buf, count, ppos, &val, MAX_PAGECACHE_ORDER);
+ if (ret <= 0)
+ goto out;
+
+ fc = fuse_ctl_file_conn_get(file);
+ if (!fc)
+ goto out;
+
+ if (val != 0 && val < READ_ONCE(fc->folio_min_order)) {
+ fuse_conn_put(fc);
+ return -EINVAL;
+ }
+
+ WRITE_ONCE(fc->folio_max_order, val);
+ fuse_conn_put(fc);
+out:
+ return ret;
+}
+
+static ssize_t fuse_conn_folio_min_order_read(struct file *file,
+ char __user *buf, size_t len,
+ loff_t *ppos)
+{
+ struct fuse_conn *fc;
+ unsigned int val;
+
+ fc = fuse_ctl_file_conn_get(file);
+ if (!fc)
+ return 0;
+
+ val = READ_ONCE(fc->folio_min_order);
+ fuse_conn_put(fc);
+
+ return fuse_conn_limit_read(file, buf, len, ppos, val);
+}
+
+static ssize_t fuse_conn_folio_min_order_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ unsigned int val = 0;
+ struct fuse_conn *fc;
+ ssize_t ret;
+
+ ret = fuse_conn_limit_write(file, buf, count, ppos, &val, MAX_PAGECACHE_ORDER);
+ if (ret <= 0)
+ goto out;
+
+ fc = fuse_ctl_file_conn_get(file);
+ if (!fc)
+ goto out;
+
+ if (READ_ONCE(fc->folio_max_order) != 0 && val > READ_ONCE(fc->folio_max_order)) {
+ fuse_conn_put(fc);
+ return -EINVAL;
+ }
+
+ WRITE_ONCE(fc->folio_min_order, val);
+ fuse_conn_put(fc);
+out:
+ return ret;
+}
+
static const struct file_operations fuse_ctl_abort_ops = {
.open = nonseekable_open,
.write = fuse_conn_abort_write,
@@ -195,6 +284,18 @@ static const struct file_operations fuse_conn_congestion_threshold_ops = {
.write = fuse_conn_congestion_threshold_write,
};
+static const struct file_operations fuse_conn_folio_max_order_ops = {
+ .open = nonseekable_open,
+ .read = fuse_conn_folio_max_order_read,
+ .write = fuse_conn_folio_max_order_write,
+};
+
+static const struct file_operations fuse_conn_folio_min_order_ops = {
+ .open = nonseekable_open,
+ .read = fuse_conn_folio_min_order_read,
+ .write = fuse_conn_folio_min_order_write,
+};
+
static struct dentry *fuse_ctl_add_dentry(struct dentry *parent,
struct fuse_conn *fc,
const char *name, int mode,
@@ -267,7 +368,13 @@ int fuse_ctl_add_conn(struct fuse_conn *fc)
NULL, &fuse_conn_max_background_ops) ||
!fuse_ctl_add_dentry(parent, fc, "congestion_threshold",
S_IFREG | 0600, NULL,
- &fuse_conn_congestion_threshold_ops))
+ &fuse_conn_congestion_threshold_ops) ||
+ !fuse_ctl_add_dentry(parent, fc, "folio_max_order",
+ S_IFREG | 0600, NULL,
+ &fuse_conn_folio_max_order_ops) ||
+ !fuse_ctl_add_dentry(parent, fc, "folio_min_order",
+ S_IFREG | 0600, NULL,
+ &fuse_conn_folio_min_order_ops))
goto err;
return 0;
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 85f738c53..e57f52931 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -450,6 +450,12 @@ struct fuse_conn {
/** @max_read: Maximum read size */
unsigned max_read;
+ /** @folio_max_order: Maximum folio order */
+ unsigned int folio_max_order;
+
+ /** @folio_min_order: Minimum folio order */
+ unsigned int folio_min_order;
+
/** @max_write: Maximum write size */
unsigned max_write;
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index d975073c6..c093a7d45 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -9,6 +9,7 @@
#include <linux/dax.h>
#include <linux/pagemap.h>
+#include <linux/log2.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/seq_file.h>
@@ -413,6 +414,18 @@ static void fuse_init_inode(struct inode *inode, struct fuse_attr *attr,
if (S_ISREG(inode->i_mode)) {
fuse_init_common(inode);
fuse_init_file_inode(inode, attr->flags);
+ if (fc->folio_max_order || fc->folio_min_order) {
+ unsigned int max_order;
+
+ max_order = fc->folio_max_order ?
+ fc->folio_max_order : MAX_PAGECACHE_ORDER;
+ mapping_set_folio_order_range(inode->i_mapping,
+ fc->folio_min_order,
+ max_order);
+ pr_info("fuse: inode %llu folio order range set to min=%u, max=%u\n",
+ (unsigned long long)inode->i_ino,
+ fc->folio_min_order, max_order);
+ }
} else if (S_ISDIR(inode->i_mode))
fuse_init_dir(inode);
else if (S_ISLNK(inode->i_mode))
diff --git a/tools/testing/selftests/filesystems/fuse/fusectl_test.c b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
index 0d1d012c3..56c849c3f 100644
--- a/tools/testing/selftests/filesystems/fuse/fusectl_test.c
+++ b/tools/testing/selftests/filesystems/fuse/fusectl_test.c
@@ -137,4 +137,37 @@ TEST_F(fusectl, abort)
ASSERT_EQ(errno, ENOTCONN);
}
+TEST_F(fusectl, folio_orders)
+{
+ char max_path[PATH_MAX];
+ char min_path[PATH_MAX];
+ int fd;
+
+ sprintf(max_path, "/sys/fs/fuse/connections/%d/folio_max_order", self->connection);
+ sprintf(min_path, "/sys/fs/fuse/connections/%d/folio_min_order", self->connection);
+
+ /* 1. Verify sysfs files exist */
+ ASSERT_EQ(0, access(max_path, F_OK));
+ ASSERT_EQ(0, access(min_path, F_OK));
+
+ /* 2. Set valid folio_max_order = 8 */
+ write_file(_metadata, max_path, "8");
+ /* 3. Set valid folio_min_order = 4 */
+ write_file(_metadata, min_path, "4");
+
+ /* 4. Reject invalid write: min_order > max_order (e.g. min=10, max=8) */
+ fd = open(min_path, O_WRONLY);
+ ASSERT_GE(fd, 0);
+ ASSERT_LT(write(fd, "10", 2), 0);
+ ASSERT_EQ(errno, EINVAL);
+ close(fd);
+
+ /* 5. Reject invalid write: max_order < min_order (e.g. max=2, min=4) */
+ fd = open(max_path, O_WRONLY);
+ ASSERT_GE(fd, 0);
+ ASSERT_LT(write(fd, "2", 1), 0);
+ ASSERT_EQ(errno, EINVAL);
+ close(fd);
+}
+
TEST_HARNESS_MAIN
--
2.45.2
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl
2026-07-03 9:28 [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl Vadlakonda Swetha
@ 2026-07-06 21:10 ` Joanne Koong
2026-07-07 1:06 ` Swetha Vadlakonda
0 siblings, 1 reply; 3+ messages in thread
From: Joanne Koong @ 2026-07-06 21:10 UTC (permalink / raw)
To: Vadlakonda Swetha; +Cc: Miklos Szeredi, fuse-devel, linux-fsdevel, linux-kernel
On Fri, Jul 3, 2026 at 2:31 AM Vadlakonda Swetha <swethav0411@gmail.com> wrote:
>
> Expose `folio_max_order` and `folio_min_order` entries under the FUSE
> control filesystem (`/sys/fs/fuse/connections/<connection_id>/`).
>
> Motivation:
> For fuse based systems like GCSFuse (Google Cloud Storage Fuse), large
> folios gives a significant boost to the read performance.
> - Without large folios, single file read perf is capped at 3GiB/s
> - With large folios, we are able to achieve 12GiB/s, 1MB blocksize
> - Writes:2GiB/s with large folios vs 1.6GiB/s without folios for 1MB bs
>
> We understand that subfolio dirty page tracking is not available for
> writeback cache with large folios. However this is not a blocker for
The buffered write and writeback paths in fuse go through iomap which
tracks subfolio dirty state.
> GCSFuse and similar cloud/network-based filesystems for the following
> reasons:
>
> 1. We generally recommend applications to use larger io sizes (>1MB)
> for writes because of the latency involved with the network calls.
> 2. Random writes & small overwrites are also not recommended for cloud
> based filesystems.
> 3. Beneficial when write back cache is explicitly disabled. Incase of
> GCSFuse, write back cache is disabled for different other reasons.
>
> Providing these controls allow individual filesystems to explicitly opt
> into large folios once they evaluate their specific workload patterns.
Could you explain the use case of this granularity? The page cache
already adjusts folio size adaptively (eg small/random i/o still has
small orders, high-order allocations fall back gracefully to
lower-order allocations under memory pressure, etc) so it's a bit
unclear to me why a tunable would be needed. It also won't enforce the
order for any inodes that are already in the cache.
There's a patch to enable large folios by default [1] and the
benchmark numbers showed improvements all around, so I don't think
this would be something individual filesystems would have to
explicitly opt into. Does the change in [1] suffice for your use case?
Thanks,
Joanne
[1] https://lore.kernel.org/fuse-devel/20260624012132.1719941-1-joannelkoong@gmail.com/
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl
2026-07-06 21:10 ` Joanne Koong
@ 2026-07-07 1:06 ` Swetha Vadlakonda
0 siblings, 0 replies; 3+ messages in thread
From: Swetha Vadlakonda @ 2026-07-07 1:06 UTC (permalink / raw)
To: Joanne Koong; +Cc: Miklos Szeredi, fuse-devel, linux-fsdevel, linux-kernel
On Tue, Jul 7, 2026 at 2:40 AM Joanne Koong <joannelkoong@gmail.com> wrote:
>
> On Fri, Jul 3, 2026 at 2:31 AM Vadlakonda Swetha <swethav0411@gmail.com> wrote:
> >
> > Expose `folio_max_order` and `folio_min_order` entries under the FUSE
> > control filesystem (`/sys/fs/fuse/connections/<connection_id>/`).
> >
> > Motivation:
> > For fuse based systems like GCSFuse (Google Cloud Storage Fuse), large
> > folios gives a significant boost to the read performance.
> > - Without large folios, single file read perf is capped at 3GiB/s
> > - With large folios, we are able to achieve 12GiB/s, 1MB blocksize
> > - Writes:2GiB/s with large folios vs 1.6GiB/s without folios for 1MB bs
> >
> > We understand that subfolio dirty page tracking is not available for
> > writeback cache with large folios. However this is not a blocker for
>
> The buffered write and writeback paths in fuse go through iomap which
> tracks subfolio dirty state.
Thanks for the review and update. I was under the impression that not
all changes
related to subfolio dirty page tracking are done.
>
> > GCSFuse and similar cloud/network-based filesystems for the following
> > reasons:
> >
> > 1. We generally recommend applications to use larger io sizes (>1MB)
> > for writes because of the latency involved with the network calls.
> > 2. Random writes & small overwrites are also not recommended for cloud
> > based filesystems.
> > 3. Beneficial when write back cache is explicitly disabled. Incase of
> > GCSFuse, write back cache is disabled for different other reasons.
> >
> > Providing these controls allow individual filesystems to explicitly opt
> > into large folios once they evaluate their specific workload patterns.
>
> Could you explain the use case of this granularity? The page cache
> already adjusts folio size adaptively (eg small/random i/o still has
> small orders, high-order allocations fall back gracefully to
> lower-order allocations under memory pressure, etc) so it's a bit
> unclear to me why a tunable would be needed. It also won't enforce the
> order for any inodes that are already in the cache.
Our primary motivation was to give fine-grained control and users can set
it as per their workload requirements. But agreed on the fallback strategy and
have a single on/off switch.
>
> There's a patch to enable large folios by default [1] and the
> benchmark numbers showed improvements all around, so I don't think
> this would be something individual filesystems would have to
> explicitly opt into. Does the change in [1] suffice for your use case?
Yes, the change in [1] completely suffices for GCSFuse.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-07 1:06 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-03 9:28 [PATCH] fuse: add folio_max_order and folio_min_order controls in fusectl Vadlakonda Swetha
2026-07-06 21:10 ` Joanne Koong
2026-07-07 1:06 ` Swetha Vadlakonda
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.