All of lore.kernel.org
 help / color / mirror / Atom feed
From: Qi Zheng <zhengqi.arch@bytedance.com>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: linux-kernel@vger.kernel.org, linux-mm@kvack.org, x86@kernel.org,
	kvm@vger.kernel.org, xen-devel@lists.xenproject.org,
	linux-erofs@lists.ozlabs.org,
	linux-f2fs-devel@lists.sourceforge.net, cluster-devel@redhat.com,
	linux-nfs@vger.kernel.org, linux-mtd@lists.infradead.org,
	rcu@vger.kernel.org, netdev@vger.kernel.org,
	dri-devel@lists.freedesktop.org, linux-arm-msm@vger.kernel.org,
	dm-devel@redhat.com, linux-raid@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	virtualization@lists.linux-foundation.org,
	linux-fsdevel@vger.kernel.org, linux-ext4@vger.kernel.org,
	linux-xfs@vger.kernel.org, linux-btrfs@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>
Subject: [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2


WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng via Linux-erofs <linux-erofs@lists.ozlabs.org>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: kvm@vger.kernel.org, dri-devel@lists.freedesktop.org,
	virtualization@lists.linux-foundation.org, linux-mm@kvack.org,
	dm-devel@redhat.com, linux-mtd@lists.infradead.org,
	x86@kernel.org, cluster-devel@redhat.com,
	xen-devel@lists.xenproject.org, linux-ext4@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, rcu@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>,
	linux-raid@vger.kernel.org, linux-nfs@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net,
	linux-xfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-erofs@lists.ozlabs.org, linux-btrfs@vger.kernel.org
Subject: [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2


WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng <zhengqi.arch@bytedance.com>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: kvm@vger.kernel.org, dri-devel@lists.freedesktop.org,
	virtualization@lists.linux-foundation.org, linux-mm@kvack.org,
	dm-devel@redhat.com, linux-mtd@lists.infradead.org,
	x86@kernel.org, cluster-devel@redhat.com,
	xen-devel@lists.xenproject.org, linux-ext4@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, rcu@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>,
	linux-raid@vger.kernel.org, linux-nfs@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net,
	linux-xfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-erofs@lists.ozlabs.org, linux-btrfs@vger.kernel.org
Subject: [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2


WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng via Linux-f2fs-devel <linux-f2fs-devel@lists.sourceforge.net>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: kvm@vger.kernel.org, dri-devel@lists.freedesktop.org,
	virtualization@lists.linux-foundation.org, linux-mm@kvack.org,
	dm-devel@redhat.com, linux-mtd@lists.infradead.org,
	x86@kernel.org, cluster-devel@redhat.com,
	xen-devel@lists.xenproject.org, linux-ext4@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, rcu@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>,
	linux-raid@vger.kernel.org, linux-nfs@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net,
	linux-xfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-erofs@lists.ozlabs.org, linux-btrfs@vger.kernel.org
Subject: [f2fs-dev] [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2



_______________________________________________
Linux-f2fs-devel mailing list
Linux-f2fs-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux-f2fs-devel

WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng <zhengqi.arch@bytedance.com>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: linux-kernel@vger.kernel.org, linux-mm@kvack.org, x86@kernel.org,
	kvm@vger.kernel.org, xen-devel@lists.xenproject.org,
	linux-erofs@lists.ozlabs.org,
	linux-f2fs-devel@lists.sourceforge.net, cluster-devel@redhat.com,
	linux-nfs@vger.kernel.org, linux-mtd@lists.infradead.org,
	rcu@vger.kernel.org, netdev@vger.kernel.org,
	dri-devel@lists.freedesktop.org, linux-arm-msm@vger.kernel.org,
	dm-devel@redhat.com, linux-raid@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	virtualization@lists.linux-foundation.org,
	linux-fsdevel@vger.kernel.org, linux-ext4@vger.kernel.org,
	linux-xfs@vger.kernel.org, linux-btrfs@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>
Subject: [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2


______________________________________________________
Linux MTD discussion mailing list
http://lists.infradead.org/mailman/listinfo/linux-mtd/

WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng <zhengqi.arch@bytedance.com>
To: akpm@linux-foundation.org, david@fromorbit.com, tkhai@ya.ru,
	vbabka@suse.cz, roman.gushchin@linux.dev, djwong@kernel.org,
	brauner@kernel.org, paulmck@kernel.org, tytso@mit.edu,
	steven.price@arm.com, cel@kernel.org, senozhatsky@chromium.org,
	yujie.liu@intel.com, gregkh@linuxfoundation.org,
	muchun.song@linux.dev
Cc: kvm@vger.kernel.org, dri-devel@lists.freedesktop.org,
	virtualization@lists.linux-foundation.org, linux-mm@kvack.org,
	dm-devel@redhat.com, linux-mtd@lists.infradead.org,
	x86@kernel.org, cluster-devel@redhat.com,
	xen-devel@lists.xenproject.org, linux-ext4@vger.kernel.org,
	linux-arm-msm@vger.kernel.org, rcu@vger.kernel.org,
	linux-bcache@vger.kernel.org,
	Qi Zheng <zhengqi.arch@bytedance.com>,
	linux-raid@vger.kernel.org, linux-nfs@vger.kernel.org,
	netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-f2fs-devel@lists.sourceforge.net,
	linux-xfs@vger.kernel.org, linux-fsdevel@vger.kernel.org,
	linux-erofs@lists.ozlabs.org, linux-btrfs@vger.kernel.org
Subject: [dm-devel] [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov@virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch@bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu@intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng@linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0@dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2

--
dm-devel mailing list
dm-devel@redhat.com
https://listman.redhat.com/mailman/listinfo/dm-devel


WARNING: multiple messages have this Message-ID (diff)
From: Qi Zheng <zhengqi.arch@bytedance.com>
To: cluster-devel.redhat.com
Subject: [Cluster-devel] [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless
Date: Mon, 24 Jul 2023 17:43:51 +0800	[thread overview]
Message-ID: <20230724094354.90817-45-zhengqi.arch@bytedance.com> (raw)
In-Reply-To: <20230724094354.90817-1-zhengqi.arch@bytedance.com>

The shrinker_rwsem is a global read-write lock in shrinkers subsystem,
which protects most operations such as slab shrink, registration and
unregistration of shrinkers, etc. This can easily cause problems in the
following cases.

1) When the memory pressure is high and there are many filesystems
   mounted or unmounted at the same time, slab shrink will be affected
   (down_read_trylock() failed).

   Such as the real workload mentioned by Kirill Tkhai:

   ```
   One of the real workloads from my experience is start
   of an overcommitted node containing many starting
   containers after node crash (or many resuming containers
   after reboot for kernel update). In these cases memory
   pressure is huge, and the node goes round in long reclaim.
   ```

2) If a shrinker is blocked (such as the case mentioned
   in [1]) and a writer comes in (such as mount a fs),
   then this writer will be blocked and cause all
   subsequent shrinker-related operations to be blocked.

Even if there is no competitor when shrinking slab, there may still be a
problem. The down_read_trylock() may become a perf hotspot with frequent
calls to shrink_slab(). Because of the poor multicore scalability of
atomic operations, this can lead to a significant drop in IPC
(instructions per cycle).

We used to implement the lockless slab shrink with SRCU [2], but then
kernel test robot reported -88.8% regression in
stress-ng.ramfs.ops_per_sec test case [3], so we reverted it [4].

This commit uses the refcount+RCU method [5] proposed by Dave Chinner
to re-implement the lockless global slab shrink. The memcg slab shrink is
handled in the subsequent patch.

For now, all shrinker instances are converted to dynamically allocated and
will be freed by kfree_rcu(). So we can use rcu_read_{lock,unlock}() to
ensure that the shrinker instance is valid.

And the shrinker instance will not be run again after unregistration. So
the structure that records the pointer of shrinker instance can be safely
freed without waiting for the RCU read-side critical section.

In this way, while we implement the lockless slab shrink, we don't need to
be blocked in unregister_shrinker().

The following are the test results:

stress-ng --timeout 60 --times --verify --metrics-brief --ramfs 9 &

1) Before applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            735238     60.00     12.37    363.70     12253.05        1955.08
for a 60.01s run time:
   1440.27s available CPU time
     12.36s user time   (  0.86%)
    363.70s system time ( 25.25%)
    376.06s total time  ( 26.11%)
load average: 10.79 4.47 1.69
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

2) After applying this patchset:

setting to a 60 second run per stressor
dispatching hogs: 9 ramfs
stressor       bogo ops real time  usr time  sys time   bogo ops/s     bogo ops/s
                          (secs)    (secs)    (secs)   (real time) (usr+sys time)
ramfs            746677     60.00     12.22    367.75     12443.70        1965.13
for a 60.01s run time:
   1440.26s available CPU time
     12.21s user time   (  0.85%)
    367.75s system time ( 25.53%)
    379.96s total time  ( 26.38%)
load average: 8.37 2.48 0.86
passed: 9: ramfs (9)
failed: 0
skipped: 0
successful run completed in 60.01s (1 min, 0.01 secs)

We can see that the ops/s has hardly changed.

[1]. https://lore.kernel.org/lkml/20191129214541.3110-1-ptikhomirov at virtuozzo.com/
[2]. https://lore.kernel.org/lkml/20230313112819.38938-1-zhengqi.arch at bytedance.com/
[3]. https://lore.kernel.org/lkml/202305230837.db2c233f-yujie.liu at intel.com/
[4]. https://lore.kernel.org/all/20230609081518.3039120-1-qi.zheng at linux.dev/
[5]. https://lore.kernel.org/lkml/ZIJhou1d55d4H1s0 at dread.disaster.area/

Signed-off-by: Qi Zheng <zhengqi.arch@bytedance.com>
---
 include/linux/shrinker.h | 19 +++++++---
 mm/shrinker.c            | 75 ++++++++++++++++++++++++++--------------
 mm/shrinker_debug.c      | 52 +++++++++++++++++++++-------
 3 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/include/linux/shrinker.h b/include/linux/shrinker.h
index 36977a70bebb..335da93cccee 100644
--- a/include/linux/shrinker.h
+++ b/include/linux/shrinker.h
@@ -4,6 +4,7 @@
 
 #include <linux/atomic.h>
 #include <linux/types.h>
+#include <linux/refcount.h>
 
 #define SHRINKER_UNIT_BITS	BITS_PER_LONG
 
@@ -86,6 +87,10 @@ struct shrinker {
 	long batch;	/* reclaim batch size, 0 = default */
 	int seeks;	/* seeks to recreate an obj */
 	unsigned flags;
+	bool registered;
+
+	refcount_t refcount;
+	struct rcu_head rcu;
 
 	void *private_data;
 
@@ -106,14 +111,13 @@ struct shrinker {
 #define DEFAULT_SEEKS 2 /* A good number if you don't know better. */
 
 /* Flags */
-#define SHRINKER_REGISTERED	(1 << 0)
-#define SHRINKER_NUMA_AWARE	(1 << 1)
-#define SHRINKER_MEMCG_AWARE	(1 << 2)
+#define SHRINKER_NUMA_AWARE	(1 << 0)
+#define SHRINKER_MEMCG_AWARE	(1 << 1)
 /*
  * It just makes sense when the shrinker is also MEMCG_AWARE for now,
  * non-MEMCG_AWARE shrinker should not have this flag set.
  */
-#define SHRINKER_NONSLAB	(1 << 3)
+#define SHRINKER_NONSLAB	(1 << 2)
 
 unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 			  int priority);
@@ -122,6 +126,13 @@ void shrinker_free_non_registered(struct shrinker *shrinker);
 void shrinker_register(struct shrinker *shrinker);
 void shrinker_unregister(struct shrinker *shrinker);
 
+static inline bool shrinker_try_get(struct shrinker *shrinker)
+{
+	return READ_ONCE(shrinker->registered) &&
+	       refcount_inc_not_zero(&shrinker->refcount);
+}
+void shrinker_put(struct shrinker *shrinker);
+
 #ifdef CONFIG_SHRINKER_DEBUG
 extern int shrinker_debugfs_add(struct shrinker *shrinker);
 extern struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
diff --git a/mm/shrinker.c b/mm/shrinker.c
index 8a1fe844f1a4..8e3334749552 100644
--- a/mm/shrinker.c
+++ b/mm/shrinker.c
@@ -2,10 +2,13 @@
 #include <linux/memcontrol.h>
 #include <linux/rwsem.h>
 #include <linux/shrinker.h>
+#include <linux/rculist.h>
+#include <linux/spinlock.h>
 #include <trace/events/vmscan.h>
 
 LIST_HEAD(shrinker_list);
 DECLARE_RWSEM(shrinker_rwsem);
+DEFINE_SPINLOCK(shrinker_lock);
 
 #ifdef CONFIG_MEMCG
 static int shrinker_nr_max;
@@ -450,6 +453,18 @@ static unsigned long do_shrink_slab(struct shrink_control *shrinkctl,
 	return freed;
 }
 
+void shrinker_put(struct shrinker *shrinker)
+{
+	if (refcount_dec_and_test(&shrinker->refcount)) {
+		spin_lock(&shrinker_lock);
+		list_del_rcu(&shrinker->list);
+		spin_unlock(&shrinker_lock);
+
+		kfree(shrinker->nr_deferred);
+		kfree_rcu(shrinker, rcu);
+	}
+}
+
 #ifdef CONFIG_MEMCG
 static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			struct mem_cgroup *memcg, int priority)
@@ -483,7 +498,8 @@ static unsigned long shrink_slab_memcg(gfp_t gfp_mask, int nid,
 			int shrinker_id = calc_shrinker_id(index, offset);
 
 			shrinker = idr_find(&shrinker_idr, shrinker_id);
-			if (unlikely(!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))) {
+			if (unlikely(!shrinker ||
+				     !READ_ONCE(shrinker->registered))) {
 				if (!shrinker)
 					clear_bit(offset, unit->map);
 				continue;
@@ -575,33 +591,42 @@ unsigned long shrink_slab(gfp_t gfp_mask, int nid, struct mem_cgroup *memcg,
 	if (!mem_cgroup_disabled() && !mem_cgroup_is_root(memcg))
 		return shrink_slab_memcg(gfp_mask, nid, memcg, priority);
 
-	if (!down_read_trylock(&shrinker_rwsem))
-		goto out;
-
-	list_for_each_entry(shrinker, &shrinker_list, list) {
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
 		struct shrink_control sc = {
 			.gfp_mask = gfp_mask,
 			.nid = nid,
 			.memcg = memcg,
 		};
 
+		if (!shrinker_try_get(shrinker))
+			continue;
+
+		/*
+		 * We can safely unlock the RCU lock here since we already
+		 * hold the refcount of the shrinker.
+		 */
+		rcu_read_unlock();
+
 		ret = do_shrink_slab(&sc, shrinker, priority);
 		if (ret == SHRINK_EMPTY)
 			ret = 0;
 		freed += ret;
+
 		/*
-		 * Bail out if someone want to register a new shrinker to
-		 * prevent the registration from being stalled for long periods
-		 * by parallel ongoing shrinking.
+		 * This shrinker may be deleted from shrinker_list and freed in
+		 * the shrinker_put() below, but this shrinker is still used for
+		 * the next traversal. So it is necessary to hold the RCU lock
+		 * first to prevent this shrinker from being freed, which also
+		 * ensures that the next shrinker that is traversed will not be
+		 * freed (even if it is deleted from shrinker_list at the same
+		 * time).
 		 */
-		if (rwsem_is_contended(&shrinker_rwsem)) {
-			freed = freed ? : 1;
-			break;
-		}
+		rcu_read_lock();
+		shrinker_put(shrinker);
 	}
 
-	up_read(&shrinker_rwsem);
-out:
+	rcu_read_unlock();
 	cond_resched();
 	return freed;
 }
@@ -686,11 +711,14 @@ EXPORT_SYMBOL(shrinker_free_non_registered);
 
 void shrinker_register(struct shrinker *shrinker)
 {
-	down_write(&shrinker_rwsem);
-	list_add_tail(&shrinker->list, &shrinker_list);
-	shrinker->flags |= SHRINKER_REGISTERED;
+	refcount_set(&shrinker->refcount, 1);
+
+	spin_lock(&shrinker_lock);
+	list_add_tail_rcu(&shrinker->list, &shrinker_list);
+	spin_unlock(&shrinker_lock);
+
 	shrinker_debugfs_add(shrinker);
-	up_write(&shrinker_rwsem);
+	WRITE_ONCE(shrinker->registered, true);
 }
 EXPORT_SYMBOL(shrinker_register);
 
@@ -699,12 +727,12 @@ void shrinker_unregister(struct shrinker *shrinker)
 	struct dentry *debugfs_entry;
 	int debugfs_id;
 
-	if (!shrinker || !(shrinker->flags & SHRINKER_REGISTERED))
+	if (!shrinker || !READ_ONCE(shrinker->registered))
 		return;
 
+	WRITE_ONCE(shrinker->registered, false);
+
 	down_write(&shrinker_rwsem);
-	list_del(&shrinker->list);
-	shrinker->flags &= ~SHRINKER_REGISTERED;
 	if (shrinker->flags & SHRINKER_MEMCG_AWARE)
 		unregister_memcg_shrinker(shrinker);
 	debugfs_entry = shrinker_debugfs_detach(shrinker, &debugfs_id);
@@ -712,9 +740,6 @@ void shrinker_unregister(struct shrinker *shrinker)
 
 	shrinker_debugfs_remove(debugfs_entry, debugfs_id);
 
-	kfree(shrinker->nr_deferred);
-	shrinker->nr_deferred = NULL;
-
-	kfree(shrinker);
+	shrinker_put(shrinker);
 }
 EXPORT_SYMBOL(shrinker_unregister);
diff --git a/mm/shrinker_debug.c b/mm/shrinker_debug.c
index f1becfd45853..c5573066adbf 100644
--- a/mm/shrinker_debug.c
+++ b/mm/shrinker_debug.c
@@ -5,6 +5,7 @@
 #include <linux/seq_file.h>
 #include <linux/shrinker.h>
 #include <linux/memcontrol.h>
+#include <linux/rculist.h>
 
 /* defined in vmscan.c */
 extern struct rw_semaphore shrinker_rwsem;
@@ -161,17 +162,21 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 {
 	struct dentry *entry;
 	char buf[128];
-	int id;
-
-	lockdep_assert_held(&shrinker_rwsem);
+	int id, ret = 0;
 
 	/* debugfs isn't initialized yet, add debugfs entries later. */
 	if (!shrinker_debugfs_root)
 		return 0;
 
+	down_write(&shrinker_rwsem);
+	if (shrinker->debugfs_entry)
+		goto fail;
+
 	id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
-	if (id < 0)
-		return id;
+	if (id < 0) {
+		ret = id;
+		goto fail;
+	}
 	shrinker->debugfs_id = id;
 
 	snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
@@ -180,7 +185,8 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 	entry = debugfs_create_dir(buf, shrinker_debugfs_root);
 	if (IS_ERR(entry)) {
 		ida_free(&shrinker_debugfs_ida, id);
-		return PTR_ERR(entry);
+		ret = PTR_ERR(entry);
+		goto fail;
 	}
 	shrinker->debugfs_entry = entry;
 
@@ -188,7 +194,10 @@ int shrinker_debugfs_add(struct shrinker *shrinker)
 			    &shrinker_debugfs_count_fops);
 	debugfs_create_file("scan", 0220, entry, shrinker,
 			    &shrinker_debugfs_scan_fops);
-	return 0;
+
+fail:
+	up_write(&shrinker_rwsem);
+	return ret;
 }
 
 int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
@@ -243,6 +252,11 @@ struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
 	shrinker->name = NULL;
 
 	*debugfs_id = entry ? shrinker->debugfs_id : -1;
+	/*
+	 * Ensure that shrinker->registered has been set to false before
+	 * shrinker->debugfs_entry is set to NULL.
+	 */
+	smp_wmb();
 	shrinker->debugfs_entry = NULL;
 
 	return entry;
@@ -266,14 +280,26 @@ static int __init shrinker_debugfs_init(void)
 	shrinker_debugfs_root = dentry;
 
 	/* Create debugfs entries for shrinkers registered at boot */
-	down_write(&shrinker_rwsem);
-	list_for_each_entry(shrinker, &shrinker_list, list)
+	rcu_read_lock();
+	list_for_each_entry_rcu(shrinker, &shrinker_list, list) {
+		if (!shrinker_try_get(shrinker))
+			continue;
+		rcu_read_unlock();
+
 		if (!shrinker->debugfs_entry) {
-			ret = shrinker_debugfs_add(shrinker);
-			if (ret)
-				break;
+			/* Paired with smp_wmb() in shrinker_debugfs_detach() */
+			smp_rmb();
+			if (READ_ONCE(shrinker->registered))
+				ret = shrinker_debugfs_add(shrinker);
 		}
-	up_write(&shrinker_rwsem);
+
+		rcu_read_lock();
+		shrinker_put(shrinker);
+
+		if (ret)
+			break;
+	}
+	rcu_read_unlock();
 
 	return ret;
 }
-- 
2.30.2


  parent reply	other threads:[~2023-07-24 10:02 UTC|newest]

Thread overview: 763+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-07-24  9:43 [PATCH v2 00/47] use refcount+RCU method to implement lockless slab shrink Qi Zheng
2023-07-24  9:43 ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43 ` [dm-devel] " Qi Zheng
2023-07-24  9:43 ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` Qi Zheng
2023-07-24  9:43 ` Qi Zheng via Linux-erofs
2023-07-24  9:43 ` Qi Zheng
2023-07-24  9:43 ` [PATCH v2 01/47] mm: vmscan: move shrinker-related code into a separate file Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-25  2:35   ` Muchun Song
2023-07-25  2:35     ` [Cluster-devel] " Muchun Song
2023-07-25  2:35     ` Muchun Song
2023-07-25  2:35     ` Muchun Song
2023-07-25  2:35     ` [dm-devel] " Muchun Song
2023-07-25  2:35     ` Muchun Song
2023-07-25  2:35     ` [f2fs-dev] " Muchun Song
2023-07-25  3:09     ` Qi Zheng
2023-07-25  3:09       ` [Cluster-devel] " Qi Zheng
2023-07-25  3:09       ` Qi Zheng via Linux-erofs
2023-07-25  3:09       ` [dm-devel] " Qi Zheng
2023-07-25  3:09       ` Qi Zheng
2023-07-25  3:09       ` Qi Zheng
2023-07-25  3:09       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-25  3:23       ` Muchun Song
2023-07-25  3:23         ` [Cluster-devel] " Muchun Song
2023-07-25  3:23         ` Muchun Song
2023-07-25  3:23         ` Muchun Song
2023-07-25  3:23         ` [dm-devel] " Muchun Song
2023-07-25  3:23         ` Muchun Song
2023-07-25  3:23         ` [f2fs-dev] " Muchun Song
2023-07-25  3:27         ` Qi Zheng
2023-07-25  3:27           ` [Cluster-devel] " Qi Zheng
2023-07-25  3:27           ` Qi Zheng via Linux-erofs
2023-07-25  3:27           ` [dm-devel] " Qi Zheng
2023-07-25  3:27           ` Qi Zheng
2023-07-25  3:27           ` Qi Zheng
2023-07-25  3:27           ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 02/47] mm: shrinker: remove redundant shrinker_rwsem in debugfs operations Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-25  3:17   ` Muchun Song
2023-07-25  3:17     ` [Cluster-devel] " Muchun Song
2023-07-25  3:17     ` Muchun Song
2023-07-25  3:17     ` Muchun Song
2023-07-25  3:17     ` [dm-devel] " Muchun Song
2023-07-25  3:17     ` Muchun Song
2023-07-25  3:17     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 03/47] mm: shrinker: add infrastructure for dynamically allocating shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24 12:25   ` Peter Zijlstra
2023-07-24 12:25     ` [Cluster-devel] " Peter Zijlstra
2023-07-24 12:25     ` [dm-devel] " Peter Zijlstra
2023-07-24 12:25     ` Peter Zijlstra
2023-07-24 12:25     ` [f2fs-dev] " Peter Zijlstra
2023-07-24 12:25     ` Peter Zijlstra
2023-07-24 12:25     ` Peter Zijlstra
2023-07-25  3:01     ` Qi Zheng
2023-07-25  3:01       ` [Cluster-devel] " Qi Zheng
2023-07-25  3:01       ` [dm-devel] " Qi Zheng
2023-07-25  3:01       ` Qi Zheng
2023-07-25  3:01       ` Qi Zheng via Linux-erofs
2023-07-25  3:01       ` Qi Zheng
2023-07-25  3:01       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-25  9:02   ` Muchun Song
2023-07-25  9:02     ` [Cluster-devel] " Muchun Song
2023-07-25  9:02     ` [dm-devel] " Muchun Song
2023-07-25  9:02     ` Muchun Song
2023-07-25  9:02     ` Muchun Song
2023-07-25  9:02     ` [f2fs-dev] " Muchun Song
2023-07-25  9:56     ` Qi Zheng
2023-07-25  9:56       ` [Cluster-devel] " Qi Zheng
2023-07-25  9:56       ` [dm-devel] " Qi Zheng
2023-07-25  9:56       ` Qi Zheng
2023-07-25  9:56       ` Qi Zheng via Linux-erofs
2023-07-25  9:56       ` Qi Zheng
2023-07-25  9:56       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-26  7:26   ` Dave Chinner
2023-07-26  7:26     ` [Cluster-devel] " Dave Chinner
2023-07-26  7:26     ` [f2fs-dev] " Dave Chinner via Linux-f2fs-devel
2023-07-26  7:26     ` [dm-devel] " Dave Chinner
2023-07-26  7:26     ` Dave Chinner
2023-07-26  7:26     ` Dave Chinner via Linux-erofs
2023-07-26  7:26     ` Dave Chinner via Virtualization
2023-07-26  7:26     ` Dave Chinner
2023-07-26  9:20     ` Qi Zheng
2023-07-26  9:20       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:20       ` [dm-devel] " Qi Zheng
2023-07-26  9:20       ` Qi Zheng
2023-07-26  9:20       ` Qi Zheng via Linux-erofs
2023-07-26  9:20       ` Qi Zheng
2023-07-26  9:20       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 04/47] kvm: mmu: dynamically allocate the x86-mmu shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-25  9:16   ` Muchun Song
2023-07-25  9:16     ` [Cluster-devel] " Muchun Song
2023-07-25  9:16     ` Muchun Song
2023-07-25  9:16     ` Muchun Song
2023-07-25  9:16     ` [dm-devel] " Muchun Song
2023-07-25  9:16     ` Muchun Song
2023-07-25  9:16     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 05/47] binder: dynamically allocate the android-binder shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:56   ` Qi Zheng
2023-07-24  9:56     ` [Cluster-devel] " Qi Zheng
2023-07-24  9:56     ` [dm-devel] " Qi Zheng
2023-07-24  9:56     ` Qi Zheng
2023-07-24  9:56     ` Qi Zheng via Linux-erofs
2023-07-24  9:56     ` Qi Zheng
2023-07-24  9:56     ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 06/47] drm/ttm: dynamically allocate the drm-ttm_pool shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-25  9:19   ` Muchun Song
2023-07-25  9:19     ` [Cluster-devel] " Muchun Song
2023-07-25  9:19     ` Muchun Song
2023-07-25  9:19     ` [dm-devel] " Muchun Song
2023-07-25  9:19     ` Muchun Song
2023-07-25  9:19     ` Muchun Song
2023-07-25  9:19     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 07/47] xenbus/backend: dynamically allocate the xen-backend shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-25  9:22   ` Muchun Song
2023-07-25  9:22     ` [Cluster-devel] " Muchun Song
2023-07-25  9:22     ` Muchun Song
2023-07-25  9:22     ` [dm-devel] " Muchun Song
2023-07-25  9:22     ` Muchun Song
2023-07-25  9:22     ` Muchun Song
2023-07-25  9:22     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 08/47] erofs: dynamically allocate the erofs-shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-25  9:24   ` Muchun Song
2023-07-25  9:24     ` [Cluster-devel] " Muchun Song
2023-07-25  9:24     ` Muchun Song
2023-07-25  9:24     ` [dm-devel] " Muchun Song
2023-07-25  9:24     ` Muchun Song
2023-07-25  9:24     ` Muchun Song
2023-07-25  9:24     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 09/47] f2fs: dynamically allocate the f2fs-shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-25  9:25   ` Muchun Song
2023-07-25  9:25     ` [Cluster-devel] " Muchun Song
2023-07-25  9:25     ` Muchun Song
2023-07-25  9:25     ` [dm-devel] " Muchun Song
2023-07-25  9:25     ` Muchun Song
2023-07-25  9:25     ` Muchun Song
2023-07-25  9:25     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 10/47] gfs2: dynamically allocate the gfs2-glock shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-26  6:43   ` Muchun Song
2023-07-26  6:43     ` [Cluster-devel] " Muchun Song
2023-07-26  6:43     ` [dm-devel] " Muchun Song
2023-07-26  6:43     ` Muchun Song
2023-07-26  6:43     ` Muchun Song
2023-07-26  6:43     ` Muchun Song
2023-07-26  6:43     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 11/47] gfs2: dynamically allocate the gfs2-qd shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  6:49   ` Muchun Song
2023-07-26  6:49     ` [Cluster-devel] " Muchun Song
2023-07-26  6:49     ` [dm-devel] " Muchun Song
2023-07-26  6:49     ` Muchun Song
2023-07-26  6:49     ` Muchun Song
2023-07-26  6:49     ` [f2fs-dev] " Muchun Song
2023-07-26  9:22     ` Qi Zheng
2023-07-26  9:22       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:22       ` [dm-devel] " Qi Zheng
2023-07-26  9:22       ` Qi Zheng via Linux-erofs
2023-07-26  9:22       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-26  9:22       ` Qi Zheng
2023-07-26  9:22       ` Qi Zheng
2023-07-24  9:43 ` [PATCH v2 12/47] NFSv4.2: dynamically allocate the nfs-xattr shrinkers Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-26  6:55   ` Muchun Song
2023-07-26  6:55     ` [Cluster-devel] " Muchun Song
2023-07-26  6:55     ` [dm-devel] " Muchun Song
2023-07-26  6:55     ` Muchun Song
2023-07-26  6:55     ` Muchun Song
2023-07-26  6:55     ` Muchun Song
2023-07-26  6:55     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 13/47] nfs: dynamically allocate the nfs-acl shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  6:57   ` Muchun Song
2023-07-26  6:57     ` [Cluster-devel] " Muchun Song
2023-07-26  6:57     ` [dm-devel] " Muchun Song
2023-07-26  6:57     ` Muchun Song
2023-07-26  6:57     ` Muchun Song
2023-07-26  6:57     ` Muchun Song
2023-07-26  6:57     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 14/47] nfsd: dynamically allocate the nfsd-filecache shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  6:59   ` Muchun Song
2023-07-26  6:59     ` [Cluster-devel] " Muchun Song
2023-07-26  6:59     ` [dm-devel] " Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 15/47] quota: dynamically allocate the dquota-cache shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  6:59   ` Muchun Song
2023-07-26  6:59     ` [Cluster-devel] " Muchun Song
2023-07-26  6:59     ` [dm-devel] " Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` Muchun Song
2023-07-26  6:59     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 16/47] ubifs: dynamically allocate the ubifs-slab shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:00   ` Muchun Song
2023-07-26  7:00     ` [Cluster-devel] " Muchun Song
2023-07-26  7:00     ` [dm-devel] " Muchun Song
2023-07-26  7:00     ` Muchun Song
2023-07-26  7:00     ` Muchun Song
2023-07-26  7:00     ` Muchun Song
2023-07-26  7:00     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 17/47] rcu: dynamically allocate the rcu-lazy shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:04   ` Muchun Song
2023-07-26  7:04     ` [Cluster-devel] " Muchun Song
2023-07-26  7:04     ` [dm-devel] " Muchun Song
2023-07-26  7:04     ` Muchun Song
2023-07-26  7:04     ` Muchun Song
2023-07-26  7:04     ` Muchun Song
2023-07-26  7:04     ` [f2fs-dev] " Muchun Song
2023-07-26  9:24     ` Qi Zheng
2023-07-26  9:24       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:24       ` [dm-devel] " Qi Zheng
2023-07-26  9:24       ` Qi Zheng
2023-07-26  9:24       ` Qi Zheng via Linux-erofs
2023-07-26  9:24       ` Qi Zheng
2023-07-26  9:24       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 18/47] rcu: dynamically allocate the rcu-kfree shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:05   ` Muchun Song
2023-07-26  7:05     ` [Cluster-devel] " Muchun Song
2023-07-26  7:05     ` [dm-devel] " Muchun Song
2023-07-26  7:05     ` Muchun Song
2023-07-26  7:05     ` Muchun Song
2023-07-26  7:05     ` [f2fs-dev] " Muchun Song
2023-07-26  7:05     ` Muchun Song
2023-07-24  9:43 ` [PATCH v2 19/47] mm: thp: dynamically allocate the thp-related shrinkers Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:10   ` Muchun Song
2023-07-26  7:10     ` [Cluster-devel] " Muchun Song
2023-07-26  7:10     ` [dm-devel] " Muchun Song
2023-07-26  7:10     ` Muchun Song
2023-07-26  7:10     ` Muchun Song
2023-07-26  7:10     ` [f2fs-dev] " Muchun Song
2023-07-26  9:27     ` Qi Zheng
2023-07-26  9:27       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:27       ` [dm-devel] " Qi Zheng
2023-07-26  9:27       ` Qi Zheng
2023-07-26  9:27       ` Qi Zheng via Linux-erofs
2023-07-26  9:27       ` Qi Zheng
2023-07-26  9:27       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 20/47] sunrpc: dynamically allocate the sunrpc_cred shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:11   ` Muchun Song
2023-07-26  7:11     ` [Cluster-devel] " Muchun Song
2023-07-26  7:11     ` [dm-devel] " Muchun Song
2023-07-26  7:11     ` Muchun Song
2023-07-26  7:11     ` Muchun Song
2023-07-26  7:11     ` Muchun Song
2023-07-26  7:11     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 21/47] mm: workingset: dynamically allocate the mm-shadow shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:13   ` Muchun Song
2023-07-26  7:13     ` [Cluster-devel] " Muchun Song
2023-07-26  7:13     ` [dm-devel] " Muchun Song
2023-07-26  7:13     ` Muchun Song
2023-07-26  7:13     ` Muchun Song
2023-07-26  7:13     ` Muchun Song
2023-07-26  7:13     ` [f2fs-dev] " Muchun Song
2023-07-26  9:28     ` Qi Zheng
2023-07-26  9:28       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:28       ` [dm-devel] " Qi Zheng
2023-07-26  9:28       ` Qi Zheng
2023-07-26  9:28       ` Qi Zheng via Linux-erofs
2023-07-26  9:28       ` Qi Zheng
2023-07-26  9:28       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 22/47] drm/i915: dynamically allocate the i915_gem_mm shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:15   ` Muchun Song
2023-07-26  7:15     ` [Cluster-devel] " Muchun Song
2023-07-26  7:15     ` [dm-devel] " Muchun Song
2023-07-26  7:15     ` Muchun Song
2023-07-26  7:15     ` Muchun Song
2023-07-26  7:15     ` Muchun Song
2023-07-26  7:15     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 23/47] drm/msm: dynamically allocate the drm-msm_gem shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:24   ` Muchun Song
2023-07-26  7:24     ` [Cluster-devel] " Muchun Song
2023-07-26  7:24     ` [dm-devel] " Muchun Song
2023-07-26  7:24     ` Muchun Song
2023-07-26  7:24     ` Muchun Song
2023-07-26  7:24     ` [f2fs-dev] " Muchun Song
2023-07-26  9:31     ` Qi Zheng
2023-07-26  9:31       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:31       ` [dm-devel] " Qi Zheng
2023-07-26  9:31       ` Qi Zheng
2023-07-26  9:31       ` Qi Zheng via Linux-erofs
2023-07-26  9:31       ` Qi Zheng
2023-07-26  9:31       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 24/47] drm/panfrost: dynamically allocate the drm-panfrost shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24 11:17   ` Steven Price
2023-07-24 11:17     ` [Cluster-devel] " Steven Price
2023-07-24 11:17     ` [dm-devel] " Steven Price
2023-07-24 11:17     ` [f2fs-dev] " Steven Price
2023-07-24 11:17     ` Steven Price
2023-07-24 11:17     ` Steven Price
2023-07-25  3:05     ` Qi Zheng
2023-07-25  3:05       ` [Cluster-devel] " Qi Zheng
2023-07-25  3:05       ` [dm-devel] " Qi Zheng
2023-07-25  3:05       ` Qi Zheng
2023-07-25  3:05       ` Qi Zheng via Linux-erofs
2023-07-25  3:05       ` Qi Zheng
2023-07-25  3:05       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 25/47] dm: dynamically allocate the dm-bufio shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:24   ` Muchun Song
2023-07-26  7:24     ` [Cluster-devel] " Muchun Song
2023-07-26  7:24     ` [dm-devel] " Muchun Song
2023-07-26  7:24     ` Muchun Song
2023-07-26  7:24     ` Muchun Song
2023-07-26  7:24     ` Muchun Song
2023-07-26  7:24     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 26/47] dm zoned: dynamically allocate the dm-zoned-meta shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:25   ` Muchun Song
2023-07-26  7:25     ` [Cluster-devel] " Muchun Song
2023-07-26  7:25     ` [dm-devel] " Muchun Song
2023-07-26  7:25     ` Muchun Song
2023-07-26  7:25     ` Muchun Song
2023-07-26  7:25     ` Muchun Song
2023-07-26  7:25     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 27/47] md/raid5: dynamically allocate the md-raid5 shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:27   ` Muchun Song
2023-07-26  7:27     ` [Cluster-devel] " Muchun Song
2023-07-26  7:27     ` [dm-devel] " Muchun Song
2023-07-26  7:27     ` Muchun Song
2023-07-26  7:27     ` Muchun Song
2023-07-26  7:27     ` Muchun Song
2023-07-26  7:27     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 28/47] bcache: dynamically allocate the md-bcache shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:32   ` Muchun Song
2023-07-26  7:32     ` [Cluster-devel] " Muchun Song
2023-07-26  7:32     ` [dm-devel] " Muchun Song
2023-07-26  7:32     ` Muchun Song
2023-07-26  7:32     ` Muchun Song
2023-07-26  7:32     ` [f2fs-dev] " Muchun Song
2023-07-26  9:33     ` Qi Zheng
2023-07-26  9:33       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:33       ` [dm-devel] " Qi Zheng
2023-07-26  9:33       ` Qi Zheng via Linux-erofs
2023-07-26  9:33       ` Qi Zheng
2023-07-26  9:33       ` Qi Zheng
2023-07-26  9:33       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 29/47] vmw_balloon: dynamically allocate the vmw-balloon shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:35   ` Muchun Song
2023-07-26  7:35     ` [Cluster-devel] " Muchun Song
2023-07-26  7:35     ` Muchun Song
2023-07-26  7:35     ` [dm-devel] " Muchun Song
2023-07-26  7:35     ` Muchun Song
2023-07-26  7:35     ` Muchun Song
2023-07-26  7:35     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 30/47] virtio_balloon: dynamically allocate the virtio-balloon shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:36   ` Muchun Song
2023-07-26  7:36     ` [Cluster-devel] " Muchun Song
2023-07-26  7:36     ` Muchun Song
2023-07-26  7:36     ` [dm-devel] " Muchun Song
2023-07-26  7:36     ` Muchun Song
2023-07-26  7:36     ` Muchun Song
2023-07-26  7:36     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 31/47] mbcache: dynamically allocate the mbcache shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-26  7:39   ` Muchun Song
2023-07-26  7:39     ` [Cluster-devel] " Muchun Song
2023-07-26  7:39     ` Muchun Song
2023-07-26  7:39     ` [dm-devel] " Muchun Song
2023-07-26  7:39     ` Muchun Song
2023-07-26  7:39     ` [f2fs-dev] " Muchun Song
2023-07-26  7:39     ` Muchun Song
2023-07-24  9:43 ` [PATCH v2 32/47] ext4: dynamically allocate the ext4-es shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:40   ` Muchun Song
2023-07-26  7:40     ` [Cluster-devel] " Muchun Song
2023-07-26  7:40     ` Muchun Song
2023-07-26  7:40     ` [dm-devel] " Muchun Song
2023-07-26  7:40     ` Muchun Song
2023-07-26  7:40     ` Muchun Song
2023-07-26  7:40     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 33/47] jbd2,ext4: dynamically allocate the jbd2-journal shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] [PATCH v2 33/47] jbd2, ext4: " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [PATCH v2 33/47] jbd2,ext4: " Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] [PATCH v2 33/47] jbd2, ext4: " Qi Zheng via Linux-f2fs-devel
2023-07-26  7:41   ` [PATCH v2 33/47] jbd2,ext4: " Muchun Song
2023-07-26  7:41     ` [Cluster-devel] [PATCH v2 33/47] jbd2, ext4: " Muchun Song
2023-07-26  7:41     ` Muchun Song
2023-07-26  7:41     ` [dm-devel] " Muchun Song
2023-07-26  7:41     ` [PATCH v2 33/47] jbd2,ext4: " Muchun Song
2023-07-26  7:41     ` Muchun Song
2023-07-26  7:41     ` [f2fs-dev] [PATCH v2 33/47] jbd2, ext4: " Muchun Song
2023-07-24  9:43 ` [PATCH v2 34/47] nfsd: dynamically allocate the nfsd-client shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24 12:17   ` Jeff Layton
2023-07-24 12:17     ` [Cluster-devel] " Jeff Layton
2023-07-24 12:17     ` [dm-devel] " Jeff Layton
2023-07-24 12:17     ` [f2fs-dev] " Jeff Layton
2023-07-24 12:17     ` Jeff Layton
2023-07-24 12:17     ` Jeff Layton
2023-07-24  9:43 ` [PATCH v2 35/47] nfsd: dynamically allocate the nfsd-reply shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24 12:17   ` Jeff Layton
2023-07-24 12:17     ` [Cluster-devel] " Jeff Layton
2023-07-24 12:17     ` [dm-devel] " Jeff Layton
2023-07-24 12:17     ` [f2fs-dev] " Jeff Layton
2023-07-24 12:17     ` Jeff Layton
2023-07-24 12:17     ` Jeff Layton
2023-07-24  9:43 ` [PATCH v2 36/47] xfs: dynamically allocate the xfs-buf shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:42   ` Muchun Song
2023-07-26  7:42     ` [Cluster-devel] " Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` [dm-devel] " Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 37/47] xfs: dynamically allocate the xfs-inodegc shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-26  7:42   ` Muchun Song
2023-07-26  7:42     ` [Cluster-devel] " Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` [dm-devel] " Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` Muchun Song
2023-07-26  7:42     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 38/47] xfs: dynamically allocate the xfs-qm shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:43   ` Muchun Song
2023-07-26  7:43     ` [Cluster-devel] " Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` [dm-devel] " Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 39/47] zsmalloc: dynamically allocate the mm-zspool shrinker Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-26  7:43   ` Muchun Song
2023-07-26  7:43     ` [Cluster-devel] " Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` [dm-devel] " Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` Muchun Song
2023-07-26  7:43     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 40/47] fs: super: dynamically allocate the s_shrink Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:45   ` Muchun Song
2023-07-26  7:45     ` [Cluster-devel] " Muchun Song
2023-07-26  7:45     ` [dm-devel] " Muchun Song
2023-07-26  7:45     ` Muchun Song
2023-07-26  7:45     ` Muchun Song
2023-07-26  7:45     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 41/47] mm: shrinker: remove old APIs Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-26  7:46   ` Muchun Song
2023-07-26  7:46     ` [Cluster-devel] " Muchun Song
2023-07-26  7:46     ` Muchun Song
2023-07-26  7:46     ` [dm-devel] " Muchun Song
2023-07-26  7:46     ` Muchun Song
2023-07-26  7:46     ` Muchun Song
2023-07-26  7:46     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 42/47] drm/ttm: introduce pool_shrink_rwsem Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-26  7:56   ` Muchun Song
2023-07-26  7:56     ` [Cluster-devel] " Muchun Song
2023-07-26  7:56     ` Muchun Song
2023-07-26  7:56     ` [dm-devel] " Muchun Song
2023-07-26  7:56     ` Muchun Song
2023-07-26  7:56     ` Muchun Song
2023-07-26  7:56     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` [PATCH v2 43/47] mm: shrinker: add a secondary array for shrinker_info::{map, nr_deferred} Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-26  9:30   ` Muchun Song
2023-07-26  9:30     ` [Cluster-devel] " Muchun Song
2023-07-26  9:30     ` Muchun Song
2023-07-26  9:30     ` [dm-devel] " Muchun Song
2023-07-26  9:30     ` Muchun Song
2023-07-26  9:30     ` Muchun Song
2023-07-26  9:30     ` [f2fs-dev] " Muchun Song
2023-07-24  9:43 ` Qi Zheng [this message]
2023-07-24  9:43   ` [Cluster-devel] [PATCH v2 44/47] mm: shrinker: make global slab shrink lockless Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-26  8:08   ` Dave Chinner
2023-07-26  8:08     ` [Cluster-devel] " Dave Chinner
2023-07-26  8:08     ` [dm-devel] " Dave Chinner
2023-07-26  8:08     ` Dave Chinner via Linux-erofs
2023-07-26  8:08     ` Dave Chinner
2023-07-26  8:08     ` [f2fs-dev] " Dave Chinner via Linux-f2fs-devel
2023-07-26  8:08     ` Dave Chinner
2023-07-26  8:08     ` Dave Chinner via Virtualization
2023-07-26  9:14     ` Qi Zheng
2023-07-26  9:14       ` [Cluster-devel] " Qi Zheng
2023-07-26  9:14       ` [dm-devel] " Qi Zheng
2023-07-26  9:14       ` Qi Zheng
2023-07-26  9:14       ` Qi Zheng via Linux-erofs
2023-07-26  9:14       ` Qi Zheng
2023-07-26  9:14       ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-26 23:09       ` Dave Chinner
2023-07-26 23:09         ` [Cluster-devel] " Dave Chinner
2023-07-26 23:09         ` [dm-devel] " Dave Chinner
2023-07-26 23:09         ` Dave Chinner
2023-07-26 23:09         ` Dave Chinner
2023-07-26 23:09         ` Dave Chinner via Linux-erofs
2023-07-26 23:09         ` Dave Chinner via Virtualization
2023-07-26 23:09         ` [f2fs-dev] " Dave Chinner via Linux-f2fs-devel
2023-07-27  3:34         ` Qi Zheng
2023-07-27  3:34           ` [Cluster-devel] " Qi Zheng
2023-07-27  3:34           ` [dm-devel] " Qi Zheng
2023-07-27  3:34           ` Qi Zheng
2023-07-27  3:34           ` Qi Zheng
2023-07-27  3:34           ` Qi Zheng via Linux-erofs
2023-07-27  3:34           ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43 ` [PATCH v2 45/47] mm: shrinker: make memcg " Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43 ` [PATCH v2 46/47] mm: shrinker: hold write lock to reparent shrinker nr_deferred Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43 ` [PATCH v2 47/47] mm: shrinker: convert shrinker_rwsem to mutex Qi Zheng
2023-07-24  9:43   ` [Cluster-devel] " Qi Zheng
2023-07-24  9:43   ` [dm-devel] " Qi Zheng
2023-07-24  9:43   ` [f2fs-dev] " Qi Zheng via Linux-f2fs-devel
2023-07-24  9:43   ` Qi Zheng
2023-07-24  9:43   ` Qi Zheng via Linux-erofs
2023-07-24  9:43   ` Qi Zheng

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230724094354.90817-45-zhengqi.arch@bytedance.com \
    --to=zhengqi.arch@bytedance.com \
    --cc=akpm@linux-foundation.org \
    --cc=brauner@kernel.org \
    --cc=cel@kernel.org \
    --cc=cluster-devel@redhat.com \
    --cc=david@fromorbit.com \
    --cc=djwong@kernel.org \
    --cc=dm-devel@redhat.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=kvm@vger.kernel.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-bcache@vger.kernel.org \
    --cc=linux-btrfs@vger.kernel.org \
    --cc=linux-erofs@lists.ozlabs.org \
    --cc=linux-ext4@vger.kernel.org \
    --cc=linux-f2fs-devel@lists.sourceforge.net \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-mm@kvack.org \
    --cc=linux-mtd@lists.infradead.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=linux-raid@vger.kernel.org \
    --cc=linux-xfs@vger.kernel.org \
    --cc=muchun.song@linux.dev \
    --cc=netdev@vger.kernel.org \
    --cc=paulmck@kernel.org \
    --cc=rcu@vger.kernel.org \
    --cc=roman.gushchin@linux.dev \
    --cc=senozhatsky@chromium.org \
    --cc=steven.price@arm.com \
    --cc=tkhai@ya.ru \
    --cc=tytso@mit.edu \
    --cc=vbabka@suse.cz \
    --cc=virtualization@lists.linux-foundation.org \
    --cc=x86@kernel.org \
    --cc=xen-devel@lists.xenproject.org \
    --cc=yujie.liu@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is 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.