* [PATCH v4 0/4] 9p: Performance improvements for build workloads
@ 2026-03-24 19:43 Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 1/4] 9p: Cache negative dentries for lookup performance Remi Pommarel
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Remi Pommarel @ 2026-03-24 19:43 UTC (permalink / raw)
To: v9fs
Cc: linux-fsdevel, linux-kernel, Eric Van Hensbergen,
Latchesar Ionkov, Dominique Martinet, Christian Schoenebeck,
Remi Pommarel
This patchset introduces several performance optimizations for the 9p
filesystem when used with cache=loose option (exclusive or read only
mounts). These improvements particularly target workloads with frequent
lookups of non-existent paths and repeated symlink resolutions.
The very state of the art benchmark consisting of cloning a fresh
hostap repository and building hostapd and wpa_supplicant for hwsim
tests (cd tests/hwsim; time ./build.sh) in a VM running on a 9pfs rootfs
(with trans=virtio,cache=loose options) has been used to test those
optimizations impact.
For reference, the build takes 0m56.492s on my laptop natively while it
completes in 2m18.702sec on the VM. This represents a significant
performance penalty considering running the same build on a VM using a
virtiofs rootfs (with "--cache always" virtiofsd option) takes around
1m32.141s. This patchset aims to bring the 9pfs build time close to
that of virtiofs, rather than the native host time, as a realistic
expectation.
This first three patches in this series focus on keeping negative
dentries in the cache, ensuring that subsequent lookups for paths known
to not exist do not require redundant 9P RPC calls. This optimization
reduces the time needed for the compiler to search for header files
across known locations. The two first patches introduce a new mount
option, ndentrycache, which specifies the number of ms to keep the
dentry in the cache. Using ndentrycache without value (i.e. keeping the
negative dentry indifinetly) shrunk build time to 1m46.198s. The third
patch enable the negative dentry caching for 24 hours by default on
cache=loose.
The fourth patch extends page cache usage to symlinks by allowing
p9_client_readlink() results to be cached. Resolving symlink is
apparently something done quite frequently during the build process and
avoiding the cost of a 9P RPC call round trip for already known symlinks
helps reduce the build time to 1m26.602s, outperforming the virtiofs
setup.
Here is summary of the different hostapd/wpa_supplicant build times:
- Baseline (no patch): 2m18.702s
- negative dentry caching (patches 1-3): 1m46.198s (23% improvement)
- Above + symlink caching (patches 1-4): 1m26.302s (an additional 18%
improvement, 37% in total)
With this ~37% performance gain, 9pfs with cache=loose can compete with
virtiofs for (at least) this specific scenario. Although this benchmark
is not the most typical, I do think that these caching optimizations
could benefit a wide range of other workflows as well.
Also sorry for the long delay for v4, it's been quite busy lately.
Changes since v3:
- Go back to s32 ndentry timeout option, rename it to negtimeout so
its easier to grep
- Bring back the missing 4 letters for *_TIMEOUT_* macro name
- Fix some alignment/space issues
Changes since v2:
- Rename v9fs_dentry_is_{expired,refresh} to ndentry
- Some grammatical fixes in couple of comments
- Rename the negative cache mount option to ndentrycache. Using
ndentrycache without value enable infinite caching while
ndentrycache=<time> enable caching for <time> milliseconds.
This allows the option to be unsigned.
- Make it more obvious v9fs_issue_read() is only called on dotl
symlinks
Changes since v1:
- Rebase on 9p-next (with new mount API conversion)
- Integrated symlink caching with the network filesystem helper
library for robustness (a lot of code expects a valid netfs context)
- Instantiate symlink dentry at creation to avoid keeping a negative
dentry in cache
- Moved IO waiting time accounting to a separate patch series
Thanks.
Remi Pommarel (4):
9p: Cache negative dentries for lookup performance
9p: Add mount option for negative dentry cache retention
9p: Set default negative dentry retention time for cache=loose
9p: Enable symlink caching in page cache
Documentation/filesystems/9p.rst | 5 ++
fs/9p/fid.c | 11 ++--
fs/9p/v9fs.c | 27 +++++++-
fs/9p/v9fs.h | 28 ++++++---
fs/9p/v9fs_vfs.h | 15 +++++
fs/9p/vfs_addr.c | 29 ++++++++-
fs/9p/vfs_dentry.c | 105 ++++++++++++++++++++++++++-----
fs/9p/vfs_inode.c | 18 +++---
fs/9p/vfs_inode_dotl.c | 72 ++++++++++++++++++---
fs/9p/vfs_super.c | 1 +
include/net/9p/client.h | 2 +
11 files changed, 265 insertions(+), 48 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 1/4] 9p: Cache negative dentries for lookup performance
2026-03-24 19:43 [PATCH v4 0/4] 9p: Performance improvements for build workloads Remi Pommarel
@ 2026-03-24 19:43 ` Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 2/4] 9p: Add mount option for negative dentry cache retention Remi Pommarel
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Remi Pommarel @ 2026-03-24 19:43 UTC (permalink / raw)
To: v9fs
Cc: linux-fsdevel, linux-kernel, Eric Van Hensbergen,
Latchesar Ionkov, Dominique Martinet, Christian Schoenebeck,
Remi Pommarel
Not caching negative dentries can result in poor performance for
workloads that repeatedly look up non-existent paths. Each such
lookup triggers a full 9P transaction with the server, adding
unnecessary overhead.
A typical example is source compilation, where multiple cc1 processes
are spawned and repeatedly search for the same missing header files
over and over again.
This change enables caching of negative dentries, so that lookups for
known non-existent paths do not require a full 9P transaction. The
cached negative dentries are retained for a configurable duration
(expressed in milliseconds), as specified by the ndentry_timeout
field in struct v9fs_session_info. If set to -1, negative dentries
are cached indefinitely.
This optimization reduces lookup overhead and improves performance for
workloads involving frequent access to non-existent paths.
Signed-off-by: Remi Pommarel <repk@triplefau.lt>
---
fs/9p/fid.c | 11 +++--
fs/9p/v9fs.c | 1 +
fs/9p/v9fs.h | 5 ++
fs/9p/v9fs_vfs.h | 15 ++++++
fs/9p/vfs_dentry.c | 105 ++++++++++++++++++++++++++++++++++------
fs/9p/vfs_inode.c | 12 +++--
fs/9p/vfs_super.c | 1 +
include/net/9p/client.h | 2 +
8 files changed, 128 insertions(+), 24 deletions(-)
diff --git a/fs/9p/fid.c b/fs/9p/fid.c
index f84412290a30..76242d450aa7 100644
--- a/fs/9p/fid.c
+++ b/fs/9p/fid.c
@@ -20,7 +20,9 @@
static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid)
{
- hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata);
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
+
+ hlist_add_head(&fid->dlist, &v9fs_dentry->head);
}
@@ -112,6 +114,7 @@ void v9fs_open_fid_add(struct inode *inode, struct p9_fid **pfid)
static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any)
{
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
struct p9_fid *fid, *ret;
p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p) uid %d any %d\n",
@@ -119,11 +122,9 @@ static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any)
any);
ret = NULL;
/* we'll recheck under lock if there's anything to look in */
- if (dentry->d_fsdata) {
- struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata;
-
+ if (!hlist_empty(&v9fs_dentry->head)) {
spin_lock(&dentry->d_lock);
- hlist_for_each_entry(fid, h, dlist) {
+ hlist_for_each_entry(fid, &v9fs_dentry->head, dlist) {
if (any || uid_eq(fid->uid, uid)) {
ret = fid;
p9_fid_get(ret);
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
index 057487efaaeb..c5dca81a553e 100644
--- a/fs/9p/v9fs.c
+++ b/fs/9p/v9fs.c
@@ -422,6 +422,7 @@ static void v9fs_apply_options(struct v9fs_session_info *v9ses,
v9ses->cache = ctx->session_opts.cache;
v9ses->uid = ctx->session_opts.uid;
v9ses->session_lock_timeout = ctx->session_opts.session_lock_timeout;
+ v9ses->ndentry_timeout_ms = ctx->session_opts.ndentry_timeout_ms;
}
/**
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index 6a12445d3858..e630c5111d74 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -91,6 +91,7 @@ enum p9_cache_bits {
* @debug: debug level
* @afid: authentication handle
* @cache: cache mode of type &p9_cache_bits
+ * @ndentry_timeout: Negative dentry lookup cache retention time in ms
* @cachetag: the tag of the cache associated with this session
* @fscache: session cookie associated with FS-Cache
* @uname: string user name to mount hierarchy as
@@ -101,6 +102,7 @@ enum p9_cache_bits {
* @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy
* @clnt: reference to 9P network client instantiated for this session
* @slist: reference to list of registered 9p sessions
+ * @ndentry_timeout_ms: Negative dentry caching retention time
*
* This structure holds state for each session instance established during
* a sys_mount() .
@@ -116,6 +118,7 @@ struct v9fs_session_info {
unsigned short debug;
unsigned int afid;
unsigned int cache;
+ unsigned int ndentry_timeout_ms;
#ifdef CONFIG_9P_FSCACHE
char *cachetag;
struct fscache_volume *fscache;
@@ -133,6 +136,8 @@ struct v9fs_session_info {
long session_lock_timeout; /* retry interval for blocking locks */
};
+#define NDENTRY_TIMEOUT_NEVER (-1U)
+
/* cache_validity flags */
#define V9FS_INO_INVALID_ATTR 0x01
diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h
index d3aefbec4de6..83c2335f438d 100644
--- a/fs/9p/v9fs_vfs.h
+++ b/fs/9p/v9fs_vfs.h
@@ -28,6 +28,19 @@
/* flags for v9fs_stat2inode() & v9fs_stat2inode_dotl() */
#define V9FS_STAT2INODE_KEEP_ISIZE 1
+/**
+ * struct v9fs_dentry - v9fs specific dentry data
+ * @head: List of fid associated with this dentry
+ * @expire_time: Lookup cache expiration time for negative dentries
+ * @rcu: used by kfree_rcu to schedule clean up job
+ */
+struct v9fs_dentry {
+ struct hlist_head head;
+ u64 expire_time;
+ struct rcu_head rcu;
+};
+#define to_v9fs_dentry(d) ((struct v9fs_dentry *)((d)->d_fsdata))
+
extern struct file_system_type v9fs_fs_type;
extern const struct address_space_operations v9fs_addr_operations;
extern const struct file_operations v9fs_file_operations;
@@ -35,6 +48,8 @@ extern const struct file_operations v9fs_file_operations_dotl;
extern const struct file_operations v9fs_dir_operations;
extern const struct file_operations v9fs_dir_operations_dotl;
extern const struct dentry_operations v9fs_dentry_operations;
+extern void v9fs_ndentry_refresh_timeout(struct dentry *dentry);
+extern void v9fs_dentry_fid_remove(struct dentry *dentry);
extern const struct dentry_operations v9fs_cached_dentry_operations;
extern struct kmem_cache *v9fs_inode_cache;
diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c
index c5bf74d547e8..e549e222602e 100644
--- a/fs/9p/vfs_dentry.c
+++ b/fs/9p/vfs_dentry.c
@@ -23,6 +23,46 @@
#include "v9fs_vfs.h"
#include "fid.h"
+/**
+ * v9fs_ndentry_is_expired - Check if negative dentry lookup has expired
+ *
+ * This should be called to know if a negative dentry should be removed from
+ * cache.
+ *
+ * @dentry: dentry in question
+ *
+ */
+static bool v9fs_ndentry_is_expired(struct dentry const *dentry)
+{
+ struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
+
+ if (v9ses->ndentry_timeout_ms == NDENTRY_TIMEOUT_NEVER)
+ return false;
+
+ return time_before_eq64(v9fs_dentry->expire_time, get_jiffies_64());
+}
+
+/**
+ * v9fs_ndentry_refresh_timeout - Refresh negative dentry lookup cache timeout
+ *
+ * This should be called when a look up yields a negative entry.
+ *
+ * @dentry: dentry in question
+ *
+ */
+void v9fs_ndentry_refresh_timeout(struct dentry *dentry)
+{
+ struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry);
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
+
+ if (v9ses->ndentry_timeout_ms == NDENTRY_TIMEOUT_NEVER)
+ return;
+
+ v9fs_dentry->expire_time = get_jiffies_64() +
+ msecs_to_jiffies(v9ses->ndentry_timeout_ms);
+}
+
/**
* v9fs_cached_dentry_delete - called when dentry refcount equals 0
* @dentry: dentry in question
@@ -33,20 +73,15 @@ static int v9fs_cached_dentry_delete(const struct dentry *dentry)
p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p)\n",
dentry, dentry);
- /* Don't cache negative dentries */
- if (d_really_is_negative(dentry))
- return 1;
- return 0;
-}
+ if (!d_really_is_negative(dentry))
+ return 0;
-/**
- * v9fs_dentry_release - called when dentry is going to be freed
- * @dentry: dentry that is being release
- *
- */
+ return v9fs_ndentry_is_expired(dentry);
+}
-static void v9fs_dentry_release(struct dentry *dentry)
+static void __v9fs_dentry_fid_remove(struct dentry *dentry)
{
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
struct hlist_node *p, *n;
struct hlist_head head;
@@ -54,13 +89,54 @@ static void v9fs_dentry_release(struct dentry *dentry)
dentry, dentry);
spin_lock(&dentry->d_lock);
- hlist_move_list((struct hlist_head *)&dentry->d_fsdata, &head);
+ hlist_move_list(&v9fs_dentry->head, &head);
spin_unlock(&dentry->d_lock);
hlist_for_each_safe(p, n, &head)
p9_fid_put(hlist_entry(p, struct p9_fid, dlist));
}
+/**
+ * v9fs_dentry_fid_remove - Release all dentry's fids
+ * @dentry: dentry in question
+ *
+ */
+void v9fs_dentry_fid_remove(struct dentry *dentry)
+{
+ __v9fs_dentry_fid_remove(dentry);
+}
+
+/**
+ * v9fs_dentry_init - Initialize v9fs dentry data
+ * @dentry: dentry in question
+ *
+ */
+static int v9fs_dentry_init(struct dentry *dentry)
+{
+ struct v9fs_dentry *v9fs_dentry = kzalloc(sizeof(*v9fs_dentry),
+ GFP_KERNEL);
+
+ if (!v9fs_dentry)
+ return -ENOMEM;
+
+ INIT_HLIST_HEAD(&v9fs_dentry->head);
+ dentry->d_fsdata = (void *)v9fs_dentry;
+ return 0;
+}
+
+/**
+ * v9fs_dentry_release - called when dentry is going to be freed
+ * @dentry: dentry that is being released
+ *
+ */
+static void v9fs_dentry_release(struct dentry *dentry)
+{
+ struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry);
+
+ __v9fs_dentry_fid_remove(dentry);
+ kfree_rcu(v9fs_dentry, rcu);
+}
+
static int __v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
{
struct p9_fid *fid;
@@ -72,7 +148,7 @@ static int __v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
inode = d_inode(dentry);
if (!inode)
- goto out_valid;
+ return !v9fs_ndentry_is_expired(dentry);
v9inode = V9FS_I(inode);
if (v9inode->cache_validity & V9FS_INO_INVALID_ATTR) {
@@ -112,7 +188,6 @@ static int __v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags)
return retval;
}
}
-out_valid:
p9_debug(P9_DEBUG_VFS, "dentry: %pd (%p) is valid\n", dentry, dentry);
return 1;
}
@@ -139,12 +214,14 @@ const struct dentry_operations v9fs_cached_dentry_operations = {
.d_revalidate = v9fs_lookup_revalidate,
.d_weak_revalidate = __v9fs_lookup_revalidate,
.d_delete = v9fs_cached_dentry_delete,
+ .d_init = v9fs_dentry_init,
.d_release = v9fs_dentry_release,
.d_unalias_trylock = v9fs_dentry_unalias_trylock,
.d_unalias_unlock = v9fs_dentry_unalias_unlock,
};
const struct dentry_operations v9fs_dentry_operations = {
+ .d_init = v9fs_dentry_init,
.d_release = v9fs_dentry_release,
.d_unalias_trylock = v9fs_dentry_unalias_trylock,
.d_unalias_unlock = v9fs_dentry_unalias_unlock,
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index 97abe65bf7c1..c82db6fe0c39 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -549,7 +549,7 @@ static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags)
/* invalidate all fids associated with dentry */
/* NOTE: This will not include open fids */
- dentry->d_op->d_release(dentry);
+ v9fs_dentry_fid_remove(dentry);
}
return retval;
}
@@ -732,14 +732,16 @@ struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry,
name = dentry->d_name.name;
fid = p9_client_walk(dfid, 1, &name, 1);
p9_fid_put(dfid);
- if (fid == ERR_PTR(-ENOENT))
+ if (fid == ERR_PTR(-ENOENT)) {
inode = NULL;
- else if (IS_ERR(fid))
+ v9fs_ndentry_refresh_timeout(dentry);
+ } else if (IS_ERR(fid)) {
inode = ERR_CAST(fid);
- else if (v9ses->cache & (CACHE_META|CACHE_LOOSE))
+ } else if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) {
inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb);
- else
+ } else {
inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
+ }
/*
* If we had a rename on the server and a parallel lookup
* for the new name, then make sure we instantiate with
diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c
index 0a1c4f7cb001..3b2a906528cd 100644
--- a/fs/9p/vfs_super.c
+++ b/fs/9p/vfs_super.c
@@ -327,6 +327,7 @@ static int v9fs_init_fs_context(struct fs_context *fc)
ctx->session_opts.uid = INVALID_UID;
ctx->session_opts.dfltuid = V9FS_DEFUID;
ctx->session_opts.dfltgid = V9FS_DEFGID;
+ ctx->session_opts.ndentry_timeout_ms = 0;
/* initialize client options */
ctx->client_opts.proto_version = p9_proto_2000L;
diff --git a/include/net/9p/client.h b/include/net/9p/client.h
index 838a94218b59..55c6cb54bd25 100644
--- a/include/net/9p/client.h
+++ b/include/net/9p/client.h
@@ -192,6 +192,7 @@ struct p9_rdma_opts {
* @dfltgid: default numeric groupid to mount hierarchy as
* @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy
* @session_lock_timeout: retry interval for blocking locks
+ * @ndentry_timeout_ms: Negative dentry lookup cache retention time in ms
*
* This strucure holds options which are parsed and will be transferred
* to the v9fs_session_info structure when mounted, and therefore largely
@@ -203,6 +204,7 @@ struct p9_session_opts {
unsigned short debug;
unsigned int afid;
unsigned int cache;
+ unsigned int ndentry_timeout_ms;
#ifdef CONFIG_9P_FSCACHE
char *cachetag;
#endif
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 2/4] 9p: Add mount option for negative dentry cache retention
2026-03-24 19:43 [PATCH v4 0/4] 9p: Performance improvements for build workloads Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 1/4] 9p: Cache negative dentries for lookup performance Remi Pommarel
@ 2026-03-24 19:43 ` Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 3/4] 9p: Set default negative dentry retention time for cache=loose Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 4/4] 9p: Enable symlink caching in page cache Remi Pommarel
3 siblings, 0 replies; 5+ messages in thread
From: Remi Pommarel @ 2026-03-24 19:43 UTC (permalink / raw)
To: v9fs
Cc: linux-fsdevel, linux-kernel, Eric Van Hensbergen,
Latchesar Ionkov, Dominique Martinet, Christian Schoenebeck,
Remi Pommarel
Introduce a new mount option, negtimeout, for v9fs that allows users
to specify how long negative dentries are retained in the cache. The
retention time can be set in milliseconds (e.g. negtimeout=10000 for
a 10secs retention time) or a negative value (e.g. negtimeout=-1) to
keep negative entries until the buffer cache management removes them.
For consistency reasons, this option should only be used in exclusive
or read-only mount scenarios, aligning with the cache=loose usage.
Signed-off-by: Remi Pommarel <repk@triplefau.lt>
---
Documentation/filesystems/9p.rst | 5 +++++
fs/9p/v9fs.c | 16 +++++++++++++++-
fs/9p/v9fs.h | 23 +++++++++++++----------
3 files changed, 33 insertions(+), 11 deletions(-)
diff --git a/Documentation/filesystems/9p.rst b/Documentation/filesystems/9p.rst
index be3504ca034a..b014c7aabba4 100644
--- a/Documentation/filesystems/9p.rst
+++ b/Documentation/filesystems/9p.rst
@@ -238,6 +238,11 @@ Options
cachetag cache tag to use the specified persistent cache.
cache tags for existing cache sessions can be listed at
/sys/fs/9p/caches. (applies only to cache=fscache)
+
+ negtimeout the duration (in milliseconds) that negative dentries (paths
+ that do not actually exist) are retained in the cache. If
+ set to a negative value, those entries are kept indefinitely
+ until evicted by the buffer cache management system
============= ===============================================================
Behavior
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
index c5dca81a553e..f16517be7fc2 100644
--- a/fs/9p/v9fs.c
+++ b/fs/9p/v9fs.c
@@ -39,7 +39,7 @@ enum {
* source if we rejected it as EINVAL */
Opt_source,
/* Options that take integer arguments */
- Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
+ Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, Opt_negtimeout,
/* String options */
Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag,
/* Options that take no arguments */
@@ -93,6 +93,7 @@ const struct fs_parameter_spec v9fs_param_spec[] = {
fsparam_string ("access", Opt_access),
fsparam_flag ("posixacl", Opt_posixacl),
fsparam_u32 ("locktimeout", Opt_locktimeout),
+ fsparam_s32 ("negtimeout", Opt_negtimeout),
/* client options */
fsparam_u32 ("msize", Opt_msize),
@@ -159,6 +160,9 @@ int v9fs_show_options(struct seq_file *m, struct dentry *root)
from_kgid_munged(&init_user_ns, v9ses->dfltgid));
if (v9ses->afid != ~0)
seq_printf(m, ",afid=%u", v9ses->afid);
+ if (v9ses->flags & V9FS_NDENTRY_TIMEOUT_SET)
+ seq_printf(m, ",negtimeout=%d",
+ (int)v9ses->ndentry_timeout_ms);
if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0)
seq_printf(m, ",uname=%s", v9ses->uname);
if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0)
@@ -337,6 +341,16 @@ int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param)
session_opts->session_lock_timeout = (long)result.uint_32 * HZ;
break;
+ case Opt_negtimeout:
+ session_opts->flags |= V9FS_NDENTRY_TIMEOUT_SET;
+ if (result.int_32 < 0) {
+ session_opts->ndentry_timeout_ms =
+ NDENTRY_TIMEOUT_NEVER;
+ } else {
+ session_opts->ndentry_timeout_ms = result.int_32;
+ }
+ break;
+
/* Options for client */
case Opt_msize:
if (result.uint_32 < 4096) {
diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h
index e630c5111d74..32179f64a603 100644
--- a/fs/9p/v9fs.h
+++ b/fs/9p/v9fs.h
@@ -24,6 +24,8 @@
* @V9FS_ACCESS_ANY: use a single attach for all users
* @V9FS_ACCESS_MASK: bit mask of different ACCESS options
* @V9FS_POSIX_ACL: POSIX ACLs are enforced
+ * @V9FS_NDENTRY_TIMEOUT_SET: Has negative dentry timeout retention time been
+ * overridden by ndentrycache mount option
*
* Session flags reflect options selected by users at mount time
*/
@@ -34,16 +36,17 @@
#define V9FS_ACL_MASK V9FS_POSIX_ACL
enum p9_session_flags {
- V9FS_PROTO_2000U = 0x01,
- V9FS_PROTO_2000L = 0x02,
- V9FS_ACCESS_SINGLE = 0x04,
- V9FS_ACCESS_USER = 0x08,
- V9FS_ACCESS_CLIENT = 0x10,
- V9FS_POSIX_ACL = 0x20,
- V9FS_NO_XATTR = 0x40,
- V9FS_IGNORE_QV = 0x80, /* ignore qid.version for cache hints */
- V9FS_DIRECT_IO = 0x100,
- V9FS_SYNC = 0x200
+ V9FS_PROTO_2000U = 0x01,
+ V9FS_PROTO_2000L = 0x02,
+ V9FS_ACCESS_SINGLE = 0x04,
+ V9FS_ACCESS_USER = 0x08,
+ V9FS_ACCESS_CLIENT = 0x10,
+ V9FS_POSIX_ACL = 0x20,
+ V9FS_NO_XATTR = 0x40,
+ V9FS_IGNORE_QV = 0x80, /* ignore qid.version for cache hints */
+ V9FS_DIRECT_IO = 0x100,
+ V9FS_SYNC = 0x200,
+ V9FS_NDENTRY_TIMEOUT_SET = 0x400,
};
/**
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 3/4] 9p: Set default negative dentry retention time for cache=loose
2026-03-24 19:43 [PATCH v4 0/4] 9p: Performance improvements for build workloads Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 1/4] 9p: Cache negative dentries for lookup performance Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 2/4] 9p: Add mount option for negative dentry cache retention Remi Pommarel
@ 2026-03-24 19:43 ` Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 4/4] 9p: Enable symlink caching in page cache Remi Pommarel
3 siblings, 0 replies; 5+ messages in thread
From: Remi Pommarel @ 2026-03-24 19:43 UTC (permalink / raw)
To: v9fs
Cc: linux-fsdevel, linux-kernel, Eric Van Hensbergen,
Latchesar Ionkov, Dominique Martinet, Christian Schoenebeck,
Remi Pommarel
For cache=loose mounts, set the default negative dentry cache retention
time to 24 hours.
Signed-off-by: Remi Pommarel <repk@triplefau.lt>
---
fs/9p/v9fs.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c
index f16517be7fc2..79b6a8b82779 100644
--- a/fs/9p/v9fs.c
+++ b/fs/9p/v9fs.c
@@ -24,6 +24,9 @@
#include "v9fs_vfs.h"
#include "cache.h"
+/* cache=loose default negative dentry retention time is 24hours */
+#define CACHE_LOOSE_NDENTRY_TIMEOUT_DEFAULT (24 * 60 * 60 * 1000)
+
static DEFINE_SPINLOCK(v9fs_sessionlist_lock);
static LIST_HEAD(v9fs_sessionlist);
struct kmem_cache *v9fs_inode_cache;
@@ -437,6 +440,13 @@ static void v9fs_apply_options(struct v9fs_session_info *v9ses,
v9ses->uid = ctx->session_opts.uid;
v9ses->session_lock_timeout = ctx->session_opts.session_lock_timeout;
v9ses->ndentry_timeout_ms = ctx->session_opts.ndentry_timeout_ms;
+
+ /* If negative dentry timeout has not been overridden set default for
+ * cache=loose
+ */
+ if (!(v9ses->flags & V9FS_NDENTRY_TIMEOUT_SET) &&
+ (v9ses->cache & CACHE_LOOSE))
+ v9ses->ndentry_timeout_ms = CACHE_LOOSE_NDENTRY_TIMEOUT_DEFAULT;
}
/**
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 4/4] 9p: Enable symlink caching in page cache
2026-03-24 19:43 [PATCH v4 0/4] 9p: Performance improvements for build workloads Remi Pommarel
` (2 preceding siblings ...)
2026-03-24 19:43 ` [PATCH v4 3/4] 9p: Set default negative dentry retention time for cache=loose Remi Pommarel
@ 2026-03-24 19:43 ` Remi Pommarel
3 siblings, 0 replies; 5+ messages in thread
From: Remi Pommarel @ 2026-03-24 19:43 UTC (permalink / raw)
To: v9fs
Cc: linux-fsdevel, linux-kernel, Eric Van Hensbergen,
Latchesar Ionkov, Dominique Martinet, Christian Schoenebeck,
Remi Pommarel
Currently, when cache=loose is enabled, file reads are cached in the
page cache, but symlink reads are not. This patch allows the results
of p9_client_readlink() to be stored in the page cache, eliminating
the need for repeated 9P transactions on subsequent symlink accesses.
This change improves performance for workloads that involve frequent
symlink resolution.
Signed-off-by: Remi Pommarel <repk@triplefau.lt>
---
fs/9p/vfs_addr.c | 29 +++++++++++++++--
fs/9p/vfs_inode.c | 6 ++--
fs/9p/vfs_inode_dotl.c | 72 +++++++++++++++++++++++++++++++++++++-----
3 files changed, 94 insertions(+), 13 deletions(-)
diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c
index 862164181bac..339f0cfdcb7b 100644
--- a/fs/9p/vfs_addr.c
+++ b/fs/9p/vfs_addr.c
@@ -70,10 +70,24 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq)
{
struct netfs_io_request *rreq = subreq->rreq;
struct p9_fid *fid = rreq->netfs_priv;
+ char *target;
unsigned long long pos = subreq->start + subreq->transferred;
- int total, err;
-
- total = p9_client_read(fid, pos, &subreq->io_iter, &err);
+ int total, err, len, n;
+
+ if (S_ISLNK(rreq->inode->i_mode)) {
+ /* p9_client_readlink() must not be called for legacy protocols
+ * 9p2000 or 9p2000.u.
+ */
+ BUG_ON(!p9_is_proto_dotl(fid->clnt));
+ err = p9_client_readlink(fid, &target);
+ len = strnlen(target, PAGE_SIZE - 1);
+ n = copy_to_iter(target, len, &subreq->io_iter);
+ if (n != len)
+ err = -EFAULT;
+ total = i_size_read(rreq->inode);
+ } else {
+ total = p9_client_read(fid, pos, &subreq->io_iter, &err);
+ }
/* if we just extended the file size, any portion not in
* cache won't be on server and is zeroes */
@@ -99,6 +113,7 @@ static void v9fs_issue_read(struct netfs_io_subrequest *subreq)
static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file)
{
struct p9_fid *fid;
+ struct dentry *dentry;
bool writing = (rreq->origin == NETFS_READ_FOR_WRITE ||
rreq->origin == NETFS_WRITETHROUGH ||
rreq->origin == NETFS_UNBUFFERED_WRITE ||
@@ -115,6 +130,14 @@ static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file)
if (!fid)
goto no_fid;
p9_fid_get(fid);
+ } else if (S_ISLNK(rreq->inode->i_mode)) {
+ dentry = d_find_alias(rreq->inode);
+ if (!dentry)
+ goto no_fid;
+ fid = v9fs_fid_lookup(dentry);
+ dput(dentry);
+ if (IS_ERR(fid))
+ goto no_fid;
} else {
fid = v9fs_fid_find_inode(rreq->inode, writing, INVALID_UID, true);
if (!fid)
diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c
index c82db6fe0c39..98644f27d6f1 100644
--- a/fs/9p/vfs_inode.c
+++ b/fs/9p/vfs_inode.c
@@ -302,10 +302,12 @@ int v9fs_init_inode(struct v9fs_session_info *v9ses,
goto error;
}
- if (v9fs_proto_dotl(v9ses))
+ if (v9fs_proto_dotl(v9ses)) {
inode->i_op = &v9fs_symlink_inode_operations_dotl;
- else
+ inode_nohighmem(inode);
+ } else {
inode->i_op = &v9fs_symlink_inode_operations;
+ }
break;
case S_IFDIR:
diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c
index 643e759eacb2..a286a078d6a6 100644
--- a/fs/9p/vfs_inode_dotl.c
+++ b/fs/9p/vfs_inode_dotl.c
@@ -686,9 +686,13 @@ v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir,
int err;
kgid_t gid;
const unsigned char *name;
+ umode_t mode;
+ struct v9fs_session_info *v9ses;
struct p9_qid qid;
struct p9_fid *dfid;
struct p9_fid *fid = NULL;
+ struct inode *inode;
+ struct posix_acl *dacl = NULL, *pacl = NULL;
name = dentry->d_name.name;
p9_debug(P9_DEBUG_VFS, "%lu,%s,%s\n", dir->i_ino, name, symname);
@@ -702,6 +706,14 @@ v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir,
gid = v9fs_get_fsgid_for_create(dir);
+ /* Update mode based on ACL value */
+ err = v9fs_acl_mode(dir, &mode, &dacl, &pacl);
+ if (err) {
+ p9_debug(P9_DEBUG_VFS, "Failed to get acl values in mknod %d\n",
+ err);
+ goto error;
+ }
+
/* Server doesn't alter fid on TSYMLINK. Hence no need to clone it. */
err = p9_client_symlink(dfid, name, symname, gid, &qid);
@@ -712,8 +724,30 @@ v9fs_vfs_symlink_dotl(struct mnt_idmap *idmap, struct inode *dir,
v9fs_invalidate_inode_attr(dir);
+ /* instantiate inode and assign the unopened fid to the dentry */
+ fid = p9_client_walk(dfid, 1, &name, 1);
+ if (IS_ERR(fid)) {
+ err = PTR_ERR(fid);
+ p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n",
+ err);
+ goto error;
+ }
+
+ v9ses = v9fs_inode2v9ses(dir);
+ inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb);
+ if (IS_ERR(inode)) {
+ err = PTR_ERR(inode);
+ p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n",
+ err);
+ goto error;
+ }
+ v9fs_set_create_acl(inode, fid, dacl, pacl);
+ v9fs_fid_add(dentry, &fid);
+ d_instantiate(dentry, inode);
+ err = 0;
error:
p9_fid_put(fid);
+ v9fs_put_acl(dacl, pacl);
p9_fid_put(dfid);
return err;
}
@@ -853,24 +887,23 @@ v9fs_vfs_mknod_dotl(struct mnt_idmap *idmap, struct inode *dir,
}
/**
- * v9fs_vfs_get_link_dotl - follow a symlink path
+ * v9fs_vfs_get_link_nocache_dotl - Resolve a symlink directly.
+ *
+ * To be used when symlink caching is not enabled.
+ *
* @dentry: dentry for symlink
* @inode: inode for symlink
* @done: destructor for return value
*/
-
static const char *
-v9fs_vfs_get_link_dotl(struct dentry *dentry,
- struct inode *inode,
- struct delayed_call *done)
+v9fs_vfs_get_link_nocache_dotl(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
{
struct p9_fid *fid;
char *target;
int retval;
- if (!dentry)
- return ERR_PTR(-ECHILD);
-
p9_debug(P9_DEBUG_VFS, "%pd\n", dentry);
fid = v9fs_fid_lookup(dentry);
@@ -884,6 +917,29 @@ v9fs_vfs_get_link_dotl(struct dentry *dentry,
return target;
}
+/**
+ * v9fs_vfs_get_link_dotl - follow a symlink path
+ * @dentry: dentry for symlink
+ * @inode: inode for symlink
+ * @done: destructor for return value
+ */
+static const char *
+v9fs_vfs_get_link_dotl(struct dentry *dentry,
+ struct inode *inode,
+ struct delayed_call *done)
+{
+ struct v9fs_session_info *v9ses;
+
+ if (!dentry)
+ return ERR_PTR(-ECHILD);
+
+ v9ses = v9fs_inode2v9ses(inode);
+ if (v9ses->cache & (CACHE_META|CACHE_LOOSE))
+ return page_get_link(dentry, inode, done);
+
+ return v9fs_vfs_get_link_nocache_dotl(dentry, inode, done);
+}
+
int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode)
{
struct p9_stat_dotl *st;
--
2.52.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-03-24 20:18 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-24 19:43 [PATCH v4 0/4] 9p: Performance improvements for build workloads Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 1/4] 9p: Cache negative dentries for lookup performance Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 2/4] 9p: Add mount option for negative dentry cache retention Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 3/4] 9p: Set default negative dentry retention time for cache=loose Remi Pommarel
2026-03-24 19:43 ` [PATCH v4 4/4] 9p: Enable symlink caching in page cache Remi Pommarel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox