From nobody Thu Sep 24 12:53:21 2026 Received: from mail-pj2-f12.google.com (mail-pj2-f12.google.com [74.125.227.140]) (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 5275E4A5C24 for ; Wed, 23 Sep 2026 12:33:31 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790166817; cv=none; b=elDTcbV1ZAbAYl3Ar4QLLdjXrHAgD4Zrt4Pk51xuyXG920kS8Wrt9K5dbVAx4xxId27YBWDOZuwcSw+QODVLFyZpEGWjc6SOA1asGzOoiDmIaG9LbJLBC2jfKRqRR1foAg1DoWAcYExsqPEuyDT9AS48luBhxin0FkUNT2GM+cE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790166817; c=relaxed/simple; bh=NgUdDd1JzQo0Y023VbbGxYFxCUtQ+JWNda0iLPrCCiw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=r5M3v6p1wd9gtKSYDJOksRCY7hOIZGtS1BRxlNdPT0lK4nH18rtMmK9bKTZOV+51RYhbqjFOQ64+jZbY8asKeAJM+Xi6ZI6HbmJvVjywygMDBQBK2cBBz6qCW+CB7DtTTV8keUxa3kLFoY0QBbr2/6APS1IAKNVIIDZDBOMABgs= 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=j7kAGXGj; arc=none smtp.client-ip=74.125.227.140 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="j7kAGXGj" Received: by mail-pj2-f12.google.com with SMTP id d9443c01a7336-2d747ec6188so3636695ad.3 for ; Wed, 23 Sep 2026 05:33:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790166809; x=1790771609; 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=jESetB1H5u5xF2KQ3sWXmhFOEqlKjQmaiAkNv9ckpCg=; b=j7kAGXGjj4D/unWfUWqDCmRIhyUojYBO60i8Fx2Orjcs4j102KAlzBrbfCE0McVxtL bHly9O57cxzdFadPBYjrSlwIASpwu2lgtrZqHSArn7BEGrcqyeltqjnEC4A5JzScAnhA 2m46vYpX5KCjQnOznw5f38URTJ9tzrOhcFsiqwZgNwP1uOW8L6G/TXS8pAlZ+1+kJVTB KTaOvFaPB7Glz56Q7EKjNpryucyPTrLIA3vO7Ek0mEeCtz/HEP+sOeonEUYs469k+fqg Ad7tZ0Rdxk4+A5nxaNy6AbWzoYGjas98G/0Ux4iW9AxXbv2wRERtZ7juCvAbGwNIeT43 83bg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790166809; x=1790771609; 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=jESetB1H5u5xF2KQ3sWXmhFOEqlKjQmaiAkNv9ckpCg=; b=Ew+rHdcPOjW5zss4/PemensgoskOa+NZW0EFEjrMN5hDmoOSpIKFLAJnrzVe+BGe+h nx6gvnhwgv+BPMjcRhVy4MBTbBgnCzVTslBhGhpqlMW4G/TjAKYTPPFJdXSliUHt2M5j yJOj6H65J6HnB9/WUZCzIpBLRPaakZbwiyRNbzikVn8BCD60gQQPcw4nwsP/SV6MO2/w qCya69s7m4Y9bqHxorC6LBrL4m/6ZHc3/LXdetNjSUDhltRzk7uVNjoIgTy6moyRkKQk +Hvl05Ag4bKQMBila+wR19OIVE85Y72LTa/iFufn6CuaL3V4+uyehT8qqrsGFooFGs+z J0Kw== X-Forwarded-Encrypted: i=1; AKwUvBwzLXj9IYnBThSa1Q0ClJjxkZu38Jtf9JmHaEUETMhxwlzNaQiOfHwv4ZCCnf9VeXuYJy4Pu78/boHp0Ek=@vger.kernel.org X-Gm-Message-State: AFuF++nUpI8uOE0qM6Aj2Hklv4E0FmqsavLni/bpNLKjFHc/4OJoRjwy ax/RGjZ8a/SmrhLaJplN4yFadfy3JD0jNZtUjI/wyMMllkQqX/NN7fF8G5dDhIQR7AA= X-Gm-Gg: AYBFou01Yu5lm2RlAAyE45SiipjCdEPBUAoMwt+pyzgooGJDzjpUyIBR2yZnFyTi6iA WSGnE2uj7EAdesX2CWW2OjUnemyckUlUz7jhWbw9scc4+uUZtcJ/ZFSwV98j0p8W0N8LFKveiLR fMzObkwMxETwXNJtZ/nvYEnA94etOsdc0fp65KqukMr0+qOkLWrSF/tgOy3je+DBJApIZeA2BnK eChU7GgpcTZTFFMCIIoA3zTuMRcHHTQlRSQjHfvl/6ZCAlqVBzIpbIXtJ4wMaa0Cgug9zBkuskG VlpXmQ0KI+sSHHFYHRwCB0/tNlFwLjG5UXXgzB7lIGLueTk+wxFAqX8+1dSMBXM49Xr4k/ZcS24 ZsySKXWpjqHFiSus3Xj+JsYfGu3zCs46lAZra8Ywm0qSYox1x/YGXPSGhqN0H5W573juMNX3oZ+ bx8/xlgNYOao0ylMAkM6y4mtaSGHBJlKfyLlqVj/eKi6WpBlZUprW7J4VdNlu1Vrwu45bOxzNJv OQbSs5MpXP+ X-Received: by 2002:a17:902:fc85:b0:2dd:c053:ec74 with SMTP id d9443c01a7336-2df69dc7525mr21266475ad.39.1790166808848; Wed, 23 Sep 2026 05:33:28 -0700 (PDT) Received: from localhost.localdomain ([47.100.192.162]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2df6a5a6d35sm9510475ad.19.2026.09.23.05.33.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Sep 2026 05:33:28 -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 v6 1/3] exfat: add a Bloom filter for negative name lookups Date: Wed, 23 Sep 2026 20:32:58 +0800 Message-Id: <20260923123300.1626723-2-anmuxixixi@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260923123300.1626723-1-anmuxixixi@gmail.com> References: <20260923123300.1626723-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: quoted-printable Content-Type: text/plain; charset="utf-8" Negative name lookups scan a directory from the beginning. Repeating this scan before creating each file makes bulk creation approach O(N^2). Add a 64 KiB per-directory Bloom filter. Build it lazily after a directory reaches 1024 on-disk entries. A definite miss skips the directory scan, while a possible match follows the normal lookup path so hash collisions cannot affect correctness. Invalidate the filter if a new entry set cannot be committed. Do the same when writing a renamed or moved entry reports an error, because the entry may already have reached disk. Signed-off-by: Yang Wen --- fs/exfat/dir.c | 140 ++++++++++++++++++++++++++++++++++++++++++++ fs/exfat/exfat_fs.h | 13 ++++ fs/exfat/inode.c | 1 + fs/exfat/namei.c | 9 ++- fs/exfat/super.c | 1 + 5 files changed, 161 insertions(+), 3 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 46514b13bebd..0c5648e23e20 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -8,6 +8,8 @@ #include #include #include +#include +#include =20 #include "exfat_raw.h" #include "exfat_fs.h" @@ -65,6 +67,140 @@ static int exfat_get_uniname_from_ext_entry(struct supe= r_block *sb, return 0; } =20 +static u32 exfat_name_filter_hash(struct super_block *sb, + const struct exfat_uni_name *name) +{ + unsigned long hash =3D init_name_hash(NULL); + int i; + + for (i =3D 0; i < name->name_len; i++) + hash =3D partial_name_hash(exfat_toupper(sb, name->name[i]), hash); + + return end_name_hash(hash); +} + +static void exfat_name_filter_indexes(struct super_block *sb, + const struct exfat_uni_name *name, + unsigned int indexes[3]) +{ + u32 hash =3D exfat_name_filter_hash(sb, name); + + indexes[0] =3D hash_32(hash, EXFAT_NAME_FILTER_ORDER); + indexes[1] =3D hash_32(hash ^ 0x9e3779b9U, EXFAT_NAME_FILTER_ORDER); + indexes[2] =3D hash_32(rol32(hash, 16) ^ 0x85ebca6bU, + EXFAT_NAME_FILTER_ORDER); +} + +void exfat_name_filter_free(struct inode *inode) +{ + struct exfat_inode_info *ei =3D EXFAT_I(inode); + + kvfree(ei->name_filter); + ei->name_filter =3D NULL; +} + +bool exfat_name_filter_maybe_contains(struct inode *inode, + const struct exfat_uni_name *name) +{ + struct exfat_inode_info *ei =3D EXFAT_I(inode); + unsigned int indexes[3]; + + if (!ei->name_filter) + return true; + + exfat_name_filter_indexes(inode->i_sb, name, indexes); + return test_bit(indexes[0], ei->name_filter) && + test_bit(indexes[1], ei->name_filter) && + test_bit(indexes[2], ei->name_filter); +} + +void exfat_name_filter_add(struct inode *inode, + const struct exfat_uni_name *name) +{ + struct exfat_inode_info *ei =3D EXFAT_I(inode); + unsigned int indexes[3]; + + if (!ei->name_filter) + return; + + exfat_name_filter_indexes(inode->i_sb, name, indexes); + __set_bit(indexes[0], ei->name_filter); + __set_bit(indexes[1], ei->name_filter); + __set_bit(indexes[2], ei->name_filter); +} + +/* + * Build a complete filter only after a directory becomes large enough for + * repeated negative linear lookups to matter. A filter hit is never trust= ed: + * it only allows definite misses to skip the on-disk scan. + */ +static void exfat_build_name_filter(struct super_block *sb, + struct exfat_inode_info *ei, + struct exfat_chain *p_dir) +{ + unsigned long *filter; + struct exfat_chain clu; + unsigned int clu_count =3D 0; + struct inode *inode =3D &ei->vfs_inode; + struct exfat_sb_info *sbi =3D EXFAT_SB(sb); + int i; + + if (ei->name_filter || + exfat_bytes_to_dentries(i_size_read(inode)) < + EXFAT_NAME_FILTER_MIN_DENTRIES) + return; + + filter =3D kvzalloc(EXFAT_NAME_FILTER_BYTES, GFP_NOFS); + if (!filter) + return; + + exfat_chain_dup(&clu, p_dir); + while (clu.dir !=3D EXFAT_EOF_CLUSTER) { + for (i =3D 0; i < sbi->dentries_per_clu; i++) { + struct exfat_uni_name name =3D { }; + struct exfat_dentry *ep; + struct buffer_head *bh; + unsigned int type; + unsigned int indexes[3]; + int len; + + ep =3D exfat_get_dentry(sb, &clu, i, &bh); + if (!ep) + goto abort; + + type =3D exfat_get_entry_type(ep); + brelse(bh); + if (type =3D=3D TYPE_UNUSED) + goto complete; + if (type !=3D TYPE_FILE && type !=3D TYPE_DIR) + continue; + + if (exfat_get_uniname_from_ext_entry(sb, &clu, i, name.name)) + goto abort; + for (len =3D 0; len <=3D MAX_NAME_LENGTH && name.name[len]; len++) + ; + if (!len || len > MAX_NAME_LENGTH) + goto abort; + name.name_len =3D len; + exfat_name_filter_indexes(sb, &name, indexes); + __set_bit(indexes[0], filter); + __set_bit(indexes[1], filter); + __set_bit(indexes[2], filter); + } + + if (exfat_chain_advance(sb, &clu, 1)) + goto abort; + if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi))) + goto abort; + } + +complete: + ei->name_filter =3D filter; + return; +abort: + kvfree(filter); +} + /* read a directory entry from the opened directory */ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_d= ir_entry *dir_entry) { @@ -1035,6 +1171,8 @@ int exfat_find_dir_entry(struct super_block *sb, stru= ct exfat_inode_info *ei, =20 if (num_entries < 0) return num_entries; + if (!exfat_name_filter_maybe_contains(&ei->vfs_inode, p_uniname)) + return -ENOENT; =20 dentries_per_clu =3D sbi->dentries_per_clu; =20 @@ -1196,6 +1334,8 @@ int exfat_find_dir_entry(struct super_block *sb, stru= ct exfat_inode_info *ei, ei->hint_femp.count =3D 0; } =20 + exfat_build_name_filter(sb, ei, p_dir); + /* initialized hint_stat */ hint_stat->clu =3D p_dir->dir; hint_stat->eidx =3D 0; diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 41a2c7dfc479..899b276f105b 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -120,6 +120,11 @@ enum { #define DIR_CACHE_SIZE \ (DIV_ROUND_UP(ES_MAX_ENTRY_NUM << DENTRY_SIZE_BITS, SECTOR_SIZE) + 1) =20 +#define EXFAT_NAME_FILTER_ORDER 19 +#define EXFAT_NAME_FILTER_BITS BIT(EXFAT_NAME_FILTER_ORDER) +#define EXFAT_NAME_FILTER_BYTES (EXFAT_NAME_FILTER_BITS >> 3) +#define EXFAT_NAME_FILTER_MIN_DENTRIES 1024 + /* Superblock flags */ #define EXFAT_FLAGS_SHUTDOWN 1 =20 @@ -285,6 +290,8 @@ struct exfat_inode_info { struct exfat_hint hint_stat; /* hint for first empty entry */ struct exfat_hint_femp hint_femp; + /* Complete, in-memory Bloom filter of directory names */ + unsigned long *name_filter; =20 spinlock_t cache_lru_lock; struct list_head cache_lru; @@ -620,6 +627,12 @@ int exfat_read_volume_label(struct super_block *sb, int exfat_write_volume_label(struct super_block *sb, struct exfat_uni_name *label); =20 +bool exfat_name_filter_maybe_contains(struct inode *inode, + const struct exfat_uni_name *name); +void exfat_name_filter_add(struct inode *inode, + const struct exfat_uni_name *name); +void exfat_name_filter_free(struct inode *inode); + static inline int exfat_chain_advance(struct super_block *sb, struct exfat_chain *chain, unsigned int step) { diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index ccd13630187e..0d0c6f817775 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -445,6 +445,7 @@ struct inode *exfat_build_inode(struct super_block *sb, void exfat_evict_inode(struct inode *inode) { truncate_inode_pages_final(&inode->i_data); + exfat_name_filter_free(inode); =20 if (!inode->i_nlink) { i_size_write(inode, 0); diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 3c5746fc57d9..84045b704303 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -512,6 +512,7 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, if (ret) { int cleanup_ret; =20 + exfat_name_filter_free(inode); cleanup_ret =3D exfat_get_dentry_set(&es, sb, &info->dir, dentry, ES_ALL_ENTRIES); if (!cleanup_ret) { @@ -526,6 +527,7 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, } =20 info->entry =3D dentry; + exfat_name_filter_add(inode, &uniname); info->flags =3D ALLOC_NO_FAT_CHAIN; info->type =3D type; =20 @@ -803,7 +805,6 @@ static int exfat_unlink(struct inode *dir, struct dentr= y *dentry) =20 /* update the directory entry */ exfat_remove_entries(inode, &es, ES_IDX_FILE, true); - err =3D exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); if (err) goto unlock; @@ -958,7 +959,6 @@ static int exfat_rmdir(struct inode *dir, struct dentry= *dentry) exfat_set_volume_dirty(sb); =20 exfat_remove_entries(inode, &es, ES_IDX_FILE, true); - err =3D exfat_put_dentry_set(&es, IS_DIRSYNC(dir)); if (err) goto unlock; @@ -1216,6 +1216,10 @@ static int __exfat_rename(struct inode *old_parent_i= node, ret =3D exfat_rename_file(new_parent_inode, &uni_name, ei); else ret =3D exfat_move_file(new_parent_inode, &uni_name, ei); + if (!ret) + exfat_name_filter_add(new_parent_inode, &uni_name); + else + exfat_name_filter_free(new_parent_inode); =20 if (!ret && new_inode) { struct exfat_entry_set_cache es; @@ -1228,7 +1232,6 @@ static int __exfat_rename(struct inode *old_parent_in= ode, } =20 exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true); - ret =3D exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode)); if (ret) goto del_out; diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 4943cef97741..4924f0fad836 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -209,6 +209,7 @@ static struct inode *exfat_alloc_inode(struct super_blo= ck *sb) if (!ei) return NULL; =20 + ei->name_filter =3D NULL; return &ei->vfs_inode; } =20 --=20 2.34.1 From nobody Thu Sep 24 12:53:21 2026 Received: from mail-pj2-f36.google.com (mail-pj2-f36.google.com [74.125.227.164]) (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 6C22D49552B for ; Wed, 23 Sep 2026 12:33:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.164 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790166832; cv=none; b=P8SRnkB+ELJot87ltvCbIZnCHK/2nqFlos0Ap8+LyjRPaQoMh+NI7aC9ShN/E19CSULwgNGupkd8otzrv+LE0OUU/ggqtrjvPZBdL9gouLI8sxdVv43QW9nsaQXe+RLa8m0c3/hIghyp32/QQYAT72HKnFE/AaoXctTncggaT9U= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790166832; c=relaxed/simple; bh=6l9516gS2SUOGkjg72Rqa9sj7iRiVKbXCTIgAWWsGz4=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=L+yq9BdF26Sz1CHwZIKyqCKWOnfR7BAltqUsDcajMZEDLZooNkcik9980qVCE2Zrbrh3FJ2A6y8P8Muzf//Dk1bqMzNbcJCNSO+EWgYnydgvGZqMKNoKCAGCIvVpSQuhBKH3rMH+c7GQ8AY52F0xIuGiHT6M40KmR99dt5npqXw= 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=OM16ftu7; arc=none smtp.client-ip=74.125.227.164 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="OM16ftu7" Received: by mail-pj2-f36.google.com with SMTP id d9443c01a7336-2d8fdc579daso5400545ad.1 for ; Wed, 23 Sep 2026 05:33:46 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790166825; x=1790771625; 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=AvsVBGUxkg9/q08FYuaFBexA0miT2gtZIuRNd0MYRn4=; b=OM16ftu7cJXsa2u9+xnr3DFIGStUzfYXJCFf4MMH9NyGYnDBQiOwzV4B7LEEKvEh4y 8hUxkzAf5VslC2dxHHon8nH0aC+IzumHsdkAezGVMRmIjaoJhajVqvDvJv1Mw93vmNv2 ZZZZR3GW0nGHJy9GkZ/dPaqe49+28PI8PqwgpMEX0yzTZHP74xmLU88MNKZNzWzrnbDZ KomJDx597xfv4MKpG9v7P03EVKw8CR5192OoNrzk0u8w68sdSmNWYjKYAbGrCyosNHUJ QtFfyYvMbNUgMTzNIiyHyOuwsFd9Qu2/LY2XVr6tT1J2cgJpfZcD9ba37qEH4JGrGump dJQQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790166825; x=1790771625; 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=AvsVBGUxkg9/q08FYuaFBexA0miT2gtZIuRNd0MYRn4=; b=eM2inFZYOIhhrkLo6bT3sCgQHID82qJzbXDsGepIVjLtEtT1KOwcqSm1CxrFeR8f7l RcOUVStJ6+yHw54xZiROSrpU1aG8Lu0YGa9vLFL0javioCFfLhjlCD83wQK6yKASxkXV VE3SP69BuxrEclf3nsk+6/lUdf31yEXt77w+uSUHZcCV6PsxVAN7s7UDub1kcQrX8bs6 t5OWGckfds4BpPQZdFBQU9Hie6e/itFUOej8b6bE1b/+nbIwf74WabYLMQVfxJbaexjS S7hk6CBl6cZrdZvWpI+DmjzxD8kCjyVRHk5Do3Rz5Y7K8ptN4WoLsxjHezw3vxi2S6Ik z7nQ== X-Forwarded-Encrypted: i=1; AKwUvBx5dzU99nxUXdBeX9rr3hybmmgaT20W/UXmC5E5qifBJnCHC49qyODi7FnoqH5fHkRU+EimBZbq/TS96A8=@vger.kernel.org X-Gm-Message-State: AFuF++nwAs07SeBucx1eFzvG39HPLOVQzBTQmzBoq/5lOWO+jydvISv4 61jY2sgoiEE6Wcq3USxc2Hf07na8Ew9J8P69W+/RCrbrg799w03Nd9DN X-Gm-Gg: AYBFou2enYpWfIBXjoNeSFnE9CsD031OqKS3O1eb17qg9ANhBcJo7qeIf7Vse37Is1D c9rH4fp/2gWevHnzGdW+rTgTr9dflCcYAk+vHBJK365reRO0n7CMyqaYdrBChKHBoCWwNfaXgeG xJmJPESxWr/MN7qauQeYbfBQmLPM9hX4DgPWD8CBdgo/rWUAMQnIYWLz7l/hrpil8p5skb8R7OT JEe851lhK7aXc7FmnT5iWP95tY0vOiXPPeBY+R85I0pOaDGPEZu/kime+Cg8WvmlawhDuMvwM4W hF/ySnKvICbFDPf+raL/bJLvD4zEk7kJ+0qpZuEWKE7BMsZsX8HSAmdh7327tr5faJ/8le1FSt7 bt8QaR1s2BKcGarpq3UHODTTkzdYJR43lVwfR4W9Lmfik/BQ9EoTJmsgHc/PXPl05iH+hTa3xSe QlyR9IWyNMxkXx+32DrN6AErVfQradIoGUT8IrZNZjKkQOWRNxKg4gUVCMI8176U1xkdIGnGQmc tQkgv+ogOej X-Received: by 2002:a17:902:cecf:b0:2dd:c100:312e with SMTP id d9443c01a7336-2df69dd595amr21612535ad.42.1790166824794; Wed, 23 Sep 2026 05:33:44 -0700 (PDT) Received: from localhost.localdomain ([47.100.192.162]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2df6a5a6d35sm9510475ad.19.2026.09.23.05.33.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Sep 2026 05:33:44 -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 v6 2/3] exfat: retain the next empty directory entry hint Date: Wed, 23 Sep 2026 20:32:59 +0800 Message-Id: <20260923123300.1626723-3-anmuxixixi@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260923123300.1626723-1-anmuxixixi@gmail.com> References: <20260923123300.1626723-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: quoted-printable Content-Type: text/plain; charset="utf-8" After a Bloom filter miss skips the name scan, empty-entry allocation can still rescan the directory from the beginning. Calculate the next empty-entry hint when a free entry set is found, but publish it only after the entry set is committed. Record the minimum entry-set size for which a saved hint is valid, so a later shorter name rescans earlier entries and can reuse smaller holes. Retain the hint while the name filter is active and invalidate it whenever create rollback, unlink, rmdir, rename, or move can free entries. This preserves the fast append path during bulk creation without allowing stale hints to skip reusable directory entries. Signed-off-by: Yang Wen --- fs/exfat/dir.c | 14 +++++++- fs/exfat/exfat_fs.h | 5 ++- fs/exfat/namei.c | 84 +++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 0c5648e23e20..08b7a50de871 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -1109,6 +1109,7 @@ static inline void exfat_reset_empty_hint(struct exfa= t_hint_femp *hint_femp) { hint_femp->eidx =3D EXFAT_HINT_NONE; hint_femp->count =3D 0; + hint_femp->min_entries =3D 0; } =20 static inline void exfat_set_empty_hint(struct exfat_inode_info *ei, @@ -1122,6 +1123,7 @@ static inline void exfat_set_empty_hint(struct exfat_= inode_info *ei, if (candi_empty->count =3D=3D 0) { candi_empty->cur =3D *clu; candi_empty->eidx =3D dentry; + candi_empty->min_entries =3D num_entries; } =20 if (entry_type =3D=3D TYPE_UNUSED) @@ -1332,6 +1334,7 @@ int exfat_find_dir_entry(struct super_block *sb, stru= ct exfat_inode_info *ei, ei->hint_femp.cur.dir =3D EXFAT_EOF_CLUSTER; ei->hint_femp.eidx =3D p_dir->size * dentries_per_clu; ei->hint_femp.count =3D 0; + ei->hint_femp.min_entries =3D num_entries; } =20 exfat_build_name_filter(sb, ei, p_dir); @@ -1431,6 +1434,7 @@ static int exfat_get_volume_label_dentry(struct super= _block *sb, hint_femp.cur =3D clu; hint_femp.eidx =3D dentry; hint_femp.count =3D 1; + hint_femp.min_entries =3D 1; } } =20 @@ -1464,6 +1468,7 @@ static int exfat_get_volume_label_dentry(struct super= _block *sb, hint_femp.cur.dir =3D EXFAT_EOF_CLUSTER; hint_femp.eidx =3D dentry; hint_femp.count =3D 0; + hint_femp.min_entries =3D 1; } =20 ei->hint_femp =3D hint_femp; @@ -1518,7 +1523,9 @@ int exfat_write_volume_label(struct super_block *sb, struct inode *root_inode =3D sb->s_root->d_inode; struct exfat_entry_set_cache es; struct exfat_chain clu; + struct exfat_hint_femp next_hint; struct exfat_dentry *ep; + bool entry_allocated =3D false; =20 if (label->name_len > EXFAT_VOLUME_LABEL_LEN) return -EINVAL; @@ -1533,7 +1540,10 @@ int exfat_write_volume_label(struct super_block *sb, goto unlock; } =20 - ret =3D exfat_find_empty_entry(root_inode, &clu, 1, &es); + ret =3D exfat_find_empty_entry(root_inode, &clu, 1, &es, + &next_hint); + if (ret >=3D 0) + entry_allocated =3D true; } =20 if (ret < 0) @@ -1558,6 +1568,8 @@ int exfat_write_volume_label(struct super_block *sb, es.modified =3D true; =20 ret =3D exfat_put_dentry_set(&es, IS_DIRSYNC(root_inode)); + if (!ret && entry_allocated) + EXFAT_I(root_inode)->hint_femp =3D next_hint; =20 unlock: mutex_unlock(&sbi->s_lock); diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 899b276f105b..3bbfa7092c0d 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -154,6 +154,8 @@ struct exfat_hint_femp { int eidx; /* count of continuous empty entry */ int count; + /* minimum entry-set size for which this hint is safe */ + int min_entries; /* the cluster that first empty slot exists in */ struct exfat_chain cur; }; @@ -581,7 +583,8 @@ extern const struct dentry_operations exfat_dentry_ops; extern const struct dentry_operations exfat_utf8_dentry_ops; int exfat_find_empty_entry(struct inode *inode, struct exfat_chain *p_dir, int num_entries, - struct exfat_entry_set_cache *es); + struct exfat_entry_set_cache *es, + struct exfat_hint_femp *next_hint); =20 /* cache.c */ int exfat_cache_init(void); diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 84045b704303..0ab018b2e24a 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -199,6 +199,54 @@ const struct dentry_operations exfat_utf8_dentry_ops = =3D { .d_compare =3D exfat_utf8_d_cmp, }; =20 +static void exfat_set_next_empty_hint(struct inode *inode, + struct exfat_chain *p_dir, int dentry, + int num_entries, + struct exfat_entry_set_cache *es, + struct exfat_hint_femp *hint_femp) +{ + struct exfat_sb_info *sbi =3D EXFAT_SB(inode->i_sb); + unsigned int next =3D dentry + num_entries; + unsigned int total =3D exfat_cluster_to_dentries(sbi, p_dir->size); + struct exfat_chain cur; + + hint_femp->min_entries =3D num_entries; + + if (next >=3D total) { + exfat_chain_set(&hint_femp->cur, EXFAT_EOF_CLUSTER, 0, + p_dir->flags); + hint_femp->eidx =3D total; + hint_femp->count =3D 0; + return; + } + + cur.dir =3D exfat_sector_to_cluster(sbi, + es->bh[es->num_bh - 1]->b_blocknr); + cur.flags =3D p_dir->flags; + cur.size =3D p_dir->size - exfat_dentries_to_cluster(sbi, next); + if (!(next & (sbi->dentries_per_clu - 1))) { + cur.size++; + if (exfat_chain_advance(inode->i_sb, &cur, 1)) { + hint_femp->eidx =3D EXFAT_HINT_NONE; + hint_femp->count =3D 0; + return; + } + } + + hint_femp->cur =3D cur; + hint_femp->eidx =3D next; + hint_femp->count =3D 0; +} + +static void exfat_invalidate_empty_hint(struct inode *inode) +{ + struct exfat_inode_info *ei =3D EXFAT_I(inode); + + ei->hint_femp.eidx =3D EXFAT_HINT_NONE; + ei->hint_femp.count =3D 0; + ei->hint_femp.min_entries =3D 0; +} + /* search EMPTY CONTINUOUS "num_entries" entries */ static int exfat_search_empty_slot(struct super_block *sb, struct exfat_hint_femp *hint_femp, struct exfat_chain *p_dir, @@ -295,7 +343,8 @@ static int exfat_check_max_dentries(struct inode *inode) */ int exfat_find_empty_entry(struct inode *inode, struct exfat_chain *p_dir, int num_entries, - struct exfat_entry_set_cache *es) + struct exfat_entry_set_cache *es, + struct exfat_hint_femp *next_hint) { int dentry, ret; unsigned int last_clu; @@ -309,8 +358,9 @@ int exfat_find_empty_entry(struct inode *inode, hint_femp.eidx =3D EXFAT_HINT_NONE; =20 if (ei->hint_femp.eidx !=3D EXFAT_HINT_NONE) { - hint_femp =3D ei->hint_femp; - ei->hint_femp.eidx =3D EXFAT_HINT_NONE; + if (num_entries >=3D ei->hint_femp.min_entries) + hint_femp =3D ei->hint_femp; + exfat_invalidate_empty_hint(inode); } =20 exfat_chain_set(p_dir, ei->start_clu, @@ -385,6 +435,9 @@ int exfat_find_empty_entry(struct inode *inode, inode->i_blocks +=3D sbi->cluster_size >> 9; } =20 + exfat_set_next_empty_hint(inode, p_dir, dentry, num_entries, es, + next_hint); + p_dir->dir =3D exfat_sector_to_cluster(sbi, es->bh[0]->b_blocknr); p_dir->size -=3D dentry >> sbi->dentries_per_clu_bits; =20 @@ -469,6 +522,7 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, struct exfat_chain clu; struct timespec64 ts =3D current_time(inode); struct exfat_entry_set_cache es; + struct exfat_hint_femp next_hint; int clu_size =3D 0; unsigned int start_clu =3D EXFAT_FREE_CLUSTER; bool dir_allocated =3D false; @@ -484,7 +538,8 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, } =20 /* exfat_find_empty_entry must be called before alloc_cluster() */ - dentry =3D exfat_find_empty_entry(inode, &info->dir, num_entries, &es); + dentry =3D exfat_find_empty_entry(inode, &info->dir, num_entries, &es, + &next_hint); if (dentry < 0) { ret =3D dentry; /* -EIO or -ENOSPC */ goto out; @@ -517,6 +572,7 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, dentry, ES_ALL_ENTRIES); if (!cleanup_ret) { exfat_remove_entries(inode, &es, ES_IDX_FILE, false); + exfat_invalidate_empty_hint(inode); cleanup_ret =3D exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); } @@ -525,6 +581,7 @@ static int exfat_add_entry(struct inode *inode, const c= har *path, exfat_free_cluster(inode, &clu); goto out; } + EXFAT_I(inode)->hint_femp =3D next_hint; =20 info->entry =3D dentry; exfat_name_filter_add(inode, &uniname); @@ -629,7 +686,8 @@ static int exfat_find(struct inode *dir, const struct q= str *qname, ei->hint_stat.clu =3D cdir.dir; ei->hint_stat.eidx =3D 0; ei->version =3D (inode_peek_iversion_raw(dir) & 0xffffffff); - ei->hint_femp.eidx =3D EXFAT_HINT_NONE; + if (!ei->name_filter) + ei->hint_femp.eidx =3D EXFAT_HINT_NONE; } =20 /* search the file name for directories */ @@ -805,6 +863,8 @@ static int exfat_unlink(struct inode *dir, struct dentr= y *dentry) =20 /* update the directory entry */ exfat_remove_entries(inode, &es, ES_IDX_FILE, true); + exfat_invalidate_empty_hint(dir); + err =3D exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); if (err) goto unlock; @@ -959,6 +1019,8 @@ static int exfat_rmdir(struct inode *dir, struct dentr= y *dentry) exfat_set_volume_dirty(sb); =20 exfat_remove_entries(inode, &es, ES_IDX_FILE, true); + exfat_invalidate_empty_hint(dir); + err =3D exfat_put_dentry_set(&es, IS_DIRSYNC(dir)); if (err) goto unlock; @@ -1033,9 +1095,10 @@ static int exfat_rename_file(struct inode *parent_in= ode, if (old_es.num_entries < num_total_entries) { int newentry; struct exfat_chain dir; + struct exfat_hint_femp next_hint; =20 newentry =3D exfat_find_empty_entry(parent_inode, &dir, - num_total_entries, &new_es); + num_total_entries, &new_es, &next_hint); if (newentry < 0) { ret =3D newentry; /* -EIO or -ENOSPC */ goto put_old_es; @@ -1067,6 +1130,7 @@ static int exfat_rename_file(struct inode *parent_ino= de, } goto put_old_es; } + EXFAT_I(parent_inode)->hint_femp =3D next_hint; =20 exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false); ei->dir =3D dir; @@ -1094,6 +1158,7 @@ static int exfat_move_file(struct inode *parent_inode, struct exfat_dentry *epmov, *epnew; struct exfat_entry_set_cache mov_es, new_es; struct exfat_chain newdir; + struct exfat_hint_femp next_hint; unsigned int num_extra_entries, num_total_entries; =20 num_new_entries =3D exfat_calc_num_entries(p_uniname); @@ -1111,7 +1176,7 @@ static int exfat_move_file(struct inode *parent_inode, num_total_entries =3D num_new_entries + num_extra_entries; =20 newentry =3D exfat_find_empty_entry(parent_inode, &newdir, - num_total_entries, &new_es); + num_total_entries, &new_es, &next_hint); if (newentry < 0) { ret =3D newentry; /* -EIO or -ENOSPC */ goto put_mov_es; @@ -1140,10 +1205,12 @@ static int exfat_move_file(struct inode *parent_ino= de, ES_ALL_ENTRIES)) { exfat_remove_entries(parent_inode, &new_es, ES_IDX_FILE, false); + exfat_invalidate_empty_hint(parent_inode); exfat_put_dentry_set(&new_es, false); } goto put_mov_es; } + EXFAT_I(parent_inode)->hint_femp =3D next_hint; =20 exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false); =20 @@ -1216,6 +1283,7 @@ static int __exfat_rename(struct inode *old_parent_in= ode, ret =3D exfat_rename_file(new_parent_inode, &uni_name, ei); else ret =3D exfat_move_file(new_parent_inode, &uni_name, ei); + exfat_invalidate_empty_hint(old_parent_inode); if (!ret) exfat_name_filter_add(new_parent_inode, &uni_name); else @@ -1232,6 +1300,8 @@ static int __exfat_rename(struct inode *old_parent_in= ode, } =20 exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true); + exfat_invalidate_empty_hint(new_parent_inode); + ret =3D exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode)); if (ret) goto del_out; --=20 2.34.1 From nobody Thu Sep 24 12:53:21 2026 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 640FE4E323D for ; Wed, 23 Sep 2026 12:33:54 +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=1790166837; cv=none; b=QNkHixX3UvkPV8YW9VPZWm2cC27hYXo9I1iEO33WdyPdpDFEYGVMaYz+IqgOJlJ1UErI/QmFC62qrYMohNTEqJTk584EqP0UszyRSXdfmWu4CYfK0fZxdoN144gzlbkpBFVsCO5UOBtPxe3dXCVZ3Uw8XITT4+SH37fgXmSXUkI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790166837; c=relaxed/simple; bh=Yqaq1Hk7u4nrzYHc+3CVcf4COPL1wBi/iltVI2RQc7w=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=FJTrATdthbGGAfVe44HimqQtswmtMKmvE8YhtVDIqPBeHiJztQffl3LHt54UdBKhHKY/nORnRLNTDkNjOShKHD2iNiPrhtyrzG0CQtY7DRX4Fjawz1O144QkvDqRMEpVbVLtKDDS3PXsthaGFfIC3fpdhdUn9eYv2GKPPmJR4Oo= 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=k99u3pUO; 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="k99u3pUO" Received: by mail-pj2-f13.google.com with SMTP id d9443c01a7336-2d747f05ffdso3694165ad.1 for ; Wed, 23 Sep 2026 05:33:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790166833; x=1790771633; 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=k99u3pUOQ1Fq5cHzCmeiYmpYwizrheSFhtStDmlYnyqbd2qF7icoG8Nfr6l+lfSKon jQ77hGQ85HbLNEM18pGONYECi7Ni2A+n9xPUAjaAcBWrM4WjNEgddAtyTsvQPzW7v7kD 2XvPYHFSe8mm1R6/b39TEUGq+OPuWR3ItcLDU5qGuWTNLpcf/0hRQEJ0XSa71Y4VGtJH UiMzRXlHHr0ua4gWptvd3ujHzDYexSJCzbrfRJY/MR8cgKMIpOhpnn7+zfOzCAy3osio nWX9K6jhG891N2NkFrTuglAxjetiDOo7kKpxYtOp5tt5qn7Zwx8cLP9mgracwyQIykj1 GJFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790166833; x=1790771633; 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=W1Lr0RaGaSDvI4024uJW7m1WrxVUw3bQsMZBM7qTT0o0DwmJRQLzBIOiUcclF4W2ji v8Lr5pBl/2cWp6LSWvwX2Zz6esXeGgH0tiPVUz6CgCz/7FJYEC4Qjh1a5chzIKRtMROf lpwaaClOaGEmaE2HtZudIyz3lL7r0kJCrA7sl2bOh08txoJfsBE6j7OqFXRcIcIzBbz6 VBx4X39qWbW3Ma2DquyEx843rdPY4PJoKYIhvg3+tDE+8O1SMKk4c7j+8IxgeNPzFYLi NB3l7bWsw+Zr+jLjhSkLtuaxyHsRGuEzM+1jIcpJgfynE3spkgaBsfyF1QsxD+58Ot3Q 8cXA== X-Forwarded-Encrypted: i=1; AKwUvBxKn+tLPD4fTBJKm+Nnm/WZsiBb2sivAedg7FTC5evWz5JANBki7Jwr6zot9whZInISxuFtW35DUu7K+b8=@vger.kernel.org X-Gm-Message-State: AFuF++nYLFMog8wNeDo0Q0fBsayEbQyz86/OxAvn84erk/BK4mkZNrKl 538K1gyNsWGzJUxpjqfFIEwIjRDlRcaulDzeEP8bBwbU5rT1wB20zPHi X-Gm-Gg: AYBFou3apLALSmTDqwrHQ/JVq5aJP1vqubT4UVCySmWTAaN+23SIHRwMHDyAoNeJlPo zcErBo511em3creP0U+3Cz9ElUgnvkPQ2ANFH/2Qz2lOyUCnH4PaboDar43GLUzKIZA+WnMoV+G K+izu53/4Ijj5xrU7V4lxoZ2DyPvsNcbQsn0ghfX9/KSPgjladzHPRbfTFSTiep1JzkOYimMyHo HsYleslpw94BlaGtSWlj+ubuvCPkZUD2ChYC8UWfSlxqKd6vlTSiaCEc9WqqV9NwTcnrzA0x8KK KpBj3RzdUzIgwmtcQrVRbUrz0BmMHm3hGA074Xf1gEqRt/owdypKV78Xtelec3pSrLy8VeZP2tC jPpJdtEBdd7Bf52sQqA4weCpkDRyiKUD8jWW2LR1JzDY4mnFTvm7XqWjRdFnWufF1FjkQUtMAeZ gjlh6bhzwYkIU//8RW1Xvtr/W84ZT2DSGYeFLylOXSHpqaeI4GfTI4a7by9G2GyehhMYOIGzsDP qJECaJz7sVJ X-Received: by 2002:a17:903:2f8c:b0:2dd:c053:e0f8 with SMTP id d9443c01a7336-2df69de16b5mr21644485ad.39.1790166831747; Wed, 23 Sep 2026 05:33:51 -0700 (PDT) Received: from localhost.localdomain ([47.100.192.162]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2df6a5a6d35sm9510475ad.19.2026.09.23.05.33.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 23 Sep 2026 05:33:51 -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 v6 3/3] exfat: reclaim name filters under memory pressure Date: Wed, 23 Sep 2026 20:33:00 +0800 Message-Id: <20260923123300.1626723-4-anmuxixixi@gmail.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260923123300.1626723-1-anmuxixixi@gmail.com> References: <20260923123300.1626723-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: quoted-printable Content-Type: text/plain; charset="utf-8" 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 =20 #include "exfat_raw.h" @@ -91,12 +92,144 @@ static void exfat_name_filter_indexes(struct super_blo= ck *sb, EXFAT_NAME_FILTER_ORDER); } =20 +static unsigned long * +exfat_name_filter_detach_locked(struct exfat_sb_info *sbi, + struct exfat_inode_info *ei) +{ + unsigned long *filter =3D ei->name_filter; + + if (!filter) + return NULL; + + ei->name_filter =3D 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 =3D 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 =3D EXFAT_I(inode); + struct exfat_sb_info *sbi; + unsigned long *filter; =20 - kvfree(ei->name_filter); - ei->name_filter =3D NULL; + if (!READ_ONCE(ei->name_filter)) + return; + + sbi =3D EXFAT_SB(inode->i_sb); + spin_lock(&sbi->name_filter_lock); + filter =3D 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 =3D shrinker->private_data; + unsigned long count; + + spin_lock(&sbi->name_filter_lock); + count =3D 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 =3D shrinker->private_data; + unsigned long freed =3D 0; + + /* Avoid reclaim recursion from a GFP_NOFS allocation under s_lock. */ + if (!mutex_trylock(&sbi->s_lock)) { + sc->nr_scanned =3D 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 =3D list_first_entry(&sbi->name_filter_lru, + struct exfat_inode_info, + name_filter_lru); + filter =3D 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 =3D freed; + return freed; +} + +void exfat_name_filter_shrinker_register(struct super_block *sb) +{ + struct exfat_sb_info *sbi =3D EXFAT_SB(sb); + struct shrinker *shrinker; + + shrinker =3D 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 =3D exfat_name_filter_count_objects; + shrinker->scan_objects =3D exfat_name_filter_scan_objects; + shrinker->private_data =3D sbi; + shrinker_register(shrinker); + sbi->name_filter_shrinker =3D shrinker; +} + +void exfat_name_filter_shrinker_unregister(struct super_block *sb) +{ + struct exfat_sb_info *sbi =3D EXFAT_SB(sb); + struct shrinker *shrinker =3D sbi->name_filter_shrinker; + + sbi->name_filter_shrinker =3D 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 =3D list_first_entry(&sbi->name_filter_lru, + struct exfat_inode_info, + name_filter_lru); + filter =3D exfat_name_filter_detach_locked(sbi, ei); + spin_unlock(&sbi->name_filter_lock); + kvfree(filter); + } } =20 bool exfat_name_filter_maybe_contains(struct inode *inode, @@ -108,6 +241,7 @@ bool exfat_name_filter_maybe_contains(struct inode *ino= de, if (!ei->name_filter) return true; =20 + 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; =20 + 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 =3D EXFAT_SB(sb); int i; =20 - 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, } =20 complete: + spin_lock(&sbi->name_filter_lock); ei->name_filter =3D 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 =20 +struct shrinker; + #define EXFAT_ROOT_INO 1 =20 /* @@ -263,6 +265,11 @@ struct exfat_sb_info { =20 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; }; =20 @@ -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; =20 spinlock_t cache_lru_lock; struct list_head cache_lru; @@ -635,6 +643,8 @@ bool exfat_name_filter_maybe_contains(struct inode *ino= de, 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); =20 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 =3D EXFAT_SB(sb); =20 + 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_blo= ck *sb) return NULL; =20 ei->name_filter =3D NULL; + INIT_LIST_HEAD(&ei->name_filter_lru); return &ei->vfs_inode; } =20 @@ -746,6 +748,8 @@ static int exfat_fill_super(struct super_block *sb, str= uct fs_context *fc) goto free_table; } =20 + exfat_name_filter_shrinker_register(sb); + return 0; =20 put_inode: @@ -845,6 +849,10 @@ static int exfat_init_fs_context(struct fs_context *fc) =20 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 =3D 0; + sbi->name_filter_shrinker =3D NULL; ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); =20 --=20 2.34.1