From: Joshua Hahn <joshua.hahnjy@gmail.com>
To: "Johannes Weiner" <hannes@cmpxchg.org>,
"Shakeel Butt" <shakeel.butt@linux.dev>,
"Michal Hocko" <mhocko@kernel.org>,
"Michal Koutný" <mkoutny@suse.com>
Cc: Roman Gushchin <roman.gushchin@linux.dev>,
Muchun Song <muchun.song@linux.dev>,
Andrew Morton <akpm@linux-foundation.org>,
Kuniyuki Iwashima <kuniyu@google.com>,
Harry Yoo <harry@kernel.org>,
David Hildenbrand <david@kernel.org>,
Lorenzo Stoakes <ljs@kernel.org>,
"Liam R. Howlett" <liam@infradead.org>,
Vlastimil Babka <vbabka@kernel.org>,
Mike Rapoport <rppt@kernel.org>,
Suren Baghdasaryan <surenb@google.com>,
Maarten Lankhorst <dev@lankhorst.se>,
Maxime Ripard <mripard@kernel.org>,
Natalie Vock <nat@pixelcluster.dev>, Tejun Heo <tj@kernel.org>,
Oscar Salvador <osalvador@suse.de>,
cgroups@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org, kernel-team@meta.com
Subject: [PATCH v8 6/7] mm/memcontrol: add stock to the memsw page counter
Date: Wed, 4 Nov 2026 15:30:27 -0800 [thread overview]
Message-ID: <20261104233029.3282438-7-joshua.hahnjy@gmail.com> (raw)
In-Reply-To: <20261104233029.3282438-1-joshua.hahnjy@gmail.com>
Attach legacy memsw page counters to their own seven-slot per-CPU
stock. Charge, refill, drain, and hotplug handling now operate on the
memory and memsw banks independently.
Factor the common drain scheduling into schedule_stock_drain() now
that both stocks use it.
If the memory charge fails, roll back only nr_pages from memsw. Any
batch surplus the memsw charge already placed in the memsw stock
stays cached, like any other stock slot.
The separate banks can hit, contend, evict, and drain independently,
so their raw counters can temporarily drift by their cached amounts.
The previous patch preserves the user-visible cgroup-v1
memory <= memory + swap invariant by reporting memory.usage_in_bytes
as the smaller raw value.
Signed-off-by: Joshua Hahn <joshua.hahnjy@gmail.com>
---
mm/memcontrol.c | 64 ++++++++++++++++++++++++++++++++++++-------------
1 file changed, 48 insertions(+), 16 deletions(-)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index d03d64969548d..aac987ebaab6f 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2057,6 +2057,13 @@ static DEFINE_PER_CPU_ALIGNED(struct page_counter_stock_pcp, memory_stock) = {
.base = &memory_stock,
};
+#ifdef CONFIG_MEMCG_V1
+static DEFINE_PER_CPU_ALIGNED(struct page_counter_stock_pcp, memsw_stock) = {
+ .lock = INIT_LOCAL_TRYLOCK(lock),
+ .base = &memsw_stock,
+};
+#endif
+
/*
* NR_OBJ_STOCK is sized so the entire hot path of obj_stock_pcp
* (lock, accounting metadata, nr_bytes[] and cached[]) fits within a
@@ -2161,12 +2168,30 @@ static bool schedule_drain_work(int cpu, struct work_struct *work)
return true;
}
+static void schedule_stock_drain(struct page_counter_stock_pcp __percpu *stock,
+ struct cgroup_subsys_state *root_css,
+ int cpu, int curcpu)
+{
+ struct page_counter_stock_pcp *pcp_stock = per_cpu_ptr(stock, cpu);
+
+ if (test_bit(FLUSHING_CACHED_CHARGE, &pcp_stock->flags) ||
+ !page_counter_stock_flush_required(pcp_stock, root_css) ||
+ test_and_set_bit(FLUSHING_CACHED_CHARGE, &pcp_stock->flags))
+ return;
+
+ if (cpu == curcpu)
+ drain_local_stock(&pcp_stock->work);
+ else if (!schedule_drain_work(cpu, &pcp_stock->work))
+ clear_bit(FLUSHING_CACHED_CHARGE, &pcp_stock->flags);
+}
+
/*
* Drains all per-CPU charge caches for given root_memcg resp. subtree
* of the hierarchy under it.
*/
void drain_all_stock(struct mem_cgroup *root_memcg)
{
+ struct cgroup_subsys_state *root_css = &root_memcg->css;
int cpu, curcpu;
/* If someone's already draining, avoid adding running more workers. */
@@ -2181,21 +2206,13 @@ void drain_all_stock(struct mem_cgroup *root_memcg)
migrate_disable();
curcpu = smp_processor_id();
for_each_online_cpu(cpu) {
- struct page_counter_stock_pcp *memory_st =
- per_cpu_ptr(&memory_stock, cpu);
struct obj_stock_pcp *obj_st = &per_cpu(obj_stock, cpu);
- if (!test_bit(FLUSHING_CACHED_CHARGE, &memory_st->flags) &&
- page_counter_stock_flush_required(memory_st,
- &root_memcg->css) &&
- !test_and_set_bit(FLUSHING_CACHED_CHARGE,
- &memory_st->flags)) {
- if (cpu == curcpu)
- drain_local_stock(&memory_st->work);
- else if (!schedule_drain_work(cpu, &memory_st->work))
- clear_bit(FLUSHING_CACHED_CHARGE,
- &memory_st->flags);
- }
+ schedule_stock_drain(&memory_stock, root_css, cpu, curcpu);
+#ifdef CONFIG_MEMCG_V1
+ if (do_memsw_account())
+ schedule_stock_drain(&memsw_stock, root_css, cpu, curcpu);
+#endif
if (!test_bit(FLUSHING_CACHED_CHARGE, &obj_st->flags) &&
obj_stock_flush_required(obj_st, root_memcg) &&
@@ -2221,6 +2238,9 @@ static int memcg_hotplug_cpu_dead(unsigned int cpu)
/* no need for the local lock */
drain_obj_stock(obj_st);
page_counter_drain_stock_fully(memory_st);
+#ifdef CONFIG_MEMCG_V1
+ page_counter_drain_stock_fully(per_cpu_ptr(&memsw_stock, cpu));
+#endif
/*
* A drain work queued before the CPU went away is executed by an
@@ -2229,6 +2249,10 @@ static int memcg_hotplug_cpu_dead(unsigned int cpu)
* the CPU comes back online.
*/
clear_bit(FLUSHING_CACHED_CHARGE, &memory_st->flags);
+#ifdef CONFIG_MEMCG_V1
+ clear_bit(FLUSHING_CACHED_CHARGE,
+ &per_cpu_ptr(&memsw_stock, cpu)->flags);
+#endif
clear_bit(FLUSHING_CACHED_CHARGE, &obj_st->flags);
return 0;
@@ -2614,8 +2638,8 @@ static int try_charge_memcg(struct mem_cgroup *memcg, gfp_t gfp_mask,
retry:
if (do_memsw_account() &&
- !page_counter_try_charge(&memcg->memsw, nr_pages, &counter, false,
- NULL)) {
+ !page_counter_try_charge(&memcg->memsw, nr_pages, &counter,
+ may_batch, NULL)) {
mem_over_limit = mem_cgroup_from_counter(counter, memsw);
goto reclaim;
}
@@ -3046,7 +3070,7 @@ static void obj_cgroup_uncharge_pages(struct obj_cgroup *objcg,
if (!mem_cgroup_is_root(memcg)) {
page_counter_refill_stock(&memcg->memory, nr_pages);
if (do_memsw_account())
- page_counter_uncharge(&memcg->memsw, nr_pages);
+ page_counter_refill_stock(&memcg->memsw, nr_pages);
}
css_put(&memcg->css);
@@ -4198,6 +4222,10 @@ mem_cgroup_css_alloc(struct cgroup_subsys_state *parent_css)
memcg->memory.stock_css = &memcg->css;
page_counter_init(&memcg->swap, &parent->swap, false);
#ifdef CONFIG_MEMCG_V1
+ if (!memcg_on_dfl) {
+ memcg->memsw.stock = &memsw_stock;
+ memcg->memsw.stock_css = &memcg->css;
+ }
WRITE_ONCE(memcg->swappiness, mem_cgroup_swappiness(parent));
memcg->memory.track_failcnt = !memcg_on_dfl;
memcg->memsw.track_failcnt = !memcg_on_dfl;
@@ -5823,6 +5851,10 @@ int __init mem_cgroup_init(void)
for_each_possible_cpu(cpu) {
INIT_WORK(&per_cpu_ptr(&memory_stock, cpu)->work,
drain_local_stock);
+#ifdef CONFIG_MEMCG_V1
+ INIT_WORK(&per_cpu_ptr(&memsw_stock, cpu)->work,
+ drain_local_stock);
+#endif
INIT_WORK(&per_cpu_ptr(&obj_stock, cpu)->work,
drain_local_obj_stock);
}
--
2.53.0-Meta
next prev parent reply other threads:[~2026-11-04 23:30 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 23:30 [PATCH v8 0/7] mm/page_counter: move stock from mem_cgroup to page_counter Joshua Hahn
2026-11-04 23:30 ` [PATCH v8 1/7] mm/memcontrol: flatten try_charge_memcg control flow Joshua Hahn
2026-11-05 6:08 ` Balbir Singh
2026-11-04 23:30 ` [PATCH v8 2/7] mm/page_counter: introduce per-CPU stock Joshua Hahn
2026-11-04 23:30 ` [PATCH v8 3/7] mm/page_counter: make page_counter_try_charge() stock-aware Joshua Hahn
2026-11-04 23:30 ` [PATCH v8 4/7] mm/memcontrol: check PF_MEMALLOC after trying the limits Joshua Hahn
2026-11-04 23:30 ` [PATCH v8 5/7] mm/memcontrol: move memory stock to page counters Joshua Hahn
2026-11-04 23:30 ` Joshua Hahn [this message]
2026-11-04 23:30 ` [PATCH v8 7/7] mm/memcontrol: restrict memory stock to leaf cgroups on v2 Joshua Hahn
2026-11-05 6:17 ` [PATCH v8 0/7] mm/page_counter: move stock from mem_cgroup to page_counter Balbir Singh
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=20261104233029.3282438-7-joshua.hahnjy@gmail.com \
--to=joshua.hahnjy@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=cgroups@vger.kernel.org \
--cc=david@kernel.org \
--cc=dev@lankhorst.se \
--cc=hannes@cmpxchg.org \
--cc=harry@kernel.org \
--cc=kernel-team@meta.com \
--cc=kuniyu@google.com \
--cc=liam@infradead.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@kernel.org \
--cc=mkoutny@suse.com \
--cc=mripard@kernel.org \
--cc=muchun.song@linux.dev \
--cc=nat@pixelcluster.dev \
--cc=osalvador@suse.de \
--cc=roman.gushchin@linux.dev \
--cc=rppt@kernel.org \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=tj@kernel.org \
--cc=vbabka@kernel.org \
/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
all inboxes | Powered by JetHome®