Linux cgroups development
 help / color / mirror / Atom feed
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
> 
> 

  reply	other threads:[~2026-07-13 14:29 UTC|newest]

Thread overview: 21+ 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 15:20     ` Youngjun Park
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