From: Usama Arif <usama.arif@linux.dev>
To: Youngjun Park <youngjun.park@lge.com>
Cc: Usama Arif <usama.arif@linux.dev>,
akpm@linux-foundation.org, chrisl@kernel.org, linux-mm@kvack.org,
cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
kasong@tencent.com, hannes@cmpxchg.org, mhocko@kernel.org,
roman.gushchin@linux.dev, shakeel.butt@linux.dev,
muchun.song@linux.dev, shikemeng@huaweicloud.com,
baoquan.he@linux.dev, baohua@kernel.org, yosry@kernel.org,
joshua.hahnjy@gmail.com, gunho.lee@lge.com, taejoon.song@lge.com,
hyungjun.cho@lge.com, baver.bae@lge.com, her0gyugyu@gmail.com
Subject: Re: [PATCH v10 2/6] mm: swap: associate swap devices with tiers
Date: Mon, 13 Jul 2026 07:28:53 -0700 [thread overview]
Message-ID: <20260713142856.102257-1-usama.arif@linux.dev> (raw)
In-Reply-To: <20260713025644.170839-3-youngjun.park@lge.com>
On Mon, 13 Jul 2026 11:56:40 +0900 Youngjun Park <youngjun.park@lge.com> wrote:
> This patch connects swap devices to the swap tier infrastructure,
> ensuring that devices are correctly assigned to tiers based on their
> priority.
>
> A `tier_mask` is added to identify the tier membership of swap devices.
> Although tier-based allocation logic is not yet implemented, this
> mapping is necessary to track which tier a device belongs to. Upon
> activation, the device is assigned to a tier by matching its priority
> against the configured tier ranges.
>
> The infrastructure allows dynamic modification of tiers, such as
> splitting or merging ranges. These operations are permitted provided
> that the tier assignment of already configured swap devices remains
> unchanged.
>
> This patch also adds the documentation for the swap tier feature,
> covering the core concepts, sysfs interface usage, and configuration
> details.
>
> Reviewed-by: Baoquan He <baoquan.he@linux.dev>
> Signed-off-by: Youngjun Park <youngjun.park@lge.com>
> ---
> Documentation/mm/index.rst | 1 +
> Documentation/mm/swap-tier.rst | 150 +++++++++++++++++++++++++++++++++
> MAINTAINERS | 1 +
> include/linux/swap.h | 1 +
> mm/swap_state.c | 2 +-
> mm/swap_tier.c | 101 +++++++++++++++++++---
> mm/swap_tier.h | 13 ++-
> mm/swapfile.c | 2 +
> 8 files changed, 257 insertions(+), 14 deletions(-)
> create mode 100644 Documentation/mm/swap-tier.rst
>
> diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
> index 13a79f5d092c..6afc45cd4b3d 100644
> --- a/Documentation/mm/index.rst
> +++ b/Documentation/mm/index.rst
> @@ -34,6 +34,7 @@ see the :doc:`admin guide <../admin-guide/mm/index>`.
> page_reclaim
> swap
> swap-table
> + swap-tier
> page_cache
> shmfs
> oom
> diff --git a/Documentation/mm/swap-tier.rst b/Documentation/mm/swap-tier.rst
> new file mode 100644
> index 000000000000..0fb4a1153a67
> --- /dev/null
> +++ b/Documentation/mm/swap-tier.rst
> @@ -0,0 +1,150 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +:Author: Chris Li <chrisl@kernel.org> Youngjun Park <youngjun.park@lge.com>
> +
> +==========
> +Swap Tier
> +==========
> +
> +Swap tier is a collection of user-named groups classified by priority ranges.
> +It acts as a facilitation layer, allowing users to manage swap devices based
> +on their speeds.
> +
> +Users are encouraged to assign swap device priorities according to device
> +speed to fully utilize this feature. While the current implementation is
> +integrated with cgroups, the concept is designed to be extensible for other
> +subsystems in the future.
> +
> +Priority Range
> +--------------
> +
> +The specified tiers must cover the entire priority range from -1
> +(DEF_SWAP_PRIO) to SHRT_MAX.
> +
> +Consistency
> +-----------
> +
> +Tier consistency is guaranteed with a focus on maximizing flexibility. When a
> +swap device is activated within a tier range, the tier covering that device's
> +priority is guaranteed not to disappear or change while the device remains
> +active. Adding a new tier may split the range of an existing tier, but the
> +active device's tier assignment remains unchanged.
> +
> +However, specifying a tier in a cgroup does not guarantee the tier's existence.
> +Consequently, the corresponding tier can disappear at any time.
> +
> +Configuration Interface
> +-----------------------
> +
> +The swap tiers can be configured via the following interface:
> +
> +/sys/kernel/mm/swap/tiers
> +
> +Operations can be performed using the following syntax:
> +
> +* Add: ``+"<tiername>":"<start_priority>"``
> +* Remove: ``-"<tiername>"``
> +
> +Tier names must consist of alphanumeric characters and underscores. Multiple
> +operations can be provided in a single write, separated by commas (",") or
> +whitespace (spaces, tabs, newlines).
> +
> +When configuring tiers, the specified value represents the **start priority**
> +of that tier. The end priority is automatically determined by the start
> +priority of the next higher tier. Consequently, adding a tier
> +automatically adjusts the ranges of adjacent tiers to ensure continuity.
> +
> +Examples
> +--------
> +
> +**1. Initialization**
> +
> +A tier starting at -1 is mandatory to cover the entire priority range up to
> +SHRT_MAX. In this example, 'HDD' starts at 50, and 'NET' covers the remaining
> +lower range starting from -1.
> +
> +::
> +
> + # echo "+HDD:50, +NET:-1" > /sys/kernel/mm/swap/tiers
> + # cat /sys/kernel/mm/swap/tiers
> + Name Idx PrioStart PrioEnd
> + HDD 0 50 32767
> + NET 1 -1 49
> +
> +**2. Adding a New Tier (split)**
> +
> +A new tier 'SSD' is added at priority 100, splitting the existing 'HDD' tier.
> +The ranges are automatically recalculated:
> +
> +* 'SSD' takes the top range (100 to SHRT_MAX).
> +* 'HDD' is adjusted to the range between 'NET' and 'SSD' (50 to 99).
> +* 'NET' remains unchanged (-1 to 49).
> +
> +::
> +
> + # echo "+SSD:100" > /sys/kernel/mm/swap/tiers
> + # cat /sys/kernel/mm/swap/tiers
> + Name Idx PrioStart PrioEnd
> + SSD 2 100 32767
> + HDD 0 50 99
> + NET 1 -1 49
> +
> +**3. Removal (merge)**
> +
> +Tiers can be removed using the '-' prefix.
> +::
> +
> + # echo "-SSD" > /sys/kernel/mm/swap/tiers
> +
> +When a tier is removed, its priority range is merged into the adjacent
> +tier. The merge direction is always upward (the tier below expands),
> +except when the lowest tier is removed — in that case the tier above
> +shifts its starting priority down to -1 to maintain full range coverage.
> +
> +::
> +
> + Initial state:
> + Name Idx PrioStart PrioEnd
> + SSD 2 100 32767
> + HDD 1 50 99
> + NET 0 -1 49
> +
> + # echo "-SSD" > /sys/kernel/mm/swap/tiers
> +
> + Name Idx PrioStart PrioEnd
> + HDD 1 50 32767 <- merged with SSD's range
> + NET 0 -1 49
> +
> + # echo "-NET" > /sys/kernel/mm/swap/tiers
> +
> + Name Idx PrioStart PrioEnd
> + HDD 1 -1 32767 <- shifted down to -1
> +
> +**4. Interaction with Active Swap Devices**
> +
> +If a swap device is active (swapon), the tier covering that device's
> +priority cannot be removed. Splitting the active tier's range is only
> +allowed above the device's priority.
> +
> +Assume a swap device is active at priority 60 (inside 'HDD' tier).
> +
> +::
> +
> + # swapon -p 60 /dev/zram0
> +
> + Name Idx PrioStart PrioEnd
> + HDD 0 50 32767
> + NET 1 -1 49
> +
> + # echo "-HDD" > /sys/kernel/mm/swap/tiers
> + -bash: echo: write error: Device or resource busy
> +
> + # echo "+SSD:60" > /sys/kernel/mm/swap/tiers
> + -bash: echo: write error: Device or resource busy
> +
> + # echo "+SSD:100" > /sys/kernel/mm/swap/tiers
> +
> + Name Idx PrioStart PrioEnd
> + SSD 2 100 32767
> + HDD 0 50 99 <- device (prio 60) stays here
> + NET 1 -1 49
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e94d1af17c39..4485a0650984 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -17237,6 +17237,7 @@ L: linux-mm@kvack.org
> S: Maintained
> F: Documentation/ABI/testing/sysfs-kernel-mm-swap
> F: Documentation/mm/swap-table.rst
> +F: Documentation/mm/swap-tier.rst
> F: include/linux/swap.h
> F: include/linux/swapfile.h
> F: include/linux/swapops.h
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index 4427c7aa16dc..2d647a8fa60f 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -244,6 +244,7 @@ struct swap_info_struct {
> struct percpu_ref users; /* indicate and keep swap device valid. */
> unsigned long flags; /* SWP_USED etc: see above */
> signed short prio; /* swap priority of this type */
> + int tier_mask; /* swap tier mask */
> struct plist_node list; /* entry in swap_active_head */
> signed char type; /* strange name for an index */
> unsigned int max; /* size of this swap device */
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index c18ff741f2e0..dcec1a6c92bd 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -1095,7 +1095,7 @@ static ssize_t tiers_store(struct kobject *kobj,
> }
> }
>
> - if (!swap_tiers_validate()) {
> + if (!swap_tiers_update()) {
> ret = -EINVAL;
> goto restore;
> }
> diff --git a/mm/swap_tier.c b/mm/swap_tier.c
> index ac7a3c2a48cb..6b57cadb3e95 100644
> --- a/mm/swap_tier.c
> +++ b/mm/swap_tier.c
> @@ -38,6 +38,8 @@ static LIST_HEAD(swap_tier_inactive_list);
> (!list_is_first(&(tier)->list, &swap_tier_active_list) ? \
> list_prev_entry((tier), list)->prio - 1 : SHRT_MAX)
>
> +#define MASK_TO_TIER(mask) (&swap_tiers[__ffs((mask))])
> +
> #define for_each_tier(tier, idx) \
> for (idx = 0, tier = &swap_tiers[0]; idx < MAX_SWAPTIER; \
> idx++, tier = &swap_tiers[idx])
> @@ -59,6 +61,26 @@ static bool swap_tier_is_active(void)
> return !list_empty(&swap_tier_active_list);
> }
>
> +static bool swap_tier_prio_in_range(struct swap_tier *tier, short prio)
> +{
> + if (tier->prio <= prio && TIER_END_PRIO(tier) >= prio)
> + return true;
> +
> + return false;
> +}
> +
> +static bool swap_tier_prio_is_used(short prio)
> +{
> + struct swap_tier *tier;
> +
> + for_each_active_tier(tier) {
> + if (tier->prio == prio)
> + return true;
> + }
> +
> + return false;
> +}
> +
> static struct swap_tier *swap_tier_lookup(const char *name)
> {
> struct swap_tier *tier;
> @@ -99,6 +121,7 @@ void swap_tiers_init(void)
> int idx;
>
> BUILD_BUG_ON(BITS_PER_TYPE(int) < MAX_SWAPTIER);
> + BUILD_BUG_ON(MAX_SWAPTIER > TIER_DEFAULT_IDX);
>
> for_each_tier(tier, idx) {
> INIT_LIST_HEAD(&tier->list);
> @@ -149,17 +172,29 @@ static struct swap_tier *swap_tier_prepare(const char *name, short prio)
> return tier;
> }
>
> -static int swap_tier_check_range(short prio)
> +static int swap_tier_can_split_range(short new_prio)
> {
> + struct swap_info_struct *p;
> struct swap_tier *tier;
>
> lockdep_assert_held(&swap_lock);
> lockdep_assert_held(&swap_tier_lock);
>
> - for_each_active_tier(tier) {
> - /* No overwrite */
> - if (tier->prio == prio)
> - return -EINVAL;
> + plist_for_each_entry(p, &swap_active_head, list) {
> + if (p->tier_mask == TIER_DEFAULT_MASK)
> + continue;
> +
> + tier = MASK_TO_TIER(p->tier_mask);
> + if (!swap_tier_prio_in_range(tier, new_prio))
> + continue;
> +
> + /*
> + * Device sits in a tier that spans new_prio;
> + * splitting here would reassign it to a
> + * different tier.
> + */
> + if (p->prio >= new_prio)
> + return -EBUSY;
> }
>
> return 0;
> @@ -199,7 +234,11 @@ int swap_tiers_add(const char *name, int prio)
> if (!swap_tier_validate_name(name))
> return -EINVAL;
>
> - ret = swap_tier_check_range(prio);
> + /* No overwrite */
> + if (swap_tier_prio_is_used(prio))
> + return -EBUSY;
> +
> + ret = swap_tier_can_split_range(prio);
> if (ret)
> return ret;
>
> @@ -226,6 +265,11 @@ int swap_tiers_remove(const char *name)
> if (!tier)
> return -EINVAL;
>
> + /* Simulate adding a tier to check for conflicts */
> + ret = swap_tier_can_split_range(tier->prio);
> + if (ret)
> + return ret;
> +
> /* Removing DEF_SWAP_PRIO merges into the higher tier. */
> if (!list_is_singular(&swap_tier_active_list)
> && tier->prio == DEF_SWAP_PRIO)
> @@ -236,13 +280,15 @@ int swap_tiers_remove(const char *name)
> return ret;
> }
>
> -static struct swap_tier swap_tiers_snap[MAX_SWAPTIER];
> /*
> - * XXX: When multiple operations (adds and removes) are submitted in a
> - * single write, reverting each individually on failure is complex and
> - * error-prone. Instead, snapshot the entire state beforehand and
> - * restore it wholesale if any operation fails.
> + * XXX: Static global snapshot buffer for batch operations. Small
> + * and used once per write, so a static global is not bad.
> + * When multiple adds/removes are submitted in a single write,
> + * reverting each individually on failure is error-prone. Instead,
> + * snapshot beforehand and restore wholesale if any operation fails.
> */
> +static struct swap_tier swap_tiers_snap[MAX_SWAPTIER];
> +
> void swap_tiers_snapshot(void)
> {
> BUILD_BUG_ON(sizeof(swap_tiers_snap) != sizeof(swap_tiers));
> @@ -282,10 +328,30 @@ void swap_tiers_snapshot_restore(void)
> }
> }
>
> -bool swap_tiers_validate(void)
> +void swap_tiers_assign_dev(struct swap_info_struct *swp)
> {
> struct swap_tier *tier;
>
> + lockdep_assert_held(&swap_lock);
> +
> + for_each_active_tier(tier) {
> + if (swap_tier_prio_in_range(tier, swp->prio)) {
> + swp->tier_mask = TIER_MASK(tier);
> + return;
> + }
> + }
> +
> + swp->tier_mask = TIER_DEFAULT_MASK;
> +}
> +
> +bool swap_tiers_update(void)
> +{
> + struct swap_tier *tier;
> + struct swap_info_struct *swp;
> +
> + lockdep_assert_held(&swap_lock);
> + lockdep_assert_held(&swap_tier_lock);
> +
> /*
> * Initial setting might not cover DEF_SWAP_PRIO.
> * Swap tier must cover the full range (DEF_SWAP_PRIO to SHRT_MAX).
> @@ -298,5 +364,16 @@ bool swap_tiers_validate(void)
> return false;
> }
>
> + /*
> + * If applied initially, the swap tier_mask may change
> + * from the default value.
> + */
> + plist_for_each_entry(swp, &swap_active_head, list) {
> + /* Tier is already configured */
> + if (swp->tier_mask != TIER_DEFAULT_MASK)
> + break;
> + swap_tiers_assign_dev(swp);
> + }
> +
> return true;
> }
> diff --git a/mm/swap_tier.h b/mm/swap_tier.h
> index a1395ec02c24..3e355f857363 100644
> --- a/mm/swap_tier.h
> +++ b/mm/swap_tier.h
> @@ -5,8 +5,15 @@
> #include <linux/types.h>
> #include <linux/spinlock.h>
>
> +/* Forward declarations */
> +struct swap_info_struct;
> +
> extern spinlock_t swap_tier_lock;
>
> +#define TIER_ALL_MASK (~0)
> +#define TIER_DEFAULT_IDX (31)
> +#define TIER_DEFAULT_MASK (1U << TIER_DEFAULT_IDX)
> +
> /* Initialization and application */
> void swap_tiers_init(void);
> ssize_t swap_tiers_sysfs_show(char *buf);
> @@ -16,5 +23,9 @@ int swap_tiers_remove(const char *name);
>
> void swap_tiers_snapshot(void);
> void swap_tiers_snapshot_restore(void);
> -bool swap_tiers_validate(void);
> +bool swap_tiers_update(void);
> +
> +/* Tier assignment */
> +void swap_tiers_assign_dev(struct swap_info_struct *swp);
> +
> #endif /* _SWAP_TIER_H */
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index ff567ad893a4..f3cff586cf30 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -3041,6 +3041,8 @@ static void _enable_swap_info(struct swap_info_struct *si)
>
> /* Add back to available list */
> add_to_avail_list(si, true);
> +
> + swap_tiers_assign_dev(si);
Could we move the assignment before the device is added to the active and
available lists?
After patch 4, swap allocation checks si->tier_mask while holding only
swap_avail_lock. But here the new device is added to swap_avail_head
before swap_tiers_assign_dev() initializes its mask.
That creates a small window where reclaim can see this swap_info_struct
with a stale tier_mask. swap_info_struct instances are reused across
swapoff/swapon, so the stale mask can come from the previous device that
occupied this slot. A memcg allowed to use the old tier could then
temporarily allocate from the newly enabled device even if that device
belongs to a different tier.
> }
>
> /*
> --
> 2.34.1
>
>
next prev parent reply other threads:[~2026-07-13 16:57 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-13 2:56 [PATCH v10 0/6] mm/swap, memcg: Introduce swap tiers for cgroup based swap control Youngjun Park
2026-07-13 2:56 ` [PATCH v10 1/6] mm: swap: introduce swap tier infrastructure Youngjun Park
2026-07-13 2:56 ` [PATCH v10 2/6] mm: swap: associate swap devices with tiers Youngjun Park
2026-07-13 14:28 ` Usama Arif [this message]
2026-07-13 2:56 ` [PATCH v10 3/6] mm: memcontrol: add interface for swap tier selection Youngjun Park
2026-07-13 2:56 ` [PATCH v10 4/6] mm: swap: filter swap allocation by memcg tier mask Youngjun Park
2026-07-13 2:56 ` [PATCH v10 5/6] selftests/mm: add a swap tier configuration test Youngjun Park
2026-07-13 2:56 ` [PATCH v10 6/6] selftests/cgroup: add a swap tier routing test Youngjun Park
2026-07-13 15:50 ` [PATCH v10 0/6] mm/swap, memcg: Introduce swap tiers for cgroup based swap control Yosry Ahmed
2026-07-13 15:57 ` Youngjun Park
2026-07-13 16:01 ` Yosry Ahmed
2026-07-13 16:22 ` Youngjun Park
2026-07-13 17:02 ` Chris Li
2026-07-13 17:11 ` Yosry Ahmed
2026-07-13 18:34 ` Chris Li
2026-07-13 18:37 ` Yosry Ahmed
2026-07-13 19:38 ` Chris Li
2026-07-13 19:57 ` Yosry Ahmed
2026-07-13 21:49 ` Chris Li
2026-07-13 17:05 ` Chris Li
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260713142856.102257-1-usama.arif@linux.dev \
--to=usama.arif@linux.dev \
--cc=akpm@linux-foundation.org \
--cc=baohua@kernel.org \
--cc=baoquan.he@linux.dev \
--cc=baver.bae@lge.com \
--cc=cgroups@vger.kernel.org \
--cc=chrisl@kernel.org \
--cc=gunho.lee@lge.com \
--cc=hannes@cmpxchg.org \
--cc=her0gyugyu@gmail.com \
--cc=hyungjun.cho@lge.com \
--cc=joshua.hahnjy@gmail.com \
--cc=kasong@tencent.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=mhocko@kernel.org \
--cc=muchun.song@linux.dev \
--cc=roman.gushchin@linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=shikemeng@huaweicloud.com \
--cc=taejoon.song@lge.com \
--cc=yosry@kernel.org \
--cc=youngjun.park@lge.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox