mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC 0/2] mm: add source selectors to memory.reclaim
@ 2026-10-19 14:13 Li Zhe
  2026-10-19 14:14 ` [RFC 1/2] mm: add source selector " Li Zhe
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Li Zhe @ 2026-10-19 14:13 UTC (permalink / raw)
  To: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	ljs, mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc
  Cc: cgroups, linux-doc, linux-kernel, linux-mm, Li Zhe

memory.reclaim is a useful proactive reclaim interface for memcgs, but
tiered memory makes "reclaim N bytes from this memcg" underspecified for
userspace controllers that manage memory placement across tiers. The
kernel's reclaim policy is aging-based, and on tiered systems aging may
demote folios before they are eventually evicted. Without a way to
declare where reclaim pressure should start, userspace can only reclaim
from the memcg as a whole and observe one aggregate progress value.

There is prior history here [1]. The old nodes= proposal tried to let
memory.reclaim scan selected NUMA nodes, but the discussion left several
ABI questions unresolved:

- nodes= did not clearly say whether the mask was a source selector, a
  reclaim domain, or a demotion-target constraint.
- The implementation did not apply the mask to the whole reclaim path,
  so demotion could allocate outside the mask. If userspace interpreted
  the mask as a domain or target constraint, the ABI did not provide
  that.
- Selecting top-tier nodes could become an implicit way to request
  demotion without uncharge. That would make userspace depend on a
  particular reclaim policy and could make future policy changes hard.
- The separate completion-accounting question remained: demotion can
  count as reclaim progress even though memory.current is not reduced.

This series takes a narrower approach. It adds a typed source= key which
declares only the source on which the existing memory.reclaim aging
pipeline should be applied. It is not a demotion target selector, not a
demotion-only goal, not an eviction-only goal, and not a change to the
existing completion accounting. Leaving source unspecified preserves the
existing memory.reclaim behavior.

In that sense, this series extends node-scoped reclaim pressure to the
memcg memory.reclaim interface. The existing node memory_reclaim
interface can apply pressure to a node, but it is not scoped to a
particular memcg. This series lets userspace apply the same kind of
source-scoped pressure to a cgroup's resident memory on selected nodes
or tiers, while still using the normal memcg reclaim path.

The main benefit of this series is to give userspace a steering knob
for per-node and per-tier memory placement control. A tiering controller
can observe the resident memory distribution across nodes or memory
tiers, and then apply reclaim pressure to the source that should be
relieved. For example, it can apply pressure to top-tier nodes so that,
under the existing tiering policy, cold folios can be aged and usually
demoted to lower tiers, or reclaim from a terminal tier when it needs
actual charge reduction.

The existing memory.reclaim interface cannot express this. It applies
pressure to the memcg as a whole, so on a multi-tier system the kernel
may make progress in a tier different from the one userspace is trying
to control. That makes feedback control of tier ratios ineffective: the
controller can observe that a specific tier is over its desired resident
footprint, but it cannot direct the next reclaim request to that tier.

Patch 1 adds source=nodes:<nodelist>, which is the basic source
selector. Patch 2 adds source=tier:<N>, where N is the memory_tierN id
exposed by /sys/devices/virtual/memory_tiering/. The tier selector is
only a convenience layer over the same source semantics: it resolves the
current tier nodelist at write time and then uses the same source-scoped
reclaim path as source=nodes:<nodelist>.

The remaining issues from the old discussion are intentionally left out
of this series. If userspace needs to control demotion targets, that
should be a separate target/demotion-policy ABI rather than folded into
source=. If userspace needs a strict "bytes actually uncharged"
completion mode, that should be discussed as a separate accounting or
goal extension because it would change the observable meaning of
memory.reclaim progress. This series only adds source selection for the
existing reclaim policy.

References:
[1] https://lore.kernel.org/all/Y5bsmpCyeryu3Zz1@dhcp22.suse.cz/

Li Zhe (2):
  mm: add source selector to memory.reclaim
  mm: add tier source selector to memory.reclaim

 Documentation/admin-guide/cgroup-v2.rst | 29 +++++++++++
 include/linux/memory-tiers.h            |  6 +++
 mm/internal.h                           |  3 +-
 mm/memcontrol-v1.c                      |  6 ++-
 mm/memcontrol.c                         | 11 +++--
 mm/memory-tiers.c                       | 24 ++++++++++
 mm/vmscan.c                             | 64 +++++++++++++++++++++++--
 7 files changed, 133 insertions(+), 10 deletions(-)

-- 
2.20.1

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

* [RFC 1/2] mm: add source selector to memory.reclaim
  2026-10-19 14:13 [RFC 0/2] mm: add source selectors to memory.reclaim Li Zhe
@ 2026-10-19 14:14 ` Li Zhe
  2026-10-19 14:51   ` Lorenzo Stoakes (ARM)
  2026-10-19 14:14 ` [RFC 2/2] mm: add tier " Li Zhe
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Li Zhe @ 2026-10-19 14:14 UTC (permalink / raw)
  To: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	ljs, mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc
  Cc: cgroups, linux-doc, linux-kernel, linux-mm, Li Zhe

memory.reclaim was added as a generic proactive reclaim
interface for memcgs, but memory tiering makes its practical
meaning less obvious. Reclaim may demote cold folios from a
faster tier to a slower tier before they are eventually evicted.
As a result, a write to memory.reclaim can either move folios
between tiers or evict them from the cgroup, depending on where
the reclaim walk finds eligible memory and whether demotion is
available.

That ambiguity is awkward for userspace controllers. A controller
that is trying to manage DRAM, CXL, or other tier occupancy cannot
direct reclaim pressure to a specific source tier or node. It can
only ask the kernel to reclaim from the memcg as a whole, while the
resulting progress is reported as one aggregate value. This makes
effective tier-ratio control largely infeasible: userspace can
observe that the distribution of memory across tiers has drifted
away from the controller's preferred balance, but it cannot ask
memory.reclaim to apply pressure to the tier that should be
relieved. It also makes controlled charge reduction unreliable: a
controller that wants to free memory from a terminal tier cannot
ask reclaim to start from that tier.

Add a typed source= selector to memory.reclaim. The first
supported form is source=nodes:<nodelist>. The selector declares
the source nodes on which userspace wants the kernel to apply the
normal reclaim aging pipeline. It constrains the source to which
reclaim pressure is applied; it is not a demotion-only goal, an
eviction-only goal, or a demotion target selector.

The selector only chooses where the existing reclaim policy is
applied. It does not change when the kernel decides to demote,
evict, activate, or keep a folio. On common tiered-memory systems,
a source in a non-terminal tier will usually make progress through
demotion when an allowed demotion target exists. A source in the
terminal tier has no lower demotion target, so successful reclaim
progress from that source is eviction.

With this primitive, userspace can build higher-level policies by
choosing different sources across requests. For example, a
controller can issue one request against top-tier nodes for tier
balancing, and another request against terminal-tier nodes when it
needs actual charge reduction. This composes tiering policies from
a simple source selector instead of adding policy-specific
memory.reclaim keys. This patch does not change the reclaim policy:
memory.reclaim continues to run the kernel's existing aging-based
reclaim pipeline, now with an optional source constraint.

Leaving source unspecified preserves the existing memory.reclaim
behavior.

The source= key is intentionally typed so future selectors can be
added without reinterpreting the nodes form. For example, later
patches may add source=tier:<selector> or other source types while
keeping source=nodes:<nodelist> as the NUMA-node source selector.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 Documentation/admin-guide/cgroup-v2.rst | 23 ++++++++++++
 mm/internal.h                           |  3 +-
 mm/memcontrol-v1.c                      |  6 ++--
 mm/memcontrol.c                         | 11 +++---
 mm/vmscan.c                             | 48 +++++++++++++++++++++++--
 5 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index 86a2a0099178..a016575010ba 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1454,6 +1454,7 @@ The following nested keys are defined.
 
 	  ==========            ================================
 	  swappiness            Swappiness value to reclaim with
+	  source                Source to apply reclaim pressure to
 	  ==========            ================================
 
 	Specifying a swappiness value instructs the kernel to perform
@@ -1464,6 +1465,28 @@ The following nested keys are defined.
 	The valid range for swappiness is [0-200, max], setting
 	swappiness=max exclusively reclaims anonymous memory.
 
+	The source key declares where the normal reclaim aging pipeline is
+	applied. It is a typed selector in the form source=<type>:<selector>.
+	Currently only source=nodes:<nodelist> is supported. For example::
+
+	  echo "1G source=nodes:0-1" > memory.reclaim
+
+	This instructs the kernel to apply reclaim pressure to memory of target
+	cgroup that are currently resident on nodes 0 and 1. Reclaim then
+	follows the normal reclaim path for selected folios. The source
+	selector does not choose a demotion or eviction policy. On tiered
+	memory systems, a source node in a non-terminal tier will usually make
+	progress through demotion when an allowed demotion target exists. A
+	source node in the terminal tier has no demotion target, so successful
+	reclaim progress from that source is eviction.
+
+	The source selector is a source constraint only. It does not select or
+	limit demotion targets; those are still chosen by the kernel's memory
+	tier demotion policy. Specifying source does not change reclaim
+	completion accounting. On tiered memory systems, completion may include
+	demotion progress that moves folios away from the selected source nodes
+	without reducing memory.current.
+
   memory.peak
 	A read-write single value file which exists on non-root cgroups.
 
diff --git a/mm/internal.h b/mm/internal.h
index 38b1165212c9..c730309d39c8 100644
--- a/mm/internal.h
+++ b/mm/internal.h
@@ -84,7 +84,8 @@ unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,
 					   unsigned long nr_pages,
 					   gfp_t gfp_mask,
 					   unsigned int reclaim_options,
-					   int *swappiness);
+					   int *swappiness,
+					   const nodemask_t *source_nodes);
 unsigned long mem_cgroup_shrink_node(struct mem_cgroup *memcg,
 				     gfp_t gfp_mask, bool noswap,
 				     pg_data_t *pgdat,
diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
index 05ef55cae4dc..00ee18f1e08c 100644
--- a/mm/memcontrol-v1.c
+++ b/mm/memcontrol-v1.c
@@ -1814,7 +1814,8 @@ static int mem_cgroup_resize_max(struct mem_cgroup *memcg,
 		}
 
 		if (!try_to_free_mem_cgroup_pages(memcg, 1, GFP_KERNEL,
-				memsw ? 0 : MEMCG_RECLAIM_MAY_SWAP, NULL)) {
+				memsw ? 0 : MEMCG_RECLAIM_MAY_SWAP,
+				NULL, NULL)) {
 			ret = -EBUSY;
 			break;
 		}
@@ -1850,7 +1851,8 @@ static int mem_cgroup_force_empty(struct mem_cgroup *memcg)
 			break;
 
 		if (!try_to_free_mem_cgroup_pages(memcg, 1, GFP_KERNEL,
-						  MEMCG_RECLAIM_MAY_SWAP, NULL))
+						  MEMCG_RECLAIM_MAY_SWAP,
+						  NULL, NULL))
 			nr_retries--;
 	}
 
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index 856a7d07586c..40a2eab03032 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2394,7 +2394,7 @@ static unsigned long reclaim_high(struct mem_cgroup *memcg,
 		nr_reclaimed += try_to_free_mem_cgroup_pages(memcg, nr_pages,
 							gfp_mask,
 							MEMCG_RECLAIM_MAY_SWAP,
-							NULL);
+							NULL, NULL);
 		psi_memstall_leave(&pflags);
 	} while ((memcg = parent_mem_cgroup(memcg)) &&
 		 !mem_cgroup_is_root(memcg));
@@ -2716,7 +2716,8 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,
 
 	psi_memstall_enter(&pflags);
 	nr_reclaimed = try_to_free_mem_cgroup_pages(mem_over_limit, nr_pages,
-						    gfp_mask, reclaim_options, NULL);
+						    gfp_mask, reclaim_options,
+						    NULL, NULL);
 	psi_memstall_leave(&pflags);
 
 	if (mem_cgroup_margin(mem_over_limit) >= nr_pages)
@@ -4878,7 +4879,8 @@ static ssize_t memory_high_write(struct kernfs_open_file *of,
 		}
 
 		reclaimed = try_to_free_mem_cgroup_pages(memcg, nr_pages - high,
-					GFP_KERNEL, MEMCG_RECLAIM_MAY_SWAP, NULL);
+					GFP_KERNEL, MEMCG_RECLAIM_MAY_SWAP,
+					NULL, NULL);
 
 		if (!reclaimed && !nr_retries--)
 			break;
@@ -4937,7 +4939,8 @@ static ssize_t memory_max_write(struct kernfs_open_file *of,
 
 		if (nr_reclaims) {
 			if (!try_to_free_mem_cgroup_pages(memcg, nr_pages - max,
-					GFP_KERNEL, MEMCG_RECLAIM_MAY_SWAP, NULL))
+					GFP_KERNEL, MEMCG_RECLAIM_MAY_SWAP,
+					NULL, NULL))
 				nr_reclaims--;
 			continue;
 		}
diff --git a/mm/vmscan.c b/mm/vmscan.c
index f11491ee9ed5..155d0ef82b48 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -6856,12 +6856,14 @@ unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,
 					   unsigned long nr_pages,
 					   gfp_t gfp_mask,
 					   unsigned int reclaim_options,
-					   int *swappiness)
+					   int *swappiness,
+					   const nodemask_t *source_nodes)
 {
 	unsigned long nr_reclaimed;
 	unsigned int noreclaim_flag;
 	struct scan_control sc = {
 		.nr_to_reclaim = max(nr_pages, SWAP_CLUSTER_MAX),
+		.nodemask = source_nodes,
 		.proactive_swappiness = swappiness,
 		.gfp_mask = (current_gfp_context(gfp_mask) & GFP_RECLAIM_MASK) |
 				(GFP_HIGHUSER_MOVABLE & ~GFP_RECLAIM_MASK),
@@ -6897,7 +6899,8 @@ unsigned long try_to_free_mem_cgroup_pages(struct mem_cgroup *memcg,
 					   unsigned long nr_pages,
 					   gfp_t gfp_mask,
 					   unsigned int reclaim_options,
-					   int *swappiness)
+					   int *swappiness,
+					   const nodemask_t *source_nodes)
 {
 	return 0;
 }
@@ -7947,20 +7950,50 @@ static unsigned long __node_reclaim(struct pglist_data *pgdat,
 enum {
 	MEMORY_RECLAIM_SWAPPINESS = 0,
 	MEMORY_RECLAIM_SWAPPINESS_MAX,
+	MEMORY_RECLAIM_SOURCE,
 	MEMORY_RECLAIM_NULL,
 };
 static const match_table_t tokens = {
 	{ MEMORY_RECLAIM_SWAPPINESS, "swappiness=%d"},
 	{ MEMORY_RECLAIM_SWAPPINESS_MAX, "swappiness=max"},
+	{ MEMORY_RECLAIM_SOURCE, "source=%s"},
 	{ MEMORY_RECLAIM_NULL, NULL },
 };
 
+#define MEMORY_RECLAIM_SOURCE_NODES	"nodes:"
+
+static int parse_memory_reclaim_source(const char *source,
+				       nodemask_t *source_nodes)
+{
+	const char *nodes;
+
+	if (strncmp(source, MEMORY_RECLAIM_SOURCE_NODES,
+		    strlen(MEMORY_RECLAIM_SOURCE_NODES)))
+		return -EINVAL;
+
+	nodes = source + strlen(MEMORY_RECLAIM_SOURCE_NODES);
+	if (!*nodes)
+		return -EINVAL;
+
+	nodes_clear(*source_nodes);
+	if (nodelist_parse(nodes, *source_nodes))
+		return -EINVAL;
+
+	nodes_and(*source_nodes, *source_nodes, node_states[N_MEMORY]);
+	if (nodes_empty(*source_nodes))
+		return -EINVAL;
+
+	return 0;
+}
+
 int user_proactive_reclaim(char *buf,
 			   struct mem_cgroup *memcg, pg_data_t *pgdat)
 {
 	unsigned int nr_retries = MAX_RECLAIM_RETRIES;
 	unsigned long nr_to_reclaim, nr_reclaimed = 0;
 	int swappiness = -1;
+	nodemask_t source_nodes;
+	const nodemask_t *source_nodemask = NULL;
 	char *old_buf, *start;
 	substring_t args[MAX_OPT_ARGS];
 	gfp_t gfp_mask = GFP_KERNEL;
@@ -7991,6 +8024,14 @@ int user_proactive_reclaim(char *buf,
 		case MEMORY_RECLAIM_SWAPPINESS_MAX:
 			swappiness = SWAPPINESS_ANON_ONLY;
 			break;
+		case MEMORY_RECLAIM_SOURCE:
+			if (!memcg || source_nodemask)
+				return -EINVAL;
+			if (parse_memory_reclaim_source(args[0].from,
+							&source_nodes))
+				return -EINVAL;
+			source_nodemask = &source_nodes;
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -8030,7 +8071,8 @@ int user_proactive_reclaim(char *buf,
 			reclaimed = try_to_free_mem_cgroup_pages(memcg,
 						 batch_size, gfp_mask,
 						 reclaim_options,
-						 swappiness == -1 ? NULL : &swappiness);
+						 swappiness == -1 ? NULL : &swappiness,
+						 source_nodemask);
 		} else {
 			struct scan_control sc = {
 				.gfp_mask = current_gfp_context(gfp_mask),
-- 
2.20.1

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

* [RFC 2/2] mm: add tier source selector to memory.reclaim
  2026-10-19 14:13 [RFC 0/2] mm: add source selectors to memory.reclaim Li Zhe
  2026-10-19 14:14 ` [RFC 1/2] mm: add source selector " Li Zhe
@ 2026-10-19 14:14 ` Li Zhe
  2026-10-19 15:21 ` [RFC 0/2] mm: add source selectors " Joshua Hahn
  2026-10-19 16:10 ` Gregory Price
  3 siblings, 0 replies; 12+ messages in thread
From: Li Zhe @ 2026-10-19 14:14 UTC (permalink / raw)
  To: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	ljs, mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc
  Cc: cgroups, linux-doc, linux-kernel, linux-mm, Li Zhe

source=nodes:<nodelist> lets userspace apply the existing
memory.reclaim aging pipeline to a specific set of NUMA nodes. Many
tiering controllers reason in terms of memory tiers rather than raw
node lists, and the memory tier core already exposes those tiers as
/sys/devices/virtual/memory_tiering/memory_tierN/nodelist.

Add source=tier:<N> as another typed source selector. The selector
uses the same N as memory_tierN and resolves it to the tier's current
memory-node mask at write time. The resulting mask is then passed to
the same source-scoped reclaim path used by source=nodes:<nodelist>.

This is only a selector convenience. It does not choose demotion
targets, does not introduce a demotion-only or eviction-only goal, and
does not change memory.reclaim completion accounting or reclaim policy.

Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
---
 Documentation/admin-guide/cgroup-v2.rst | 22 ++++++++-----
 include/linux/memory-tiers.h            |  6 ++++
 mm/memory-tiers.c                       | 24 ++++++++++++++
 mm/vmscan.c                             | 42 +++++++++++++++++--------
 4 files changed, 73 insertions(+), 21 deletions(-)

diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
index a016575010ba..d6e9b275fcfd 100644
--- a/Documentation/admin-guide/cgroup-v2.rst
+++ b/Documentation/admin-guide/cgroup-v2.rst
@@ -1467,18 +1467,24 @@ The following nested keys are defined.
 
 	The source key declares where the normal reclaim aging pipeline is
 	applied. It is a typed selector in the form source=<type>:<selector>.
-	Currently only source=nodes:<nodelist> is supported. For example::
+
+	Currently source=nodes:<nodelist> and source=tier:<N> are supported.
+	For example::
 
 	  echo "1G source=nodes:0-1" > memory.reclaim
+	  echo "1G source=tier:4" > memory.reclaim
 
 	This instructs the kernel to apply reclaim pressure to memory of target
-	cgroup that are currently resident on nodes 0 and 1. Reclaim then
-	follows the normal reclaim path for selected folios. The source
-	selector does not choose a demotion or eviction policy. On tiered
-	memory systems, a source node in a non-terminal tier will usually make
-	progress through demotion when an allowed demotion target exists. A
-	source node in the terminal tier has no demotion target, so successful
-	reclaim progress from that source is eviction.
+	cgroup that are currently resident on the selected source. The nodes
+	form selects nodes directly. The tier form selects the nodes currently
+	in /sys/devices/virtual/memory_tiering/memory_tierN/nodelist, where N
+	is the memory tier id. Reclaim then follows the normal reclaim path
+	for selected folios. The source selector does not choose a demotion or
+	eviction policy. On tiered memory systems, a source node in a
+	non-terminal tier will usually make progress through demotion when an
+	allowed demotion target exists. A source node in the terminal tier has
+	no demotion target, so successful reclaim progress from that source is
+	eviction.
 
 	The source selector is a source constraint only. It does not select or
 	limit demotion targets; those are still chosen by the kernel's memory
diff --git a/include/linux/memory-tiers.h b/include/linux/memory-tiers.h
index 7999c58629ee..71fa51ba317d 100644
--- a/include/linux/memory-tiers.h
+++ b/include/linux/memory-tiers.h
@@ -49,6 +49,7 @@ int mt_calc_adistance(int node, int *adist);
 int mt_set_default_dram_perf(int nid, struct access_coordinate *perf,
 			     const char *source);
 int mt_perf_to_adistance(struct access_coordinate *perf, int *adist);
+int mt_get_memory_tier_nodemask(int tier, nodemask_t *nodes);
 struct memory_dev_type *mt_find_alloc_memory_type(int adist,
 						  struct list_head *memory_types);
 void mt_put_memory_types(struct list_head *memory_types);
@@ -142,6 +143,11 @@ static inline int mt_perf_to_adistance(struct access_coordinate *perf, int *adis
 	return -EIO;
 }
 
+static inline int mt_get_memory_tier_nodemask(int tier, nodemask_t *nodes)
+{
+	return -EINVAL;
+}
+
 static inline struct memory_dev_type *mt_find_alloc_memory_type(int adist,
 								struct list_head *memory_types)
 {
diff --git a/mm/memory-tiers.c b/mm/memory-tiers.c
index 54851d8a195b..75ec73b224a3 100644
--- a/mm/memory-tiers.c
+++ b/mm/memory-tiers.c
@@ -180,6 +180,30 @@ static ssize_t nodelist_show(struct device *dev,
 }
 static DEVICE_ATTR_RO(nodelist);
 
+int mt_get_memory_tier_nodemask(int tier, nodemask_t *nodes)
+{
+	struct memory_tier *memtier;
+	int ret = -EINVAL;
+
+	if (tier < 0)
+		return -EINVAL;
+
+	mutex_lock(&memory_tier_lock);
+	list_for_each_entry(memtier, &memory_tiers, list) {
+		if (memtier->dev.id != tier)
+			continue;
+
+		*nodes = get_memtier_nodemask(memtier);
+		nodes_and(*nodes, *nodes, node_states[N_MEMORY]);
+		if (!nodes_empty(*nodes))
+			ret = 0;
+		break;
+	}
+	mutex_unlock(&memory_tier_lock);
+
+	return ret;
+}
+
 static struct attribute *memtier_dev_attrs[] = {
 	&dev_attr_nodelist.attr,
 	NULL
diff --git a/mm/vmscan.c b/mm/vmscan.c
index 155d0ef82b48..2abbce265af6 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -7961,29 +7961,45 @@ static const match_table_t tokens = {
 };
 
 #define MEMORY_RECLAIM_SOURCE_NODES	"nodes:"
+#define MEMORY_RECLAIM_SOURCE_TIER	"tier:"
 
 static int parse_memory_reclaim_source(const char *source,
 				       nodemask_t *source_nodes)
 {
 	const char *nodes;
+	const char *tier;
+	int tier_id;
 
-	if (strncmp(source, MEMORY_RECLAIM_SOURCE_NODES,
-		    strlen(MEMORY_RECLAIM_SOURCE_NODES)))
-		return -EINVAL;
+	if (!strncmp(source, MEMORY_RECLAIM_SOURCE_NODES,
+		     strlen(MEMORY_RECLAIM_SOURCE_NODES))) {
+		nodes = source + strlen(MEMORY_RECLAIM_SOURCE_NODES);
+		if (!*nodes)
+			return -EINVAL;
 
-	nodes = source + strlen(MEMORY_RECLAIM_SOURCE_NODES);
-	if (!*nodes)
-		return -EINVAL;
+		nodes_clear(*source_nodes);
+		if (nodelist_parse(nodes, *source_nodes))
+			return -EINVAL;
 
-	nodes_clear(*source_nodes);
-	if (nodelist_parse(nodes, *source_nodes))
-		return -EINVAL;
+		nodes_and(*source_nodes, *source_nodes, node_states[N_MEMORY]);
+		if (nodes_empty(*source_nodes))
+			return -EINVAL;
 
-	nodes_and(*source_nodes, *source_nodes, node_states[N_MEMORY]);
-	if (nodes_empty(*source_nodes))
-		return -EINVAL;
+		return 0;
+	}
 
-	return 0;
+	if (!strncmp(source, MEMORY_RECLAIM_SOURCE_TIER,
+		     strlen(MEMORY_RECLAIM_SOURCE_TIER))) {
+		tier = source + strlen(MEMORY_RECLAIM_SOURCE_TIER);
+		if (!*tier)
+			return -EINVAL;
+
+		if (kstrtoint(tier, 10, &tier_id))
+			return -EINVAL;
+
+		return mt_get_memory_tier_nodemask(tier_id, source_nodes);
+	}
+
+	return -EINVAL;
 }
 
 int user_proactive_reclaim(char *buf,
-- 
2.20.1

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

* Re: [RFC 1/2] mm: add source selector to memory.reclaim
  2026-10-19 14:14 ` [RFC 1/2] mm: add source selector " Li Zhe
@ 2026-10-19 14:51   ` Lorenzo Stoakes (ARM)
  2026-10-20  8:36     ` Li Zhe
  0 siblings, 1 reply; 12+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-19 14:51 UTC (permalink / raw)
  To: Li Zhe
  Cc: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On Mon, Oct 19, 2026 at 10:14:00PM +0800, Li Zhe wrote:
> memory.reclaim was added as a generic proactive reclaim
> interface for memcgs, but memory tiering makes its practical
...
> The source= key is intentionally typed so future selectors can be
> added without reinterpreting the nodes form. For example, later
> patches may add source=tier:<selector> or other source types while
> keeping source=nodes:<nodelist> as the NUMA-node source selector.

Similar comment for the commit message as below re: simplicity.

>
> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
> ---
>  Documentation/admin-guide/cgroup-v2.rst | 23 ++++++++++++
>  mm/internal.h                           |  3 +-
>  mm/memcontrol-v1.c                      |  6 ++--
>  mm/memcontrol.c                         | 11 +++---
>  mm/vmscan.c                             | 48 +++++++++++++++++++++++--
>  5 files changed, 81 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
> index 86a2a0099178..a016575010ba 100644
> --- a/Documentation/admin-guide/cgroup-v2.rst
> +++ b/Documentation/admin-guide/cgroup-v2.rst
> @@ -1454,6 +1454,7 @@ The following nested keys are defined.
>
>  	  ==========            ================================
>  	  swappiness            Swappiness value to reclaim with
> +	  source                Source to apply reclaim pressure to
>  	  ==========            ================================
>
>  	Specifying a swappiness value instructs the kernel to perform
> @@ -1464,6 +1465,28 @@ The following nested keys are defined.
>  	The valid range for swappiness is [0-200, max], setting
>  	swappiness=max exclusively reclaims anonymous memory.
>
> +	The source key declares where the normal reclaim aging pipeline is
> +	applied. It is a typed selector in the form source=<type>:<selector>.
> +	Currently only source=nodes:<nodelist> is supported. For example::
> +
> +	  echo "1G source=nodes:0-1" > memory.reclaim
> +
> +	This instructs the kernel to apply reclaim pressure to memory of target
> +	cgroup that are currently resident on nodes 0 and 1. Reclaim then
> +	follows the normal reclaim path for selected folios. The source
> +	selector does not choose a demotion or eviction policy. On tiered
> +	memory systems, a source node in a non-terminal tier will usually make
> +	progress through demotion when an allowed demotion target exists. A
> +	source node in the terminal tier has no demotion target, so successful
> +	reclaim progress from that source is eviction.
> +
> +	The source selector is a source constraint only. It does not select or
> +	limit demotion targets; those are still chosen by the kernel's memory
> +	tier demotion policy. Specifying source does not change reclaim
> +	completion accounting. On tiered memory systems, completion may include
> +	demotion progress that moves folios away from the selected source nodes
> +	without reducing memory.current.
> +

I'm no cgroup expert so I can't really comment on the concept overall, but can I
ask please that you try to make this documentation FAR simpler and clearer?

I have no idea what an 'aging pipeline' is, then the paragraphs dive into
significant detail and I don't see why you would want this or what it's useful
for?

You should lead with that, and keep things simple, only introducing detail
later.

Right now it's 'types:selector' (only 1 trivial example given) -> reclaim
follows 'normal' path (what is 'normal'?) -> for selected folios (who selects
them? How is it relevant to _an admin_?) -> source selector (what?) does NOT
choose a demotion/eviction policy (neither defined in document) -> source node
in non-terminal etc. etc.

It's very typical of LLM-generated text - it treats the reader as a computer
with a large mental 'stack' for storing this information.

Humans don't work that way.

The document targets _admins_ and this text strikes me as very very confusing
for kernel developers, let alone admins.

It also seems to assume a lot of knowledge.

--
Cheers, Lorenzo

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-19 14:13 [RFC 0/2] mm: add source selectors to memory.reclaim Li Zhe
  2026-10-19 14:14 ` [RFC 1/2] mm: add source selector " Li Zhe
  2026-10-19 14:14 ` [RFC 2/2] mm: add tier " Li Zhe
@ 2026-10-19 15:21 ` Joshua Hahn
  2026-10-20  9:06   ` Li Zhe
  2026-10-19 16:10 ` Gregory Price
  3 siblings, 1 reply; 12+ messages in thread
From: Joshua Hahn @ 2026-10-19 15:21 UTC (permalink / raw)
  To: Li Zhe
  Cc: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	ljs, mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On Mon, 19 Oct 2026 22:13:59 +0800 "Li Zhe" <lizhe.67@bytedance.com> wrote:

> memory.reclaim is a useful proactive reclaim interface for memcgs, but
> tiered memory makes "reclaim N bytes from this memcg" underspecified for
> userspace controllers that manage memory placement across tiers. The
> kernel's reclaim policy is aging-based, and on tiered systems aging may
> demote folios before they are eventually evicted. Without a way to
> declare where reclaim pressure should start, userspace can only reclaim
> from the memcg as a whole and observe one aggregate progress value.

Hi Li,

Thank you for working on this patch. I think it's quite interesting.
I have many comments about implementation detials but for now let's
discuss the direction of this series first.

> There is prior history here [1]. The old nodes= proposal tried to let
> memory.reclaim scan selected NUMA nodes, but the discussion left several
> ABI questions unresolved:
> 
> - nodes= did not clearly say whether the mask was a source selector, a
>   reclaim domain, or a demotion-target constraint.
> - The implementation did not apply the mask to the whole reclaim path,
>   so demotion could allocate outside the mask. If userspace interpreted
>   the mask as a domain or target constraint, the ABI did not provide
>   that.
> - Selecting top-tier nodes could become an implicit way to request
>   demotion without uncharge. That would make userspace depend on a
>   particular reclaim policy and could make future policy changes hard.
> - The separate completion-accounting question remained: demotion can
>   count as reclaim progress even though memory.current is not reduced.
> 
> This series takes a narrower approach.

I think this is the way to go.

> It adds a typed source= key which
> declares only the source on which the existing memory.reclaim aging
> pipeline should be applied.

Calling it an "aging pipeline" is a big confusing to me. Maybe just call
it eviction targets or reclaim targets?

> It is not a demotion target selector, not a
> demotion-only goal, not an eviction-only goal, and not a change to the
> existing completion accounting. Leaving source unspecified preserves the
> existing memory.reclaim behavior.
> 
> In that sense, this series extends node-scoped reclaim pressure to the
> memcg memory.reclaim interface. The existing node memory_reclaim
> interface can apply pressure to a node, but it is not scoped to a
> particular memcg. This series lets userspace apply the same kind of
> source-scoped pressure to a cgroup's resident memory on selected nodes
> or tiers, while still using the normal memcg reclaim path.
> 
> The main benefit of this series is to give userspace a steering knob
> for per-node and per-tier memory placement control. A tiering controller
> can observe the resident memory distribution across nodes or memory
> tiers, and then apply reclaim pressure to the source that should be
> relieved. For example, it can apply pressure to top-tier nodes so that,
> under the existing tiering policy, cold folios can be aged and usually
> demoted to lower tiers, or reclaim from a terminal tier when it needs
> actual charge reduction.

To me I think the other interesting aspect is being able to evict memory
from lower tiers out to swap so that toptier always has room for
demotions, preventing swapouts from higher tiers. In fact I think for
my interests this is the more important use case.

> The existing memory.reclaim interface cannot express this. It applies
> pressure to the memcg as a whole, so on a multi-tier system the kernel
> may make progress in a tier different from the one userspace is trying
> to control. That makes feedback control of tier ratios ineffective: the
> controller can observe that a specific tier is over its desired resident
> footprint, but it cannot direct the next reclaim request to that tier.

This part is confusing to me. The kernel curently doesn't observe
per-tier-memcg memory footprint and try to manage it, does it?
( that is what I'm trying to solve currently in my tiered memcg series ; -)
  but as it stands right now, I don't think there is any feedback loop )

> Patch 1 adds source=nodes:<nodelist>, which is the basic source
> selector.

In general I agree with Lorenzo, it took me a second to realize that the
bulk of this series is just adding a nodemask to
try_to_free_mem_cgroup_pages. It might be helpful to just call it
"reclaim" instead of calling it an aging pipeline.

> Patch 2 adds source=tier:<N>, where N is the memory_tierN id
> exposed by /sys/devices/virtual/memory_tiering/. The tier selector is
> only a convenience layer over the same source semantics: it resolves the
> current tier nodelist at write time and then uses the same source-scoped
> reclaim path as source=nodes:<nodelist>.

Personally I think that it makes more sense to just start with a
node-based reclaim interface, and then extending it to tiers. It seems
like the tier usecase is entirely doable with just a nodemask interface.

And in this case, since we're just selecting the sources of reclaim,
tiers don't really matter, right? That is, we don't care if the memory
gets demoted or swapped out, we just care that the footprint for the
tier memcg goes down. 

> The remaining issues from the old discussion are intentionally left out
> of this series. If userspace needs to control demotion targets, that
> should be a separate target/demotion-policy ABI rather than folded into
> source=. If userspace needs a strict "bytes actually uncharged"
> completion mode, that should be discussed as a separate accounting or
> goal extension because it would change the observable meaning of
> memory.reclaim progress. This series only adds source selection for the
> existing reclaim policy.

100% agree.

Thanks Li, I hope you have a great day!
Joshua
 
> References:
> [1] https://lore.kernel.org/all/Y5bsmpCyeryu3Zz1@dhcp22.suse.cz/
> 
> Li Zhe (2):
>   mm: add source selector to memory.reclaim
>   mm: add tier source selector to memory.reclaim
> 
>  Documentation/admin-guide/cgroup-v2.rst | 29 +++++++++++
>  include/linux/memory-tiers.h            |  6 +++
>  mm/internal.h                           |  3 +-
>  mm/memcontrol-v1.c                      |  6 ++-
>  mm/memcontrol.c                         | 11 +++--
>  mm/memory-tiers.c                       | 24 ++++++++++
>  mm/vmscan.c                             | 64 +++++++++++++++++++++++--
>  7 files changed, 133 insertions(+), 10 deletions(-)
> 
> -- 
> 2.20.1

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-19 14:13 [RFC 0/2] mm: add source selectors to memory.reclaim Li Zhe
                   ` (2 preceding siblings ...)
  2026-10-19 15:21 ` [RFC 0/2] mm: add source selectors " Joshua Hahn
@ 2026-10-19 16:10 ` Gregory Price
  2026-10-20 13:01   ` Li Zhe
  3 siblings, 1 reply; 12+ messages in thread
From: Gregory Price @ 2026-10-19 16:10 UTC (permalink / raw)
  To: Li Zhe
  Cc: akpm, david, ziy, joshua.hahnjy, rakie.kim, ying.huang, ljs,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On Mon, Oct 19, 2026 at 10:13:59PM +0800, Li Zhe wrote:
> memory.reclaim is a useful proactive reclaim interface for memcgs, but
> tiered memory makes "reclaim N bytes from this memcg" underspecified for
> userspace controllers that manage memory placement across tiers. The
> kernel's reclaim policy is aging-based, and on tiered systems aging may
> demote folios before they are eventually evicted. Without a way to
> declare where reclaim pressure should start, userspace can only reclaim
> from the memcg as a whole and observe one aggregate progress value.

This is not a good argument for pushing the complexity of managing tiers
into userspace - it is an argument for fixing the reclaim interface to
handle demotion and eviction sanely - which presently we do not do.

My best examples:
  1) the watermarks don't make sense for lower tiers that need to absorb
     large amounts of demotions from upper tiers.  It's likely lower
     tiers need larger WMARK_PROMO to let low-tier kswapd run longer.

  2) proactive and async reclaim current reclaim top-down, which results
     in swapping directly from top-tiers because lower tiers don't have
     space to absorb demotions.  We should experiment with async /
     proactive reclaim applying bottom-up (lowest tier first) so aging
     the top-tiers demotes into lower tiers more successfully.

     direct reclaim should stay top-down because we want the best page
     with the least work to minimize stalls.

I have a strong suspicion if we made these change, then memcg.reclaim
would work more or less the way you'd want - and anything else can be
managed more directly with madvise/process_madvise.

I also have concerns with extending memory.reclaim this way permanently
locking in reclaim implementation details that prevent us from making
changes like this in the future.

~Gregory

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

* Re: [RFC 1/2] mm: add source selector to memory.reclaim
  2026-10-19 14:51   ` Lorenzo Stoakes (ARM)
@ 2026-10-20  8:36     ` Li Zhe
  0 siblings, 0 replies; 12+ messages in thread
From: Li Zhe @ 2026-10-20  8:36 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM)
  Cc: akpm, david, gourry, ziy, joshua.hahnjy, rakie.kim, ying.huang,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On 10/19/26 10:51 PM, Lorenzo Stoakes (ARM) wrote:
> On Mon, Oct 19, 2026 at 10:14:00PM +0800, Li Zhe wrote:
>> memory.reclaim was added as a generic proactive reclaim
>> interface for memcgs, but memory tiering makes its practical
> ...
>> The source= key is intentionally typed so future selectors can be
>> added without reinterpreting the nodes form. For example, later
>> patches may add source=tier:<selector> or other source types while
>> keeping source=nodes:<nodelist> as the NUMA-node source selector.
> Similar comment for the commit message as below re: simplicity.
>
>> Signed-off-by: Li Zhe <lizhe.67@bytedance.com>
>> ---
>>   Documentation/admin-guide/cgroup-v2.rst | 23 ++++++++++++
>>   mm/internal.h                           |  3 +-
>>   mm/memcontrol-v1.c                      |  6 ++--
>>   mm/memcontrol.c                         | 11 +++---
>>   mm/vmscan.c                             | 48 +++++++++++++++++++++++--
>>   5 files changed, 81 insertions(+), 10 deletions(-)
>>
>> diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst
>> index 86a2a0099178..a016575010ba 100644
>> --- a/Documentation/admin-guide/cgroup-v2.rst
>> +++ b/Documentation/admin-guide/cgroup-v2.rst
>> @@ -1454,6 +1454,7 @@ The following nested keys are defined.
>>
>>   	  ==========            ================================
>>   	  swappiness            Swappiness value to reclaim with
>> +	  source                Source to apply reclaim pressure to
>>   	  ==========            ================================
>>
>>   	Specifying a swappiness value instructs the kernel to perform
>> @@ -1464,6 +1465,28 @@ The following nested keys are defined.
>>   	The valid range for swappiness is [0-200, max], setting
>>   	swappiness=max exclusively reclaims anonymous memory.
>>
>> +	The source key declares where the normal reclaim aging pipeline is
>> +	applied. It is a typed selector in the form source=<type>:<selector>.
>> +	Currently only source=nodes:<nodelist> is supported. For example::
>> +
>> +	  echo "1G source=nodes:0-1" > memory.reclaim
>> +
>> +	This instructs the kernel to apply reclaim pressure to memory of target
>> +	cgroup that are currently resident on nodes 0 and 1. Reclaim then
>> +	follows the normal reclaim path for selected folios. The source
>> +	selector does not choose a demotion or eviction policy. On tiered
>> +	memory systems, a source node in a non-terminal tier will usually make
>> +	progress through demotion when an allowed demotion target exists. A
>> +	source node in the terminal tier has no demotion target, so successful
>> +	reclaim progress from that source is eviction.
>> +
>> +	The source selector is a source constraint only. It does not select or
>> +	limit demotion targets; those are still chosen by the kernel's memory
>> +	tier demotion policy. Specifying source does not change reclaim
>> +	completion accounting. On tiered memory systems, completion may include
>> +	demotion progress that moves folios away from the selected source nodes
>> +	without reducing memory.current.
>> +
> I'm no cgroup expert so I can't really comment on the concept overall, but can I
> ask please that you try to make this documentation FAR simpler and clearer?
>
> I have no idea what an 'aging pipeline' is, then the paragraphs dive into
> significant detail and I don't see why you would want this or what it's useful
> for?
By "aging pipeline" I meant the combined reclaim behavior that may
demote some folios and evict others, depending on the existing reclaim
policy. But I agree that this wording is confusing here.
>
> You should lead with that, and keep things simple, only introducing detail
> later.
>
> Right now it's 'types:selector' (only 1 trivial example given) -> reclaim
> follows 'normal' path (what is 'normal'?) -> for selected folios (who selects
> them? How is it relevant to _an admin_?) -> source selector (what?) does NOT
> choose a demotion/eviction policy (neither defined in document) -> source node
> in non-terminal etc. etc.
>
> It's very typical of LLM-generated text - it treats the reader as a computer
> with a large mental 'stack' for storing this information.
>
> Humans don't work that way.
>
> The document targets _admins_ and this text strikes me as very very confusing
> for kernel developers, let alone admins.
>
> It also seems to assume a lot of knowledge.
Sorry for the confusion. I will rewrite the cgroup-v2.rst text in v2 to
be much clearer and easier to read.

Thanks,
Zhe
>
> --
> Cheers, Lorenzo

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-19 15:21 ` [RFC 0/2] mm: add source selectors " Joshua Hahn
@ 2026-10-20  9:06   ` Li Zhe
  0 siblings, 0 replies; 12+ messages in thread
From: Li Zhe @ 2026-10-20  9:06 UTC (permalink / raw)
  To: Joshua Hahn
  Cc: akpm, david, gourry, ziy, rakie.kim, ying.huang, ljs, mhocko,
	muchun.song, roman.gushchin, shakeel.butt, weixugc, cgroups,
	linux-doc, linux-kernel, linux-mm

On 10/19/26 11:21 PM, Joshua Hahn wrote:
> On Mon, 19 Oct 2026 22:13:59 +0800 "Li Zhe" <lizhe.67@bytedance.com> wrote:
>
>> memory.reclaim is a useful proactive reclaim interface for memcgs, but
>> tiered memory makes "reclaim N bytes from this memcg" underspecified for
>> userspace controllers that manage memory placement across tiers. The
>> kernel's reclaim policy is aging-based, and on tiered systems aging may
>> demote folios before they are eventually evicted. Without a way to
>> declare where reclaim pressure should start, userspace can only reclaim
>> from the memcg as a whole and observe one aggregate progress value.
> Hi Li,
>
> Thank you for working on this patch. I think it's quite interesting.
> I have many comments about implementation detials but for now let's
> discuss the direction of this series first.
>
>> There is prior history here [1]. The old nodes= proposal tried to let
>> memory.reclaim scan selected NUMA nodes, but the discussion left several
>> ABI questions unresolved:
>>
>> - nodes= did not clearly say whether the mask was a source selector, a
>>    reclaim domain, or a demotion-target constraint.
>> - The implementation did not apply the mask to the whole reclaim path,
>>    so demotion could allocate outside the mask. If userspace interpreted
>>    the mask as a domain or target constraint, the ABI did not provide
>>    that.
>> - Selecting top-tier nodes could become an implicit way to request
>>    demotion without uncharge. That would make userspace depend on a
>>    particular reclaim policy and could make future policy changes hard.
>> - The separate completion-accounting question remained: demotion can
>>    count as reclaim progress even though memory.current is not reduced.
>>
>> This series takes a narrower approach.
> I think this is the way to go.


Thanks for taking a look.

>
>> It adds a typed source= key which
>> declares only the source on which the existing memory.reclaim aging
>> pipeline should be applied.
> Calling it an "aging pipeline" is a big confusing to me. Maybe just call
> it eviction targets or reclaim targets?

Thanks. By "aging pipeline" I meant the combined reclaim behavior that
may demote some folios and evict others, depending on the existing
reclaim policy. But this wording is confusing here. I will use "reclaim
targets" in v2.
>
>> It is not a demotion target selector, not a
>> demotion-only goal, not an eviction-only goal, and not a change to the
>> existing completion accounting. Leaving source unspecified preserves the
>> existing memory.reclaim behavior.
>>
>> In that sense, this series extends node-scoped reclaim pressure to the
>> memcg memory.reclaim interface. The existing node memory_reclaim
>> interface can apply pressure to a node, but it is not scoped to a
>> particular memcg. This series lets userspace apply the same kind of
>> source-scoped pressure to a cgroup's resident memory on selected nodes
>> or tiers, while still using the normal memcg reclaim path.
>>
>> The main benefit of this series is to give userspace a steering knob
>> for per-node and per-tier memory placement control. A tiering controller
>> can observe the resident memory distribution across nodes or memory
>> tiers, and then apply reclaim pressure to the source that should be
>> relieved. For example, it can apply pressure to top-tier nodes so that,
>> under the existing tiering policy, cold folios can be aged and usually
>> demoted to lower tiers, or reclaim from a terminal tier when it needs
>> actual charge reduction.
> To me I think the other interesting aspect is being able to evict memory
> from lower tiers out to swap so that toptier always has room for
> demotions, preventing swapouts from higher tiers. In fact I think for
> my interests this is the more important use case.


Thanks for pointing this out. This is indeed another important use
case. I will add this use case to the cover letter in v2.

>
>> The existing memory.reclaim interface cannot express this. It applies
>> pressure to the memcg as a whole, so on a multi-tier system the kernel
>> may make progress in a tier different from the one userspace is trying
>> to control. That makes feedback control of tier ratios ineffective: the
>> controller can observe that a specific tier is over its desired resident
>> footprint, but it cannot direct the next reclaim request to that tier.
> This part is confusing to me. The kernel curently doesn't observe
> per-tier-memcg memory footprint and try to manage it, does it?
> ( that is what I'm trying to solve currently in my tiered memcg series ; -)
>    but as it stands right now, I don't think there is any feedback loop )


Right, I did not mean that the kernel has such a feedback loop today.
The feedback loop I had in mind is a userspace controller loop: userspace
observes the memcg's resident distribution, for example from per-node
memcg stats, maps nodes to tiers, and then issues memory.reclaim against
the source it wants to relieve.

The wording is confusing because it can sound like an existing kernel
policy. I will reword this in v2.

I have also looked at your tiered memcg series. If it is merged, I think
it will be very useful for tiering use cases. I hope that work can move
forward as well.

>
>> Patch 1 adds source=nodes:<nodelist>, which is the basic source
>> selector.
> In general I agree with Lorenzo, it took me a second to realize that the
> bulk of this series is just adding a nodemask to
> try_to_free_mem_cgroup_pages. It might be helpful to just call it
> "reclaim" instead of calling it an aging pipeline.


Agreed. I will simplify both the cover letter and the documentation in v2.

>
>> Patch 2 adds source=tier:<N>, where N is the memory_tierN id
>> exposed by /sys/devices/virtual/memory_tiering/. The tier selector is
>> only a convenience layer over the same source semantics: it resolves the
>> current tier nodelist at write time and then uses the same source-scoped
>> reclaim path as source=nodes:<nodelist>.
> Personally I think that it makes more sense to just start with a
> node-based reclaim interface, and then extending it to tiers. It seems
> like the tier usecase is entirely doable with just a nodemask interface.


That is reasonable. source=tier is only a convenience wrapper around the
same node mask semantics. It is not required for the core ABI.

I can drop patch 2 in v2 and start with source=nodes only.

>
> And in this case, since we're just selecting the sources of reclaim,
> tiers don't really matter, right? That is, we don't care if the memory
> gets demoted or swapped out, we just care that the footprint for the
> tier memcg goes down.


Yes, that matches the intended semantics. source= selects the resident
source to apply reclaim pressure to. It does not choose whether progress
comes from demotion or eviction.

So for source=tier, the tier only provides a convenient way to resolve a
set of source nodes. The actual reclaim semantics are still the same as
source=nodes.

>
>> The remaining issues from the old discussion are intentionally left out
>> of this series. If userspace needs to control demotion targets, that
>> should be a separate target/demotion-policy ABI rather than folded into
>> source=. If userspace needs a strict "bytes actually uncharged"
>> completion mode, that should be discussed as a separate accounting or
>> goal extension because it would change the observable meaning of
>> memory.reclaim progress. This series only adds source selection for the
>> existing reclaim policy.
> 100% agree.
>
> Thanks Li, I hope you have a great day!
> Joshua


Thanks for taking a look.

Thanks,
Zhe

>   
>> References:
>> [1] https://lore.kernel.org/all/Y5bsmpCyeryu3Zz1@dhcp22.suse.cz/
>>
>> Li Zhe (2):
>>    mm: add source selector to memory.reclaim
>>    mm: add tier source selector to memory.reclaim
>>
>>   Documentation/admin-guide/cgroup-v2.rst | 29 +++++++++++
>>   include/linux/memory-tiers.h            |  6 +++
>>   mm/internal.h                           |  3 +-
>>   mm/memcontrol-v1.c                      |  6 ++-
>>   mm/memcontrol.c                         | 11 +++--
>>   mm/memory-tiers.c                       | 24 ++++++++++
>>   mm/vmscan.c                             | 64 +++++++++++++++++++++++--
>>   7 files changed, 133 insertions(+), 10 deletions(-)
>>
>> -- 
>> 2.20.1

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-19 16:10 ` Gregory Price
@ 2026-10-20 13:01   ` Li Zhe
  2026-10-22  3:56     ` Gregory Price (Meta)
  0 siblings, 1 reply; 12+ messages in thread
From: Li Zhe @ 2026-10-20 13:01 UTC (permalink / raw)
  To: Gregory Price
  Cc: akpm, david, ziy, joshua.hahnjy, rakie.kim, ying.huang, ljs,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On 10/20/26 12:10 AM, Gregory Price wrote:
> On Mon, Oct 19, 2026 at 10:13:59PM +0800, Li Zhe wrote:
>> memory.reclaim is a useful proactive reclaim interface for memcgs, but
>> tiered memory makes "reclaim N bytes from this memcg" underspecified for
>> userspace controllers that manage memory placement across tiers. The
>> kernel's reclaim policy is aging-based, and on tiered systems aging may
>> demote folios before they are eventually evicted. Without a way to
>> declare where reclaim pressure should start, userspace can only reclaim
>> from the memcg as a whole and observe one aggregate progress value.
> This is not a good argument for pushing the complexity of managing tiers
> into userspace - it is an argument for fixing the reclaim interface to
> handle demotion and eviction sanely - which presently we do not do.
>
> My best examples:
>    1) the watermarks don't make sense for lower tiers that need to absorb
>       large amounts of demotions from upper tiers.  It's likely lower
>       tiers need larger WMARK_PROMO to let low-tier kswapd run longer.
>
>    2) proactive and async reclaim current reclaim top-down, which results
>       in swapping directly from top-tiers because lower tiers don't have
>       space to absorb demotions.  We should experiment with async /
>       proactive reclaim applying bottom-up (lowest tier first) so aging
>       the top-tiers demotes into lower tiers more successfully.
>
>       direct reclaim should stay top-down because we want the best page
>       with the least work to minimize stalls.
>
> I have a strong suspicion if we made these change, then memcg.reclaim
> would work more or less the way you'd want - and anything else can be
> managed more directly with madvise/process_madvise.
>
> I also have concerns with extending memory.reclaim this way permanently
> locking in reclaim implementation details that prevent us from making
> changes like this in the future.
>
> ~Gregory


Thanks for the feedback.

I agree that the kernel reclaim and demotion behavior still has room for
improvement, and the watermark changes and the bottom-up proactive/async
reclaim idea you mentioned may be useful to explore for some tiering
scenarios. But I think there is also an unavoidable userspace policy
component here. The kernel can improve whole-cgroup reclaim, but it
cannot know every controller's placement goal.

For example, one controller may want to keep a specific per-tier
resident ratio for a cgroup. Another use case, as Joshua pointed out in
another reply, is reclaiming from a lower tier first so that later
top-tier reclaim has room for demotion. A single default memory.reclaim
algorithm cannot express these per-cgroup policy choices.

I am not sure whether changing the default whole-cgroup reclaim behavior
in this direction could affect existing userspace dependencies. In the
old nodes= discussion, Mina mentioned that Meta's reclaim stack relied on
memory.reclaim triggering demotion [1].

I also do not intend source= to lock in current reclaim implementation
details. It should stay consistent with the current design: source= only
selects the resident source to which reclaim pressure is applied, while
the kernel remains free to change the reclaim policy.

The typed source= form also leaves room for future source selectors
without changing the meaning of source=nodes.

madvise and process_madvise are useful when userspace knows the target
virtual ranges or processes, but they are not a memcg-level interface
for applying reclaim pressure to a selected resident source.

So I tend to prefer the more conservative opt-in approach in this
patchset.

[1] https://lkml.org/lkml/2022/12/13/79

Thanks,
Zhe

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-20 13:01   ` Li Zhe
@ 2026-10-22  3:56     ` Gregory Price (Meta)
  2026-10-22  9:43       ` Li Zhe
  0 siblings, 1 reply; 12+ messages in thread
From: Gregory Price (Meta) @ 2026-10-22  3:56 UTC (permalink / raw)
  To: Li Zhe
  Cc: Gregory Price, akpm, david, ziy, joshua.hahnjy, rakie.kim,
	ying.huang, ljs, mhocko, muchun.song, roman.gushchin,
	shakeel.butt, weixugc, cgroups, linux-doc, linux-kernel,
	linux-mm

On 2026-10-20 21:01 +0800, Li Zhe wrote:
> On 10/20/26 12:10 AM, Gregory Price wrote:
> 
> I agree that the kernel reclaim and demotion behavior still has room for
> improvement, and the watermark changes and the bottom-up proactive/async
> reclaim idea you mentioned may be useful to explore for some tiering
> scenarios. But I think there is also an unavoidable userspace policy
> component here. The kernel can improve whole-cgroup reclaim, but it
> cannot know every controller's placement goal.
> 
> For example, one controller may want to keep a specific per-tier
> resident ratio for a cgroup. Another use case, as Joshua pointed out in
> another reply, is reclaiming from a lower tier first so that later
> top-tier reclaim has room for demotion. A single default memory.reclaim
> algorithm cannot express these per-cgroup policy choices.
> 

The latter of these should probably be some level of the system default,
that's why I brought it up as well.  (proactive reclaim should prefer to
make progress from the coldest tier first, then work its way up).

I'm not convinced a reclaim interface is the right way to affect a
per-tier resident ratio policy.  That feels like a hack to get around
solving the harder problem of something like tiered memcg counters
(which would otherwise drive reclaim to work exactly the way you
describe for resident ratios).

> I am not sure whether changing the default whole-cgroup reclaim behavior
> in this direction could affect existing userspace dependencies. In the
> old nodes= discussion, Mina mentioned that Meta's reclaim stack relied on
> memory.reclaim triggering demotion [1].
>

Yes and on its surface this is something I could see Meta using, however
I question whether we're simply trying to find a convenient way around
fixing reclaim to do the right thing (not invert the LRU, apply per-tier
memcg limits, etc).

> I also do not intend source= to lock in current reclaim implementation
> details. It should stay consistent with the current design: source= only
> selects the resident source to which reclaim pressure is applied, while
> the kernel remains free to change the reclaim policy.
>

Well, sort of, you get exposed to the implementation details by having
to negotiate what is considered progress under different scenarios.

Lets take one example - what should happen when:
 - 2 node system, node0=DRAM, node1=CXL
 - zswap is configured
 - echo 1G source=nodelist:1 > memory.reclaim
 - node 1 is pressured, zsmalloc falls back to node0 to allocate

Should that be considered progress? We reclaimed from node1, but
increased pressure on node0 - which might have kicked kswapd or memcg
accounting to demote memory from node0 to node1.

Should zswap also take that nodelist as a constraint?

At the very least i think you need to add details in your cover that
demonstrates the process of doing something like per-node resident
ratios, and show that it works as you intend.

-- 
~Gregory


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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-22  3:56     ` Gregory Price (Meta)
@ 2026-10-22  9:43       ` Li Zhe
  2026-10-22 14:11         ` Gregory Price
  0 siblings, 1 reply; 12+ messages in thread
From: Li Zhe @ 2026-10-22  9:43 UTC (permalink / raw)
  To: Gregory Price (Meta)
  Cc: akpm, david, ziy, joshua.hahnjy, rakie.kim, ying.huang, ljs,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On 10/22/26 11:56 AM, Gregory Price (Meta) wrote:
> On 2026-10-20 21:01 +0800, Li Zhe wrote:
>> On 10/20/26 12:10 AM, Gregory Price wrote:
>>
>> I agree that the kernel reclaim and demotion behavior still has room for
>> improvement, and the watermark changes and the bottom-up proactive/async
>> reclaim idea you mentioned may be useful to explore for some tiering
>> scenarios. But I think there is also an unavoidable userspace policy
>> component here. The kernel can improve whole-cgroup reclaim, but it
>> cannot know every controller's placement goal.
>>
>> For example, one controller may want to keep a specific per-tier
>> resident ratio for a cgroup. Another use case, as Joshua pointed out in
>> another reply, is reclaiming from a lower tier first so that later
>> top-tier reclaim has room for demotion. A single default memory.reclaim
>> algorithm cannot express these per-cgroup policy choices.
>>
> The latter of these should probably be some level of the system default,
> that's why I brought it up as well.  (proactive reclaim should prefer to
> make progress from the coldest tier first, then work its way up).
>
> I'm not convinced a reclaim interface is the right way to affect a
> per-tier resident ratio policy.  That feels like a hack to get around
> solving the harder problem of something like tiered memcg counters
> (which would otherwise drive reclaim to work exactly the way you
> describe for resident ratios).


Thanks for the detailed comments.

I agree that improving the default proactive/async reclaim behavior can
be useful, and I also agree that tiered memcg counters/limits would be
useful. I do not see source=nodes as a replacement for those directions.
They solve different parts of the problem: tiered counters/limits provide
accounting and policy targets, while source=nodes is only an actuator for
proactive reclaim.

So I think these directions are compatible. A better default reclaim
policy or tiered memcg limits can improve kernel-driven behavior, while
source=nodes gives userspace an explicit way to apply reclaim pressure
to a selected resident source.

>
>> I am not sure whether changing the default whole-cgroup reclaim behavior
>> in this direction could affect existing userspace dependencies. In the
>> old nodes= discussion, Mina mentioned that Meta's reclaim stack relied on
>> memory.reclaim triggering demotion [1].
>>
> Yes and on its surface this is something I could see Meta using, however
> I question whether we're simply trying to find a convenient way around
> fixing reclaim to do the right thing (not invert the LRU, apply per-tier
> memcg limits, etc).


That is fair, and I do not intend this to avoid improving reclaim or
adding better tier-aware memcg accounting. A better memory.reclaim
algorithm would also make source=nodes more useful, because source=nodes
still uses the existing reclaim path after selecting the resident source.
The goal here is narrower: expose a source-scoped proactive reclaim
actuator while keeping the kernel's reclaim policy in control. The old
nodes= discussion had ABI problems, but it also showed that a
nodemask-like source selector is a useful primitive for finer-grained
proactive reclaim control [1].

>
>> I also do not intend source= to lock in current reclaim implementation
>> details. It should stay consistent with the current design: source= only
>> selects the resident source to which reclaim pressure is applied, while
>> the kernel remains free to change the reclaim policy.
>>
> Well, sort of, you get exposed to the implementation details by having
> to negotiate what is considered progress under different scenarios.
>
> Lets take one example - what should happen when:
>   - 2 node system, node0=DRAM, node1=CXL
>   - zswap is configured
>   - echo 1G source=nodelist:1 > memory.reclaim
>   - node 1 is pressured, zsmalloc falls back to node0 to allocate
>
> Should that be considered progress? We reclaimed from node1, but
> increased pressure on node0 - which might have kicked kswapd or memcg
> accounting to demote memory from node0 to node1.
>
> Should zswap also take that nodelist as a constraint?


This is a good example of why source=nodes should not be described as a
one-shot net footprint guarantee. Assuming source=nodes:1, the request
applies reclaim pressure to memcg memory resident on node1. That may
free some space on node1 in the short term, which can provide room for a
later node0-to-node1 demotion step. If zswap allocation, kswapd, or memcg
reclaim later moves pressure back across tiers and node1 usage rises
again, that is part of the dynamic reclaim/tiering behavior.

So userspace cannot rely on one write to memory.reclaim to produce a
final per-node distribution. The intended usage is a feedback loop: the
controller observes the memcg's resident distribution, chooses the source
nodes it wants to relieve, applies source-scoped reclaim pressure,
observes the new distribution, and iterates.

I do not think this patch should make source=nodes constrain zswap pool
allocations. That would be a different and much stronger semantic. The
current intent is only to constrain the resident source from which memcg
reclaim applies pressure, while completion accounting remains the
existing memory.reclaim accounting.

>
> At the very least i think you need to add details in your cover that
> demonstrates the process of doing something like per-node resident
> ratios, and show that it works as you intend.
Agreed. I will update the cover letter to make this model clearer. I
will also make clear that source=nodes is not a final net per-node
footprint guarantee and does not change memory.reclaim completion
accounting.

[1] https://lkml.org/lkml/2022/12/14/194

Thanks,
Zhe

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

* Re: [RFC 0/2] mm: add source selectors to memory.reclaim
  2026-10-22  9:43       ` Li Zhe
@ 2026-10-22 14:11         ` Gregory Price
  0 siblings, 0 replies; 12+ messages in thread
From: Gregory Price @ 2026-10-22 14:11 UTC (permalink / raw)
  To: Li Zhe
  Cc: akpm, david, ziy, joshua.hahnjy, rakie.kim, ying.huang, ljs,
	mhocko, muchun.song, roman.gushchin, shakeel.butt, weixugc,
	cgroups, linux-doc, linux-kernel, linux-mm

On Thu, Oct 22, 2026 at 05:43:49PM +0800, Li Zhe wrote:
> On 10/22/26 11:56 AM, Gregory Price (Meta) wrote:
> 
> So userspace cannot rely on one write to memory.reclaim to produce a
> final per-node distribution. The intended usage is a feedback loop: the
> controller observes the memcg's resident distribution, chooses the source
> nodes it wants to relieve, applies source-scoped reclaim pressure,
> observes the new distribution, and iterates.
>

I think the critical question is what CAN userspace rely on?

We can make a bunch of feedback loop interfaces, but there's no
guarantee they'll actually be useful.

> >
> > At the very least i think you need to add details in your cover that
> > demonstrates the process of doing something like per-node resident
> > ratios, and show that it works as you intend.
>
> Agreed. I will update the cover letter to make this model clearer. I
> will also make clear that source=nodes is not a final net per-node
> footprint guarantee and does not change memory.reclaim completion
> accounting.
> 

To be clear, I think not just making it clearer, but if you can show
some tests that demonstrate the reliable behavior of the interface
extension under different scenarios - that would be compelling.

~Gregory

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

end of thread, other threads:[~2026-10-22 14:11 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-19 14:13 [RFC 0/2] mm: add source selectors to memory.reclaim Li Zhe
2026-10-19 14:14 ` [RFC 1/2] mm: add source selector " Li Zhe
2026-10-19 14:51   ` Lorenzo Stoakes (ARM)
2026-10-20  8:36     ` Li Zhe
2026-10-19 14:14 ` [RFC 2/2] mm: add tier " Li Zhe
2026-10-19 15:21 ` [RFC 0/2] mm: add source selectors " Joshua Hahn
2026-10-20  9:06   ` Li Zhe
2026-10-19 16:10 ` Gregory Price
2026-10-20 13:01   ` Li Zhe
2026-10-22  3:56     ` Gregory Price (Meta)
2026-10-22  9:43       ` Li Zhe
2026-10-22 14:11         ` Gregory Price

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

all inboxes | Powered by JetHome®