All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths
@ 2026-08-21 10:05 Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 1/6] exfat: remove dead hint_bmap updates in I/O and truncate paths Chi Zhiling
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

The iomap mapping paths currently serialize all inode mappings on the
sbi->s_lock mutex. This series converts it to an rw_semaphore and takes
it in read mode on the mapping paths so that mappings of distinct inodes
proceed in parallel. The preceding patches make the paths safe to run
concurrently: bitmap_lock covers the allocation state, atomic bit ops
cover the volume dirty flag, and the FAT2 mirror copy is serialized
against writeback.

Same-inode access stays serialized by the exclusive inode_lock; writeback
does not hold it but is safe because truncate flushes and truncates the
page cache first and cannot run concurrently with writeback. The lock
ordering (inode_lock -> s_lock -> bitmap_lock) is unchanged, so no new
deadlock scenarios are introduced.

A per-inode read-write lock was also considered, but its implementation
turned out to be considerably more complex, so this series relaxes the
existing s_lock instead. Any suggestions on that approach are welcome.

The series passes the xfstests exfat suite. In Unixbench, the fstime-w
score improved from 3977196.8 to 4746349.6 (~+19%). Any comments and
suggestions are welcome.

Chi Zhiling (6):
  exfat: remove dead hint_bmap updates in I/O and truncate paths
  exfat: take bitmap_lock at the start of exfat_alloc_cluster()
  exfat: use atomic bit ops for volume dirty flag
  exfat: lock FAT2 buffer while copying mirrored FAT entries
  exfat: convert s_lock mutex to rw_semaphore using write lock
  exfat: take s_lock in read mode for iomap mapping paths

 fs/exfat/dir.c       | 14 +++++++-------
 fs/exfat/exfat_fs.h  |  5 ++---
 fs/exfat/exfat_raw.h |  4 ++--
 fs/exfat/fatent.c    | 20 +++++++++++++-------
 fs/exfat/file.c      |  8 ++------
 fs/exfat/inode.c     | 15 +++++----------
 fs/exfat/iomap.c     |  4 ++--
 fs/exfat/namei.c     | 26 +++++++++++++-------------
 fs/exfat/super.c     | 42 +++++++++++++++++++++---------------------
 9 files changed, 67 insertions(+), 71 deletions(-)

-- 
2.53.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 1/6] exfat: remove dead hint_bmap updates in I/O and truncate paths
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 2/6] exfat: take bitmap_lock at the start of exfat_alloc_cluster() Chi Zhiling
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

hint_bmap is only consumed by exfat_readdir() during directory
iteration. exfat_map_cluster() runs on the file I/O path (via iomap)
and never reaches directory iteration, and __exfat_truncate() only
resets the hint when the inode is already being evicted, so drop
them along with the now-unused local_clu_offset.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/file.c  | 4 ----
 fs/exfat/inode.c | 5 -----
 2 files changed, 9 deletions(-)

diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index a2a9ee1a2004..29c5db12288e 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -278,10 +278,6 @@ int __exfat_truncate(struct inode *inode)
 	/* clear exfat cache */
 	exfat_cache_inval_inode(inode);
 
-	/* hint information */
-	ei->hint_bmap.off = EXFAT_EOF_CLUSTER;
-	ei->hint_bmap.clu = EXFAT_EOF_CLUSTER;
-
 	/* hint_stat will be used if this is directory. */
 	ei->hint_stat.eidx = 0;
 	ei->hint_stat.clu = ei->start_clu;
diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
index ccd13630187e..d041f893b1c3 100644
--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -144,7 +144,6 @@ int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
 	struct super_block *sb = inode->i_sb;
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 	struct exfat_inode_info *ei = EXFAT_I(inode);
-	unsigned int local_clu_offset = clu_offset;
 	unsigned int num_to_be_allocated = 0, num_clusters;
 
 	num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
@@ -234,10 +233,6 @@ int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
 			*balloc = true;
 	}
 
-	/* hint information */
-	ei->hint_bmap.off = local_clu_offset;
-	ei->hint_bmap.clu = *clu;
-
 	return 0;
 }
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 2/6] exfat: take bitmap_lock at the start of exfat_alloc_cluster()
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 1/6] exfat: remove dead hint_bmap updates in I/O and truncate paths Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 3/6] exfat: use atomic bit ops for volume dirty flag Chi Zhiling
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

Acquire sbi->bitmap_lock at the top of exfat_alloc_cluster() so the
used_clusters/clu_srch_ptr validation and updates are covered by the
lock, removing a window where the early checks raced with concurrent
allocations.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/fatent.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index a8b11e2ce43f..2db959669563 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -427,19 +427,22 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
 	struct super_block *sb = inode->i_sb;
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
+	mutex_lock(&sbi->bitmap_lock);
+
 	total_cnt = EXFAT_DATA_CLUSTER_COUNT(sbi);
 
 	if (unlikely(total_cnt < sbi->used_clusters)) {
 		exfat_fs_error_ratelimit(sb,
 			"%s: invalid used clusters(t:%u,u:%u)\n",
 			__func__, total_cnt, sbi->used_clusters);
-		return -EIO;
+		ret = -EIO;
+		goto unlock;
 	}
 
-	if (num_alloc > total_cnt - sbi->used_clusters)
-		return -ENOSPC;
-
-	mutex_lock(&sbi->bitmap_lock);
+	if (num_alloc > total_cnt - sbi->used_clusters) {
+		ret = -ENOSPC;
+		goto unlock;
+	}
 
 	hint_clu = p_chain->dir;
 	/* find new cluster */
@@ -516,8 +519,8 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
 done:
 			sbi->clu_srch_ptr = hint_clu;
 			sbi->used_clusters += p_chain->size;
-			mutex_unlock(&sbi->bitmap_lock);
-			return 0;
+			ret = 0;
+			goto unlock;
 		}
 
 		hint_clu = new_clu + 1;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 3/6] exfat: use atomic bit ops for volume dirty flag
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 1/6] exfat: remove dead hint_bmap updates in I/O and truncate paths Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 2/6] exfat: take bitmap_lock at the start of exfat_alloc_cluster() Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 4/6] exfat: lock FAT2 buffer while copying mirrored FAT entries Chi Zhiling
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

After converting s_lock to a reader-writer lock, multiple operations can
execute concurrently under the read lock. As a result, multiple threads may
concurrently update sbi->vol_flags, causing a race in the existing
read-modify-write sequence.

Convert sbi->vol_flags to an unsigned long bitmap and use
test_and_set_bit() / test_and_clear_bit() to atomically update the volume
dirty bit. Only the thread performing the 0 -> 1 transition updates the
boot sector, avoiding concurrent unsynchronized modifications of vol_flags
and the boot-sector buffer.

Remove vol_flags_persistent, since the bitmap can directly retain the
MEDIA_FAILURE state while preserving the complete 16-bit on-disk volume
flags field when writing it back.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/exfat_fs.h  |  3 +--
 fs/exfat/exfat_raw.h |  4 ++--
 fs/exfat/super.c     | 28 ++++++++++++----------------
 3 files changed, 15 insertions(+), 20 deletions(-)

diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index a9131fe03302..f1505c013248 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -232,8 +232,7 @@ struct exfat_sb_info {
 	unsigned int num_FAT_sectors; /* num of FAT sectors */
 	unsigned int root_dir; /* root dir cluster */
 	unsigned int dentries_per_clu; /* num of dentries per cluster */
-	unsigned int vol_flags; /* volume flags */
-	unsigned int vol_flags_persistent; /* volume flags to retain */
+	unsigned long vol_flags; /* volume flags (bitmap) */
 	struct buffer_head *boot_bh; /* buffer_head of BOOT sector */
 
 	unsigned int map_clu; /* allocation bitmap start cluster */
diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h
index ec70cd35bba0..222fee5c2adf 100644
--- a/fs/exfat/exfat_raw.h
+++ b/fs/exfat/exfat_raw.h
@@ -14,8 +14,8 @@
 
 #define EXFAT_MAX_FILE_LEN	255
 
-#define VOLUME_DIRTY		0x0002
-#define MEDIA_FAILURE		0x0004
+#define VOLUME_DIRTY_BIT	1
+#define MEDIA_FAILURE_BIT	2
 
 #define EXFAT_EOF_CLUSTER	0xFFFFFFFFu
 #define EXFAT_BAD_CLUSTER	0xFFFFFFF7u
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index a9ea36ba2693..491273d8eeb6 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -69,27 +69,18 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
 	return 0;
 }
 
-static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags)
+static int exfat_sync_vol_flags(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 	struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data;
 
-	/* retain persistent-flags */
-	new_flags |= sbi->vol_flags_persistent;
-
-	/* flags are not changed */
-	if (sbi->vol_flags == new_flags)
-		return 0;
-
-	sbi->vol_flags = new_flags;
-
 	/* skip updating volume dirty flag,
 	 * if this volume has been mounted with read-only
 	 */
 	if (sb_rdonly(sb))
 		return 0;
 
-	p_boot->vol_flags = cpu_to_le16(new_flags);
+	p_boot->vol_flags = cpu_to_le16((unsigned short)READ_ONCE(sbi->vol_flags));
 
 	set_buffer_uptodate(sbi->boot_bh);
 	mark_buffer_dirty(sbi->boot_bh);
@@ -103,14 +94,20 @@ int exfat_set_volume_dirty(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
-	return exfat_set_vol_flags(sb, sbi->vol_flags | VOLUME_DIRTY);
+	if (test_and_set_bit(VOLUME_DIRTY_BIT, &sbi->vol_flags))
+		return 0;
+
+	return exfat_sync_vol_flags(sb);
 }
 
 int exfat_clear_volume_dirty(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
-	return exfat_set_vol_flags(sb, sbi->vol_flags & ~VOLUME_DIRTY);
+	if (!test_and_clear_bit(VOLUME_DIRTY_BIT, &sbi->vol_flags))
+		return 0;
+
+	return exfat_sync_vol_flags(sb);
 }
 
 static int exfat_show_options(struct seq_file *m, struct dentry *root)
@@ -509,7 +506,6 @@ static int exfat_read_boot_sector(struct super_block *sb)
 		(sbi->cluster_size_bits - DENTRY_SIZE_BITS);
 
 	sbi->vol_flags = le16_to_cpu(p_boot->vol_flags);
-	sbi->vol_flags_persistent = sbi->vol_flags & (VOLUME_DIRTY | MEDIA_FAILURE);
 	sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER;
 
 	/* check consistencies */
@@ -526,9 +522,9 @@ static int exfat_read_boot_sector(struct super_block *sb)
 		return -EINVAL;
 	}
 
-	if (sbi->vol_flags & VOLUME_DIRTY)
+	if (test_bit(VOLUME_DIRTY_BIT, &sbi->vol_flags))
 		exfat_warn(sb, "Volume was not properly unmounted. Some data may be corrupt. Please run fsck.");
-	if (sbi->vol_flags & MEDIA_FAILURE)
+	if (test_bit(MEDIA_FAILURE_BIT, &sbi->vol_flags))
 		exfat_warn(sb, "Medium has reported failures. Some data may be lost.");
 
 	/*
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 4/6] exfat: lock FAT2 buffer while copying mirrored FAT entries
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
                   ` (2 preceding siblings ...)
  2026-08-21 10:05 ` [RFC PATCH v1 3/6] exfat: use atomic bit ops for volume dirty flag Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 5/6] exfat: convert s_lock mutex to rw_semaphore using write lock Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 6/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

exfat_mirror_bh() updates the FAT2 buffer without serializing the copy
against writeback. Once s_lock is converted to a read-write lock,
multiple read-side operations may run concurrently with writeback.
This can allow writeback to observe a partially updated FAT2 block.

Lock the destination buffer while copying the FAT entry data to
serialize the update with writeback and prevent torn FAT2 writes.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/fatent.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c
index 2db959669563..f6ad2825ad19 100644
--- a/fs/exfat/fatent.c
+++ b/fs/exfat/fatent.c
@@ -24,7 +24,10 @@ static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh)
 		c_bh = sb_getblk(sb, sec2);
 		if (!c_bh)
 			return -ENOMEM;
+		/* Serialize the copy with writeback to avoid a torn FAT2 write */
+		lock_buffer(c_bh);
 		memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize);
+		unlock_buffer(c_bh);
 		err = exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS);
 		brelse(c_bh);
 	}
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 5/6] exfat: convert s_lock mutex to rw_semaphore using write lock
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
                   ` (3 preceding siblings ...)
  2026-08-21 10:05 ` [RFC PATCH v1 4/6] exfat: lock FAT2 buffer while copying mirrored FAT entries Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  2026-08-21 10:05 ` [RFC PATCH v1 6/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

Convert sbi->s_lock from a mutex to an rw_semaphore and acquire it in
write mode at every existing use site. This is a mechanical conversion
with no behavioral change, preparing the lock for read-mode access in
the iomap mapping paths so that concurrent mappings of distinct inodes
can proceed in parallel.

Lock ordering stays consistent (inode_lock -> s_lock -> bitmap_lock),
so no deadlock is introduced.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/dir.c      | 14 +++++++-------
 fs/exfat/exfat_fs.h |  2 +-
 fs/exfat/file.c     |  4 ++--
 fs/exfat/inode.c    | 10 +++++-----
 fs/exfat/iomap.c    |  4 ++--
 fs/exfat/namei.c    | 26 +++++++++++++-------------
 fs/exfat/super.c    | 10 +++++-----
 7 files changed, 35 insertions(+), 35 deletions(-)

diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c
index fe73b1380c5d..ea454574260e 100644
--- a/fs/exfat/dir.c
+++ b/fs/exfat/dir.c
@@ -223,7 +223,7 @@ static int exfat_iterate(struct file *file, struct dir_context *ctx)
 	if (err)
 		goto out;
 get_new:
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 
 	if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode))
 		goto end_of_dir;
@@ -255,7 +255,7 @@ static int exfat_iterate(struct file *file, struct dir_context *ctx)
 		inum = iunique(sb, EXFAT_ROOT_INO);
 	}
 
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum,
 			(de.attr & EXFAT_ATTR_SUBDIR) ? DT_DIR : DT_REG))
 		goto out;
@@ -266,7 +266,7 @@ static int exfat_iterate(struct file *file, struct dir_context *ctx)
 	if (!cpos && fake_offset)
 		cpos = ITER_POS_FILLED_DOTS;
 	ctx->pos = cpos;
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 out:
 	/*
 	 * To improve performance, free namebuf after unlock sb_lock.
@@ -1294,7 +1294,7 @@ int exfat_read_volume_label(struct super_block *sb, struct exfat_uni_name *label
 	struct exfat_entry_set_cache es;
 	struct exfat_dentry *ep;
 
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 
 	memset(label_out, 0, sizeof(*label_out));
 	ret = exfat_get_volume_label_dentry(sb, &es);
@@ -1322,7 +1322,7 @@ int exfat_read_volume_label(struct super_block *sb, struct exfat_uni_name *label
 
 	exfat_put_dentry_set(&es, false);
 unlock:
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 	return ret;
 }
 
@@ -1339,7 +1339,7 @@ int exfat_write_volume_label(struct super_block *sb,
 	if (label->name_len > EXFAT_VOLUME_LABEL_LEN)
 		return -EINVAL;
 
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 
 	ret = exfat_get_volume_label_dentry(sb, &es);
 	if (ret == -ENOENT) {
@@ -1376,6 +1376,6 @@ int exfat_write_volume_label(struct super_block *sb,
 	ret = exfat_put_dentry_set(&es, IS_DIRSYNC(root_inode));
 
 unlock:
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 	return ret;
 }
diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h
index f1505c013248..16ce508e0946 100644
--- a/fs/exfat/exfat_fs.h
+++ b/fs/exfat/exfat_fs.h
@@ -246,7 +246,7 @@ struct exfat_sb_info {
 
 	unsigned long s_exfat_flags; /* Exfat superblock flags */
 
-	struct mutex s_lock; /* superblock lock */
+	struct rw_semaphore s_lock; /* superblock lock */
 	struct mutex bitmap_lock; /* bitmap lock */
 	struct exfat_mount_options options;
 	struct nls_table *nls_io; /* Charset used for input and display */
diff --git a/fs/exfat/file.c b/fs/exfat/file.c
index 29c5db12288e..8abef3d2294a 100644
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@ -297,7 +297,7 @@ static void exfat_truncate(struct inode *inode)
 	struct exfat_inode_info *ei = EXFAT_I(inode);
 	int err;
 
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 	if (ei->start_clu == 0) {
 		/*
 		 * Empty start_clu != ~0 (not allocated)
@@ -312,7 +312,7 @@ static void exfat_truncate(struct inode *inode)
 
 	inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
 write_size:
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 }
 
 int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c
index d041f893b1c3..a89826e08e1c 100644
--- a/fs/exfat/inode.c
+++ b/fs/exfat/inode.c
@@ -116,16 +116,16 @@ int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
 	if (unlikely(exfat_forced_shutdown(inode->i_sb)))
 		return -EIO;
 
-	mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
+	down_write(&EXFAT_SB(inode->i_sb)->s_lock);
 	ret = __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
-	mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
+	up_write(&EXFAT_SB(inode->i_sb)->s_lock);
 
 	return ret;
 }
 
 void exfat_sync_inode(struct inode *inode)
 {
-	lockdep_assert_held(&EXFAT_SB(inode->i_sb)->s_lock);
+	lockdep_assert_held_write(&EXFAT_SB(inode->i_sb)->s_lock);
 	__exfat_write_inode(inode, 1);
 }
 
@@ -443,9 +443,9 @@ void exfat_evict_inode(struct inode *inode)
 
 	if (!inode->i_nlink) {
 		i_size_write(inode, 0);
-		mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
+		down_write(&EXFAT_SB(inode->i_sb)->s_lock);
 		__exfat_truncate(inode);
-		mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
+		up_write(&EXFAT_SB(inode->i_sb)->s_lock);
 	}
 
 	clear_inode(inode);
diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index 0c805bf6676a..bc8bdfa9bb80 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -68,7 +68,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
 	num_clusters = exfat_bytes_to_cluster_round_up(sbi,
 			offset + length) - exfat_bytes_to_cluster(sbi, offset);
 
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 	iomap->bdev = inode->i_sb->s_bdev;
 	iomap->offset = offset;
 
@@ -135,7 +135,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
 
 	iomap->flags |= IOMAP_F_MERGED;
 out:
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 	return err;
 }
 
diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c
index f26f987a34cf..0c28040e97d6 100644
--- a/fs/exfat/namei.c
+++ b/fs/exfat/namei.c
@@ -565,7 +565,7 @@ static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
 	if (unlikely(exfat_forced_shutdown(sb)))
 		return -EIO;
 
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 	exfat_set_volume_dirty(sb);
 	err = exfat_add_entry(dir, dentry->d_name.name, TYPE_FILE, &info);
 	if (err)
@@ -592,7 +592,7 @@ static int exfat_create(struct mnt_idmap *idmap, struct inode *dir,
 
 	d_instantiate(dentry, inode);
 unlock:
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	return err;
 }
 
@@ -732,7 +732,7 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
 	int err;
 	loff_t i_pos;
 
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 	err = exfat_find(dir, &dentry->d_name, &info);
 	if (err) {
 		if (unlikely(err != -ENOENT))
@@ -761,12 +761,12 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
 		 */
 		d_move(alias, dentry);
 		iput(inode);
-		mutex_unlock(&EXFAT_SB(sb)->s_lock);
+		up_write(&EXFAT_SB(sb)->s_lock);
 		return alias;
 	}
 	dput(alias);
 out:
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	if (!inode)
 		exfat_d_version_set(dentry, inode_query_iversion(dir));
 
@@ -785,7 +785,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
 	if (unlikely(exfat_forced_shutdown(sb)))
 		return -EIO;
 
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 	if (ei->dir.dir == DIR_DELETED) {
 		exfat_err(sb, "abnormal access to deleted dentry");
 		err = -ENOENT;
@@ -821,7 +821,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
 	exfat_unhash_inode(inode);
 	exfat_d_version_set(dentry, inode_query_iversion(dir));
 unlock:
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	return err;
 }
 
@@ -838,7 +838,7 @@ static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	if (unlikely(exfat_forced_shutdown(sb)))
 		return ERR_PTR(-EIO);
 
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 	exfat_set_volume_dirty(sb);
 	err = exfat_add_entry(dir, dentry->d_name.name, TYPE_DIR, &info);
 	if (err)
@@ -867,7 +867,7 @@ static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir,
 	d_instantiate(dentry, inode);
 
 unlock:
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	return err ? ERR_PTR(err) : NULL;
 }
 
@@ -929,7 +929,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
 	if (unlikely(exfat_forced_shutdown(sb)))
 		return -EIO;
 
-	mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
+	down_write(&EXFAT_SB(inode->i_sb)->s_lock);
 
 	if (ei->dir.dir == DIR_DELETED) {
 		exfat_err(sb, "abnormal access to deleted dentry");
@@ -979,7 +979,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
 	exfat_unhash_inode(inode);
 	exfat_d_version_set(dentry, inode_query_iversion(dir));
 unlock:
-	mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
+	up_write(&EXFAT_SB(inode->i_sb)->s_lock);
 	return err;
 }
 
@@ -1282,7 +1282,7 @@ static int exfat_rename(struct mnt_idmap *idmap,
 	if (flags & ~RENAME_NOREPLACE)
 		return -EINVAL;
 
-	mutex_lock(&EXFAT_SB(sb)->s_lock);
+	down_write(&EXFAT_SB(sb)->s_lock);
 	old_inode = old_dentry->d_inode;
 	new_inode = new_dentry->d_inode;
 
@@ -1334,7 +1334,7 @@ static int exfat_rename(struct mnt_idmap *idmap,
 	}
 
 unlock:
-	mutex_unlock(&EXFAT_SB(sb)->s_lock);
+	up_write(&EXFAT_SB(sb)->s_lock);
 	return err;
 }
 
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 491273d8eeb6..72a35f4079b4 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -45,11 +45,11 @@ static void exfat_put_super(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 	exfat_clear_volume_dirty(sb);
 	exfat_free_bitmap(sbi);
 	brelse(sbi->boot_bh);
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 }
 
 static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
@@ -771,9 +771,9 @@ static int exfat_reconfigure(struct fs_context *fc)
 	fc->sb_flags |= SB_NODIRATIME;
 
 	sync_filesystem(sb);
-	mutex_lock(&sbi->s_lock);
+	down_write(&sbi->s_lock);
 	exfat_clear_volume_dirty(sb);
-	mutex_unlock(&sbi->s_lock);
+	up_write(&sbi->s_lock);
 
 	if (new_opts->allow_utime == (unsigned short)-1)
 		new_opts->allow_utime = ~new_opts->fs_dmask & 0022;
@@ -820,7 +820,7 @@ static int exfat_init_fs_context(struct fs_context *fc)
 	if (!sbi)
 		return -ENOMEM;
 
-	mutex_init(&sbi->s_lock);
+	init_rwsem(&sbi->s_lock);
 	mutex_init(&sbi->bitmap_lock);
 	ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL,
 			DEFAULT_RATELIMIT_BURST);
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH v1 6/6] exfat: take s_lock in read mode for iomap mapping paths
  2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
                   ` (4 preceding siblings ...)
  2026-08-21 10:05 ` [RFC PATCH v1 5/6] exfat: convert s_lock mutex to rw_semaphore using write lock Chi Zhiling
@ 2026-08-21 10:05 ` Chi Zhiling
  5 siblings, 0 replies; 7+ messages in thread
From: Chi Zhiling @ 2026-08-21 10:05 UTC (permalink / raw)
  To: exfat, linux-kernel; +Cc: Namjae Jeon, Sungjong Seo, Yuezhang Mo, Chi Zhiling

From: Chi Zhiling <chizhiling@kylinos.cn>

The iomap mapping paths mainly access the FAT chain of the file itself.
Concurrent mappings of distinct inodes can therefore proceed in
parallel by taking s_lock in read mode instead of write mode. The
superblock-wide state shared between files is already protected by the
preceding patches: the allocation bitmap, used_clusters and clu_srch_ptr
by bitmap_lock, and the volume dirty flag / boot sector by atomic bit
ops with a single writer on the 0 -> 1 transition.

Concurrent access to the same inode stays serialized by the exclusive
inode_lock held in exfat_file_write_iter().

Writeback takes the read lock even though it does not hold inode_lock.
Folios under writeback are marked writeback, and truncate first flushes
and truncates the page cache, so truncate cannot run concurrently with
writeback. Writeback therefore only maps clusters that are still
committed or owned by the inode and cannot race with cluster freeing.

The lock ordering remains inode_lock -> s_lock -> bitmap_lock, so this
change does not introduce any new deadlock scenarios.

Signed-off-by: Chi Zhiling <chizhiling@kylinos.cn>
---
 fs/exfat/iomap.c | 4 ++--
 fs/exfat/super.c | 4 ++++
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c
index bc8bdfa9bb80..147e9da01a47 100644
--- a/fs/exfat/iomap.c
+++ b/fs/exfat/iomap.c
@@ -68,7 +68,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
 	num_clusters = exfat_bytes_to_cluster_round_up(sbi,
 			offset + length) - exfat_bytes_to_cluster(sbi, offset);
 
-	down_write(&sbi->s_lock);
+	down_read(&sbi->s_lock);
 	iomap->bdev = inode->i_sb->s_bdev;
 	iomap->offset = offset;
 
@@ -135,7 +135,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
 
 	iomap->flags |= IOMAP_F_MERGED;
 out:
-	up_write(&sbi->s_lock);
+	up_read(&sbi->s_lock);
 	return err;
 }
 
diff --git a/fs/exfat/super.c b/fs/exfat/super.c
index 72a35f4079b4..64ec4d2d1bf6 100644
--- a/fs/exfat/super.c
+++ b/fs/exfat/super.c
@@ -94,6 +94,8 @@ int exfat_set_volume_dirty(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
+	lockdep_assert_held(&sbi->s_lock);
+
 	if (test_and_set_bit(VOLUME_DIRTY_BIT, &sbi->vol_flags))
 		return 0;
 
@@ -104,6 +106,8 @@ int exfat_clear_volume_dirty(struct super_block *sb)
 {
 	struct exfat_sb_info *sbi = EXFAT_SB(sb);
 
+	lockdep_assert_held_write(&sbi->s_lock);
+
 	if (!test_and_clear_bit(VOLUME_DIRTY_BIT, &sbi->vol_flags))
 		return 0;
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-08-21 10:06 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-21 10:05 [RFC PATCH 0/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 1/6] exfat: remove dead hint_bmap updates in I/O and truncate paths Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 2/6] exfat: take bitmap_lock at the start of exfat_alloc_cluster() Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 3/6] exfat: use atomic bit ops for volume dirty flag Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 4/6] exfat: lock FAT2 buffer while copying mirrored FAT entries Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 5/6] exfat: convert s_lock mutex to rw_semaphore using write lock Chi Zhiling
2026-08-21 10:05 ` [RFC PATCH v1 6/6] exfat: take s_lock in read mode for iomap mapping paths Chi Zhiling

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.