mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH v2 0/3] fs/proc: split the inode list for procfs
@ 2026-09-20  7:28 Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 1/3] fs/drop_caches: skip filesystems without page cache Huang Shijie
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Huang Shijie @ 2026-09-20  7:28 UTC (permalink / raw)
  To: viro, brauner
  Cc: zhongyuan, fangbaoshun, yingzhiwei, jack, dev.jain, ljs,
	adobriyan, akpm, hpa, ryan.roberts, mjguzik, joel.granados,
	jannh, oleg, aleksa, legion, kees, joannelkoong, tj, libaokun,
	ebiggers, sandeen, linux-fsdevel, linux-kernel, Huang Shijie

The global s_inode_list_lock is heavily contended in procfs
on a 384-CPU, 12-NUMA-node Hygon machine running Hadoop TestDFSIO:
  #hadoop jar xxxx.jar TestDFSIO -read -nrFiles 1000 -size 100MB

The hadoop will create lot of threads during the test.
The perf shows it consuming ~90% of the lock hotspot in procfs.
The lock is hit from both directions:
   -- inode creation (~49%) :
           getdents64 ->
              proc_readfd_common ->
	         new_inode ->
		   inode_sb_list_add()

   -- inode eviction (~41%):
           process exit ->
	      release_task ->
	         proc_invalidate_siblings_dcache ->
		     evict ->
		        inode_sb_list_del()

This patch set tries to resolve this issue by:
    patch 1: "fs/drop_caches: skip filesystems without page cache"
 	add a new flag for filesystems without page cache.
    patch 2: Add a new helper to detect the empty inode list    
    patch 3: split the inode list.


After this patch set, the above TestDFSIO can get over 50% better performance
in exec time. And the procfs lock hotspot becomes nearly 1%.

Huang Shijie (3):
  fs/drop_caches: skip filesystems without page cache
  fs/super: introduce a helper sb_inodes_empty()
  fs/proc: split the inode list

 fs/drop_caches.c               |   3 +
 fs/inode.c                     |  79 ++++++++++------
 fs/proc/inode.c                |  53 +++++++++++
 fs/proc/internal.h             |   2 +
 fs/proc/root.c                 |   8 +-
 fs/super.c                     |  45 +++++++--
 include/linux/fs/super_types.h |  17 ++++
 include/linux/proc_fs.h        |   1 +
 security/landlock/fs.c         | 162 ++++++++++++++++++---------------
 9 files changed, 260 insertions(+), 110 deletions(-)

-- 
2.53.0



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

* [RFC PATCH v2 1/3] fs/drop_caches: skip filesystems without page cache
  2026-09-20  7:28 [RFC PATCH v2 0/3] fs/proc: split the inode list for procfs Huang Shijie
@ 2026-09-20  7:28 ` Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 2/3] fs/super: introduce a helper sb_inodes_empty() Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 3/3] fs/proc: split the inode list Huang Shijie
  2 siblings, 0 replies; 4+ messages in thread
From: Huang Shijie @ 2026-09-20  7:28 UTC (permalink / raw)
  To: viro, brauner
  Cc: zhongyuan, fangbaoshun, yingzhiwei, jack, dev.jain, ljs,
	adobriyan, akpm, hpa, ryan.roberts, mjguzik, joel.granados,
	jannh, oleg, aleksa, legion, kees, joannelkoong, tj, libaokun,
	ebiggers, sandeen, linux-fsdevel, linux-kernel, Huang Shijie

Add a new flag SB_I_NO_PAGECACHE for superblock.

Skip scanning the inode lists of filesystems that have no page cache
in drop_pagecache_sb(), as indicated by the SB_I_NO_PAGECACHE flag.

This patch only changes the procfs.

Signed-off-by: Huang Shijie <huangsj@hygon.cn>
---
 fs/drop_caches.c               | 3 +++
 fs/proc/root.c                 | 2 +-
 include/linux/fs/super_types.h | 1 +
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/drop_caches.c b/fs/drop_caches.c
index 49f56a598ecb..27caf748b3c5 100644
--- a/fs/drop_caches.c
+++ b/fs/drop_caches.c
@@ -20,6 +20,9 @@ static void drop_pagecache_sb(struct super_block *sb, void *unused)
 {
 	struct inode *inode, *toput_inode = NULL;
 
+	if (sb->s_iflags & SB_I_NO_PAGECACHE)
+		return;
+
 	spin_lock(&sb->s_inode_list_lock);
 	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
 		spin_lock(&inode->i_lock);
diff --git a/fs/proc/root.c b/fs/proc/root.c
index 99adddfeb4a4..d45f5af5ab53 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -266,7 +266,7 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 		return ret;
 
 	/* User space would break if executables or devices appear on proc */
-	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV;
+	s->s_iflags |= SB_I_NOEXEC | SB_I_NODEV | SB_I_NO_PAGECACHE;
 	s->s_flags |= SB_NODIRATIME | SB_NOSUID | SB_NOEXEC;
 	s->s_blocksize = 1024;
 	s->s_blocksize_bits = 10;
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index ecd96aeb1cee..1a596caf58a8 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -352,5 +352,6 @@ struct super_block {
 #define SB_I_NOIDMAP	0x00002000	/* No idmapped mounts on this superblock */
 #define SB_I_ALLOW_HSM	0x00004000	/* Allow HSM events on this superblock */
 #define SB_I_NO_DATA_INTEGRITY	0x00008000 /* fs cannot guarantee data persistence on sync */
+#define SB_I_NO_PAGECACHE	0x00010000 /* this file system has no page cache */
 
 #endif /* _LINUX_FS_SUPER_TYPES_H */
-- 
2.53.0



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

* [RFC PATCH v2 2/3] fs/super: introduce a helper sb_inodes_empty()
  2026-09-20  7:28 [RFC PATCH v2 0/3] fs/proc: split the inode list for procfs Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 1/3] fs/drop_caches: skip filesystems without page cache Huang Shijie
@ 2026-09-20  7:28 ` Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 3/3] fs/proc: split the inode list Huang Shijie
  2 siblings, 0 replies; 4+ messages in thread
From: Huang Shijie @ 2026-09-20  7:28 UTC (permalink / raw)
  To: viro, brauner
  Cc: zhongyuan, fangbaoshun, yingzhiwei, jack, dev.jain, ljs,
	adobriyan, akpm, hpa, ryan.roberts, mjguzik, joel.granados,
	jannh, oleg, aleksa, legion, kees, joannelkoong, tj, libaokun,
	ebiggers, sandeen, linux-fsdevel, linux-kernel, Huang Shijie

Introduce a helper sb_inodes_empty() which is used to
detect if the inodes list is empty.

Signed-off-by: Huang Shijie <huangsj@hygon.cn>
---
 fs/super.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/super.c b/fs/super.c
index 9d4025213521..e25ded0bc9a2 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -710,6 +710,11 @@ void retire_super(struct super_block *sb)
 }
 EXPORT_SYMBOL(retire_super);
 
+static bool sb_inodes_empty(struct super_block *sb)
+{
+	return list_empty(&sb->s_inodes);
+}
+
 /**
  *	generic_shutdown_super	-	common helper for ->kill_sb()
  *	@sb: superblock to kill
@@ -760,7 +765,7 @@ void generic_shutdown_super(struct super_block *sb)
 		 */
 		fscrypt_destroy_keyring(sb);
 
-		if (CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes), NULL,
+		if (CHECK_DATA_CORRUPTION(!sb_inodes_empty(sb), NULL,
 				"VFS: Busy inodes after unmount of %s (%s)",
 				sb->s_id, sb->s_type->name)) {
 			/*
-- 
2.53.0



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

* [RFC PATCH v2 3/3] fs/proc: split the inode list
  2026-09-20  7:28 [RFC PATCH v2 0/3] fs/proc: split the inode list for procfs Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 1/3] fs/drop_caches: skip filesystems without page cache Huang Shijie
  2026-09-20  7:28 ` [RFC PATCH v2 2/3] fs/super: introduce a helper sb_inodes_empty() Huang Shijie
@ 2026-09-20  7:28 ` Huang Shijie
  2 siblings, 0 replies; 4+ messages in thread
From: Huang Shijie @ 2026-09-20  7:28 UTC (permalink / raw)
  To: viro, brauner
  Cc: zhongyuan, fangbaoshun, yingzhiwei, jack, dev.jain, ljs,
	adobriyan, akpm, hpa, ryan.roberts, mjguzik, joel.granados,
	jannh, oleg, aleksa, legion, kees, joannelkoong, tj, libaokun,
	ebiggers, sandeen, linux-fsdevel, linux-kernel, Huang Shijie

The global s_inode_list_lock is heavily contended in procfs
on a 384-CPU, 12-NUMA-node Hygon machine running Hadoop TestDFSIO:
  #hadoop jar xxxx.jar TestDFSIO -read -nrFiles 1000 -size 100MB

The perf shows it consuming ~90% of the lock hotspot.
The lock is hit from both directions:
   -- inode creation (~49%) :
           getdents64 ->
              proc_readfd_common ->
	         new_inode ->
		   inode_sb_list_add()

   -- inode eviction (~41%)
           process exit ->
	      release_task ->
	         proc_invalidate_siblings_dcache ->
		     evict ->
		        inode_sb_list_del()

This patch spreads the inode list across per-shard locks for procfs:
   --- Add three fields in super_block:
        shards   : the pointer for the array of inode_shard.
        nr_shards: the size of the array
	s_inode_list_sharded: whether or not to use a sharded inode list

        struct inode_shard is cacheline-aligned to avoid false
        sharing between shard locks on different NUMA nodes.

   --- Add inode_list_add()/inode_list_del() callbacks to super_operations;
       procfs implements them to round-robin inodes
       onto nr_shards = DIV_ROUND_UP(num_possible_cpus(), 32)
       shards allocated at mount time, each protected by its own spinlock.

   --- For procfs, the "unmount" will call evict_inodes(),
       generic_shutdown_super() and hook_sb_delete() which will
       iterate the shards when the super_block inode list is sharded.
       Change these functions to work with the sharded inode list.

This reduces the s_inode_list_lock hotspot from ~90% to ~1% in TestDFSIO.
And we can improve the hadoop performance over 50%.

Signed-off-by: Huang Shijie <huangsj@hygon.cn>
---
 fs/inode.c                     |  79 ++++++++++------
 fs/proc/inode.c                |  53 +++++++++++
 fs/proc/internal.h             |   2 +
 fs/proc/root.c                 |   6 ++
 fs/super.c                     |  40 ++++++--
 include/linux/fs/super_types.h |  16 ++++
 include/linux/proc_fs.h        |   1 +
 security/landlock/fs.c         | 162 ++++++++++++++++++---------------
 8 files changed, 250 insertions(+), 109 deletions(-)

diff --git a/fs/inode.c b/fs/inode.c
index ba7da39be4a3..80acb9113487 100644
--- a/fs/inode.c
+++ b/fs/inode.c
@@ -635,6 +635,10 @@ void inode_sb_list_add(struct inode *inode)
 {
 	struct super_block *sb = inode->i_sb;
 
+	if (sb->s_inode_list_sharded && sb->s_op->inode_list_add) {
+		sb->s_op->inode_list_add(sb, inode);
+		return;
+	}
 	spin_lock(&sb->s_inode_list_lock);
 	list_add(&inode->i_sb_list, &sb->s_inodes);
 	spin_unlock(&sb->s_inode_list_lock);
@@ -646,6 +650,10 @@ static inline void inode_sb_list_del(struct inode *inode)
 	struct super_block *sb = inode->i_sb;
 
 	if (!list_empty(&inode->i_sb_list)) {
+		if (sb->s_inode_list_sharded && sb->s_op->inode_list_del) {
+			sb->s_op->inode_list_del(sb, inode);
+			return;
+		}
 		spin_lock(&sb->s_inode_list_lock);
 		list_del_init(&inode->i_sb_list);
 		spin_unlock(&sb->s_inode_list_lock);
@@ -879,41 +887,56 @@ void evict_inodes(struct super_block *sb)
 {
 	struct inode *inode;
 	LIST_HEAD(dispose);
+	struct list_head *head;
+	spinlock_t *lock;
+	unsigned int nr, i;
+	const bool sharded = sb->s_inode_list_sharded;
 
+	nr = sharded ? sb->nr_shards : 1;
 again:
-	spin_lock(&sb->s_inode_list_lock);
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-		if (icount_read_once(inode))
-			continue;
-
-		spin_lock(&inode->i_lock);
-		if (icount_read(inode)) {
-			spin_unlock(&inode->i_lock);
-			continue;
-		}
-		if (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE)) {
-			spin_unlock(&inode->i_lock);
-			continue;
+	for (i = 0; i < nr; i++) {
+		if (sharded) {
+			head = &sb->shards[i].list;
+			lock = &sb->shards[i].lock;
+		} else {
+			head = &sb->s_inodes;
+			lock = &sb->s_inode_list_lock;
 		}
 
-		inode_state_set(inode, I_FREEING);
-		inode_lru_list_del(inode);
-		spin_unlock(&inode->i_lock);
-		list_add(&inode->i_lru, &dispose);
+		spin_lock(lock);
+		list_for_each_entry(inode, head, i_sb_list) {
+			if (icount_read_once(inode))
+				continue;
 
-		/*
-		 * We can have a ton of inodes to evict at unmount time given
-		 * enough memory, check to see if we need to go to sleep for a
-		 * bit so we don't livelock.
-		 */
-		if (need_resched()) {
-			spin_unlock(&sb->s_inode_list_lock);
-			cond_resched();
-			dispose_list(&dispose);
-			goto again;
+			spin_lock(&inode->i_lock);
+			if (icount_read(inode)) {
+				spin_unlock(&inode->i_lock);
+				continue;
+			}
+			if (inode_state_read(inode) & (I_NEW | I_FREEING | I_WILL_FREE)) {
+				spin_unlock(&inode->i_lock);
+				continue;
+			}
+
+			inode_state_set(inode, I_FREEING);
+			inode_lru_list_del(inode);
+			spin_unlock(&inode->i_lock);
+			list_add(&inode->i_lru, &dispose);
+
+			/*
+			 * We can have a ton of inodes to evict at unmount time given
+			 * enough memory, check to see if we need to go to sleep for a
+			 * bit so we don't livelock.
+			 */
+			if (need_resched()) {
+				spin_unlock(lock);
+				cond_resched();
+				dispose_list(&dispose);
+				goto again;
+			}
 		}
+		spin_unlock(lock);
 	}
-	spin_unlock(&sb->s_inode_list_lock);
 
 	dispose_list(&dispose);
 }
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index b7634f975d98..aeaf2e94e7ba 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -65,6 +65,7 @@ static struct inode *proc_alloc_inode(struct super_block *sb)
 	ei->sysctl_entry = NULL;
 	INIT_HLIST_NODE(&ei->sibling_inodes);
 	ei->ns_ops = NULL;
+	ei->shard_idx = 0;
 	return &ei->vfs_inode;
 }
 
@@ -80,6 +81,56 @@ static void proc_free_inode(struct inode *inode)
 	kmem_cache_free(proc_inode_cachep, PROC_I(inode));
 }
 
+static void proc_inode_list_add(struct super_block *sb, struct inode *inode)
+{
+	struct proc_fs_info *fs_info = proc_sb_info(sb);
+	struct proc_inode *ei = PROC_I(inode);
+	struct inode_shard *shard;
+	unsigned int idx, seq;
+
+	seq = atomic_fetch_inc(&fs_info->shard_seq);
+	idx = seq % sb->nr_shards;
+	ei->shard_idx = idx;
+	shard = &sb->shards[idx];
+
+	spin_lock(&shard->lock);
+	list_add(&inode->i_sb_list, &shard->list);
+	spin_unlock(&shard->lock);
+}
+
+static void proc_inode_list_del(struct super_block *sb, struct inode *inode)
+{
+	struct proc_inode *ei = PROC_I(inode);
+	struct inode_shard *shard = &sb->shards[ei->shard_idx];
+
+	spin_lock(&shard->lock);
+	list_del_init(&inode->i_sb_list);
+	spin_unlock(&shard->lock);
+}
+
+#define PROC_LIST_ALIGN	32
+int proc_init_inode_shards(struct super_block *sb)
+{
+	struct inode_shard *shards;
+	unsigned int nr;
+	int i;
+
+	nr = DIV_ROUND_UP(num_possible_cpus(), PROC_LIST_ALIGN);
+	shards = kcalloc(nr, sizeof(*shards), GFP_KERNEL);
+	if (!shards)
+		return -ENOMEM;
+
+	for (i = 0; i < nr; i++) {
+		INIT_LIST_HEAD(&shards[i].list);
+		spin_lock_init(&shards[i].lock);
+	}
+
+	sb->shards = shards;
+	sb->nr_shards = nr;
+	sb->s_inode_list_sharded = true;
+	return 0;
+}
+
 static void init_once(void *foo)
 {
 	struct proc_inode *ei = (struct proc_inode *) foo;
@@ -191,6 +242,8 @@ const struct super_operations proc_sops = {
 	.evict_inode	= proc_evict_inode,
 	.statfs		= simple_statfs,
 	.show_options	= proc_show_options,
+	.inode_list_add	= proc_inode_list_add,
+	.inode_list_del	= proc_inode_list_del,
 };
 
 enum {BIAS = -1U<<31};
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 04bd6c9e65a7..edbe357263c2 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -127,6 +127,7 @@ struct proc_inode {
 	struct hlist_node sibling_inodes;
 	const struct proc_ns_operations *ns_ops;
 	struct inode vfs_inode;
+	unsigned int shard_idx;
 } __randomize_layout;
 
 /*
@@ -317,6 +318,7 @@ void proc_init_kmemcache(void);
 void proc_invalidate_siblings_dcache(struct hlist_head *inodes, spinlock_t *lock);
 void set_proc_pid_nlink(void);
 extern struct inode *proc_get_inode(struct super_block *, struct proc_dir_entry *);
+extern int proc_init_inode_shards(struct super_block *);
 extern void proc_entry_rundown(struct proc_dir_entry *);
 
 /*
diff --git a/fs/proc/root.c b/fs/proc/root.c
index d45f5af5ab53..fb5d68da85f4 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -275,6 +275,10 @@ static int proc_fill_super(struct super_block *s, struct fs_context *fc)
 	s->s_time_gran = 1;
 	s->s_fs_info = fs_info;
 
+	ret = proc_init_inode_shards(s);
+	if (ret)
+		return ret;
+
 	if (fs_info->pidonly == PROC_PIDONLY_ON)
 		s->s_iflags |= SB_I_RESTRICTED_VARIANT;
 
@@ -359,6 +363,8 @@ static void proc_kill_sb(struct super_block *sb)
 	struct proc_fs_info *fs_info = proc_sb_info(sb);
 
 	kill_anon_super(sb);
+	if (sb->s_inode_list_sharded)
+		kfree(sb->shards);
 	if (fs_info) {
 		put_pid_ns(fs_info->pid_ns);
 		put_cred(fs_info->mounter_cred);
diff --git a/fs/super.c b/fs/super.c
index e25ded0bc9a2..e44db4505238 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -712,7 +712,16 @@ EXPORT_SYMBOL(retire_super);
 
 static bool sb_inodes_empty(struct super_block *sb)
 {
-	return list_empty(&sb->s_inodes);
+	unsigned int i;
+
+	if (!sb->s_inode_list_sharded)
+		return list_empty(&sb->s_inodes);
+
+	for (i = 0; i < sb->nr_shards; i++)
+		if (!list_empty(&sb->shards[i].list))
+			return false;
+
+	return true;
 }
 
 /**
@@ -774,14 +783,29 @@ void generic_shutdown_super(struct super_block *sb)
 			 * iput_final() or such crashes cleanly.
 			 */
 			struct inode *inode;
-
-			spin_lock(&sb->s_inode_list_lock);
-			list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-				inode->i_op = VFS_PTR_POISON;
-				inode->i_sb = VFS_PTR_POISON;
-				inode->i_mapping = VFS_PTR_POISON;
+			struct list_head *head;
+			spinlock_t *lock;
+			unsigned int nr, i;
+			const bool sharded = sb->s_inode_list_sharded;
+
+			nr = sharded ? sb->nr_shards : 1;
+			for (i = 0; i < nr; i++) {
+				if (sharded) {
+					head = &sb->shards[i].list;
+					lock = &sb->shards[i].lock;
+				} else {
+					head = &sb->s_inodes;
+					lock = &sb->s_inode_list_lock;
+				}
+
+				spin_lock(lock);
+				list_for_each_entry(inode, head, i_sb_list) {
+					inode->i_op = VFS_PTR_POISON;
+					inode->i_sb = VFS_PTR_POISON;
+					inode->i_mapping = VFS_PTR_POISON;
+				}
+				spin_unlock(lock);
 			}
-			spin_unlock(&sb->s_inode_list_lock);
 		}
 	}
 	/*
diff --git a/include/linux/fs/super_types.h b/include/linux/fs/super_types.h
index 1a596caf58a8..f06f293b46be 100644
--- a/include/linux/fs/super_types.h
+++ b/include/linux/fs/super_types.h
@@ -130,8 +130,20 @@ struct super_operations {
 
 	/* Report a filesystem error */
 	void (*report_error)(const struct fserror_event *event);
+
+	void (*inode_list_add)(struct super_block *sb, struct inode *inode);
+	void (*inode_list_del)(struct super_block *sb, struct inode *inode);
 };
 
+/*
+ * Sharded inode list which is used to spread s_inode_list_lock contention
+ * across per-shard locks.
+ */
+struct inode_shard {
+	struct list_head	list;
+	spinlock_t		lock;
+} ____cacheline_aligned_in_smp;
+
 struct super_block {
 	struct list_head			s_list;		/* Keep this first */
 	dev_t					s_dev;		/* search index; _not_ kdev_t */
@@ -269,6 +281,10 @@ struct super_block {
 	 */
 	int s_stack_depth;
 
+	bool					s_inode_list_sharded;
+	struct inode_shard			*shards;
+	unsigned int				nr_shards;
+
 	/* s_inode_list_lock protects s_inodes */
 	spinlock_t				s_inode_list_lock ____cacheline_aligned_in_smp;
 	struct list_head			s_inodes;	/* all inodes */
diff --git a/include/linux/proc_fs.h b/include/linux/proc_fs.h
index 47d7deaeed8f..37eed34f3a93 100644
--- a/include/linux/proc_fs.h
+++ b/include/linux/proc_fs.h
@@ -70,6 +70,7 @@ struct proc_fs_info {
 	const struct cred *mounter_cred;
 	enum proc_hidepid hide_pid;
 	enum proc_pidonly pidonly;
+	atomic_t shard_seq;
 	struct rcu_head rcu;
 };
 
diff --git a/security/landlock/fs.c b/security/landlock/fs.c
index 330a1871bf94..8360a6a8a3ad 100644
--- a/security/landlock/fs.c
+++ b/security/landlock/fs.c
@@ -1383,98 +1383,114 @@ static void hook_inode_free_security_rcu(void *inode_security)
 static void hook_sb_delete(struct super_block *const sb)
 {
 	struct inode *inode, *prev_inode = NULL;
+	unsigned int nr, i;
 
 	if (!landlock_initialized)
 		return;
 
-	spin_lock(&sb->s_inode_list_lock);
-	list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
-		struct landlock_object *object;
+	nr = sb->s_inode_list_sharded ? sb->nr_shards : 1;
 
-		/* Only handles referenced inodes. */
-		if (!icount_read_once(inode))
-			continue;
+	for (i = 0; i < nr; i++) {
+		struct list_head *head;
+		spinlock_t *lock;
 
-		/*
-		 * Protects against concurrent modification of inode (e.g.
-		 * from get_inode_object()).
-		 */
-		spin_lock(&inode->i_lock);
-		/*
-		 * Checks I_FREEING and I_WILL_FREE  to protect against a race
-		 * condition when release_inode() just called iput(), which
-		 * could lead to a NULL dereference of inode->security or a
-		 * second call to iput() for the same Landlock object.  Also
-		 * checks I_NEW because such inode cannot be tied to an object.
-		 */
-		if (inode_state_read(inode) &
-		    (I_FREEING | I_WILL_FREE | I_NEW)) {
-			spin_unlock(&inode->i_lock);
-			continue;
+		if (sb->s_inode_list_sharded) {
+			head = &sb->shards[i].list;
+			lock = &sb->shards[i].lock;
+		} else {
+			head = &sb->s_inodes;
+			lock = &sb->s_inode_list_lock;
 		}
 
-		rcu_read_lock();
-		object = rcu_dereference(landlock_inode(inode)->object);
-		if (!object) {
-			rcu_read_unlock();
-			spin_unlock(&inode->i_lock);
-			continue;
-		}
-		/* Keeps a reference to this inode until the next loop walk. */
-		__iget(inode);
-		spin_unlock(&inode->i_lock);
+		spin_lock(lock);
+		list_for_each_entry(inode, head, i_sb_list) {
+			struct landlock_object *object;
 
-		/*
-		 * If there is no concurrent release_inode() ongoing, then we
-		 * are in charge of calling iput() on this inode, otherwise we
-		 * will just wait for it to finish.
-		 */
-		spin_lock(&object->lock);
-		if (object->underobj == inode) {
-			object->underobj = NULL;
-			spin_unlock(&object->lock);
-			rcu_read_unlock();
+			/* Only handles referenced inodes. */
+			if (!icount_read_once(inode))
+				continue;
 
 			/*
-			 * Because object->underobj was not NULL,
-			 * release_inode() and get_inode_object() guarantee
-			 * that it is safe to reset
-			 * landlock_inode(inode)->object while it is not NULL.
-			 * It is therefore not necessary to lock inode->i_lock.
+			 * Protects against concurrent modification of inode (e.g.
+			 * from get_inode_object()).
 			 */
-			rcu_assign_pointer(landlock_inode(inode)->object, NULL);
+			spin_lock(&inode->i_lock);
 			/*
-			 * At this point, we own the ihold() reference that was
-			 * originally set up by get_inode_object() and the
-			 * __iget() reference that we just set in this loop
-			 * walk.  Therefore there are at least two references
-			 * on the inode.
+			 * Checks I_FREEING and I_WILL_FREE  to protect against a race
+			 * condition when release_inode() just called iput(), which
+			 * could lead to a NULL dereference of inode->security or a
+			 * second call to iput() for the same Landlock object.  Also
+			 * checks I_NEW because such inode cannot be tied to an object.
 			 */
-			iput_not_last(inode);
-		} else {
-			spin_unlock(&object->lock);
-			rcu_read_unlock();
-		}
+			if (inode_state_read(inode) &
+			    (I_FREEING | I_WILL_FREE | I_NEW)) {
+				spin_unlock(&inode->i_lock);
+				continue;
+			}
+
+			rcu_read_lock();
+			object = rcu_dereference(landlock_inode(inode)->object);
+			if (!object) {
+				rcu_read_unlock();
+				spin_unlock(&inode->i_lock);
+				continue;
+			}
+			/* Keeps a reference to this inode until the next loop walk. */
+			__iget(inode);
+			spin_unlock(&inode->i_lock);
 
-		if (prev_inode) {
-			/*
-			 * At this point, we still own the __iget() reference
-			 * that we just set in this loop walk.  Therefore we
-			 * can drop the list lock and know that the inode won't
-			 * disappear from under us until the next loop walk.
-			 */
-			spin_unlock(&sb->s_inode_list_lock);
 			/*
-			 * We can now actually put the inode reference from the
-			 * previous loop walk, which is not needed anymore.
+			 * If there is no concurrent release_inode() ongoing, then we
+			 * are in charge of calling iput() on this inode, otherwise we
+			 * will just wait for it to finish.
 			 */
-			iput(prev_inode);
-			cond_resched();
-			spin_lock(&sb->s_inode_list_lock);
+			spin_lock(&object->lock);
+			if (object->underobj == inode) {
+				object->underobj = NULL;
+				spin_unlock(&object->lock);
+				rcu_read_unlock();
+
+				/*
+				 * Because object->underobj was not NULL,
+				 * release_inode() and get_inode_object() guarantee
+				 * that it is safe to reset
+				 * landlock_inode(inode)->object while it is not NULL.
+				 * It is therefore not necessary to lock inode->i_lock.
+				 */
+				rcu_assign_pointer(landlock_inode(inode)->object, NULL);
+				/*
+				 * At this point, we own the ihold() reference that was
+				 * originally set up by get_inode_object() and the
+				 * __iget() reference that we just set in this loop
+				 * walk.  Therefore there are at least two references
+				 * on the inode.
+				 */
+				iput_not_last(inode);
+			} else {
+				spin_unlock(&object->lock);
+				rcu_read_unlock();
+			}
+
+			if (prev_inode) {
+				/*
+				 * At this point, we still own the __iget() reference
+				 * that we just set in this loop walk.  Therefore we
+				 * can drop the list lock and know that the inode won't
+				 * disappear from under us until the next loop walk.
+				 */
+				spin_unlock(lock);
+				/*
+				 * We can now actually put the inode reference from the
+				 * previous loop walk, which is not needed anymore.
+				 */
+				iput(prev_inode);
+				cond_resched();
+				spin_lock(lock);
+			}
+			prev_inode = inode;
 		}
-		prev_inode = inode;
+		spin_unlock(lock);
 	}
-	spin_unlock(&sb->s_inode_list_lock);
 
 	/* Puts the inode reference from the last loop walk, if any. */
 	if (prev_inode)
-- 
2.53.0



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

end of thread, other threads:[~2026-09-20  7:32 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-20  7:28 [RFC PATCH v2 0/3] fs/proc: split the inode list for procfs Huang Shijie
2026-09-20  7:28 ` [RFC PATCH v2 1/3] fs/drop_caches: skip filesystems without page cache Huang Shijie
2026-09-20  7:28 ` [RFC PATCH v2 2/3] fs/super: introduce a helper sb_inodes_empty() Huang Shijie
2026-09-20  7:28 ` [RFC PATCH v2 3/3] fs/proc: split the inode list Huang Shijie

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®