[PATCH] fs: reduce work in fdget_pos()

From: Mateusz Guzik
Date: Wed Mar 19 2025 - 12:41:50 EST


1. predict the file was found
2. explicitly compare the ref to "one", ignoring the dead zone

The latter arguably improves the behavior to begin with. Suppose the
count turned bad -- the previously used ref routine is going to check
for it and return 0, indicating the count does not necessitate taking
->f_pos_lock. But there very well may be several users.

i.e. not paying for special-casing the dead zone improves semantics.

Sizes are as follows (in bytes; gcc 13, x86-64):
stock: 316
likely(): 296
likely()+ref: 278

Signed-off-by: Mateusz Guzik <mjguzik@xxxxxxxxx>
---
fs/file.c | 5 +++--
include/linux/file_ref.h | 14 ++++++++++++++
2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/fs/file.c b/fs/file.c
index ddefb5c80398..190463e61934 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -1193,7 +1193,8 @@ struct fd fdget_raw(unsigned int fd)
static inline bool file_needs_f_pos_lock(struct file *file)
{
return (file->f_mode & FMODE_ATOMIC_POS) &&
- (file_count(file) > 1 || file->f_op->iterate_shared);
+ (__file_ref_read_raw(&file->f_ref) != FILE_REF_ONEREF ||
+ file->f_op->iterate_shared);
}

bool file_seek_cur_needs_f_lock(struct file *file)
@@ -1211,7 +1212,7 @@ struct fd fdget_pos(unsigned int fd)
struct fd f = fdget(fd);
struct file *file = fd_file(f);

- if (file && file_needs_f_pos_lock(file)) {
+ if (likely(file) && file_needs_f_pos_lock(file)) {
f.word |= FDPUT_POS_UNLOCK;
mutex_lock(&file->f_pos_lock);
}
diff --git a/include/linux/file_ref.h b/include/linux/file_ref.h
index 6ef92d765a66..7db62fbc0500 100644
--- a/include/linux/file_ref.h
+++ b/include/linux/file_ref.h
@@ -208,4 +208,18 @@ static inline unsigned long file_ref_read(file_ref_t *ref)
return c >= FILE_REF_RELEASED ? 0 : c + 1;
}

+/*
+ * __file_ref_read_raw - Return the value stored in ref->refcnt
+ * @ref: Pointer to the reference count
+ *
+ * Return: The raw value found in the counter
+ *
+ * A hack for file_needs_f_pos_lock(), you probably want to use
+ * file_ref_read() instead.
+ */
+static inline unsigned long __file_ref_read_raw(file_ref_t *ref)
+{
+ return atomic_long_read(&ref->refcnt);
+}
+
#endif
--
2.43.0