Documentation/mm/index.rst | 1 + Documentation/mm/swap-tier.rst | 55 +++++ MAINTAINERS | 3 + include/linux/swap.h | 5 +- mm/Makefile | 2 +- mm/swap.h | 2 + mm/swap_tier.c | 421 +++++++++++++++++++++++++++++++++ mm/swap_tier.h | 71 ++++++ mm/swapfile.c | 173 ++++++++------ 9 files changed, 658 insertions(+), 75 deletions(-) create mode 100644 Documentation/mm/swap-tier.rst create mode 100644 mm/swap_tier.c create mode 100644 mm/swap_tier.h
This is RFC v11 of the swap tier series [1], reworked after the v10 discussion [2][3]. Motivation ========== After the v10 discussion, I would like to first land the parts that do not affect users, before anything touches memcg, with two goals in mind. - Serve as a bridge that causes no trouble when the tier interface and memcg support are introduced later. - Make per-cgroup swap device selection possible. While thinking this through, I came across some insights that I'd like to discuss with the community. Hence this RFC v11. As a starting point, let's look at what swap priority already gives us. - Devices with different priorities are used in priority order. - Devices with the same priority are used round-robin. From this point of view, a priority itself can be seen as a tier rather than a property of a device. Each distinct priority is a tier, the devices with that priority hang off it, and allocation walks the tiers. This is one of the insights that led me here. In the v10 design, a tier was a priority range, so one tier could contain multiple priorities. That raised two questions. - If devices in a tier are allocated in priority order, aren't they effectively different tiers? - Is there a real use case for several same-priority round-robin groups inside one tier? If each distinct priority is its own tier, both questions go away. A tier is one round-robin group, and ordering exists only between tiers. (Anyone who wants priority-based allocation inside a tier can still get it through a separate tier interface later. mix allocation policy? also possible. we have interface.) Either way, no explicit tier interface like the one in v10 is needed yet. Even when real tiers work are introduced, the view stays the same. - We want an ordering between tier A and tier B. - Devices in one tier form one service speed group. How they share allocations may become flexible, and will normally stay round-robin as it is today. - On top of that, we will add backend transfer between tiers after swap virtualization. This series changes the current swap code to follow that view, which gives us the following. - Per-cgroup swap can later fit into a swap tier interface without trouble. - A base for the per-priority allocation queue series [4]. - It is close to a refactoring of the existing structure, so userspace is not tied to an implementation. Kairui, Lian, would it be okay to use this as the groundwork for [4]? What do you think? Per-cgroup swap in debugfs ========================== Patches 3 and 4 let a memory cgroup choose its tiers through debugfs. # swapon -p 100 /dev/nvme0n1p2 # swapon -p 50 /dev/sdb2 # cat /sys/kernel/debug/swap/tiers Idx Prio 0 100 1 50 # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps its index for its lifetime, so the mask keeps selecting the same tier across swapon and swapoff. Masks are kept in a list keyed by cgroup ID rather than in struct mem_cgroup, so the series does not touch memcg code and needs no Kconfig option. A mask applies only to its own cgroup and is not inherited by child cgroups. This debugfs interface is a stepping stone. Once the tier model settles, /sys/kernel/mm/swap/tiers would list and name the tiers, and a memcg knob such as memory.swap.tiers.max would take tier names and turn them into this mask. Future direction ================ This is not done in this series. If the tiers introduced here settle, which differs from giving tiers priority ranges, work can continue along these lines depending on the discussion. The following are common to both. - The per-priority allocation queue series from Kairui and Lian [4]. - Add /sys/kernel/mm/swap/tiers. - A memcg memory.swap.tiers.max interface. - Per-tier allocation policy (an extension that needs discussion). If tiers are assigned by priority ranges as before, the tiers here have to be related to those ranges. When a tier is assigned at runtime, the devices that fall into its range are merged into that tier. If tiers are extended the way they work here, a tier stays a priority and the interface only adds names. A tier starts with a default name, such as its priority, and can be renamed through the tier interface. (I think there are a lot of possible way which we can discuss.) Change log ========== v11 - Reworked after the v10 discussion [2][3], with no new user ABI. - A swap tier is now a single swap priority, created and removed by swapon and swapoff. /sys/kernel/mm/swap/tiers and CONFIG_NR_SWAP_TIERS are dropped. - The active and available swap device lists move into the tiers. (#1, #2 patches) - Per-cgroup tier selection moves from memory.swap.tiers.max to debugfs. (#3 patch) - Masks are kept in a cgroup ID list instead of struct mem_cgroup, and are no longer inherited by child cgroups. (#3 patch) - Dropped the selftests, which tested the dropped interfaces. - Rebased on recent mm-new. - v10 link: https://lore.kernel.org/linux-mm/20260713025644.170839-1-youngjun.park@lge.com/ Changes up to v10 are in the v10 cover letter [1]. [1] https://lore.kernel.org/linux-mm/20260713025644.170839-1-youngjun.park@lge.com/ [2] https://lore.kernel.org/linux-mm/amDCIl51NoNPL7Op@cmpxchg.org/ [3] https://lore.kernel.org/linux-mm/amIlqHQ40baRoz3O@cmpxchg.org/ [4] https://lore.kernel.org/linux-mm/20260829-swap-pcp-priq-v2-0-68d3d925578c@gmail.com/ Youngjun Park (4): mm: swap: introduce swap tier infrastructure mm: swap: allocate swap slots from swap tiers mm: swap: add a debugfs interface for memcg tier selection mm: swap: filter swap allocation by memcg tier mask Documentation/mm/index.rst | 1 + Documentation/mm/swap-tier.rst | 55 +++++ MAINTAINERS | 3 + include/linux/swap.h | 5 +- mm/Makefile | 2 +- mm/swap.h | 2 + mm/swap_tier.c | 421 +++++++++++++++++++++++++++++++++ mm/swap_tier.h | 71 ++++++ mm/swapfile.c | 173 ++++++++------ 9 files changed, 658 insertions(+), 75 deletions(-) create mode 100644 Documentation/mm/swap-tier.rst create mode 100644 mm/swap_tier.c create mode 100644 mm/swap_tier.h base-commit: b08a65b93426d86e3f354d655d6225397b591877 -- 2.48.1
On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote: > Per-cgroup swap in debugfs > ========================== > > Patches 3 and 4 let a memory cgroup choose its tiers through debugfs. > > # swapon -p 100 /dev/nvme0n1p2 > # swapon -p 50 /dev/sdb2 > # cat /sys/kernel/debug/swap/tiers > Idx Prio > 0 100 > 1 50 > # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers > > Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps > its index for its lifetime, so the mask keeps selecting the same tier > across swapon and swapoff. Can the cgroup be given a priority limit? That would have pretty obvious inheritance semantics: root `- batch (memory.swap.prio.max = 20) `- task (memory.swap.prio.max = max) `- logs (memory.swap.prio.max = 10) `- interactive (memory.swap.prio.max = max) `- task (memory.swap.prio.max)
On 2026-09-16 16:04, Johannes Weiner wrote:
> On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote:
> > Per-cgroup swap in debugfs
> > ==========================
> >
> > Patches 3 and 4 let a memory cgroup choose its tiers through debugfs.
> >
> > # swapon -p 100 /dev/nvme0n1p2
> > # swapon -p 50 /dev/sdb2
> > # cat /sys/kernel/debug/swap/tiers
> > Idx Prio
> > 0 100
> > 1 50
> > # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers
> >
> > Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps
> > its index for its lifetime, so the mask keeps selecting the same tier
> > across swapon and swapoff.
>
Hello Johannes,
Sorry for the late reply on a good suggestion :)
> Can the cgroup be given a priority limit? That would have pretty
> obvious inheritance semantics:
> root
> `- batch (memory.swap.prio.max = 20)
> `- task (memory.swap.prio.max = max)
> `- logs (memory.swap.prio.max = 10)
> `- interactive (memory.swap.prio.max = max)
> `- task (memory.swap.prio.max)
Right, the inheritance is clear and easy to understand, and with this I
can pre-define the limit without knowing the mask value.
But first, let me check the intent. Is the point that capping batch keeps
it from taking the faster tiers, so they are left for interactive?
If so, that matches our use case. Latency sensitive workloads get the
fast tiers, non-latency sensitive ones get the slow tiers. But...
Even then, the reverse cannot be expressed. A cap only cuts from the top,
so a latency sensitive workload given max can still fall back to the slow
tiers once the fast ones fill up. For example,
tier0 tier1 tier2 tier3
0 10 20 30
there is no way to say "use tier0 and tier1, but never fall back to tier2
or tier3". To cover that, the interface would also need a min value, or
some way to express a range.
And even a range is not enough. Excluding only tier2 leaves a hole in the
middle, which no min/max pair can express. That needs per-tier selection,
which is what the mask, and what I'd carry over to the memcg
interface later (Currently memcg.swap.tiers.max).
How do you think?
Thanks!
Youngjun Park
On Mon, Sep 21, 2026 at 01:20:11AM +0900, Youngjun Park wrote: > On 2026-09-16 16:04, Johannes Weiner wrote: > > On Thu, Sep 17, 2026 at 03:34:33AM +0900, Youngjun Park wrote: > > > Per-cgroup swap in debugfs > > > ========================== > > > > > > Patches 3 and 4 let a memory cgroup choose its tiers through debugfs. > > > > > > # swapon -p 100 /dev/nvme0n1p2 > > > # swapon -p 50 /dev/sdb2 > > > # cat /sys/kernel/debug/swap/tiers > > > Idx Prio > > > 0 100 > > > 1 50 > > > # echo "/batch 0x2" > /sys/kernel/debug/swap/memcg_tiers > > > > > > Bit i of the mask is tier i, so /batch swaps only to sdb2. A tier keeps > > > its index for its lifetime, so the mask keeps selecting the same tier > > > across swapon and swapoff. > > > > Hello Johannes, > > Sorry for the late reply on a good suggestion :) No worries, and same ^_^ > > Can the cgroup be given a priority limit? That would have pretty > > obvious inheritance semantics: > > root > > `- batch (memory.swap.prio.max = 20) > > `- task (memory.swap.prio.max = max) > > `- logs (memory.swap.prio.max = 10) > > `- interactive (memory.swap.prio.max = max) > > `- task (memory.swap.prio.max) > > Right, the inheritance is clear and easy to understand, and with this I > can pre-define the limit without knowing the mask value. > > But first, let me check the intent. Is the point that capping batch keeps > it from taking the faster tiers, so they are left for interactive? Yes, basically, that's what I tried to express. Interactive has access to all available capacity. Batch only has access to lower tiers. > If so, that matches our use case. Latency sensitive workloads get the > fast tiers, non-latency sensitive ones get the slow tiers. But... > > Even then, the reverse cannot be expressed. A cap only cuts from the top, > so a latency sensitive workload given max can still fall back to the slow > tiers once the fast ones fill up. For example, > > tier0 tier1 tier2 tier3 > 0 10 20 30 > > there is no way to say "use tier0 and tier1, but never fall back to tier2 > or tier3". To cover that, the interface would also need a min value, or > some way to express a range. Correct, this isn't covered by the above. > And even a range is not enough. Excluding only tier2 leaves a hole in the > middle, which no min/max pair can express. That needs per-tier selection, > which is what the mask, and what I'd carry over to the memcg > interface later (Currently memcg.swap.tiers.max). > > How do you think? I think it could help to aggregate the usecases in the cover letter. Your cover letter describes how it works, which is great, but it would be good to understand better what the constraints are, how it fits in with other existing control surface and broader usage models. With the above, yes, you can restrict who gets access to the privileged tiers top down, but not bottom up. Is that an issue? Keep in mind the alternative is cutting privileged groups OFF from certain available capacity. This seems somewhat counter-intuitive to me, and doesn't reflect a clean privilege hierarchy anymore. If you can think of a good usecase, memory.swap.prio.min would be certainly a natural extension. But we should get the usecase laid out. The requirement to punch holes is the one I can relate to least. Why would a cgroup need access to good tiers and bad tiers, but skip the middle ones? This would seem less like tiering/hierarchy and more like flat per-cgroup swap pools but with obstacles.
© 2016 - 2026 Red Hat, Inc.