* [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; 6+ 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 related [flat|nested] 6+ 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; 6+ 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 related [flat|nested] 6+ 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-19 16:10 ` Gregory Price
3 siblings, 0 replies; 6+ 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] 6+ messages in thread