Linux cgroups development
 help / color / mirror / Atom feed
* [PATCH 2/3] mm/zswap: Implement proactive writeback
From: Hao Jia @ 2026-05-11 10:51 UTC (permalink / raw)
  To: akpm, tj, hannes, shakeel.butt, mhocko, yosry, mkoutny, nphamcs,
	chengming.zhou, muchun.song, roman.gushchin
  Cc: cgroups, linux-mm, linux-kernel, linux-doc, Hao Jia
In-Reply-To: <20260511105149.75584-1-jiahao.kernel@gmail.com>

From: Hao Jia <jiahao1@lixiang.com>

Zswap currently writes back pages to backing swap devices reactively,
triggered either by memory pressure via the shrinker or by the pool
reaching its size limit. This reactive approach offers no precise
control over when writeback happens, which can disturb latency-sensitive
workloads, and it cannot direct writeback at a specific memory cgroup.
However, there are scenarios where users might want to proactively
write back cold pages from zswap to the backing swap device, for
example, to free up memory for other applications or to prepare for
upcoming memory-intensive workloads.

Therefore, implement a proactive writeback mechanism for zswap by
adding a new cgroup interface file memory.zswap.proactive_writeback
within the memory controller.

Users can trigger writeback by writing to this file with the following
parameters:
- max=<bytes>: The maximum amount of memory to write back (optional,
  default: unlimited).
- <age>: The minimum age of the pages to write back. Only pages that
  have been in zswap for at least this duration will be written back.

Example usage:
  # Write back pages older than 1 hour (3600 seconds), max 10MB
  echo "max=10M 3600" > memory.zswap.proactive_writeback

The implementation consists of:
1. Add store_time to struct zswap_entry to record when each entry was
   inserted into zswap, used for proactive writeback age comparison.
2. Introduce struct zswap_shrink_walk_arg, passed as the cb_arg to
   list_lru_walk_one() in both the shrinker and proactive paths. It
   carries the per-invocation cutoff_time and proactive flag down to
   shrink_memcg_cb(), and propagates the encountered_page_in_swapcache
   out-signal from the callback back to the caller.
3. Modify the callback function shrink_memcg_cb() to proactively
   writeback zswap_entries that meet the time threshold.
4. Add zswap_proactive_writeback() as the proactive writeback driver:
   a per-node batched list_lru_walk_one() loop bounded by the
   writeback budget.

Signed-off-by: Hao Jia <jiahao1@lixiang.com>
---
 Documentation/admin-guide/cgroup-v2.rst |  24 ++++
 include/linux/zswap.h                   |   8 ++
 mm/memcontrol.c                         |  76 ++++++++++
 mm/zswap.c                              | 176 ++++++++++++++++++++++--
 4 files changed, 276 insertions(+), 8 deletions(-)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 6efd0095ed99..05b664b3b3e8 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1908,6 +1908,30 @@ The following nested keys are defined.
 	This setting has no effect if zswap is disabled, and swapping
 	is allowed unless memory.swap.max is set to 0.
 
+  memory.zswap.proactive_writeback
+	A write-only nested-keyed file which exists in non-root cgroups.
+
+	This interface allows proactive writeback of pages from the zswap
+	pool to the backing swap device. This is useful to offload cold
+	pages from the zswap pool to the slower swap device. It is only
+	available if zswap writeback is enabled.
+
+	Users can trigger writeback by writing to this file with the following
+	parameters:
+
+	- "max=<bytes>" : Optional. The maximum amount of data to write back.
+	  (default: unlimited). Please note that the kernel can over or under
+	  writeback this value.
+
+	- "<age>" : Required. The minimum age of the pages to write back
+	  (in seconds). Only pages that have been in the zswap pool for at
+	  least this amount of time will be written back.
+
+	Example::
+
+	  # Write back pages older than 1 hour (3600 seconds), max 10MB
+	  echo "max=10M 3600" > memory.zswap.proactive_writeback
+
   memory.pressure
 	A read-only nested-keyed file.
 
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index efa6b551217e..7a51b4f95017 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -44,6 +44,8 @@ void zswap_lruvec_state_init(struct lruvec *lruvec);
 void zswap_folio_swapin(struct folio *folio);
 bool zswap_is_enabled(void);
 bool zswap_never_enabled(void);
+int zswap_proactive_writeback(struct mem_cgroup *root, unsigned long nr_max_writeback,
+			      ktime_t cutoff);
 #else
 
 struct zswap_lruvec_state {};
@@ -78,6 +80,12 @@ static inline bool zswap_never_enabled(void)
 	return true;
 }
 
+static inline int zswap_proactive_writeback(struct mem_cgroup *root,
+					    unsigned long nr_max_writeback, ktime_t cutoff)
+{
+	return 0;
+}
+
 #endif
 
 #endif /* _LINUX_ZSWAP_H */
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 409c41359dc8..ba7f7b1954a8 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -70,6 +70,7 @@
 #include "memcontrol-v1.h"
 
 #include <linux/uaccess.h>
+#include <linux/parser.h>
 
 #define CREATE_TRACE_POINTS
 #include <trace/events/memcg.h>
@@ -5891,6 +5892,76 @@ static ssize_t zswap_writeback_write(struct kernfs_open_file *of,
 	return nbytes;
 }
 
+enum {
+	ZSWAP_WRITEBACK_MAX,
+	ZSWAP_WRITEBACK_AGE,
+	ZSWAP_WRITEBACK_ERR,
+};
+
+static const match_table_t zswap_writeback_tokens = {
+	{ ZSWAP_WRITEBACK_MAX, "max=%s" },
+	{ ZSWAP_WRITEBACK_AGE, "%u" },
+	{ ZSWAP_WRITEBACK_ERR, NULL },
+};
+
+static ssize_t zswap_proactive_writeback_write(struct kernfs_open_file *of,
+					       char *buf, size_t nbytes,
+					       loff_t off)
+{
+	struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of));
+	unsigned long nr_max_writeback = ULONG_MAX;
+	substring_t args[MAX_OPT_ARGS];
+	unsigned int age_sec;
+	bool age_set = false;
+	ktime_t cutoff_time;
+	char *token, *end;
+	int err;
+
+	if (!mem_cgroup_zswap_writeback_enabled(memcg))
+		return -EINVAL;
+
+	buf = strstrip(buf);
+
+	while ((token = strsep(&buf, " ")) != NULL) {
+		if (!strlen(token))
+			continue;
+
+		switch (match_token(token, zswap_writeback_tokens, args)) {
+		case ZSWAP_WRITEBACK_MAX:
+			nr_max_writeback = memparse(args[0].from, &end);
+			if (*end != '\0')
+				return -EINVAL;
+			nr_max_writeback >>= PAGE_SHIFT;
+			break;
+		case ZSWAP_WRITEBACK_AGE:
+			if (age_set)
+				return -EINVAL;
+
+			if (match_uint(&args[0], &age_sec))
+				return -EINVAL;
+			age_set = true;
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+	if (!age_set || !age_sec || !nr_max_writeback)
+		return -EINVAL;
+
+	cutoff_time = ktime_sub(ktime_get_boottime(),
+				ns_to_ktime((u64)age_sec * NSEC_PER_SEC));
+	/* age_sec >= uptime: no entry can be that old, skip the walk. */
+	if (ktime_to_ns(cutoff_time) <= 0)
+		return nbytes;
+
+	err = zswap_proactive_writeback(memcg, nr_max_writeback, cutoff_time);
+	if (err)
+		return err;
+
+	return nbytes;
+}
+
 static struct cftype zswap_files[] = {
 	{
 		.name = "zswap.current",
@@ -5908,6 +5979,11 @@ static struct cftype zswap_files[] = {
 		.seq_show = zswap_writeback_show,
 		.write = zswap_writeback_write,
 	},
+	{
+		.name = "zswap.proactive_writeback",
+		.flags = CFTYPE_NOT_ON_ROOT,
+		.write = zswap_proactive_writeback_write,
+	},
 	{ }	/* terminate */
 };
 #endif /* CONFIG_ZSWAP */
diff --git a/mm/zswap.c b/mm/zswap.c
index 19538d6f169a..1173ac6836fa 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -36,6 +36,7 @@
 #include <linux/workqueue.h>
 #include <linux/list_lru.h>
 #include <linux/zsmalloc.h>
+#include <linux/timekeeping.h>
 
 #include "swap.h"
 #include "internal.h"
@@ -160,6 +161,12 @@ struct zswap_pool {
 	char tfm_name[CRYPTO_MAX_ALG_NAME];
 };
 
+struct zswap_shrink_walk_arg {
+	ktime_t cutoff_time;
+	bool proactive;
+	bool encountered_page_in_swapcache;
+};
+
 /* Global LRU lists shared by all zswap pools. */
 static struct list_lru zswap_list_lru;
 
@@ -183,6 +190,7 @@ static struct shrinker *zswap_shrinker;
  * handle - zsmalloc allocation handle that stores the compressed page data
  * objcg - the obj_cgroup that the compressed memory is charged to
  * lru - handle to the pool's lru used to evict pages.
+ * store_time - Time when the entry was stored, for proactive writeback.
  */
 struct zswap_entry {
 	swp_entry_t swpentry;
@@ -192,6 +200,7 @@ struct zswap_entry {
 	unsigned long handle;
 	struct obj_cgroup *objcg;
 	struct list_head lru;
+	ktime_t store_time;
 };
 
 static struct xarray *zswap_trees[MAX_SWAPFILES];
@@ -1148,10 +1157,19 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o
 				       void *arg)
 {
 	struct zswap_entry *entry = container_of(item, struct zswap_entry, lru);
-	bool *encountered_page_in_swapcache = (bool *)arg;
-	swp_entry_t swpentry;
+	struct zswap_shrink_walk_arg *walk_arg = arg;
+	bool proactive_wb = walk_arg && walk_arg->proactive;
 	enum lru_status ret = LRU_REMOVED_RETRY;
 	int writeback_result;
+	swp_entry_t swpentry;
+
+	/*
+	 * For proactive writeback, rotate young entries to the LRU tail
+	 * so that subsequent list_lru_walk_one() batches start past
+	 * them.
+	 */
+	if (proactive_wb && ktime_after(entry->store_time, walk_arg->cutoff_time))
+		return LRU_ROTATE;
 
 	/*
 	 * Second chance algorithm: if the entry has its referenced bit set, give it
@@ -1160,7 +1178,9 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o
 	 */
 	if (entry->referenced) {
 		entry->referenced = false;
-		return LRU_ROTATE;
+		/* Proactive writeback is an explicit hint; don't rotate. */
+		if (!proactive_wb)
+			return LRU_ROTATE;
 	}
 
 	/*
@@ -1214,9 +1234,9 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o
 		 * into the warmer region. We should terminate shrinking (if we're in the dynamic
 		 * shrinker context).
 		 */
-		if (writeback_result == -EEXIST && encountered_page_in_swapcache) {
+		if (writeback_result == -EEXIST && walk_arg) {
 			ret = LRU_STOP;
-			*encountered_page_in_swapcache = true;
+			walk_arg->encountered_page_in_swapcache = true;
 		}
 	} else {
 		zswap_written_back_pages++;
@@ -1228,8 +1248,12 @@ static enum lru_status shrink_memcg_cb(struct list_head *item, struct list_lru_o
 static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
 		struct shrink_control *sc)
 {
+	struct zswap_shrink_walk_arg walk_arg = {
+		.cutoff_time = KTIME_MAX,
+		.proactive = false,
+		.encountered_page_in_swapcache = false,
+	};
 	unsigned long shrink_ret;
-	bool encountered_page_in_swapcache = false;
 
 	if (!zswap_shrinker_enabled ||
 			!mem_cgroup_zswap_writeback_enabled(sc->memcg)) {
@@ -1238,9 +1262,9 @@ static unsigned long zswap_shrinker_scan(struct shrinker *shrinker,
 	}
 
 	shrink_ret = list_lru_shrink_walk(&zswap_list_lru, sc, &shrink_memcg_cb,
-		&encountered_page_in_swapcache);
+					  &walk_arg);
 
-	if (encountered_page_in_swapcache)
+	if (walk_arg.encountered_page_in_swapcache)
 		return SHRINK_STOP;
 
 	return shrink_ret ? shrink_ret : SHRINK_STOP;
@@ -1508,6 +1532,7 @@ static bool zswap_store_page(struct page *page,
 	entry->swpentry = page_swpentry;
 	entry->objcg = objcg;
 	entry->referenced = true;
+	entry->store_time = ktime_get_boottime();
 	if (entry->length) {
 		INIT_LIST_HEAD(&entry->lru);
 		zswap_lru_add(&zswap_list_lru, entry);
@@ -1681,6 +1706,141 @@ int zswap_load(struct folio *folio)
 	return 0;
 }
 
+/* Cap LRU scan to this many entries per page of remaining budget. */
+#define ZSWAP_PROACTIVE_WB_SCAN_RATIO	16UL
+/*
+ * Batch size for proactive writeback, used both as the per-memcg
+ * writeback target in the outer memcg loop and as the per-walk budget
+ * for list_lru_walk_one().
+ */
+#define ZSWAP_PROACTIVE_WB_BATCH	128UL
+
+/*
+ * Walk @memcg's per-node LRUs, writing back entries older than @cutoff
+ * up to @nr_to_write pages. Returns the number of pages written back,
+ * or -ENOENT if @memcg is a zombie or has writeback disabled.
+ */
+static long zswap_proactive_shrink_memcg(struct mem_cgroup *memcg,
+					 ktime_t cutoff,
+					 unsigned long nr_to_write)
+{
+	unsigned long nr_written = 0;
+	int nid;
+
+	if (!mem_cgroup_zswap_writeback_enabled(memcg))
+		return -ENOENT;
+
+	if (!mem_cgroup_online(memcg))
+		return -ENOENT;
+
+	for_each_node_state(nid, N_NORMAL_MEMORY) {
+		struct zswap_shrink_walk_arg walk_arg = {
+			.cutoff_time = cutoff,
+			.proactive = true,
+			.encountered_page_in_swapcache = false,
+		};
+		unsigned long nr_to_scan, nr_scanned = 0;
+
+		/*
+		 * Cap by LRU length: bounds rewalks when entries keep
+		 * rotating (young or referenced).
+		 */
+		nr_to_scan = list_lru_count_one(&zswap_list_lru, nid, memcg);
+		if (!nr_to_scan)
+			continue;
+
+		/*
+		 * Cap by SCAN_RATIO * remaining budget: bounds scan cost
+		 * to the remaining writeback budget.
+		 */
+		nr_to_scan = min(nr_to_scan,
+				 (nr_to_write - nr_written) * ZSWAP_PROACTIVE_WB_SCAN_RATIO);
+
+		while (nr_scanned < nr_to_scan) {
+			unsigned long nr_to_walk = min(ZSWAP_PROACTIVE_WB_BATCH,
+						       nr_to_scan - nr_scanned);
+
+			if (signal_pending(current))
+				return nr_written;
+
+			/*
+			 * Account the committed budget rather than the walker's
+			 * actual delta: if the list empties under us the walker
+			 * visits nothing and nr_scanned would never advance.
+			 */
+			nr_scanned += nr_to_walk;
+
+			nr_written += list_lru_walk_one(&zswap_list_lru, nid, memcg,
+							&shrink_memcg_cb, &walk_arg,
+							&nr_to_walk);
+
+			if (nr_written >= nr_to_write)
+				return nr_written;
+			if (walk_arg.encountered_page_in_swapcache)
+				break;
+
+			cond_resched();
+		}
+	}
+
+	return nr_written;
+}
+
+int zswap_proactive_writeback(struct mem_cgroup *root,
+			      unsigned long nr_max_writeback,
+			      ktime_t cutoff)
+{
+	struct mem_cgroup *memcg;
+	unsigned long nr_written = 0;
+	int failures = 0, attempts = 0;
+
+	/*
+	 * Writeback will be aborted with -EAGAIN if @nr_written is still
+	 * zero and we encounter the following MAX_RECLAIM_RETRIES times:
+	 * - No writeback-candidate memcgs found in a subtree walk.
+	 * - A writeback-candidate memcg wrote back zero pages.
+	 */
+	while (nr_written < nr_max_writeback) {
+		unsigned long nr_to_write;
+		long shrunk;
+
+		if (signal_pending(current))
+			return -EINTR;
+
+		memcg = zswap_mem_cgroup_iter(root);
+
+		if (!memcg) {
+			/*
+			 * Continue without incrementing failures if we found
+			 * candidate memcgs in the last subtree walk.
+			 */
+			if (!attempts && ++failures == MAX_RECLAIM_RETRIES)
+				goto out;
+			attempts = 0;
+			continue;
+		}
+
+		nr_to_write = min(nr_max_writeback - nr_written,
+				  ZSWAP_PROACTIVE_WB_BATCH);
+		shrunk = zswap_proactive_shrink_memcg(memcg, cutoff, nr_to_write);
+		mem_cgroup_put(memcg);
+
+		/* Writeback-disabled or offline: skip without counting. */
+		if (shrunk == -ENOENT)
+			continue;
+
+		++attempts;
+		if (shrunk > 0)
+			nr_written += shrunk;
+		else if (++failures == MAX_RECLAIM_RETRIES)
+			goto out;
+
+		cond_resched();
+	}
+out:
+	return nr_written ? 0 : -EAGAIN;
+}
+
 void zswap_invalidate(swp_entry_t swp)
 {
 	pgoff_t offset = swp_offset(swp);
-- 
2.34.1


^ permalink raw reply related

* [PATCH 1/3] mm/zswap: Make shrink_worker writeback cursor per-memcg
From: Hao Jia @ 2026-05-11 10:51 UTC (permalink / raw)
  To: akpm, tj, hannes, shakeel.butt, mhocko, yosry, mkoutny, nphamcs,
	chengming.zhou, muchun.song, roman.gushchin
  Cc: cgroups, linux-mm, linux-kernel, linux-doc, Hao Jia
In-Reply-To: <20260511105149.75584-1-jiahao.kernel@gmail.com>

From: Hao Jia <jiahao1@lixiang.com>

The zswap background writeback worker shrink_worker() uses a global
cursor zswap_next_shrink, protected by zswap_shrink_lock, to round-robin
across the online memcgs under root_mem_cgroup.

Proactive writeback, about to be introduced by
memory.zswap.proactive_writeback, also wants a similar per-memcg cursor
that is scoped to the specified memcg, so that repeated invocations
against the same memcg make forward progress across its descendant
memcgs instead of restarting from the first child memcg each time.

Naturally, group the cursor and its protecting spinlock into a
zswap_wb_iter struct, and make it a member of struct mem_cgroup to
realize per-memcg cursor management. Accordingly, shrink_worker() now
uses the lock and cursor in root_mem_cgroup->zswap_wb_iter.

Because the cursor is now per-memcg, the offline cleanup must visit
every ancestor that could be holding a reference to the dying memcg.
Factor out __zswap_memcg_offline_cleanup() and walk from dead_memcg up
to the root.

No functional change intended for shrink_worker().

Signed-off-by: Hao Jia <jiahao1@lixiang.com>
---
 include/linux/memcontrol.h |   6 ++
 include/linux/zswap.h      |   9 +++
 mm/memcontrol.c            |   3 +
 mm/zswap.c                 | 116 +++++++++++++++++++++++++------------
 4 files changed, 98 insertions(+), 36 deletions(-)

diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index dc3fa687759b..00ae646a3a15 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -228,6 +228,12 @@ struct mem_cgroup {
 	 * swap, and from being swapped out on zswap store failures.
 	 */
 	bool zswap_writeback;
+
+	/*
+	 * Per-memcg writeback cursor: root by shrink_worker, non-root by
+	 * proactive writeback.
+	 */
+	struct zswap_wb_iter zswap_wb_iter;
 #endif
 
 	/* vmpressure notifications */
diff --git a/include/linux/zswap.h b/include/linux/zswap.h
index 30c193a1207e..efa6b551217e 100644
--- a/include/linux/zswap.h
+++ b/include/linux/zswap.h
@@ -11,6 +11,15 @@ extern atomic_long_t zswap_stored_pages;
 
 #ifdef CONFIG_ZSWAP
 
+/* Iteration cursor for zswap writeback over a memcg's subtree. */
+struct zswap_wb_iter {
+	/* protects @pos against concurrent advances */
+	spinlock_t lock;
+	struct mem_cgroup *pos;
+};
+
+void zswap_wb_iter_init(struct zswap_wb_iter *iter);
+
 struct zswap_lruvec_state {
 	/*
 	 * Number of swapped in pages from disk, i.e not found in the zswap pool.
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index c03d4787d466..409c41359dc8 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -4022,6 +4022,9 @@ static struct mem_cgroup *mem_cgroup_alloc(struct mem_cgroup *parent)
 	INIT_LIST_HEAD(&memcg->memory_peaks);
 	INIT_LIST_HEAD(&memcg->swap_peaks);
 	spin_lock_init(&memcg->peaks_lock);
+#ifdef CONFIG_ZSWAP
+	zswap_wb_iter_init(&memcg->zswap_wb_iter);
+#endif
 	memcg->socket_pressure = get_jiffies_64();
 #if BITS_PER_LONG < 64
 	seqlock_init(&memcg->socket_pressure_seqlock);
diff --git a/mm/zswap.c b/mm/zswap.c
index 4b5149173b0e..19538d6f169a 100644
--- a/mm/zswap.c
+++ b/mm/zswap.c
@@ -163,9 +163,6 @@ struct zswap_pool {
 /* Global LRU lists shared by all zswap pools. */
 static struct list_lru zswap_list_lru;
 
-/* The lock protects zswap_next_shrink updates. */
-static DEFINE_SPINLOCK(zswap_shrink_lock);
-static struct mem_cgroup *zswap_next_shrink;
 static struct work_struct zswap_shrink_work;
 static struct shrinker *zswap_shrinker;
 
@@ -717,28 +714,85 @@ void zswap_folio_swapin(struct folio *folio)
 	}
 }
 
-/*
- * This function should be called when a memcg is being offlined.
+void zswap_wb_iter_init(struct zswap_wb_iter *iter)
+{
+	spin_lock_init(&iter->lock);
+}
+
+#ifdef CONFIG_MEMCG
+/**
+ * zswap_mem_cgroup_iter - advance the writeback cursor
+ * @root: subtree root whose cursor to advance
+ *
+ * Advance @root->zswap_wb_iter.pos to @root itself or the next online
+ * descendant. Passing root_mem_cgroup yields a global walk.
+ *
+ * The cursor is retained across invocations, so successive calls walk
+ * @root's subtree cyclically in pre-order and, after %NULL, restart
+ * from the beginning.
  *
- * Since the global shrinker shrink_worker() may hold a reference
- * of the memcg, we must check and release the reference in
- * zswap_next_shrink.
+ * The returned memcg carries an extra reference; release it with
+ * mem_cgroup_put().
  *
- * shrink_worker() must handle the case where this function releases
- * the reference of memcg being shrunk.
+ * Return: the next online memcg in @root's subtree, or @root itself,
+ * with an extra reference, or %NULL after a full round-trip.
  */
-void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg)
+static struct mem_cgroup *zswap_mem_cgroup_iter(struct mem_cgroup *root)
 {
-	/* lock out zswap shrinker walking memcg tree */
-	spin_lock(&zswap_shrink_lock);
-	if (zswap_next_shrink == memcg) {
+	struct mem_cgroup *memcg;
+
+	spin_lock(&root->zswap_wb_iter.lock);
+	do {
+		memcg = mem_cgroup_iter(root, root->zswap_wb_iter.pos, NULL);
+		root->zswap_wb_iter.pos = memcg;
+	} while (memcg && !mem_cgroup_tryget_online(memcg));
+	spin_unlock(&root->zswap_wb_iter.lock);
+
+	return memcg;
+}
+
+/*
+ * If @root's cursor currently points at @dead_memcg, advance it to the
+ * next online descendant so @dead_memcg can be freed.
+ */
+static void __zswap_memcg_offline_cleanup(struct mem_cgroup *root,
+					  struct mem_cgroup *dead_memcg)
+{
+	spin_lock(&root->zswap_wb_iter.lock);
+	if (root->zswap_wb_iter.pos == dead_memcg) {
 		do {
-			zswap_next_shrink = mem_cgroup_iter(NULL, zswap_next_shrink, NULL);
-		} while (zswap_next_shrink && !mem_cgroup_online(zswap_next_shrink));
+			root->zswap_wb_iter.pos =
+				mem_cgroup_iter(root,
+						root->zswap_wb_iter.pos, NULL);
+		} while (root->zswap_wb_iter.pos &&
+			 !mem_cgroup_online(root->zswap_wb_iter.pos));
 	}
-	spin_unlock(&zswap_shrink_lock);
+	spin_unlock(&root->zswap_wb_iter.lock);
 }
 
+/*
+ * Called when a memcg is being offlined. If @memcg or any of its
+ * ancestors has a cursor pointing at @memcg, it must be advanced
+ * past @memcg before @memcg can be freed. Walk the chain and
+ * release such references.
+ */
+void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg)
+{
+	struct mem_cgroup *parent = memcg;
+
+	do {
+		__zswap_memcg_offline_cleanup(parent, memcg);
+	} while ((parent = parent_mem_cgroup(parent)));
+}
+#else /* !CONFIG_MEMCG */
+static struct mem_cgroup *zswap_mem_cgroup_iter(struct mem_cgroup *root)
+{
+	return NULL;
+}
+
+void zswap_memcg_offline_cleanup(struct mem_cgroup *memcg) { }
+#endif /* CONFIG_MEMCG */
+
 /*********************************
 * zswap entry functions
 **********************************/
@@ -1328,38 +1382,28 @@ static void shrink_worker(struct work_struct *w)
 	 * - No writeback-candidate memcgs found in a memcg tree walk.
 	 * - Shrinking a writeback-candidate memcg failed.
 	 *
-	 * We save iteration cursor memcg into zswap_next_shrink,
+	 * We save the iteration cursor in root_mem_cgroup->zswap_wb_iter.pos,
 	 * which can be modified by the offline memcg cleaner
 	 * zswap_memcg_offline_cleanup().
 	 *
 	 * Since the offline cleaner is called only once, we cannot leave an
-	 * offline memcg reference in zswap_next_shrink.
+	 * offline memcg reference in root_mem_cgroup->zswap_wb_iter.pos.
 	 * We can rely on the cleaner only if we get online memcg under lock.
 	 *
 	 * If we get an offline memcg, we cannot determine if the cleaner has
 	 * already been called or will be called later. We must put back the
 	 * reference before returning from this function. Otherwise, the
-	 * offline memcg left in zswap_next_shrink will hold the reference
-	 * until the next run of shrink_worker().
+	 * offline memcg left in root_mem_cgroup->zswap_wb_iter.pos will hold
+	 * the reference until the next run of shrink_worker().
 	 */
 	do {
 		/*
-		 * Start shrinking from the next memcg after zswap_next_shrink.
-		 * When the offline cleaner has already advanced the cursor,
-		 * advancing the cursor here overlooks one memcg, but this
-		 * should be negligibly rare.
-		 *
-		 * If we get an online memcg, keep the extra reference in case
-		 * the original one obtained by mem_cgroup_iter() is dropped by
-		 * zswap_memcg_offline_cleanup() while we are shrinking the
-		 * memcg.
+		 * Start shrinking from the next memcg after
+		 * root_mem_cgroup->zswap_wb_iter.pos. When the offline cleaner
+		 * has already advanced the cursor, advancing the cursor here
+		 * overlooks one memcg, but this should be negligibly rare.
 		 */
-		spin_lock(&zswap_shrink_lock);
-		do {
-			memcg = mem_cgroup_iter(NULL, zswap_next_shrink, NULL);
-			zswap_next_shrink = memcg;
-		} while (memcg && !mem_cgroup_tryget_online(memcg));
-		spin_unlock(&zswap_shrink_lock);
+		memcg = zswap_mem_cgroup_iter(root_mem_cgroup);
 
 		if (!memcg) {
 			/*
-- 
2.34.1


^ permalink raw reply related

* [PATCH 0/3] mm/zswap: Implement per-cgroup proactive writeback
From: Hao Jia @ 2026-05-11 10:51 UTC (permalink / raw)
  To: akpm, tj, hannes, shakeel.butt, mhocko, yosry, mkoutny, nphamcs,
	chengming.zhou, muchun.song, roman.gushchin
  Cc: cgroups, linux-mm, linux-kernel, linux-doc, Hao Jia

From: Hao Jia <jiahao1@lixiang.com>

Zswap currently writes back pages to backing swap devices reactively,
triggered either by memory pressure via the shrinker or by the pool
reaching its size limit. However, this reactive approach makes writeback
timing indeterminate and can disrupt latency-sensitive workloads when
eviction happens to coincide with a critical execution window.

Furthermore, in certain scenarios, it is desirable to trigger writeback
in advance to free up memory. For example, users may want to prepare for
an upcoming memory-intensive workload by flushing cold memory to the
backing storage when the system is relatively idle.

To address these issues, this patch series introduces a per-cgroup
interface that allows users to proactively write back cold compressed
pages from zswap to the backing swap device.

Users can trigger writeback by writing to this interface with the following
parameters:

- "max=<bytes>" : Optional. The maximum amount of data to write back.
    (default: unlimited).
- "<age>" : Required. The minimum age of the pages to write back
    (in seconds). Only pages that have been in the zswap pool for at
    least this amount of time will be written back.

Example usage:
  # Write back pages older than 1 hour (3600 seconds), max 10MB
  echo "max=10M 3600" > memory.zswap.proactive_writeback

Patch 1: Move the global zswap shrink cursor into struct mem_cgroup as a
  per-memcg zswap_wb_iter, so patch 2 can scope writeback to a given memcg
  and make forward progress across its subtree on repeated invocations.

Patch 2: Add the memory.zswap.proactive_writeback cgroupv2 interface,
  allowing users to trigger writeback with optional size limit and
  age threshold.

Patch 3: Add a zswpwb_proactive counter to memory.stat and /proc/vmstat
  to track the number of writebacks triggered by proactive writeback.

Hao Jia (3):
  mm/zswap: Make shrink_worker writeback cursor per-memcg
  mm/zswap: Implement proactive writeback
  mm/zswap: Add per-memcg stat for proactive writeback

 Documentation/admin-guide/cgroup-v2.rst |  28 +++
 include/linux/memcontrol.h              |   6 +
 include/linux/vm_event_item.h           |   1 +
 include/linux/zswap.h                   |  17 ++
 mm/memcontrol.c                         |  80 +++++++
 mm/vmstat.c                             |   1 +
 mm/zswap.c                              | 303 ++++++++++++++++++++----
 7 files changed, 390 insertions(+), 46 deletions(-)

--
2.34.1

^ permalink raw reply

* Re: [PATCH] cgroup: Keep favordynmods enabled once per-threadgroup rwsem is active
From: Guopeng Zhang @ 2026-05-11  9:53 UTC (permalink / raw)
  To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný
  Cc: Yi Tao, cgroups, linux-kernel
In-Reply-To: <22d91fdc-db54-4bcf-bc5a-2a496cc43057@huaweicloud.com>



在 2026/5/11 17:05, Chen Ridong 写道:
> 
> 
> On 2026/5/11 16:16, Guopeng Zhang wrote:
>> cgroup_enable_per_threadgroup_rwsem is a one-way switch. Once it is
>> enabled, cgroup.procs writes use the per-threadgroup rwsem and
>> cgroup_threadgroup_change_begin()/end() use the same global state to
>> decide whether to take and release the per-threadgroup rwsem.
>>
>> The disable path warned that the per-threadgroup rwsem mechanism could not
>> be disabled but still called rcu_sync_exit() and cleared
>> CGRP_ROOT_FAVOR_DYNMODS. That partially disabled favordynmods while the
>> global per-threadgroup rwsem mode remained enabled: cgroup.procs writes
>> would continue to use the per-threadgroup rwsem, while
>> cgroup_threadgroup_change_begin()/end() could observe the exited rcu_sync
>> state. The root would also no longer report favordynmods.
>>
>> Make the transition match the documented one-way semantics. Call
>> rcu_sync_enter() only for the first favordynmods enable, and make later
>> disable attempts a no-op after warning once the per-threadgroup rwsem mode
>> has been enabled.
>>
>> Fixes: 0568f89d4fb8 ("cgroup: replace global percpu_rwsem with per threadgroup resem when writing to cgroup.procs")
>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>> ---
>> Manual AB test:
>>
>> Before this patch:
>>   enable favordynmods:
>>     cgroup2 opts: rw,relatime,favordynmods
>>   disable attempt:
>>     cgroup2 opts: rw,relatime
>>   dmesg:
>>     cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled
>>
>> After this patch:
>>   enable favordynmods:
>>     cgroup2 opts: rw,relatime,favordynmods
>>   disable attempt:
>>     cgroup2 opts: rw,relatime,favordynmods
>>   dmesg:
>>     cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled
>>
>>  kernel/cgroup/cgroup.c | 11 +++++------
>>  1 file changed, 5 insertions(+), 6 deletions(-)
>>
>> diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
>> index 6152add0c5eb..fd10fb5b3598 100644
>> --- a/kernel/cgroup/cgroup.c
>> +++ b/kernel/cgroup/cgroup.c
>> @@ -1297,14 +1297,13 @@ void cgroup_favor_dynmods(struct cgroup_root *root, bool favor)
>>  	 */
>>  	percpu_down_write(&cgroup_threadgroup_rwsem);
>>  	if (favor && !favoring) {
>> -		cgroup_enable_per_threadgroup_rwsem = true;
>> -		rcu_sync_enter(&cgroup_threadgroup_rwsem.rss);
>> +		if (!cgroup_enable_per_threadgroup_rwsem) {
> 
> Is this branch redundant? I think if (favor && !favoring) alone should suffice —
> or can the outer condition be true twice (i.e., can this block be entered
> multiple times)?
> Hi Ridong,

Thanks for taking a look.

I don't think the inner check is redundant. `favoring` is per-root, while
`cgroup_enable_per_threadgroup_rwsem` is global.

For example, root A may have already enabled favordynmods:

cgroup_enable_per_threadgroup_rwsem = true
rcu_sync_enter(&cgroup_threadgroup_rwsem.rss) has already been called

Then root B may enable favordynmods for the first time:

favoring is false for root B
favor && !favoring is true
but cgroup_enable_per_threadgroup_rwsem is already true

In that case, we only need to set `CGRP_ROOT_FAVOR_DYNMODS` for root B and
should not call `rcu_sync_enter()` again.

That said, Tejun pointed out that the visible flag state is ambiguous either
way after a disable attempt, so please consider this patch withdrawn.
-- 
Best regards,
Guopeng


^ permalink raw reply

* Re: [PATCH] cgroup: Keep favordynmods enabled once per-threadgroup rwsem is active
From: Guopeng Zhang @ 2026-05-11  9:52 UTC (permalink / raw)
  To: Tejun Heo
  Cc: Johannes Weiner, Michal Koutný, Yi Tao, cgroups,
	linux-kernel
In-Reply-To: <8440961feb374c1a7eb6a751d2d9ae0c@kernel.org>



在 2026/5/11 16:56, Tejun Heo 写道:
> Hello, Guopeng.
> 
> Thanks for the patch.
> 
> I don't think this is worth changing. The mechanism is one-way, so on a
> disable attempt show_options has to lie one way or the other: clear the flag
> and it reports nofavordynmods while per-threadgroup rwsem is still in effect,
> keep the flag and it reports favordynmods after the user asked to turn it
> off. The pr_warn_once is what actually tells the user what happened. Neither
> flag choice is meaningfully better, and the underlying ambiguity is out of
> scope to address here. Without a stronger justification I'd rather leave the
> existing behavior alone.
> 
Hi, Tejun.

Thanks for the explanation.

I see your point. After a disable attempt, either value of the visible
mount option can be misleading, and the warning is what tells the user the
actual state.

Please consider this patch withdrawn.

Thanks,
Guopeng

> Thanks.
> 
> --
> tejun


^ permalink raw reply

* Re: [RFC PATCH v5 20/29] sched/deadline: Allow deeper hierarchies of RT cgroups
From: luca abeni @ 2026-05-11  9:40 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Tejun Heo, Yuri Andriaccio, Ingo Molnar, Juri Lelli,
	Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, linux-kernel, Yuri Andriaccio,
	hannes, mkoutny, cgroups
In-Reply-To: <20260507105331.GQ1026330@noisy.programming.kicks-ass.net>

Hi all,

On Thu, 7 May 2026 12:53:31 +0200
Peter Zijlstra <peterz@infradead.org> wrote:
[...]
> > - This has the same problem with cgroup1's rt cgroup sched support
> > where there is no way to have a permissive default configuration,
> > which means that users who don't really care about distributing rt
> > shares hierarchically would get blocked from running rt processes
> > by default, which basically forces distros to disable rt cgroup
> > sched support. This is not new but it'd be a shame to put in all
> > the work and the end result is that most people don't even have
> > access to the feature.  
> 
> Right, but cgroup-v2 allows enabling/disabling specific controllers
> for a (sub)-hierarchy, right? So if the controller is not enabled (by
> default), it will fall back to putting the tasks in whatever parent
> does have it on, and by default the root group would have and would
> accept tasks.

We are discussing this issue with Yuri, and we have a doubt: if we
disable the RT-CPU controller for a cgroup, would it be possible to
enable it for its children?
(In other words: if we want the RT-CPU controller to be enabled for
some "leaf" cgroups, we need to enable it for their parents, right?)



			Thanks,
				Luca

^ permalink raw reply

* Unable to handle kernel NULL pointer dereference in percpu_ref_get()
From: Geert Uytterhoeven @ 2026-05-11  9:32 UTC (permalink / raw)
  To: cgroups, netdev; +Cc: linux-riscv, Linux-Renesas

Hi all,

After merging v7.1-rc3, booting the kernel on RZ/Five hung.
When retrying with "earlycon keep_bootcon", I managed to catch the
crash report below.  Unfortunately I could not reproduce it after that,
hence no bisection result, but perhaps this rings a bell?

    Unable to handle kernel NULL pointer dereference at virtual
address 0000000000000000
    Current swapper/0 pgtable: 4K pagesize, 39-bit VAs, pgdp=0x00000000494f7000
    [0000000000000000] pgd=0000000000000000, p4d=0000000000000000,
pud=0000000000000000
    Oops [#1]
    CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Not tainted
7.1.0-rc3-rzfive-02022-ge48a16cf2dd1 #515 PREEMPT
    Hardware name: Renesas SMARC EVK based on r9a07g043f01 (DT)
    epc : percpu_ref_get+0x32/0x40
     ra : percpu_ref_get+0x10/0x40
    epc : ffffffff80089562 ra : ffffffff80089540 sp : ffffffc60000bb10
     gp : ffffffff812ea908 tp : ffffffd6018c9a00 t0 : ffffffd601997800
     t1 : ffffffff80df6a2f t2 : 69676552203a5445 s0 : ffffffc60000bb30
     s1 : ffffffff81291f18 a0 : ffffffff81291f18 a1 : 0000000000000002
     a2 : 0000000000000000 a3 : 0000000000000001 a4 : 0000000000000000
     a5 : 0000000200000022 a6 : 0000000000000508 a7 : 0000000000000038
     s2 : ffffffff8128f4f8 s3 : 0000000000001000 s4 : ffffffff812c9978
     s5 : 0000000000000010 s6 : 0000000000000000 s7 : ffffffff812ee3f8
     s8 : ffffffff80a21e58 s9 : 0000000000000008 s10: ffffffff808000aa
     s11: 0000000000000000 t3 : ffffffff812fd06f t4 : ffffffff812fd06f
     t5 : ffffffff812fd070 t6 : ffffffd6018aaa7c ssp : 0000000000000000
    status: 0000000200000100 badaddr: 0000000000000000 cause: 000000000000000d
    [<ffffffff80089562>] percpu_ref_get+0x32/0x40
    [<ffffffff8008eac8>] cgroup_sk_alloc+0x40/0x6e
    [<ffffffff8055ae8a>] sk_alloc+0xb4/0xdc
    [<ffffffff805b7718>] __netlink_create+0x32/0x8a
    [<ffffffff805b984a>] __netlink_kernel_create+0x60/0x182
    [<ffffffff80584492>] rtnetlink_net_init+0x4e/0x7a
    [<ffffffff8056aacc>] ops_init+0xc6/0xf8
    [<ffffffff8056bc3c>] register_pernet_operations+0xd0/0x148
    [<ffffffff8056bce2>] register_pernet_subsys+0x2e/0x48
    [<ffffffff8082ccb4>] rtnetlink_init+0x18/0x54
    [<ffffffff8082d27c>] netlink_proto_init+0x11c/0x140
    [<ffffffff8080100a>] do_one_initcall+0x70/0x13c
    [<ffffffff808013b2>] kernel_init_freeable+0x274/0x276
    [<ffffffff806bda22>] kernel_init+0x1e/0x12a
    [<ffffffff8000d368>] ret_from_fork_kernel+0x10/0xa2
    [<ffffffff806c37f6>] ret_from_fork_kernel_asm+0x16/0x18
    Code: 00e7 20ef d9fe 60e2 6442 64a2 6105 8082 77f3 1001 (6314) 8b89
    ---[ end trace 0000000000000000 ]---
    note: swapper/0[1] exited with irqs disabled
    BUG: sleeping function called from invalid context at
include/linux/percpu-rwsem.h:51
    in_atomic(): 0, irqs_disabled(): 0, non_block: 0, pid: 1, name: swapper/0
    preempt_count: 0, expected: 0
    RCU nest depth: 2, expected: 0
    CPU: 0 UID: 0 PID: 1 Comm: swapper/0 Tainted: G      D
7.1.0-rc3-rzfive-02022-ge48a16cf2dd1 #515 PREEMPT
    Tainted: [D]=DIE
    Hardware name: Renesas SMARC EVK based on r9a07g043f01 (DT)
    Call Trace:
    [<ffffffff8000fab6>] dump_backtrace+0x1c/0x24
    [<ffffffff80001226>] show_stack+0x2a/0x34
    [<ffffffff8000b57a>] dump_stack_lvl+0x32/0x4a
    [<ffffffff8000b5a6>] dump_stack+0x14/0x1c
    [<ffffffff8003861a>] __might_resched+0x110/0x11a
    [<ffffffff80038678>] __might_sleep+0x54/0x60
    [<ffffffff8002382e>] exit_signals+0x1e/0x140
    [<ffffffff80019858>] do_exit+0x14a/0x642
    [<ffffffff80019e24>] __riscv_sys_exit+0x0/0x1c
    [<ffffffff8000f50a>] die+0xdc/0xea
    [<ffffffff800015d4>] die_kernel_fault+0x1da/0x1e6
    [<ffffffff80012d20>] no_context+0x38/0x40
    [<ffffffff80012db2>] handle_page_fault+0x5e/0x23c
    [<ffffffff806bc9f8>] do_page_fault+0x1e/0x36
    [<ffffffff806c36da>] handle_exception+0x15e/0x16a
    [<ffffffff80089562>] percpu_ref_get+0x32/0x40
    [<ffffffff8008eac8>] cgroup_sk_alloc+0x40/0x6e
    [<ffffffff8055ae8a>] sk_alloc+0xb4/0xdc
    [<ffffffff805b7718>] __netlink_create+0x32/0x8a
    [<ffffffff805b984a>] __netlink_kernel_create+0x60/0x182
    [<ffffffff80584492>] rtnetlink_net_init+0x4e/0x7a
    [<ffffffff8056aacc>] ops_init+0xc6/0xf8
    [<ffffffff8056bc3c>] register_pernet_operations+0xd0/0x148
    [<ffffffff8056bce2>] register_pernet_subsys+0x2e/0x48
    [<ffffffff8082ccb4>] rtnetlink_init+0x18/0x54
    [<ffffffff8082d27c>] netlink_proto_init+0x11c/0x140
    [<ffffffff8080100a>] do_one_initcall+0x70/0x13c
    [<ffffffff808013b2>] kernel_init_freeable+0x274/0x276
    [<ffffffff806bda22>] kernel_init+0x1e/0x12a
    [<ffffffff8000d368>] ret_from_fork_kernel+0x10/0xa2
    [<ffffffff806c37f6>] ret_from_fork_kernel_asm+0x16/0x18
    Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b
    ---[ end Kernel panic - not syncing: Attempted to kill init!
exitcode=0x0000000b ]---

Thanks!

Gr{oetje,eeting}s,

                        Geert

-- 
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- geert@linux-m68k.org

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds

^ permalink raw reply

* Re: [RFC PATCH v5 20/29] sched/deadline: Allow deeper hierarchies of RT cgroups
From: Juri Lelli @ 2026-05-11  9:29 UTC (permalink / raw)
  To: luca abeni
  Cc: Peter Zijlstra, Tejun Heo, Yuri Andriaccio, Ingo Molnar,
	Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, linux-kernel, Yuri Andriaccio,
	hannes, mkoutny, cgroups
In-Reply-To: <20260507183931.3915dc59@nowhere>

On 07/05/26 18:39, luca abeni wrote:
> Hi,
> 
> On Thu, 7 May 2026 17:03:41 +0200
> Juri Lelli <juri.lelli@redhat.com> wrote:
> 
> > On 07/05/26 12:53, Peter Zijlstra wrote:
> > > On Tue, May 05, 2026 at 09:56:58AM -1000, Tejun Heo wrote:  
> > 
> > ...
> > 
> > > > - However, the cpu controller is a threaded controller which
> > > > means that it can have threaded sub-hierarchy where the
> > > > no-internal-process rule doesn't apply. This was created
> > > > explicitly for cpu controller. The proposed change blocks it
> > > > effectively forcing cpu controller into regular domain controller
> > > > behavior subject to no-internal-process rule. Note these are
> > > > enforced at controller granularity and this means that users who
> > > > use the threaded mode will be forced to pick between the two.  
> > > 
> > > Right... this then means we need two controls, one to do
> > > hierarchical bandwidth distribution, and one to assign bandwidth to
> > > the internal group -- which is then subject to its own bandwidth
> > > distribution constraint.
> > > 
> > > This might be a little confusing, but there is no way around that
> > > AFAICT.  
> > 
> > Just to check if I'm following, you are thinking something like below?
> > 
> > groupA/
> >   cpu.rt.max = "50 50 100"       <- 0.5 from root
> >   cpu.rt.internal = "20 20 100"  <- 0.2 from groupA for threads at
> > this level
> >   + threadA                               <
> >   + threadB                               <
> >   +- group1/
> >        cpu.rt.max = "30 30 100"  <- 0.3 from groupA
> >        + threadC
> > 
> > And we still keep it flat, so 2 dl-entities (per CPU), one handles
> > threads at groupA level and the other threads inside group1?
> 
> An alternative idea I was thinking about: we create 2 dl entities (one
> for "groupA" and one for "group1"); we set cpu.rt.max for groupA, and
> we subtract group1's utilization from it (so, if groupA's cpu.rt.max is
> "50 100" and group1's cpu.rt.max is "30 100", groupA is served by a dl
> entity (50-30,100)=(20,100) while group1 is served by a dl entity
> (30,100)).
> 
> Basically, with this idea the "internal" reservation is automatically
> computed based on rt.max and on the children cgroups. A possible issue
> is that if the children consume all the groupA's utilization the groupA
> RT tasks remain with 0 runtime (and never execute).

While I like the automatic approach, I also fear that it might be more
difficult to maintain/use from a systemd admin perspective, e.g. I
cannot make a subgroup reservation bigger because there are threads
running in the parent group which consume all the remaining (internal)
bandwidth. If we make it explicit it seems easier to see where bandwidth
is allocated at all levels.

Peter? Tejun? What do we want to do with this interface?


^ permalink raw reply

* Re: [PATCH v3 2/2] cgroup/cpuset: reserve DL bandwidth only for root-domain moves
From: Juri Lelli @ 2026-05-11  9:17 UTC (permalink / raw)
  To: Guopeng Zhang
  Cc: Waiman Long, Tejun Heo, Michal Koutný, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Chen Ridong, Johannes Weiner,
	Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, K Prateek Nayak, Gabriele Monaco,
	Will Deacon, linux-kernel, cgroups
In-Reply-To: <20260509102031.97608-3-zhangguopeng@kylinos.cn>

On Sat, 09 May 2026 18:20:31 +0800, Guopeng Zhang <zhangguopeng@kylinos.cn> wrote:
> cpuset_can_attach() currently adds the bandwidth of all migrating
> SCHED_DEADLINE tasks to sum_migrate_dl_bw. If the source and destination
> cpuset effective CPU masks do not overlap, the whole sum is then
> reserved in the destination root domain.
> 
> set_cpus_allowed_dl(), however, subtracts bandwidth from the source
> root domain only when the affinity change really moves the task between
> root domains. A DL task can move between cpusets that are still in the
> same root domain, so including that task in sum_migrate_dl_bw can reserve
> destination bandwidth without a matching source-side subtraction.
> 
> [...]

Acked-by: Juri Lelli <juri.lelli@redhat.com>
Tested-by: Juri Lelli <juri.lelli@redhat.com>

-- 
Juri Lelli <juri.lelli@redhat.com>


^ permalink raw reply

* Re: [PATCH v3 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Juri Lelli @ 2026-05-11  9:17 UTC (permalink / raw)
  To: Guopeng Zhang
  Cc: Waiman Long, Tejun Heo, Michal Koutný, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Chen Ridong, Johannes Weiner,
	Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
	Mel Gorman, Valentin Schneider, K Prateek Nayak, Gabriele Monaco,
	Will Deacon, linux-kernel, cgroups
In-Reply-To: <20260509102031.97608-2-zhangguopeng@kylinos.cn>

On Sat, 09 May 2026 18:20:30 +0800, Guopeng Zhang <zhangguopeng@kylinos.cn> wrote:
> cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
> state in the destination cpuset while walking the taskset.
> 
> If a later task_can_attach() or security_task_setscheduler() check
> fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
> and does not call cpuset_cancel_attach() for it. The partially
> accumulated state is then left behind and can be consumed by a later
> attach, corrupting cpuset DL task accounting and pending DL bandwidth
> accounting.
> 
> [...]

Reviewed-by: Juri Lelli <juri.lelli@redhat.com>
Tested-by: Juri Lelli <juri.lelli@redhat.com>

-- 
Juri Lelli <juri.lelli@redhat.com>


^ permalink raw reply

* Re: [PATCH] cgroup: Keep favordynmods enabled once per-threadgroup rwsem is active
From: Chen Ridong @ 2026-05-11  9:05 UTC (permalink / raw)
  To: Guopeng Zhang, Tejun Heo, Johannes Weiner, Michal Koutný
  Cc: Yi Tao, cgroups, linux-kernel
In-Reply-To: <20260511081607.83490-1-zhangguopeng@kylinos.cn>



On 2026/5/11 16:16, Guopeng Zhang wrote:
> cgroup_enable_per_threadgroup_rwsem is a one-way switch. Once it is
> enabled, cgroup.procs writes use the per-threadgroup rwsem and
> cgroup_threadgroup_change_begin()/end() use the same global state to
> decide whether to take and release the per-threadgroup rwsem.
> 
> The disable path warned that the per-threadgroup rwsem mechanism could not
> be disabled but still called rcu_sync_exit() and cleared
> CGRP_ROOT_FAVOR_DYNMODS. That partially disabled favordynmods while the
> global per-threadgroup rwsem mode remained enabled: cgroup.procs writes
> would continue to use the per-threadgroup rwsem, while
> cgroup_threadgroup_change_begin()/end() could observe the exited rcu_sync
> state. The root would also no longer report favordynmods.
> 
> Make the transition match the documented one-way semantics. Call
> rcu_sync_enter() only for the first favordynmods enable, and make later
> disable attempts a no-op after warning once the per-threadgroup rwsem mode
> has been enabled.
> 
> Fixes: 0568f89d4fb8 ("cgroup: replace global percpu_rwsem with per threadgroup resem when writing to cgroup.procs")
> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
> ---
> Manual AB test:
> 
> Before this patch:
>   enable favordynmods:
>     cgroup2 opts: rw,relatime,favordynmods
>   disable attempt:
>     cgroup2 opts: rw,relatime
>   dmesg:
>     cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled
> 
> After this patch:
>   enable favordynmods:
>     cgroup2 opts: rw,relatime,favordynmods
>   disable attempt:
>     cgroup2 opts: rw,relatime,favordynmods
>   dmesg:
>     cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled
> 
>  kernel/cgroup/cgroup.c | 11 +++++------
>  1 file changed, 5 insertions(+), 6 deletions(-)
> 
> diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
> index 6152add0c5eb..fd10fb5b3598 100644
> --- a/kernel/cgroup/cgroup.c
> +++ b/kernel/cgroup/cgroup.c
> @@ -1297,14 +1297,13 @@ void cgroup_favor_dynmods(struct cgroup_root *root, bool favor)
>  	 */
>  	percpu_down_write(&cgroup_threadgroup_rwsem);
>  	if (favor && !favoring) {
> -		cgroup_enable_per_threadgroup_rwsem = true;
> -		rcu_sync_enter(&cgroup_threadgroup_rwsem.rss);
> +		if (!cgroup_enable_per_threadgroup_rwsem) {

Is this branch redundant? I think if (favor && !favoring) alone should suffice —
or can the outer condition be true twice (i.e., can this block be entered
multiple times)?


> +			cgroup_enable_per_threadgroup_rwsem = true;
> +			rcu_sync_enter(&cgroup_threadgroup_rwsem.rss);
> +		}
>  		root->flags |= CGRP_ROOT_FAVOR_DYNMODS;
>  	} else if (!favor && favoring) {
> -		if (cgroup_enable_per_threadgroup_rwsem)
> -			pr_warn_once("cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled\n");
> -		rcu_sync_exit(&cgroup_threadgroup_rwsem.rss);
> -		root->flags &= ~CGRP_ROOT_FAVOR_DYNMODS;
> +		pr_warn_once("cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled\n");
>  	}
>  	percpu_up_write(&cgroup_threadgroup_rwsem);
>  }

-- 
Best regards,
Ridong


^ permalink raw reply

* Re: [PATCH v3 01/12] mm, swap: simplify swap cache allocation helper
From: Kairui Song @ 2026-05-11  8:57 UTC (permalink / raw)
  To: Chris Li
  Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
	Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
	Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
	Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
	Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
	Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <CACePvbVNwaU=609oJmAwqxse4WTApj30rU9hB_ym=FLjKMk2PA@mail.gmail.com>

On Wed, May 6, 2026 at 9:51 PM Chris Li <chrisl@kernel.org> wrote:
>
> On Tue, Apr 21, 2026 at 8:16 AM Kairui Song via B4 Relay
> <devnull+kasong.tencent.com@kernel.org> wrote:
> >
> > From: Kairui Song <kasong@tencent.com>
> >
> > Instead of trying to return the existing folio if the entry is already
> > cached, simply return an error code if the allocation fails and drop the
>
> Nitpick: Spell out which function changes the return type here. It is
> __swap_cache_prepare_and_add()

Good idea.

>
> > output argument. And introduce proper wrappers that handle the
>
> Nitpick: Spell out the helper function. It is swap_cache_read_folio().
> > allocation failure in different ways.
>
> >
> > For async swapin and readahead, the caller only wants to ensure that a
> > swap-in read is issued when the allocation succeeded. And for zswap swap
> > out, the caller will abort if the allocation failed because the entry is
> > gone or cached already.
>
> Should you add no functional change expected?

Yes indeed, there is no functional change.

>
> >
> > Signed-off-by: Kairui Song <kasong@tencent.com>
>
> Very nice clean ups. I like it. Here are some nitpicks; feel free to
> ignore them.
>
> Acked-by: Chris Li <chrisl@kerel.org>

Thanks.

>
> Nitpick: IS_ERR() only checks that the pointer is in the error code
> range. If the pointer is -EEXIST, it will always be in the error code
> range. I think the "IS_ERR(folio)" test can be dropped.

Agreed. Actually, I didn't add IS_ERR in V1 then Sashiko complained
that it should be added. I just checked the API documentation again
and existing patterns, there is indeed no rule to prohibit the direct
check. Let me drop it, I also like it better that way, and maybe just
ignore Sashiko next time.

^ permalink raw reply

* Re: [PATCH] cgroup: Keep favordynmods enabled once per-threadgroup rwsem is active
From: Tejun Heo @ 2026-05-11  8:56 UTC (permalink / raw)
  To: Guopeng Zhang
  Cc: Johannes Weiner, Michal Koutný, Yi Tao, cgroups,
	linux-kernel
In-Reply-To: <20260511081607.83490-1-zhangguopeng@kylinos.cn>

Hello, Guopeng.

Thanks for the patch.

I don't think this is worth changing. The mechanism is one-way, so on a
disable attempt show_options has to lie one way or the other: clear the flag
and it reports nofavordynmods while per-threadgroup rwsem is still in effect,
keep the flag and it reports favordynmods after the user asked to turn it
off. The pr_warn_once is what actually tells the user what happened. Neither
flag choice is meaningfully better, and the underlying ambiguity is out of
scope to address here. Without a stronger justification I'd rather leave the
existing behavior alone.

Thanks.

--
tejun

^ permalink raw reply

* Re: [PATCH v2] cgroup/cpuset: skip hardwall ancestor scan in cpuset v2 in cpuset_current_node_allowed()
From: Tejun Heo @ 2026-05-11  8:55 UTC (permalink / raw)
  To: Chen Wandun, Wandun Chen
  Cc: longman, chenridong, hannes, mkoutny, cgroups, linux-kernel
In-Reply-To: <20260511081838.862889-1-chenwandun@lixiang.com>

Hello,

Applied to cgroup/for-7.2 after back-merging for-7.1-fixes to pick up
dde2f938d02f ("cgroup/cpuset: move PF_EXITING check before
__GFP_HARDWALL in cpuset_current_node_allowed()"), which was needed
as a dependency.

Thanks.

--
tejun

^ permalink raw reply

* Re: [PATCH] selftests/cgroup: fix misleading debug message in test_cgfreezer_time_child
From: Tejun Heo @ 2026-05-11  8:36 UTC (permalink / raw)
  To: Tao Cui; +Cc: hannes, mkoutny, shuah, cgroups
In-Reply-To: <20260511062520.256160-1-cuitao@kylinos.cn>

Hello,

Applied to cgroup/for-7.2.

Thanks.

--
tejun

^ permalink raw reply

* Re: [PATCH] selftests/cgroup: fix child process escaping to parent cleanup in test_cpucg_nice
From: Tejun Heo @ 2026-05-11  8:36 UTC (permalink / raw)
  To: Tao Cui; +Cc: hannes, mkoutny, shuah, cgroups
In-Reply-To: <20260511061508.255649-1-cuitao@kylinos.cn>

Hello,

Applied to cgroup/for-7.2.

Thanks.

--
tejun

^ permalink raw reply

* Re: [PATCH] selftests/cgroup: Add NULL check after malloc in cgroup_util.c
From: Tejun Heo @ 2026-05-11  8:35 UTC (permalink / raw)
  To: Hongfu Li
  Cc: hannes, mkoutny, shuah, jthoughton, seanjc, zhangguopeng, cgroups,
	linux-kselftest, linux-kernel
In-Reply-To: <20260511060853.1873161-1-lihongfu@kylinos.cn>

Hello,

Applied to cgroup/for-7.2.

Thanks.

--
tejun

^ permalink raw reply

* [PATCH v2] cgroup/cpuset: skip hardwall ancestor scan in cpuset v2 in cpuset_current_node_allowed()
From: Wandun Chen @ 2026-05-11  8:18 UTC (permalink / raw)
  To: longman, chenridong, tj, hannes, mkoutny; +Cc: cgroups, linux-kernel

From: Chen Wandun <chenwandun@lixiang.com>

Cgroup v2 doesn't have the concept of memory hardwall, only top_cpuset
has CS_MEM_EXCLUSIVE/CS_MEM_HARDWALL flags, nearest_hardwall_ancestor
always returns top_cpuset with all nodes set, so no need to acquire
callback_lock and scan up cpuset.

Suggested-by: Michal Koutný <mkoutny@suse.com>
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>

---
v1 --> v2:
use cpuset_v2 instead of is_in_v2_mode, suggested by Tejun.
---
 kernel/cgroup/cpuset.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index a48901a0416a..cbd9e7fc800e 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -4231,6 +4231,9 @@ bool cpuset_current_node_allowed(int node, gfp_t gfp_mask)
 	if (gfp_mask & __GFP_HARDWALL)	/* If hardwall request, stop here */
 		return false;
 
+	if (cpuset_v2())
+		return true;
+
 	/* Not hardwall and node outside mems_allowed: scan up cpusets */
 	spin_lock_irqsave(&callback_lock, flags);
 
-- 
2.43.0


^ permalink raw reply related

* Re: [PATCH v3 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Tejun Heo @ 2026-05-11  8:18 UTC (permalink / raw)
  To: Guopeng Zhang, Waiman Long, Tejun Heo, Michal Koutný,
	Ingo Molnar, Peter Zijlstra, Juri Lelli, Chen Ridong
  Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
	cgroups
In-Reply-To: <20260509102031.97608-2-zhangguopeng@kylinos.cn>

Hello,

Applied 1/2 to cgroup/for-7.1-fixes with Cc: stable@vger.kernel.org # v6.10+.

Thanks.

--
tejun

^ permalink raw reply

* [PATCH] cgroup: Keep favordynmods enabled once per-threadgroup rwsem is active
From: Guopeng Zhang @ 2026-05-11  8:16 UTC (permalink / raw)
  To: Tejun Heo, Johannes Weiner, Michal Koutný
  Cc: Yi Tao, cgroups, linux-kernel, Guopeng Zhang

cgroup_enable_per_threadgroup_rwsem is a one-way switch. Once it is
enabled, cgroup.procs writes use the per-threadgroup rwsem and
cgroup_threadgroup_change_begin()/end() use the same global state to
decide whether to take and release the per-threadgroup rwsem.

The disable path warned that the per-threadgroup rwsem mechanism could not
be disabled but still called rcu_sync_exit() and cleared
CGRP_ROOT_FAVOR_DYNMODS. That partially disabled favordynmods while the
global per-threadgroup rwsem mode remained enabled: cgroup.procs writes
would continue to use the per-threadgroup rwsem, while
cgroup_threadgroup_change_begin()/end() could observe the exited rcu_sync
state. The root would also no longer report favordynmods.

Make the transition match the documented one-way semantics. Call
rcu_sync_enter() only for the first favordynmods enable, and make later
disable attempts a no-op after warning once the per-threadgroup rwsem mode
has been enabled.

Fixes: 0568f89d4fb8 ("cgroup: replace global percpu_rwsem with per threadgroup resem when writing to cgroup.procs")
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
Manual AB test:

Before this patch:
  enable favordynmods:
    cgroup2 opts: rw,relatime,favordynmods
  disable attempt:
    cgroup2 opts: rw,relatime
  dmesg:
    cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled

After this patch:
  enable favordynmods:
    cgroup2 opts: rw,relatime,favordynmods
  disable attempt:
    cgroup2 opts: rw,relatime,favordynmods
  dmesg:
    cgroup: cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled

 kernel/cgroup/cgroup.c | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 6152add0c5eb..fd10fb5b3598 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -1297,14 +1297,13 @@ void cgroup_favor_dynmods(struct cgroup_root *root, bool favor)
 	 */
 	percpu_down_write(&cgroup_threadgroup_rwsem);
 	if (favor && !favoring) {
-		cgroup_enable_per_threadgroup_rwsem = true;
-		rcu_sync_enter(&cgroup_threadgroup_rwsem.rss);
+		if (!cgroup_enable_per_threadgroup_rwsem) {
+			cgroup_enable_per_threadgroup_rwsem = true;
+			rcu_sync_enter(&cgroup_threadgroup_rwsem.rss);
+		}
 		root->flags |= CGRP_ROOT_FAVOR_DYNMODS;
 	} else if (!favor && favoring) {
-		if (cgroup_enable_per_threadgroup_rwsem)
-			pr_warn_once("cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled\n");
-		rcu_sync_exit(&cgroup_threadgroup_rwsem.rss);
-		root->flags &= ~CGRP_ROOT_FAVOR_DYNMODS;
+		pr_warn_once("cgroup favordynmods: per threadgroup rwsem mechanism can't be disabled\n");
 	}
 	percpu_up_write(&cgroup_threadgroup_rwsem);
 }
-- 
2.43.0

^ permalink raw reply related

* [PATCH] selftests/cgroup: fix misleading debug message in test_cgfreezer_time_child
From: Tao Cui @ 2026-05-11  6:25 UTC (permalink / raw)
  To: tj, hannes, mkoutny, shuah, cgroups; +Cc: Tao Cui

The debug message says "Expect ctime <= ptime" when the test actually
expects ctime > ptime (child's freeze time should exceed parent's,
which is zero). Fix the message to match the actual expectation.

Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
 tools/testing/selftests/cgroup/test_freezer.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/cgroup/test_freezer.c b/tools/testing/selftests/cgroup/test_freezer.c
index 160a9e6ad277..0569e93fa6b0 100644
--- a/tools/testing/selftests/cgroup/test_freezer.c
+++ b/tools/testing/selftests/cgroup/test_freezer.c
@@ -1353,7 +1353,7 @@ static int test_cgfreezer_time_child(const char *root)
 	}
 
 	if (ctime <= ptime) {
-		debug("Expect ctime (%ld) <= ptime (%ld)\n", ctime, ptime);
+		debug("Expect ctime (%ld) > ptime (%ld)\n", ctime, ptime);
 		goto cleanup;
 	}
 
-- 
2.43.0


^ permalink raw reply related

* [PATCH] selftests/cgroup: fix child process escaping to parent cleanup in test_cpucg_nice
From: Tao Cui @ 2026-05-11  6:15 UTC (permalink / raw)
  To: tj, hannes, mkoutny, shuah, cgroups; +Cc: Tao Cui

In test_cpucg_nice, the forked child process incorrectly jumps to the
parent's cleanup label on cg_write failure. This causes the child to
attempt cg_destroy on cgroups the parent is still using, and then
return to main() to continue executing tests as if it were the parent.

Replace goto cleanup with exit(EXIT_FAILURE) in the child process.

Signed-off-by: Tao Cui <cuitao@kylinos.cn>
---
 tools/testing/selftests/cgroup/test_cpu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tools/testing/selftests/cgroup/test_cpu.c b/tools/testing/selftests/cgroup/test_cpu.c
index c83f05438d7c..7a40d76b9548 100644
--- a/tools/testing/selftests/cgroup/test_cpu.c
+++ b/tools/testing/selftests/cgroup/test_cpu.c
@@ -278,7 +278,7 @@ static int test_cpucg_nice(const char *root)
 		char buf[64];
 		snprintf(buf, sizeof(buf), "%d", getpid());
 		if (cg_write(cpucg, "cgroup.procs", buf))
-			goto cleanup;
+			exit(EXIT_FAILURE);
 
 		/* Try to keep niced CPU usage as constrained to hog_cpu as possible */
 		nice(1);
-- 
2.43.0


^ permalink raw reply related

* [PATCH] selftests/cgroup: Add NULL check after malloc in cgroup_util.c
From: Hongfu Li @ 2026-05-11  6:08 UTC (permalink / raw)
  To: tj, hannes, mkoutny, shuah, jthoughton, seanjc, zhangguopeng
  Cc: cgroups, linux-kselftest, linux-kernel, Hongfu Li

Add NULL checks after malloc() in three helper functions to prevent
NULL pointer dereference on memory allocation failure.
- cg_name()
- cg_name_indexed()
- cg_control()

These functions allocate memory with malloc() but previously called
snprintf() unconditionally, which would trigger undefined behavior
if allocation fails.

Signed-off-by: Hongfu Li <lihongfu@kylinos.cn>
---
 tools/testing/selftests/cgroup/lib/cgroup_util.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/tools/testing/selftests/cgroup/lib/cgroup_util.c b/tools/testing/selftests/cgroup/lib/cgroup_util.c
index 6a7295347e90..0c5e6d4bbc3a 100644
--- a/tools/testing/selftests/cgroup/lib/cgroup_util.c
+++ b/tools/testing/selftests/cgroup/lib/cgroup_util.c
@@ -59,7 +59,8 @@ char *cg_name(const char *root, const char *name)
 	size_t len = strlen(root) + strlen(name) + 2;
 	char *ret = malloc(len);
 
-	snprintf(ret, len, "%s/%s", root, name);
+	if (ret)
+		snprintf(ret, len, "%s/%s", root, name);
 
 	return ret;
 }
@@ -69,7 +70,8 @@ char *cg_name_indexed(const char *root, const char *name, int index)
 	size_t len = strlen(root) + strlen(name) + 10;
 	char *ret = malloc(len);
 
-	snprintf(ret, len, "%s/%s_%d", root, name, index);
+	if (ret)
+		snprintf(ret, len, "%s/%s_%d", root, name, index);
 
 	return ret;
 }
@@ -79,7 +81,8 @@ char *cg_control(const char *cgroup, const char *control)
 	size_t len = strlen(cgroup) + strlen(control) + 2;
 	char *ret = malloc(len);
 
-	snprintf(ret, len, "%s/%s", cgroup, control);
+	if (ret)
+		snprintf(ret, len, "%s/%s", cgroup, control);
 
 	return ret;
 }
-- 
2.25.1


^ permalink raw reply related

* Re: [PATCH 1/3] cgroup/cpuset: Fix deadline bandwidth leak in cpuset_can_attach()
From: Waiman Long @ 2026-05-11  5:10 UTC (permalink / raw)
  To: Aaron Tomlin, tsbogend, paul, jmorris, serge, mingo, peterz,
	juri.lelli, vincent.guittot, stephen.smalley.work, casey, tj,
	hannes, mkoutny
  Cc: chenridong, dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
	kprateek.nayak, omosnace, kees, neelx, sean, chjohnst, steve,
	mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
	linux-security-module, selinux, linux-kernel
In-Reply-To: <20260509164847.939294-2-atomlin@atomlin.com>


On 5/9/26 12:48 PM, Aaron Tomlin wrote:
> During a cgroup migration, cpuset_can_attach() iterates over the
> provided taskset. If a task within the batch is a deadline (DL) task,
> the destination cpuset's DL metrics (i.e., nr_migrate_dl_tasks and
> sum_migrate_dl_bw) are appropriately incremented.
>
> However, if a subsequent task in the same migration batch fails the
> task_can_attach() check, the loop aborts and jumps directly to
> out_unlock. Consequently, any DL metrics accumulated from previously
> processed tasks in the batch remain permanently inflated in the
> destination cpuset. Because the migration is subsequently aborted by the
> cgroup core, cpuset_cancel_attach() is never invoked to unwind these
> specific increments.
>
> This behaviour results in a permanent leak of deadline bandwidth, which
> incorrectly restricts the admission control capacity of the destination
> cpuset.
>
> To resolve this, introduce an out_unlock_reset failure path that
> conditionally invokes reset_migrate_dl_data(). This guarantees that if a
> batch migration is aborted for any reason, the pending DL metrics are
> safely reset before returning the error.
>
> Fixes: 0a67b847e1f06 ("cpuset: Allow setscheduler regardless of manipulated task")

That is not the commit that introduced the bug. Anyway, there is already 
another patch sent recently to fix this bug. See

https://lore.kernel.org/lkml/20260509102031.97608-2-zhangguopeng@kylinos.cn/

Cheers,
Longman



^ permalink raw reply

* Re: [PATCH v3 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Waiman Long @ 2026-05-11  5:04 UTC (permalink / raw)
  To: Guopeng Zhang, Tejun Heo, Michal Koutný, Ingo Molnar,
	Peter Zijlstra, Juri Lelli, Chen Ridong
  Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
	Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
	K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
	cgroups
In-Reply-To: <20260509102031.97608-2-zhangguopeng@kylinos.cn>

On 5/9/26 6:20 AM, Guopeng Zhang wrote:
> cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
> state in the destination cpuset while walking the taskset.
>
> If a later task_can_attach() or security_task_setscheduler() check
> fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
> and does not call cpuset_cancel_attach() for it. The partially
> accumulated state is then left behind and can be consumed by a later
> attach, corrupting cpuset DL task accounting and pending DL bandwidth
> accounting.
>
> Reset the pending DL migration state from the common error exit when
> ret is non-zero. Successful can_attach() keeps the state for
> cpuset_attach() or cpuset_cancel_attach().
>
> Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
> ---
>   kernel/cgroup/cpuset.c | 8 ++++----
>   1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index e3a081a07c6d..b9c839538900 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -3050,16 +3050,13 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>   		int cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
>   
>   		if (unlikely(cpu >= nr_cpu_ids)) {
> -			reset_migrate_dl_data(cs);
>   			ret = -EINVAL;
>   			goto out_unlock;
>   		}
>   
>   		ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
> -		if (ret) {
> -			reset_migrate_dl_data(cs);
> +		if (ret)
>   			goto out_unlock;
> -		}
>   
>   		cs->dl_bw_cpu = cpu;
>   	}
> @@ -3070,7 +3067,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>   	 * changes which zero cpus/mems_allowed.
>   	 */
>   	cs->attach_in_progress++;
> +
>   out_unlock:
> +	if (ret)
> +		reset_migrate_dl_data(cs);
>   	mutex_unlock(&cpuset_mutex);
>   	return ret;
>   }
Reviewed-by: Waiman Long <longman@redhat.com>


^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox