From: Qiqi Liu <[email protected]>
This RFC introduces per-tier memory cgroup accounting. Each cgroup
tracks its memory usage per memory tier (e.g. DRAM, CXL), exposed
through a new memory.tier control file that reports per-tier usage
and accepts independent high (soft) and max (hard) limits per tier.
By default these limits are auto-derived from memory.high / memory.max
based on per-tier capacity ratios, and can be manually overridden.
The implementation integrates with the existing memory tiering and
demotion infrastructure. Per-tier usage (anonymous and file) is tracked
via dedicated page counters, and cross-tier migrations (e.g. demotion
from DRAM to CXL) correctly re-account charges. When a tier hits its
high limit, async reclaim is triggered within that tier's NUMA nodes;
exceeding max enforces reclaim scoped to the tier's own nodes, or OOM.
The feature is fully opt-in. When disabled, no extra counters or
charge/uncharge paths are created, memory.tier reads empty, and there
is no measurable overhead.
Why per-tier limits?
-------------------
On tiered memory systems, memory.max constrains total usage but cannot
express "keep fast-tier usage under X". Without per-tier limits, a
workload can monopolise DRAM, pushing other cgroups onto slower tiers.
This series gives each cgroup independent high (soft) and max (hard)
limits per tier, exposed and set through a new memory.tier file.
By default those limits auto-derive from memory.high / memory.max by
capacity ratio; writing memory.tier pins a tier.
This series takes a different approach from Joshua Hahn's toptier RFC [1],
tracking a separate page_counter per (memcg, tier) for N-tier support and
exposing writable per-tier limits under a cgroup mount option.
Patch structure
---------------
1/8 mm/memory-tiers: add node_to_tier_id and tier_id_to_nodemask
2/8 mm/vmscan: add try_to_free_mem_cgroup_pages_nodemask
3/8 mm/memcontrol: add per-tier page counter infrastructure and lifecycle
4/8 mm/memcontrol: add per-tier charge and uncharge
5/8 mm/memcontrol: add per-cpu stock for tier charge/uncharge
6/8 mm/memcontrol: add memory.tier control file
7/8 mm/memcontrol: auto-derive tier high/max from memory.high/max
8/8 cgroup: add memory_tiered_limits cgroup mount option
Patches 1-2 are infrastructure (helpers in memory-tiers and vmscan).
Patches 3-5 add the core accounting: counter lifecycle (3),
per-page charge/uncharge (4), and stock batching (5).
Patch 6 adds the userspace file. Patch 7 adds auto-derivation. Patch 8
gates everything behind a mount option + kernel cmdline, so the feature
adds no measurable overhead when not opted in.
Usage
-----
Boot with:
cgroup_memory_tiered_limits=1
Or remount at runtime (affects newly created cgroups only):
mount -o remount,memory_tiered_limits /sys/fs/cgroup
Per-tier limits and usage can then be read from and written to
memory.tier.
Scope and limitations
---------------------
- Only LRU folios (anonymous and file pages) are tier-accounted. Kernel
memory and socket buffers are not yet accounted per tier; support for
these is planned as follow-up work.
- Per-tier memory.min and memory.low protections are not implemented.
These can be added later by extending the per-tier interface to
expose and enforce min/low protection.
- The command-line parameter mirrors cgroup_favordynmods; automatic
enablement via the cgroup mount path is left to userspace.
Testing
-------
Tested on QEMU with fake NUMA (DRAM tier 4 + CXL tier 22), with
cgroup_memory_tiered_limits=1 on the kernel command line and demotion
enabled.
Set up a cgroup, apply per-tier limits, and run a memory-intensive
workload:
$ mkdir /sys/fs/cgroup/mycgroup
$ cd /sys/fs/cgroup/mycgroup
$ echo "tier4.high=200000000" > memory.tier
$ echo "tier4.max=300000000" > memory.tier
$ echo 1 > /sys/kernel/mm/numa/demotion_enabled
$ cgexec -g memory:/mycgroup ~/stream --ntimes 5 --malloc &
$ cat memory.tier
tier4.current=296488960
tier4.high=199999488
tier4.max=299999232
tier22.current=1625464832
tier22.high=max
tier22.max=max
DRAM (tier4) usage stays under tier4.max (hard limit, no OOM) but exceeds
tier4.high (soft limit, suggesting that async reclaim is in progress);
CXL (tier22) absorbs the overflow via demotion.
Also verified:
- tierN.current tracks per-tier usage (anon + file).
- Cross-tier migration (demotion) correctly re-accounts.
- memory.high / memory.max auto-derives tierN.high / tierN.max.
- Manual override (writing a number to memory.tier) pins the limit.
- Tier max enforcement triggers reclaim scoped to the tier's nodes.
- Feature fully off (no mount option): no counters, no charge/uncharge,
memory.tier exists but reads empty.
Open questions
--------------
- Should kmem/slab tier accounting be included in this series or deferred
to a follow-up?
- Should per-tier memory.min and memory.low protection be part of this
series or left for later?
[1] https://lore.kernel.org/all/[email protected]/
Signed-off-by: Qiqi Liu <[email protected]>
Qiqi Liu (8):
mm/memory-tiers: add node_to_tier_id and tier_id_to_nodemask
mm/vmscan: add try_to_free_mem_cgroup_pages_nodemask
mm/memcontrol: add per-tier page counter infrastructure and lifecycle
mm/memcontrol: add per-tier charge and uncharge
mm/memcontrol: add per-cpu stock for tier charge/uncharge
mm/memcontrol: add memory.tier control file
mm/memcontrol: auto-derive tier high/max from memory.high/max
cgroup: add memory_tiered_limits cgroup mount option
include/linux/cgroup-defs.h | 5 +
include/linux/memcontrol.h | 26 ++
include/linux/memory-tiers.h | 12 +
include/linux/swap.h | 6 +
kernel/cgroup/cgroup.c | 21 +
mm/memcontrol.c | 786 ++++++++++++++++++++++++++++++++++-
mm/memory-tiers.c | 58 +++
mm/vmscan.c | 26 +-
8 files changed, 936 insertions(+), 4 deletions(-)
--
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.