From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from kanga.kvack.org (kanga.kvack.org [205.233.56.17]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id F1C69C43458 for ; Mon, 13 Jul 2026 02:57:03 +0000 (UTC) Received: by kanga.kvack.org (Postfix) id EF6826B0088; Sun, 12 Jul 2026 22:56:57 -0400 (EDT) Received: by kanga.kvack.org (Postfix, from userid 40) id E57D86B008A; Sun, 12 Jul 2026 22:56:57 -0400 (EDT) X-Delivered-To: int-list-linux-mm@kvack.org Received: by kanga.kvack.org (Postfix, from userid 63042) id CAB576B008C; Sun, 12 Jul 2026 22:56:57 -0400 (EDT) X-Delivered-To: linux-mm@kvack.org Received: from relay.hostedemail.com (smtprelay0012.hostedemail.com [216.40.44.12]) by kanga.kvack.org (Postfix) with ESMTP id 73D546B0088 for ; Sun, 12 Jul 2026 22:56:57 -0400 (EDT) Received: from smtpin09.hostedemail.com (lb01a-stub [10.200.18.249]) by unirelay03.hostedemail.com (Postfix) with ESMTP id 97327A09D9 for ; Mon, 13 Jul 2026 02:56:56 +0000 (UTC) X-FDA: 84982241232.09.0F36A38 Received: from lgeamrelo03.lge.com (lgeamrelo03.lge.com [156.147.51.102]) by imf08.hostedemail.com (Postfix) with ESMTP id 19C45160002 for ; Mon, 13 Jul 2026 02:56:53 +0000 (UTC) Authentication-Results: imf08.hostedemail.com; dkim=none; spf=pass (imf08.hostedemail.com: domain of youngjun.park@lge.com designates 156.147.51.102 as permitted sender) smtp.mailfrom=youngjun.park@lge.com; dmarc=pass (policy=none) header.from=lge.com ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=hostedemail.com; s=arc-20220608; t=1783911415; h=from:from:sender:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1GuwzSznvT54fA5UrYS760CslYNRK5mtnEOJtoZ2HZg=; b=bnpvgRu8/CZ2aZSkCaA5bsB/5DlOA/gNsjvHAtkrGskqh3CjUcqIx/o78vgUyQXU2diwVH 1Xz4EtErSb4ZypgH3XYhF8Ga55HC4tkP6jhimR/6CH6+lTgKYQ72Wo/0+S/CuKD3aV4vNc 19UBnXvEoGV8nZg5Vp9BKqwd9tQ0YXQ= ARC-Authentication-Results: i=1; imf08.hostedemail.com; dkim=none; spf=pass (imf08.hostedemail.com: domain of youngjun.park@lge.com designates 156.147.51.102 as permitted sender) smtp.mailfrom=youngjun.park@lge.com; dmarc=pass (policy=none) header.from=lge.com ARC-Seal: i=1; a=rsa-sha256; d=hostedemail.com; s=arc-20220608; cv=none; t=1783911415; b=e9zXuXFUvCU/iEc6f5J1GGNuqwbacq57rqFNRTqs0iNsPcMpmCZbfJDMa14ISOiL3rUB09 rsCgnoVylv8DaLGHyrXlAcwcY2HstZ4vKCdIoGmGjE5Q9MTB/Y2NzH/ps43RKT+W8QPVTY qP5I6PVUgGLwrdMd23OozK1HxuWP0S0= Received: from unknown (HELO yjaykim-PowerEdge-T330.lge.net) (10.177.112.156) by 156.147.51.102 with ESMTP; 13 Jul 2026 11:56:52 +0900 X-Original-SENDERIP: 10.177.112.156 X-Original-MAILFROM: youngjun.park@lge.com From: Youngjun Park To: akpm@linux-foundation.org Cc: chrisl@kernel.org, youngjun.park@lge.com, 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: [PATCH v10 3/6] mm: memcontrol: add interface for swap tier selection Date: Mon, 13 Jul 2026 11:56:41 +0900 Message-Id: <20260713025644.170839-4-youngjun.park@lge.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260713025644.170839-1-youngjun.park@lge.com> References: <20260713025644.170839-1-youngjun.park@lge.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Rspam-User: X-Rspamd-Server: rspam04 X-Rspamd-Queue-Id: 19C45160002 X-Stat-Signature: dc8ycj7wh4foqn83cpjbtiser9i8uimt X-HE-Tag: 1783911413-122755 X-HE-Meta: U2FsdGVkX19Rq8fg3HdP8AIn6m7vHK5tIn1PcywCmjjvisqyJuG35tJHt5KGBeXXq3y6JNuhxRqPFyDzqXQCCpIW8YtDeMdsxfJjttlvVaUShXFhG5HA/bHfpG3SxFIrI7HlE1ygL2agZR4TDlHDamrkMQVoVU/CyKsqQJ1/R427axFqkSXeah31ivyXThJ/FBbVUVc90GGmOEm3B4i8cruXdMumHgLmeoHJJRUawmS+V1VQlIJNtlWfTpaLkCRvMsasl7c4+JgV58/vlg82hqQT93wbrGjC+TjU0ScBFUNzqJcNxFypCnE5Ffk82scSqL/P7NRc+m9PAq9mGCC1n74Y5UfN2m680mU0I8Biwn1zPf6swP85hyf8la8VmR5InrPpLcJ55hdxXBdkQX4qz4j1EeqYmDuT7crgSPMtAeG+6Rztz7HV/r+/M1+lpORC0ZLikj9e0YDo0mgsym4+PSBC8BuYLeLgVEBSPSWccjcsvSYfCk02jeUPLt7YXLocDZxavmDUBk93rbvSmolv+UHNZ00X/YQlA/prZV4gY0mWfMOEagGHtnmHtAGm837+OUCkFPIp7aESOJVPSbqVyc8UPn2tvKNOpL2OLfu20gCIlt7llPKLPoQN/XgxJoi6A4RvRZvt+xCGTa3Nhfp8jEewvwWvMmMjKN4+5i/Zd0U2IUVbA4X4abwV4taYZAwdARhaC+r41l4MqyaqzeH/Aczx1koTRFvPmXvo4ex9YgKbgN9m0T/3V06XYtSp5x95Y7zxTjMTzCRbYKZlhTxkXF0quO9RRBL37QohFd+3Q/mU9O3N+AGXY9904PG+q0NVTbPyjHHkdXHcYjTFd++sILvVEsXo5uW+aKFwGaAmu17ej6nSZPjyu35lcgi/cB8d8P08hjLaY9QVStGn/8GqbGgAW0HyG3yc9buQPH+dHy0jNhr5k+wC4vEIz3kpyZl1n1nlGm/eWoe8j88kBq2 yj/5qadh lCo2OQ06lPnL8RTty6TVLuiWGukwUGiJY6LriZcCzhTCvbVl6Qtffa3FVeJqVY0jqPH+ljTDGbqlGasqFwTB9tdQADvMngtUzqnASvMicmYz9D844ixWm+msNLI2L3v/cxGrtzXGsCwg/kxDtb49awmFxLayj4ff70pSqzvstjipsfZsM2nA8eskDJmOyBipkePYZotH7JRGAIcuwfp18Kn/UZAX1kif7DA+O97laClxj7l8jh3UBYw6YClxFn7ZpMV6I Sender: owner-linux-mm@kvack.org Precedence: bulk X-Loop: owner-majordomo@kvack.org List-ID: List-Subscribe: List-Unsubscribe: Introduce memory.swap.tiers.max, a flat-keyed file listing each tier defined in /sys/kernel/mm/swap/tiers with its state, "max" (allowed, the default) or "0" (disabled). A tier is one bit in the cgroup's tier mask, so writing " max" or " 0" sets or clears that bit. Since the current use case lacks amount control, it only supports "max" (on) and "0" (off). Therefore, it does not track per-tier swap usage, relying instead on a fast runtime bitmask check. We maintain both `mask` and `effective_mask`. The `effective_mask` is strictly bounded by the parent (e.g., if a parent is "0", the child's effective state is "0" even if its `mask` is "max"). Maintaining this separately avoids costly cgroup tree traversals to check ancestors at runtime. Suggested-by: Shakeel Butt Suggested-by: Yosry Ahmed Signed-off-by: Youngjun Park --- Documentation/admin-guide/cgroup-v2.rst | 20 +++++ Documentation/mm/swap-tier.rst | 9 +++ include/linux/memcontrol.h | 5 ++ mm/memcontrol.c | 67 ++++++++++++++++ mm/swap_state.c | 5 +- mm/swap_tier.c | 102 +++++++++++++++++++++++- mm/swap_tier.h | 57 +++++++++++-- 7 files changed, 255 insertions(+), 10 deletions(-) diff --git a/Documentation/admin-guide/cgroup-v2.rst b/Documentation/admin-guide/cgroup-v2.rst index 993446ab66d0..4b0b4f00ad6e 100644 --- a/Documentation/admin-guide/cgroup-v2.rst +++ b/Documentation/admin-guide/cgroup-v2.rst @@ -1850,6 +1850,26 @@ The following nested keys are defined. Swap usage hard limit. If a cgroup's swap usage reaches this limit, anonymous memory of the cgroup will not be swapped out. + memory.swap.tiers.max + A read-write flat-keyed file which exists on non-root + cgroups. The default is "max" for every tier. + + Limits the swap tiers this cgroup may swap to. Tiers are + defined globally in /sys/kernel/mm/swap/tiers and listed here, + one per line. When read, the values are displayed in descending + order of the tiers (highest tier first):: + + max + 0 + ... + + Currently, only "max" and "0" are supported. "max" allows the + tier, "0" disables it. Each write sets a single " max" + or " 0" pair. + + A child may only narrow what its parent allows. A tier an + ancestor disabled stays disabled regardless of the value here. + memory.swap.events A read-only flat-keyed file which exists on non-root cgroups. The following entries are defined. Unless specified diff --git a/Documentation/mm/swap-tier.rst b/Documentation/mm/swap-tier.rst index 0fb4a1153a67..addbc495de8c 100644 --- a/Documentation/mm/swap-tier.rst +++ b/Documentation/mm/swap-tier.rst @@ -15,6 +15,15 @@ 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. +Use case +--------- + +Users can perform selective swapping by choosing a swap tier assigned according +to speed within a cgroup. + +For more information on cgroup v2, please refer to +``Documentation/admin-guide/cgroup-v2.rst``. + Priority Range -------------- diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h index 8724ff417ad4..895da2cc1a69 100644 --- a/include/linux/memcontrol.h +++ b/include/linux/memcontrol.h @@ -281,6 +281,11 @@ struct mem_cgroup { struct lru_gen_mm_list mm_list; #endif +#ifdef CONFIG_SWAP + int tier_mask; + int tier_effective_mask; +#endif + #ifdef CONFIG_MEMCG_V1 /* Legacy consumer-oriented counters */ struct page_counter kmem; /* v1 only */ diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 929be41cadd4..6403d6b3ca41 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -68,6 +68,7 @@ #include #include "slab.h" #include "memcontrol-v1.h" +#include "swap_tier.h" #include @@ -4244,6 +4245,8 @@ static int mem_cgroup_css_online(struct cgroup_subsys_state *css) refcount_set(&memcg->id.ref, 1); css_get(css); + swap_tiers_memcg_inherit_mask(memcg); + /* * Ensure mem_cgroup_from_private_id() works once we're fully online. * @@ -5798,6 +5801,64 @@ static int swap_events_show(struct seq_file *m, void *v) return 0; } +static int swap_tier_max_show(struct seq_file *m, void *v) +{ + struct mem_cgroup *memcg = mem_cgroup_from_seq(m); + + swap_tiers_mask_show(m, memcg); + return 0; +} + +static ssize_t swap_tier_max_write(struct kernfs_open_file *of, + char *buf, size_t nbytes, loff_t off) +{ + struct mem_cgroup *memcg = mem_cgroup_from_css(of_css(of)); + char *pos, *name, *val; + bool enable; + int mask; + int ret = 0; + + pos = strstrip(buf); + name = strsep(&pos, " \t\n"); + if (!name || !*name) + return -EINVAL; + if (pos) + pos = skip_spaces(pos); + val = strsep(&pos, " \t\n"); + if (!val || !*val) + return -EINVAL; + if (pos && *skip_spaces(pos)) + return -EINVAL; + + if (!strcmp(val, "max")) + enable = true; + else if (!strcmp(val, "0")) + enable = false; + else + return -EINVAL; + + spin_lock(&swap_tier_lock); + mask = swap_tiers_mask_lookup(name); + if (!mask) { + ret = -EINVAL; + goto out; + } + + /* + * tier_mask is set per memcg here; the effective mask is clamped + * to the parent's in swap_tiers_memcg_sync_mask(). + */ + if (enable) + WRITE_ONCE(memcg->tier_mask, memcg->tier_mask | mask); + else + WRITE_ONCE(memcg->tier_mask, memcg->tier_mask & ~mask); + + swap_tiers_memcg_sync_mask(memcg); +out: + spin_unlock(&swap_tier_lock); + return ret ? ret : nbytes; +} + static struct cftype swap_files[] = { { .name = "swap.current", @@ -5830,6 +5891,12 @@ static struct cftype swap_files[] = { .file_offset = offsetof(struct mem_cgroup, swap_events_file), .seq_show = swap_events_show, }, + { + .name = "swap.tiers.max", + .flags = CFTYPE_NOT_ON_ROOT, + .seq_show = swap_tier_max_show, + .write = swap_tier_max_write, + }, { } /* terminate */ }; diff --git a/mm/swap_state.c b/mm/swap_state.c index dcec1a6c92bd..ba090fa80bf1 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -1053,6 +1053,7 @@ static ssize_t tiers_store(struct kobject *kobj, char *p, *token, *name, *tmp; int ret = 0; short prio; + int mask = 0; tmp = kstrdup(buf, GFP_KERNEL); if (!tmp) @@ -1085,7 +1086,7 @@ static ssize_t tiers_store(struct kobject *kobj, goto restore; break; case '-': - ret = swap_tiers_remove(token + 1); + ret = swap_tiers_remove(token + 1, &mask); if (ret) goto restore; break; @@ -1095,7 +1096,7 @@ static ssize_t tiers_store(struct kobject *kobj, } } - if (!swap_tiers_update()) { + if (!swap_tiers_update(mask)) { ret = -EINVAL; goto restore; } diff --git a/mm/swap_tier.c b/mm/swap_tier.c index 6b57cadb3e95..98bfee760b8d 100644 --- a/mm/swap_tier.c +++ b/mm/swap_tier.c @@ -253,7 +253,7 @@ int swap_tiers_add(const char *name, int prio) return ret; } -int swap_tiers_remove(const char *name) +int swap_tiers_remove(const char *name, int *mask) { int ret = 0; struct swap_tier *tier; @@ -276,6 +276,7 @@ int swap_tiers_remove(const char *name) list_prev_entry(tier, list)->prio = DEF_SWAP_PRIO; swap_tier_inactivate(tier); + *mask |= TIER_MASK(tier); return ret; } @@ -344,7 +345,24 @@ void swap_tiers_assign_dev(struct swap_info_struct *swp) swp->tier_mask = TIER_DEFAULT_MASK; } -bool swap_tiers_update(void) +#ifdef CONFIG_MEMCG +static void swap_tier_memcg_propagate(int mask) +{ + struct mem_cgroup *child; + + for_each_mem_cgroup_tree(child, root_mem_cgroup) { + WRITE_ONCE(child->tier_mask, child->tier_mask | mask); + WRITE_ONCE(child->tier_effective_mask, + child->tier_effective_mask | mask); + } +} +#else +static void swap_tier_memcg_propagate(int mask) +{ +} +#endif + +bool swap_tiers_update(int mask) { struct swap_tier *tier; struct swap_info_struct *swp; @@ -375,5 +393,85 @@ bool swap_tiers_update(void) swap_tiers_assign_dev(swp); } + /* + * When a tier is removed, its index (bit position in the mask) becomes + * free for reassignment to a future tier. If a memcg had previously + * disabled this tier (cleared the bit in its swap.tiers.max file), the + * effective mask would keep that bit clear -- meaning the new tier at + * the same index would be silently unavailable, an invisible cgroup + * constraint left behind by a tier that no longer exists. + * + * To prevent this, OR the removed tier's mask bit into every memcg's + * tier_mask and tier_effective_mask. This resets the bit so the new + * tier is accessible by default; users who want to restrict it must + * explicitly disable it after the tier is re-created. + */ + if (mask) + swap_tier_memcg_propagate(mask); + return true; } + +#ifdef CONFIG_MEMCG +void swap_tiers_mask_show(struct seq_file *m, struct mem_cgroup *memcg) +{ + struct swap_tier *tier; + int mask; + + spin_lock(&swap_tier_lock); + mask = READ_ONCE(memcg->tier_mask); + + for_each_active_tier(tier) + seq_printf(m, "%s %s\n", tier->name, + (mask & TIER_MASK(tier)) ? "max" : "0"); + spin_unlock(&swap_tier_lock); +} + +int swap_tiers_mask_lookup(const char *name) +{ + struct swap_tier *tier; + + lockdep_assert_held(&swap_tier_lock); + + for_each_active_tier(tier) { + if (!strcmp(name, tier->name)) + return TIER_MASK(tier); + } + + return 0; +} + +static void __swap_tier_memcg_inherit_mask(struct mem_cgroup *memcg, + struct mem_cgroup *parent) +{ + int parent_mask = parent + ? READ_ONCE(parent->tier_effective_mask) + : TIER_ALL_MASK; + + WRITE_ONCE(memcg->tier_effective_mask, + parent_mask & READ_ONCE(memcg->tier_mask)); +} + +/* Computes the initial effective mask from the parent's effective mask. */ +void swap_tiers_memcg_inherit_mask(struct mem_cgroup *memcg) +{ + spin_lock(&swap_tier_lock); + memcg->tier_mask = TIER_ALL_MASK; + __swap_tier_memcg_inherit_mask(memcg, parent_mem_cgroup(memcg)); + spin_unlock(&swap_tier_lock); +} + +/* + * Called when a memcg's tier_mask is modified. Walks the subtree + * and recomputes each descendant's effective mask against its parent. + */ +void swap_tiers_memcg_sync_mask(struct mem_cgroup *memcg) +{ + struct mem_cgroup *child; + + lockdep_assert_held(&swap_tier_lock); + + for_each_mem_cgroup_tree(child, memcg) + __swap_tier_memcg_inherit_mask(child, parent_mem_cgroup(child)); +} +#endif diff --git a/mm/swap_tier.h b/mm/swap_tier.h index 3e355f857363..e2f0cf32035b 100644 --- a/mm/swap_tier.h +++ b/mm/swap_tier.h @@ -10,22 +10,67 @@ 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); int swap_tiers_add(const char *name, int prio); -int swap_tiers_remove(const char *name); +int swap_tiers_remove(const char *name, int *mask); void swap_tiers_snapshot(void); void swap_tiers_snapshot_restore(void); -bool swap_tiers_update(void); +bool swap_tiers_update(int mask); /* Tier assignment */ void swap_tiers_assign_dev(struct swap_info_struct *swp); +#define TIER_ALL_MASK (~0) +#define TIER_DEFAULT_IDX (31) +#define TIER_DEFAULT_MASK (1U << TIER_DEFAULT_IDX) + +#if defined(CONFIG_SWAP) && defined(CONFIG_MEMCG) +/* Memcg related functions */ +void swap_tiers_mask_show(struct seq_file *m, struct mem_cgroup *memcg); +void swap_tiers_memcg_inherit_mask(struct mem_cgroup *memcg); +void swap_tiers_memcg_sync_mask(struct mem_cgroup *memcg); +int swap_tiers_mask_lookup(const char *name); +static inline int folio_tier_effective_mask(struct folio *folio) +{ + struct mem_cgroup *memcg; + int mask = TIER_ALL_MASK; + + rcu_read_lock(); + memcg = folio_memcg(folio); + if (memcg) + mask = READ_ONCE(memcg->tier_effective_mask); + rcu_read_unlock(); + + return mask; +} +#else +static inline void swap_tiers_mask_show(struct seq_file *m, + struct mem_cgroup *memcg) {} +static inline void swap_tiers_memcg_inherit_mask(struct mem_cgroup *memcg) {} +static inline void swap_tiers_memcg_sync_mask(struct mem_cgroup *memcg) {} +static inline int swap_tiers_mask_lookup(const char *name) +{ + return 0; +} +static inline int folio_tier_effective_mask(struct folio *folio) +{ + return TIER_ALL_MASK; +} +#endif + +/** + * swap_tiers_mask_test - Check if the tier mask is valid + * @tier_mask: The tier mask to check + * @mask: The mask to compare against + * + * Return: true if condition matches, false otherwise + */ +static inline bool swap_tiers_mask_test(int tier_mask, int mask) +{ + return tier_mask & mask; +} #endif /* _SWAP_TIER_H */ -- 2.34.1