From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj2-f13.google.com (mail-pj2-f13.google.com [74.125.227.141]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id A85A3363082 for ; Sat, 19 Sep 2026 15:23:26 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.141 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789831408; cv=none; b=PzQXGkkStK8Y3IXr1PK50JNh3VkIbelPePbdKbJlZD1CuW0lypD1l3AY3Gl16I2v3BMhpLrYOsIv0sShmJFkThzsX3GDye1hzjniM76N0LTq0+eYMsDqIYCE++Luf1f0kAhex0WV3Ko88KzrFR3lLdBqH8ac89wpmsSmypiFzsM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789831408; c=relaxed/simple; bh=Yqaq1Hk7u4nrzYHc+3CVcf4COPL1wBi/iltVI2RQc7w=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=C+3YkrKbORO6pvMfF+MwyyVD1KISpigOA/bvsvVkd9xspLsQz8FVKMxSi8BeDGpHj50mb5WvdFN5fIHDZ3YmSOT9oTYxMH4QJgPTFWQhFWBJ+AzRCbhNQCa+y0PVS4rhaOpzRNJtKNQ8sV92qr+l8SyN5H5OkDGWX4Msrjjqn2w= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=ECPgg0Ba; arc=none smtp.client-ip=74.125.227.141 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="ECPgg0Ba" Received: by mail-pj2-f13.google.com with SMTP id 98e67ed59e1d1-39b5b07ec78so1191730a91.3 for ; Sat, 19 Sep 2026 08:23:26 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1789831406; x=1790436206; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=qR79KzzQEHA1BCgom4FGsnXuKP2YSduNO1GGm9Xb7l8=; b=ECPgg0BaGm42QEN4JUQ5Oe0CGZ8IkK/Pb9i+pjmPfNu+4Es4Ddwca6MYPQy5RZUn2l 1jHh15WVKy9Y8aPg2BC7Ej7/NyBBLbixGvgaNc1mVkBh7OYbfrMIhBBHY41w+6Ka3eew sOGMh0t0F1gN9abo/BrjjExgmQ13Sb8Bj3qIAnlOyd/HhLrQZmUStD/eSZIT+FGGG4zh 98xh87wkcYwGprK+asgE0pHDTjHictQgzhGlWDt1BtA0wEPV4RiSEQL40FBuQ1zoXP6w cTOfASSjcl5QKsr2aGyqN07PVNR+EF/X838eoqGFjwRpk+7dlsPC7CvZ3hWwZg+qwmm9 cvPQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789831406; x=1790436206; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=qR79KzzQEHA1BCgom4FGsnXuKP2YSduNO1GGm9Xb7l8=; b=Fov1Ik4o9x6bMXwiOB/2UZGoUJUhTY3TvXzw6YckYVRs9+HMIWuMK2jxrl0TpHbWUI SLMXLK86OXn4CzFisTG0inwmDxuGJuPKfElAwKqrLinrPg6X05Q6+/tl1ZmgKB2n1Bun f5Pl2DBuwp+cF0FGU5sgP+EwTNHzDNQQ2fVP6C+9Iwxn8f+r0C/g7cfRq2tatw8xeD9c fqxCn55OyIuQYbm8nyLRRsqi46zmGKgLq6t8PzLxwA9Mv3IQFPDaJZwNSYK56XlgkRFu QzYwn7KHM+EjS7xmR/ysN3rWhiNuxnwbWLNUdempSucF3FS+n4CWsz8gaoEPF/DQ1ltV yY2A== X-Forwarded-Encrypted: i=1; AKwUvBwtoIV9DmpDCkgFd5X/jawMDQCYp2kXSx8Og5uJISMnYwYM9ndDQ+OwVDatJp42s1SICH1pjUxnLxe6IKA=@vger.kernel.org X-Gm-Message-State: AFuF++kkdzdR/8aS25oLznPOIrWTjiPOVgXM6x5yDxm7JincnIXU1HCg lK5EYg3HcqcgFiegBa1vCd/VZLjz/R4WOgrA893tApyEOgREVxy9R5lp X-Gm-Gg: AYBFou2U5k8cJioK32Yo3w+fVaU6ArDm4R/m4x+btgBA4/ES4/g9+oZWRYmekbQ5aHN yyBgIBNaSRrn6Yw3lfs6JFcYaD74DAvp+ds8iVxhm2zr3lP+5f/uR+FCCWR6o7xs94hwnCOpHAe jAqTLwjxFLyCsnx86gGGZ3uhxof7wyBppX4gkT8DtNk7x0eX+ajiuU9hZZtPWM4zE1Crp+JaEjZ e0pUUfLNq0Yi93ZSLms2r5G7arUrIGz+lUrEumDrrxdv6hLlfULT1TEq7qPTsk9aCoiMcPSo3QZ vH3coOijcGTqMLRVr4DOmNy4VirUfNfz/dOJ44E1j5YTeqZ+Uw0PzS9Vn0ToRq+AguzZan8hgBL 07ItFT42PkhDt0/g1oaR2jwQIX7GPyChInPxhgKQWiRVvqmUbE6b6p8H4C76MTtcIE4vFW9amSb 2ZfzHDegP4I5wKotbH9xoYu70hIjORSPz/Y2WD62PYA2dotjHu5MwOa2UT5CjKPIIlUbMuaRwtS PsIHm656aBUja4VNkHsUTI= X-Received: by 2002:a17:90b:548d:b0:398:c3ca:1ebc with SMTP id 98e67ed59e1d1-39e54d11d8dmr10521409a91.18.1789831405770; Sat, 19 Sep 2026 08:23:25 -0700 (PDT) Received: from localhost.localdomain ([47.100.192.162]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-3a0309d5760sm326230a91.4.2026.09.19.08.23.22 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 19 Sep 2026 08:23:24 -0700 (PDT) From: Yang Wen 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 Subject: [PATCH v5 3/3] exfat: reclaim name filters under memory pressure Date: Sat, 19 Sep 2026 23:22:40 +0800 Message-Id: <20260919152240.1507914-4-anmuxixixi@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260919152240.1507914-1-anmuxixixi@gmail.com> References: <20260919152240.1507914-1-anmuxixixi@gmail.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit 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 --- 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 #include #include +#include #include #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 #include +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