[RFC PATCH 8/8] cgroup: add memory_tiered_limits cgroup mount option
From: liuqiqi
Date: Mon Aug 17 2026 - 22:38:42 EST
From: Qiqi Liu <liuqiqi@xxxxxxxxxx>
Introduce the memory_tiered_limits cgroup v2 mount option to enable
tier-aware memory control. The option follows the same pattern as
memory_recursiveprot and memory_hugetlb_accounting.
When enabled, per-tier accounting, charging, stock batching, auto-
derivation, and the memory.tier control file are active. When disabled
(default), all tier-specific code is gated at entry points: no counters
are created, no charge/uncharge occurs, and memory.tier remains empty.
This ensures no measurable overhead for systems that do not opt in.
Usage:
mount -t cgroup2 none /sys/fs/cgroup -o memory_tiered_limits
Because the cgroup2 mount is owned by the init system, early-boot
cgroups are created before userspace can specify mount options. To
cover these boot-time cgroups, mirror the cgroup_favordynmods approach
and add a kernel command-line parameter,
cgroup_memory_tiered_limits=<bool>.
This sets CGRP_ROOT_MEMORY_TIERED_LIMITS on all cgroup2 mounts by
default. The mount option remains available for runtime, per-mount
control.
Signed-off-by: Qiqi Liu <liuqiqi@xxxxxxxxxx>
---
include/linux/cgroup-defs.h | 5 +++++
include/linux/memcontrol.h | 12 ++++++++++++
kernel/cgroup/cgroup.c | 21 +++++++++++++++++++++
mm/memcontrol.c | 22 ++++++++++++++++++----
4 files changed, 56 insertions(+), 4 deletions(-)
diff --git a/include/linux/cgroup-defs.h b/include/linux/cgroup-defs.h
index de2cd6238c2a..4324d68dbce9 100644
--- a/include/linux/cgroup-defs.h
+++ b/include/linux/cgroup-defs.h
@@ -129,6 +129,11 @@ enum {
* Enable legacy local pids.events.
*/
CGRP_ROOT_PIDS_LOCAL_EVENTS = (1 << 20),
+
+ /*
+ * Enable tier-aware limits for the memory controller.
+ */
+ CGRP_ROOT_MEMORY_TIERED_LIMITS = (1 << 21),
};
/* cftype->flags */
diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
index 8848bc5eeb24..094b9a839977 100644
--- a/include/linux/memcontrol.h
+++ b/include/linux/memcontrol.h
@@ -544,6 +544,18 @@ static inline bool mem_cgroup_disabled(void)
return !cgroup_subsys_enabled(memory_cgrp_subsys);
}
+#ifdef CONFIG_NUMA
+static inline bool mem_cgroup_tiered_limits(void)
+{
+ return cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_TIERED_LIMITS;
+}
+#else
+static inline bool mem_cgroup_tiered_limits(void)
+{
+ return false;
+}
+#endif
+
static inline void mem_cgroup_protection(struct mem_cgroup *root,
struct mem_cgroup *memcg,
unsigned long *min,
diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c
index 38f8d9df8fbc..da94bcda0859 100644
--- a/kernel/cgroup/cgroup.c
+++ b/kernel/cgroup/cgroup.c
@@ -231,6 +231,7 @@ static u32 have_release_callback __read_mostly;
static u32 have_canfork_callback __read_mostly;
static bool have_favordynmods __ro_after_init = IS_ENABLED(CONFIG_CGROUP_FAVOR_DYNMODS);
+static bool have_memory_tiered_limits __ro_after_init;
/*
* Write protected by cgroup_mutex and write-lock of cgroup_threadgroup_rwsem,
@@ -1985,6 +1986,7 @@ enum cgroup2_param {
Opt_memory_recursiveprot,
Opt_memory_hugetlb_accounting,
Opt_pids_localevents,
+ Opt_memory_tiered_limits,
nr__cgroup2_params
};
@@ -1995,6 +1997,7 @@ static const struct fs_parameter_spec cgroup2_fs_parameters[] = {
fsparam_flag("memory_recursiveprot", Opt_memory_recursiveprot),
fsparam_flag("memory_hugetlb_accounting", Opt_memory_hugetlb_accounting),
fsparam_flag("pids_localevents", Opt_pids_localevents),
+ fsparam_flag("memory_tiered_limits", Opt_memory_tiered_limits),
{}
};
@@ -2027,6 +2030,9 @@ static int cgroup2_parse_param(struct fs_context *fc, struct fs_parameter *param
case Opt_pids_localevents:
ctx->flags |= CGRP_ROOT_PIDS_LOCAL_EVENTS;
return 0;
+ case Opt_memory_tiered_limits:
+ ctx->flags |= CGRP_ROOT_MEMORY_TIERED_LIMITS;
+ return 0;
}
return -EINVAL;
}
@@ -2068,6 +2074,11 @@ static void apply_cgroup_root_flags(unsigned int root_flags)
cgrp_dfl_root.flags |= CGRP_ROOT_PIDS_LOCAL_EVENTS;
else
cgrp_dfl_root.flags &= ~CGRP_ROOT_PIDS_LOCAL_EVENTS;
+
+ if (root_flags & CGRP_ROOT_MEMORY_TIERED_LIMITS)
+ cgrp_dfl_root.flags |= CGRP_ROOT_MEMORY_TIERED_LIMITS;
+ else
+ cgrp_dfl_root.flags &= ~CGRP_ROOT_MEMORY_TIERED_LIMITS;
}
}
@@ -2085,6 +2096,8 @@ static int cgroup_show_options(struct seq_file *seq, struct kernfs_root *kf_root
seq_puts(seq, ",memory_hugetlb_accounting");
if (cgrp_dfl_root.flags & CGRP_ROOT_PIDS_LOCAL_EVENTS)
seq_puts(seq, ",pids_localevents");
+ if (cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_TIERED_LIMITS)
+ seq_puts(seq, ",memory_tiered_limits");
return 0;
}
@@ -2363,6 +2376,8 @@ static int cgroup_init_fs_context(struct fs_context *fc)
if (have_favordynmods)
ctx->flags |= CGRP_ROOT_FAVOR_DYNMODS;
+ if (have_memory_tiered_limits)
+ ctx->flags |= CGRP_ROOT_MEMORY_TIERED_LIMITS;
return 0;
}
@@ -7214,6 +7229,12 @@ static int __init cgroup_favordynmods_setup(char *str)
}
__setup("cgroup_favordynmods=", cgroup_favordynmods_setup);
+static int __init cgroup_memory_tiered_limits_setup(char *str)
+{
+ return (kstrtobool(str, &have_memory_tiered_limits) == 0);
+}
+__setup("cgroup_memory_tiered_limits=", cgroup_memory_tiered_limits_setup);
+
/**
* css_tryget_online_from_dir - get corresponding css from a cgroup dentry
* @dentry: directory dentry of interest
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index f39a702d2301..891051f164ff 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2462,7 +2462,7 @@ static void memcg_charge_tier_id(struct mem_cgroup *memcg, int tier_id,
{
struct memcg_tier_counter *tc;
- if (tier_id < 0)
+ if (!mem_cgroup_tiered_limits() || tier_id < 0)
return;
rcu_read_lock();
tc = memcg_tier_counter_find(memcg, tier_id);
@@ -2476,7 +2476,7 @@ static void memcg_uncharge_tier_id(struct mem_cgroup *memcg, int tier_id,
{
struct memcg_tier_counter *tc;
- if (tier_id < 0)
+ if (!mem_cgroup_tiered_limits() || tier_id < 0)
return;
rcu_read_lock();
tc = memcg_tier_counter_find(memcg, tier_id);
@@ -2581,6 +2581,9 @@ static void tier_update_derived_limits(struct mem_cgroup *memcg)
unsigned long total;
int i, nr_entries;
+ if (!mem_cgroup_tiered_limits())
+ return;
+
spin_lock(&tier_cap_lock);
total = tier_total_capacity;
nr_entries = nr_tier_entries;
@@ -3198,6 +3201,9 @@ static void refill_tier_stock(struct mem_cgroup *memcg, int tier_id,
uint8_t pages;
int i;
+ if (!mem_cgroup_tiered_limits())
+ return;
+
/* Too big to cache: direct uncharge, leave the stock untouched. */
if (nr_pages > MEMCG_CHARGE_BATCH) {
rcu_read_lock();
@@ -3252,7 +3258,7 @@ static int try_charge_memcg_tier(struct mem_cgroup *memcg, gfp_t gfp_mask,
bool drained = false;
nodemask_t nodes, *nmp = NULL;
- if (tier_id < 0)
+ if (!mem_cgroup_tiered_limits() || tier_id < 0)
return 0;
rcu_read_lock();
@@ -4736,7 +4742,7 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
page_counter_init(&memcg->memory, &parent->memory, memcg_on_dfl);
page_counter_init(&memcg->swap, &parent->swap, false);
- {
+ if (mem_cgroup_tiered_limits()) {
int nid, tid;
for_each_online_node(nid) {
@@ -5459,6 +5465,8 @@ static int memory_tier_show(struct seq_file *m, void *v)
struct mem_cgroup *memcg = mem_cgroup_from_seq(m);
struct memcg_tier_counter *tc;
+ if (!mem_cgroup_tiered_limits())
+ return 0;
rcu_read_lock();
list_for_each_entry_rcu(tc, &memcg->tier_counters, list) {
seq_printf(m, "tier%d.current=%llu\n", tc->tier_id,
@@ -5484,6 +5492,9 @@ static ssize_t memory_tier_write(struct kernfs_open_file *of,
char knob[8], *p;
int tier_id, err;
+ if (!mem_cgroup_tiered_limits())
+ return -EOPNOTSUPP;
+
buf = strstrip(buf);
if (sscanf(buf, "tier%d.%7[^=]", &tier_id, knob) != 2)
return -EINVAL;
@@ -6267,6 +6278,9 @@ static int __meminit memcg_tier_hotplug_cb(struct notifier_block *self,
struct mem_cgroup *memcg;
int tid;
+ if (!mem_cgroup_tiered_limits())
+ return notifier_from_errno(0);
+
switch (action) {
case NODE_ADDED_FIRST_MEMORY:
tid = node_to_tier_id(nn->nid);
--
2.43.0