[PATCH v2 0/4] memcg: move memcgid refcount to objcg to unpin dying memcgs

From: Bingfang Guo via B4 Relay

Date: Mon Sep 21 2026 - 02:17:14 EST


Changes in v2:
- Remove the NULL test and use the root in lru_gen_test_recent() to fix
the uninitialized lruvec pointer. (Shakeel)
- Wrap rcu section in the swap charging path with scoped_guard.
(Shakeel)
- Replace rcu lock with lockdep assertion in mem_cgroup_private_id_put().
(Shakeel)
- Keep memcg->private_id datatype unchanged and send it separately later
for simplicity of this series. (Muchun)
- Add blank lines between variable definitions and assertions. (Muchun)
- Remove redundant comments. (Muchun & Shakeel)
- Collect Acked-by tags.
- Link to v1: https://lore.kernel.org/r/20260918-bingfangguo-memcgid-rework-v1-0-5bbf3220d88f@xxxxxxxxxxx

Although the dying memcg problem caused by LRU pages is fixed, I can
still see many dying memcgs on some workloads that use shmem and those
pages are swapped out. For example, programs populating logs to tmpfs or
containers sharing data using shmem. This series binds the memcgid
refcount to objcgs so dying memcgs can be freed normally in this case.

The memcg private ID identifies memcgs for objects that can outlive the
cgroup itself: swap entries and workingset shadows. Today the ID's
refcount is embedded in the css, and every outstanding ID reference
(mostly swap entries) pins the css, keeping the entire memcg alive.
This causes a problem: A swapped-out page holds a memcgid reference that
pins the css, so the memcg cannot be freed until the page is swapped
back in and charged back to its online parent.

The work done by Muchun Song and Qi Zheng already charges folios to the
objcg, which is reparented to its parent when the memcg offlines. This
series applies similar idea to the memcg private ID: the ID's refcount
moves from the css into the objcg, and the memcgid xarray holds a
reference to an objcg instead of pinning the css. When the memcg
offlines, the objcg is reparented and any remaining memcgid references
resolve to the ancestor, so swapped-out pages no longer pin the dying
memcg and get the online parent naturally on swapin.

Unbinding the ID from the memcg has three consequences the series has to
deal with:

1. The ID stops pinning the memcg, so the paths that relied on the ID
reference to keep the memcg alive have to hold the RCU read lock
instead. (Patch 1)

2. Charge and uncharge no longer necessarily happen on the same memcg:
swapout charges the folio's memcg, while the slot free resolves the
nearest live ancestor. The counters are hierarchical, and the
MEMCG_SWAP stat is either reparented at offline (v1) or not visible
(v2), so nothing leaks. But "does this entry carry a counter
charge at all" can no longer be answered from the resolved memcg:
root is skipped only because root's swap is not accounted, and a
non-root ID whose memcg was reparented into root still carries a
charge that must be released. Patch 2 adds
mem_cgroup_private_id_is_root() and makes all three swap paths
decide on the ID's root status.

3. An ID can now outlive the memcg it was allocated to, so the memcg
resolved from an ID is not necessarily the memcg the ID was handed
out for. Callers that need exactly that memcg (list_lru, the
workingset and MGLRU shadow tests) now get NULL and skip the entry,
while the swap paths, which only need something to account to, get
the nearest live ancestor. (Patch 4)

The series is now four patches. The first three are preparation that
keeps today's semantics while the ID is still bound to the css. Only the
last patch changes behavior.

** Testing **

RFC v1 contains some scripts and codes for reproducing the problem and
testing the fix [1].

[1] https://lore.kernel.org/r/20260813-memcgid-objcg-v1-0-83d21c685b77@xxxxxxxxxxx

Signed-off-by: Bingfang Guo <bingfangguo@xxxxxxxxxxx>
---
Bingfang Guo (4):
memcg: keep swap charging under RCU protection
memcg: base swap charge accounting on memcgid root status
memcg: manipulate memcg private ID references by ID
memcg: move memcg private ID refcount to objcg

include/linux/memcontrol.h | 7 +--
mm/list_lru.c | 2 +-
mm/memcontrol-v1.c | 21 ++++----
mm/memcontrol-v1.h | 8 ++-
mm/memcontrol.c | 125 +++++++++++++++++++++++++++++++--------------
mm/workingset.c | 2 +-
6 files changed, 109 insertions(+), 56 deletions(-)
---
base-commit: 8d61431ed2607386b427752505379536eb634ce8
change-id: 20260827-bingfangguo-memcgid-rework-938e25ecaba2

Best regards,
--
Bingfang Guo <bingfangguo@xxxxxxxxxxx>