From: Yang Wen <anmuxixixi@gmail.com>
To: linkinjeon@kernel.org, sj1557.seo@samsung.com, chizhiling@163.com
Cc: yuezhang.mo@sony.com, exfat@lists.linux.dev,
linux-kernel@vger.kernel.org, Yang Wen <anmuxixixi@gmail.com>
Subject: [PATCH v5 3/3] exfat: reclaim name filters under memory pressure
Date: Sat, 19 Sep 2026 23:22:40 +0800 [thread overview]
Message-ID: <20260919152240.1507914-4-anmuxixixi@gmail.com> (raw)
In-Reply-To: <20260919152240.1507914-1-anmuxixixi@gmail.com>
Each active large directory can hold a 64 KiB name filter. Add a
per-superblock LRU and shrinker so filters can be reclaimed when memory is
tight.
Signed-off-by: Yang Wen <anmuxixixi@gmail.com>
---
fs/exfat/dir.c | 145 +++++++++++++++++++++++++++++++++++++++++++-
fs/exfat/exfat_fs.h | 10 +++
fs/exfat/super.c | 8 +++
3 files changed, 160 insertions(+), 3 deletions(-)
diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index 08b7a50de871..013b7e43a289 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -9,6 +9,7 @@
#include <linux/buffer_head.h>
#include <linux/filelock.h>
#include <linux/hash.h>
+#include <linux/shrinker.h>
#include <linux/stringhash.h>
#include "exfat_raw.h"
@@ -91,12 +92,144 @@ static void exfat_name_filter_indexes(struct super_block *sb,
EXFAT_NAME_FILTER_ORDER);
}
+static unsigned long *
+exfat_name_filter_detach_locked(struct exfat_sb_info *sbi,
+ struct exfat_inode_info *ei)
+{
+ unsigned long *filter = ei->name_filter;
+
+ if (!filter)
+ return NULL;
+
+ ei->name_filter = NULL;
+ list_del_init(&ei->name_filter_lru);
+ sbi->name_filter_count--;
+ return filter;
+}
+
+static void exfat_name_filter_touch(struct exfat_inode_info *ei)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(ei->vfs_inode.i_sb);
+
+ spin_lock(&sbi->name_filter_lock);
+ if (ei->name_filter)
+ list_move_tail(&ei->name_filter_lru, &sbi->name_filter_lru);
+ spin_unlock(&sbi->name_filter_lock);
+}
+
void exfat_name_filter_free(struct inode *inode)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
+ struct exfat_sb_info *sbi;
+ unsigned long *filter;
- kvfree(ei->name_filter);
- ei->name_filter = NULL;
+ if (!READ_ONCE(ei->name_filter))
+ return;
+
+ sbi = EXFAT_SB(inode->i_sb);
+ spin_lock(&sbi->name_filter_lock);
+ filter = exfat_name_filter_detach_locked(sbi, ei);
+ spin_unlock(&sbi->name_filter_lock);
+ kvfree(filter);
+}
+
+static unsigned long
+exfat_name_filter_count_objects(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct exfat_sb_info *sbi = shrinker->private_data;
+ unsigned long count;
+
+ spin_lock(&sbi->name_filter_lock);
+ count = sbi->name_filter_count;
+ spin_unlock(&sbi->name_filter_lock);
+
+ return count ? count : SHRINK_EMPTY;
+}
+
+static unsigned long
+exfat_name_filter_scan_objects(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct exfat_sb_info *sbi = shrinker->private_data;
+ unsigned long freed = 0;
+
+ /* Avoid reclaim recursion from a GFP_NOFS allocation under s_lock. */
+ if (!mutex_trylock(&sbi->s_lock)) {
+ sc->nr_scanned = 0;
+ return SHRINK_STOP;
+ }
+
+ while (freed < sc->nr_to_scan) {
+ struct exfat_inode_info *ei;
+ unsigned long *filter;
+
+ spin_lock(&sbi->name_filter_lock);
+ if (list_empty(&sbi->name_filter_lru)) {
+ spin_unlock(&sbi->name_filter_lock);
+ break;
+ }
+
+ ei = list_first_entry(&sbi->name_filter_lru,
+ struct exfat_inode_info,
+ name_filter_lru);
+ filter = exfat_name_filter_detach_locked(sbi, ei);
+ spin_unlock(&sbi->name_filter_lock);
+
+ kvfree(filter);
+ freed++;
+ cond_resched();
+ }
+
+ mutex_unlock(&sbi->s_lock);
+ sc->nr_scanned = freed;
+ return freed;
+}
+
+void exfat_name_filter_shrinker_register(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct shrinker *shrinker;
+
+ shrinker = shrinker_alloc(SHRINKER_NONSLAB,
+ "exfat-name-filter:%s", sb->s_id);
+ if (!shrinker) {
+ exfat_warn(sb, "failed to allocate name filter shrinker");
+ return;
+ }
+
+ shrinker->count_objects = exfat_name_filter_count_objects;
+ shrinker->scan_objects = exfat_name_filter_scan_objects;
+ shrinker->private_data = sbi;
+ shrinker_register(shrinker);
+ sbi->name_filter_shrinker = shrinker;
+}
+
+void exfat_name_filter_shrinker_unregister(struct super_block *sb)
+{
+ struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ struct shrinker *shrinker = sbi->name_filter_shrinker;
+
+ sbi->name_filter_shrinker = NULL;
+ shrinker_free(shrinker);
+
+ for (;;) {
+ struct exfat_inode_info *ei;
+ unsigned long *filter;
+
+ spin_lock(&sbi->name_filter_lock);
+ if (list_empty(&sbi->name_filter_lru)) {
+ spin_unlock(&sbi->name_filter_lock);
+ break;
+ }
+
+ ei = list_first_entry(&sbi->name_filter_lru,
+ struct exfat_inode_info,
+ name_filter_lru);
+ filter = exfat_name_filter_detach_locked(sbi, ei);
+ spin_unlock(&sbi->name_filter_lock);
+ kvfree(filter);
+ }
}
bool exfat_name_filter_maybe_contains(struct inode *inode,
@@ -108,6 +241,7 @@ bool exfat_name_filter_maybe_contains(struct inode *inode,
if (!ei->name_filter)
return true;
+ exfat_name_filter_touch(ei);
exfat_name_filter_indexes(inode->i_sb, name, indexes);
return test_bit(indexes[0], ei->name_filter) &&
test_bit(indexes[1], ei->name_filter) &&
@@ -123,6 +257,7 @@ void exfat_name_filter_add(struct inode *inode,
if (!ei->name_filter)
return;
+ exfat_name_filter_touch(ei);
exfat_name_filter_indexes(inode->i_sb, name, indexes);
__set_bit(indexes[0], ei->name_filter);
__set_bit(indexes[1], ei->name_filter);
@@ -145,7 +280,7 @@ static void exfat_build_name_filter(struct super_block *sb,
struct exfat_sb_info *sbi = EXFAT_SB(sb);
int i;
- if (ei->name_filter ||
+ if (!sbi->name_filter_shrinker || ei->name_filter ||
exfat_bytes_to_dentries(i_size_read(inode)) <
EXFAT_NAME_FILTER_MIN_DENTRIES)
return;
@@ -195,7 +330,11 @@ static void exfat_build_name_filter(struct super_block *sb,
}
complete:
+ spin_lock(&sbi->name_filter_lock);
ei->name_filter = filter;
+ list_add_tail(&ei->name_filter_lru, &sbi->name_filter_lru);
+ sbi->name_filter_count++;
+ spin_unlock(&sbi->name_filter_lock);
return;
abort:
kvfree(filter);
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index 3bbfa7092c0d..b3edb6fdfca6 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -14,6 +14,8 @@
#include <uapi/linux/exfat.h>
#include <linux/buffer_head.h>
+struct shrinker;
+
#define EXFAT_ROOT_INO 1
/*
@@ -263,6 +265,11 @@ struct exfat_sb_info {
spinlock_t inode_hash_lock;
struct hlist_head inode_hashtable[EXFAT_HASH_SIZE];
+ /* Protects name_filter_lru and name_filter_count. */
+ spinlock_t name_filter_lock;
+ struct list_head name_filter_lru;
+ unsigned long name_filter_count;
+ struct shrinker *name_filter_shrinker;
struct rcu_head rcu;
};
@@ -294,6 +301,7 @@ struct exfat_inode_info {
struct exfat_hint_femp hint_femp;
/* Complete, in-memory Bloom filter of directory names */
unsigned long *name_filter;
+ struct list_head name_filter_lru;
spinlock_t cache_lru_lock;
struct list_head cache_lru;
@@ -635,6 +643,8 @@ bool exfat_name_filter_maybe_contains(struct inode *inode,
void exfat_name_filter_add(struct inode *inode,
const struct exfat_uni_name *name);
void exfat_name_filter_free(struct inode *inode);
+void exfat_name_filter_shrinker_register(struct super_block *sb);
+void exfat_name_filter_shrinker_unregister(struct super_block *sb);
static inline int exfat_chain_advance(struct super_block *sb,
struct exfat_chain *chain, unsigned int step)
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 4924f0fad836..465e33c9ba49 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -46,6 +46,7 @@ static void exfat_put_super(struct super_block *sb)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
+ exfat_name_filter_shrinker_unregister(sb);
mutex_lock(&sbi->s_lock);
exfat_clear_volume_dirty(sb);
exfat_free_bitmap(sbi);
@@ -210,6 +211,7 @@ static struct inode *exfat_alloc_inode(struct super_block *sb)
return NULL;
ei->name_filter = NULL;
+ INIT_LIST_HEAD(&ei->name_filter_lru);
return &ei->vfs_inode;
}
@@ -746,6 +748,8 @@ static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
goto free_table;
}
+ exfat_name_filter_shrinker_register(sb);
+
return 0;
put_inode:
@@ -845,6 +849,10 @@ static int exfat_init_fs_context(struct fs_context *fc)
mutex_init(&sbi->s_lock);
mutex_init(&sbi->bitmap_lock);
+ spin_lock_init(&sbi->name_filter_lock);
+ INIT_LIST_HEAD(&sbi->name_filter_lru);
+ sbi->name_filter_count = 0;
+ sbi->name_filter_shrinker = NULL;
ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL,
DEFAULT_RATELIMIT_BURST);
--
2.34.1
prev parent reply other threads:[~2026-09-19 15:23 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-19 15:22 [PATCH v5 0/3] exfat: speed up file creation in large directories Yang Wen
2026-09-19 15:22 ` [PATCH v5 1/3] exfat: add a Bloom filter for negative name lookups Yang Wen
2026-09-19 15:22 ` [PATCH v5 2/3] exfat: retain the next empty directory entry hint Yang Wen
2026-09-19 15:22 ` Yang Wen [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260919152240.1507914-4-anmuxixixi@gmail.com \
--to=anmuxixixi@gmail.com \
--cc=chizhiling@163.com \
--cc=exfat@lists.linux.dev \
--cc=linkinjeon@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=sj1557.seo@samsung.com \
--cc=yuezhang.mo@sony.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®