* [PATCH v3 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Guopeng Zhang @ 2026-05-09 10:20 UTC (permalink / raw)
To: Waiman Long, Tejun Heo, Michal Koutný, Ingo Molnar,
Peter Zijlstra, Juri Lelli, Chen Ridong
Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups, Guopeng Zhang
In-Reply-To: <20260509102031.97608-1-zhangguopeng@kylinos.cn>
cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
state in the destination cpuset while walking the taskset.
If a later task_can_attach() or security_task_setscheduler() check
fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
and does not call cpuset_cancel_attach() for it. The partially
accumulated state is then left behind and can be consumed by a later
attach, corrupting cpuset DL task accounting and pending DL bandwidth
accounting.
Reset the pending DL migration state from the common error exit when
ret is non-zero. Successful can_attach() keeps the state for
cpuset_attach() or cpuset_cancel_attach().
Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
---
kernel/cgroup/cpuset.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index e3a081a07c6d..b9c839538900 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -3050,16 +3050,13 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
int cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
if (unlikely(cpu >= nr_cpu_ids)) {
- reset_migrate_dl_data(cs);
ret = -EINVAL;
goto out_unlock;
}
ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
- if (ret) {
- reset_migrate_dl_data(cs);
+ if (ret)
goto out_unlock;
- }
cs->dl_bw_cpu = cpu;
}
@@ -3070,7 +3067,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
* changes which zero cpus/mems_allowed.
*/
cs->attach_in_progress++;
+
out_unlock:
+ if (ret)
+ reset_migrate_dl_data(cs);
mutex_unlock(&cpuset_mutex);
return ret;
}
--
2.43.0
^ permalink raw reply related
* [PATCH v3 0/2] cgroup/cpuset: fix DL attach accounting
From: Guopeng Zhang @ 2026-05-09 10:20 UTC (permalink / raw)
To: Waiman Long, Tejun Heo, Michal Koutný, Ingo Molnar,
Peter Zijlstra, Juri Lelli, Chen Ridong
Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups, Guopeng Zhang
Hi,
This v3 series contains two cpuset fixes for SCHED_DEADLINE attach
accounting.
Patch 1 fixes an internal cpuset_can_attach() failure path where
temporary DL migration state can be left behind if a later per-task
check fails before cpuset marks attach_in_progress.
Patch 2 keeps cpuset DL bandwidth reservation aligned with the condition
used by set_cpus_allowed_dl() for source-side bandwidth removal. It keeps
counting all migrating DL tasks for cpuset task accounting, but reserves
destination DL bandwidth only for tasks that actually need a root-domain
bandwidth move.
Guopeng Zhang (2):
cgroup/cpuset: reset DL migration state on can_attach() failure
cgroup/cpuset: reserve DL bandwidth only for root-domain moves
include/linux/sched/deadline.h | 9 ++++++++
kernel/cgroup/cpuset-internal.h | 1 +
kernel/cgroup/cpuset.c | 39 ++++++++++++++++++---------------
kernel/sched/deadline.c | 13 ++++++++---
4 files changed, 41 insertions(+), 21 deletions(-)
---
Changes in v3:
- Patch 1: use common ret != 0 cleanup in cpuset_can_attach(), as
suggested by Waiman Long and Chen Ridong.
- Patch 2: drop task_cpu_possible_mask() / attach-target-mask handling
as suggested by Waiman Long.
- Patch 2: keep the change limited to reserving DL bandwidth only for
tasks that need a root-domain bandwidth move.
- Leave the broader can_attach()/attach() transaction model unchanged.
Changes in v2:
- Split the original change into two patches.
- Add a separate fix for resetting pending DL migration state on
cpuset_can_attach() failure.
- Clarify that nr_migrate_dl_tasks counts all migrating DL tasks for
cpuset task accounting, while sum_migrate_dl_bw only tracks bandwidth
needing destination root-domain reservation.
v2:
https://lore.kernel.org/all/20260507103310.35849-1-zhangguopeng@kylinos.cn/
v1:
https://lore.kernel.org/all/20260421083449.95750-1-zhangguopeng@kylinos.cn
--
2.43.0
^ permalink raw reply
* Re: [PATCH] cgroup/cpuset: skip hardwall ancestor scan in v2 mode in cpuset_current_node_allowed()
From: Wandun @ 2026-05-09 9:36 UTC (permalink / raw)
To: Tejun Heo; +Cc: longman, chenridong, hannes, mkoutny, cgroups, linux-kernel
In-Reply-To: <202d0aa0f8da75986a895ffbd564b78d@kernel.org>
On 5/8/26 23:51, Tejun Heo wrote:
> Hello,
>
> is_in_v2_mode() is also true for v1 mounted with cpuset_v2_mode, where
> cpuset.mem_exclusive / cpuset.mem_hardwall are still settable. Would
> that be a problem here? cpuset_v2() looks like a tighter fit.
You're right, it is a problem.
Under v1 + cpuset_v2_mode, CS_MEM_HARDWALL/CS_MEM_EXCLUSIVE can be set
on non-root cpuset cgroup, so can't directly return true;
I will fix it in v2.
Best regards,
Wandun
>
> Thanks.
>
> --
> tejun
^ permalink raw reply
* [PATCH] selftests/cgroup: Fix cg_read_strcmp() empty string comparison
From: Hongfu Li @ 2026-05-09 8:03 UTC (permalink / raw)
To: tj, hannes, mkoutny, shuah, seanjc, jthoughton, zhangguopeng
Cc: cgroups, linux-kselftest, linux-kernel, Hongfu Li
cg_read_strcmp() allocated a buffer sized to strlen(expected) + 1,
then passed it to read_text() which calls read(fd, buf, size-1).
When comparing against an empty string (""), strlen("") = 0 gives a
1-byte buffer, and read() is asked to read 0 bytes. The file content
is never actually read, so strcmp("", buf) always returns 0 regardless
of the real content. This caused cg_test_proc_killed() to always
report the cgroup as empty immediately, making OOM tests pass without
verifying that processes were killed.
Signed-off-by: Hongfu Li <lihongfu@kylinos.cn>
---
tools/testing/selftests/cgroup/lib/cgroup_util.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/cgroup/lib/cgroup_util.c b/tools/testing/selftests/cgroup/lib/cgroup_util.c
index 6a7295347e90..42f54936f4bb 100644
--- a/tools/testing/selftests/cgroup/lib/cgroup_util.c
+++ b/tools/testing/selftests/cgroup/lib/cgroup_util.c
@@ -106,8 +106,9 @@ int cg_read_strcmp(const char *cgroup, const char *control,
/* Handle the case of comparing against empty string */
if (!expected)
return -1;
- else
- size = strlen(expected) + 1;
+
+ /* needs size > 1, otherwise cg_read() reads 0 bytes */
+ size = (expected[0] == '\0') ? 2 : strlen(expected) + 1;
buf = malloc(size);
if (!buf)
--
2.25.1
^ permalink raw reply related
* [tj-cgroup:for-7.1-fixes] BUILD SUCCESS dde2f938d02f2c740d49bb5113dea941f941026a
From: kernel test robot @ 2026-05-09 1:34 UTC (permalink / raw)
To: Tejun Heo; +Cc: cgroups
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-7.1-fixes
branch HEAD: dde2f938d02f2c740d49bb5113dea941f941026a cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
elapsed time: 1448m
configs tested: 356
configs skipped: 10
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allmodconfig gcc-15.2.0
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-19
arc allyesconfig clang-23
arc allyesconfig gcc-15.2.0
arc defconfig gcc-15.2.0
arc haps_hs_smp_defconfig gcc-15.2.0
arc nsimosci_hs_smp_defconfig gcc-15.2.0
arc randconfig-001 gcc-8.5.0
arc randconfig-001-20260508 gcc-12.5.0
arc randconfig-001-20260508 gcc-8.5.0
arc randconfig-001-20260509 gcc-9.5.0
arc randconfig-002 gcc-8.5.0
arc randconfig-002-20260508 gcc-12.5.0
arc randconfig-002-20260508 gcc-8.5.0
arc randconfig-002-20260509 gcc-9.5.0
arm allnoconfig clang-23
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm allyesconfig gcc-15.2.0
arm defconfig gcc-15.2.0
arm h3600_defconfig gcc-15.2.0
arm neponset_defconfig gcc-15.2.0
arm randconfig-001 gcc-8.5.0
arm randconfig-001-20260508 gcc-12.5.0
arm randconfig-001-20260508 gcc-8.5.0
arm randconfig-001-20260509 gcc-9.5.0
arm randconfig-002 gcc-8.5.0
arm randconfig-002-20260508 gcc-12.5.0
arm randconfig-002-20260508 gcc-8.5.0
arm randconfig-002-20260509 gcc-9.5.0
arm randconfig-003 gcc-8.5.0
arm randconfig-003-20260508 gcc-12.5.0
arm randconfig-003-20260508 gcc-8.5.0
arm randconfig-003-20260509 gcc-9.5.0
arm randconfig-004 gcc-8.5.0
arm randconfig-004-20260508 gcc-12.5.0
arm randconfig-004-20260508 gcc-8.5.0
arm randconfig-004-20260509 gcc-9.5.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260508 gcc-14.3.0
arm64 randconfig-001-20260509 gcc-10.5.0
arm64 randconfig-002-20260508 gcc-14.3.0
arm64 randconfig-002-20260509 gcc-10.5.0
arm64 randconfig-003-20260508 gcc-14.3.0
arm64 randconfig-003-20260509 gcc-10.5.0
arm64 randconfig-004-20260508 gcc-14.3.0
arm64 randconfig-004-20260509 gcc-10.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260508 gcc-14.3.0
csky randconfig-001-20260509 gcc-10.5.0
csky randconfig-002-20260508 gcc-14.3.0
csky randconfig-002-20260509 gcc-10.5.0
hexagon allmodconfig clang-17
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig clang-23
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001 gcc-11.5.0
hexagon randconfig-001-20260508 clang-23
hexagon randconfig-001-20260508 gcc-11.5.0
hexagon randconfig-001-20260509 clang-17
hexagon randconfig-002 gcc-11.5.0
hexagon randconfig-002-20260508 clang-23
hexagon randconfig-002-20260508 gcc-11.5.0
hexagon randconfig-002-20260509 clang-17
i386 allmodconfig clang-20
i386 allnoconfig gcc-14
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 allyesconfig gcc-14
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260508 gcc-14
i386 buildonly-randconfig-001-20260509 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260508 gcc-14
i386 buildonly-randconfig-002-20260509 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260508 gcc-14
i386 buildonly-randconfig-003-20260509 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260508 gcc-14
i386 buildonly-randconfig-004-20260509 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260508 gcc-14
i386 buildonly-randconfig-005-20260509 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260508 gcc-14
i386 buildonly-randconfig-006-20260509 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001 gcc-14
i386 randconfig-001-20260508 gcc-14
i386 randconfig-001-20260509 clang-20
i386 randconfig-002 gcc-14
i386 randconfig-002-20260508 gcc-14
i386 randconfig-002-20260509 clang-20
i386 randconfig-003 gcc-14
i386 randconfig-003-20260508 gcc-14
i386 randconfig-003-20260509 clang-20
i386 randconfig-004 gcc-14
i386 randconfig-004-20260508 gcc-14
i386 randconfig-004-20260509 clang-20
i386 randconfig-005 gcc-14
i386 randconfig-005-20260508 gcc-14
i386 randconfig-005-20260509 clang-20
i386 randconfig-006 gcc-14
i386 randconfig-006-20260508 gcc-14
i386 randconfig-006-20260509 clang-20
i386 randconfig-007 gcc-14
i386 randconfig-007-20260508 gcc-14
i386 randconfig-007-20260509 clang-20
i386 randconfig-011-20260508 gcc-13
i386 randconfig-011-20260509 gcc-14
i386 randconfig-012-20260508 gcc-13
i386 randconfig-012-20260509 gcc-14
i386 randconfig-013-20260508 gcc-13
i386 randconfig-013-20260509 gcc-14
i386 randconfig-014-20260508 gcc-13
i386 randconfig-014-20260509 gcc-14
i386 randconfig-015-20260508 gcc-13
i386 randconfig-015-20260509 gcc-14
i386 randconfig-016-20260508 gcc-13
i386 randconfig-016-20260509 gcc-14
i386 randconfig-017-20260508 gcc-13
i386 randconfig-017-20260509 gcc-14
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001 gcc-11.5.0
loongarch randconfig-001-20260508 clang-23
loongarch randconfig-001-20260508 gcc-11.5.0
loongarch randconfig-001-20260509 clang-17
loongarch randconfig-002 gcc-11.5.0
loongarch randconfig-002-20260508 clang-23
loongarch randconfig-002-20260508 gcc-11.5.0
loongarch randconfig-002-20260509 clang-17
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k allyesconfig gcc-15.2.0
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips loongson3_defconfig gcc-15.2.0
mips omega2p_defconfig clang-23
mips rt305x_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001 gcc-11.5.0
nios2 randconfig-001-20260508 clang-23
nios2 randconfig-001-20260508 gcc-11.5.0
nios2 randconfig-001-20260509 clang-17
nios2 randconfig-002 gcc-11.5.0
nios2 randconfig-002-20260508 clang-23
nios2 randconfig-002-20260508 gcc-11.5.0
nios2 randconfig-002-20260509 clang-17
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
openrisc simple_smp_defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc allyesconfig gcc-15.2.0
parisc defconfig gcc-15.2.0
parisc randconfig-001 gcc-9.5.0
parisc randconfig-001-20260508 gcc-9.5.0
parisc randconfig-001-20260509 gcc-11.5.0
parisc randconfig-002 gcc-9.5.0
parisc randconfig-002-20260508 gcc-9.5.0
parisc randconfig-002-20260509 gcc-11.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc holly_defconfig clang-23
powerpc randconfig-001 gcc-9.5.0
powerpc randconfig-001-20260508 gcc-9.5.0
powerpc randconfig-001-20260509 gcc-11.5.0
powerpc randconfig-002 gcc-9.5.0
powerpc randconfig-002-20260508 gcc-9.5.0
powerpc randconfig-002-20260509 gcc-11.5.0
powerpc tqm8555_defconfig gcc-15.2.0
powerpc64 randconfig-001 gcc-9.5.0
powerpc64 randconfig-001-20260508 gcc-9.5.0
powerpc64 randconfig-001-20260509 gcc-11.5.0
powerpc64 randconfig-002 gcc-9.5.0
powerpc64 randconfig-002-20260508 gcc-9.5.0
powerpc64 randconfig-002-20260509 gcc-11.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260508 clang-23
riscv randconfig-001-20260509 clang-23
riscv randconfig-002-20260508 clang-23
riscv randconfig-002-20260509 clang-23
s390 allmodconfig clang-18
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260508 clang-23
s390 randconfig-001-20260509 clang-23
s390 randconfig-002-20260508 clang-23
s390 randconfig-002-20260509 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh allyesconfig gcc-15.2.0
sh defconfig gcc-14
sh magicpanelr2_defconfig gcc-15.2.0
sh polaris_defconfig gcc-15.2.0
sh randconfig-001-20260508 clang-23
sh randconfig-001-20260509 clang-23
sh randconfig-002-20260508 clang-23
sh randconfig-002-20260509 clang-23
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-12
sparc randconfig-001-20260508 gcc-12
sparc randconfig-001-20260509 clang-23
sparc randconfig-002 gcc-12
sparc randconfig-002-20260508 gcc-12
sparc randconfig-002-20260509 clang-23
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-12
sparc64 randconfig-001-20260508 gcc-12
sparc64 randconfig-001-20260509 clang-23
sparc64 randconfig-002 gcc-12
sparc64 randconfig-002-20260508 gcc-12
sparc64 randconfig-002-20260509 clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-12
um randconfig-001-20260508 gcc-12
um randconfig-001-20260509 clang-23
um randconfig-002 gcc-12
um randconfig-002-20260508 gcc-12
um randconfig-002-20260509 clang-23
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001 gcc-14
x86_64 buildonly-randconfig-001-20260508 gcc-14
x86_64 buildonly-randconfig-002 gcc-14
x86_64 buildonly-randconfig-002-20260508 gcc-14
x86_64 buildonly-randconfig-003 gcc-14
x86_64 buildonly-randconfig-003-20260508 gcc-14
x86_64 buildonly-randconfig-004 gcc-14
x86_64 buildonly-randconfig-004-20260508 gcc-14
x86_64 buildonly-randconfig-005 gcc-14
x86_64 buildonly-randconfig-005-20260508 gcc-14
x86_64 buildonly-randconfig-006 gcc-14
x86_64 buildonly-randconfig-006-20260508 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001 clang-20
x86_64 randconfig-001-20260508 clang-20
x86_64 randconfig-001-20260509 gcc-14
x86_64 randconfig-002 clang-20
x86_64 randconfig-002-20260508 clang-20
x86_64 randconfig-002-20260509 gcc-14
x86_64 randconfig-003 clang-20
x86_64 randconfig-003-20260508 clang-20
x86_64 randconfig-003-20260509 gcc-14
x86_64 randconfig-004 clang-20
x86_64 randconfig-004-20260508 clang-20
x86_64 randconfig-004-20260509 gcc-14
x86_64 randconfig-005 clang-20
x86_64 randconfig-005-20260508 clang-20
x86_64 randconfig-005-20260509 gcc-14
x86_64 randconfig-006 clang-20
x86_64 randconfig-006-20260508 clang-20
x86_64 randconfig-006-20260509 gcc-14
x86_64 randconfig-011 gcc-14
x86_64 randconfig-011-20260508 gcc-14
x86_64 randconfig-011-20260509 gcc-14
x86_64 randconfig-012 gcc-14
x86_64 randconfig-012-20260508 gcc-14
x86_64 randconfig-012-20260509 gcc-14
x86_64 randconfig-013 gcc-14
x86_64 randconfig-013-20260508 gcc-14
x86_64 randconfig-013-20260509 gcc-14
x86_64 randconfig-014 gcc-14
x86_64 randconfig-014-20260508 gcc-14
x86_64 randconfig-014-20260509 gcc-14
x86_64 randconfig-015 gcc-14
x86_64 randconfig-015-20260508 gcc-14
x86_64 randconfig-015-20260509 gcc-14
x86_64 randconfig-016 gcc-14
x86_64 randconfig-016-20260508 gcc-14
x86_64 randconfig-016-20260509 gcc-14
x86_64 randconfig-071 gcc-14
x86_64 randconfig-071-20260508 gcc-14
x86_64 randconfig-071-20260509 clang-20
x86_64 randconfig-072 gcc-14
x86_64 randconfig-072-20260508 gcc-14
x86_64 randconfig-072-20260509 clang-20
x86_64 randconfig-073 gcc-14
x86_64 randconfig-073-20260508 gcc-14
x86_64 randconfig-073-20260509 clang-20
x86_64 randconfig-074 gcc-14
x86_64 randconfig-074-20260508 gcc-14
x86_64 randconfig-074-20260509 clang-20
x86_64 randconfig-075 gcc-14
x86_64 randconfig-075-20260508 gcc-14
x86_64 randconfig-075-20260509 clang-20
x86_64 randconfig-076 gcc-14
x86_64 randconfig-076-20260508 gcc-14
x86_64 randconfig-076-20260509 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-12
xtensa randconfig-001-20260508 gcc-12
xtensa randconfig-001-20260509 clang-23
xtensa randconfig-002 gcc-12
xtensa randconfig-002-20260508 gcc-12
xtensa randconfig-002-20260509 clang-23
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [tj-cgroup:for-next] BUILD SUCCESS c8c84430640e5210140eb0b6d7b4ae06883c0bd1
From: kernel test robot @ 2026-05-09 1:33 UTC (permalink / raw)
To: Tejun Heo; +Cc: cgroups
tree/branch: https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-next
branch HEAD: c8c84430640e5210140eb0b6d7b4ae06883c0bd1 Merge branch 'for-7.1-fixes' into for-next
elapsed time: 1448m
configs tested: 344
configs skipped: 9
The following configs have been built successfully.
More configs may be tested in the coming days.
tested configs:
alpha allnoconfig gcc-15.2.0
alpha allyesconfig gcc-15.2.0
alpha defconfig gcc-15.2.0
arc allmodconfig clang-16
arc allnoconfig gcc-15.2.0
arc allyesconfig clang-19
arc allyesconfig clang-23
arc defconfig gcc-15.2.0
arc haps_hs_smp_defconfig gcc-15.2.0
arc nsimosci_hs_smp_defconfig gcc-15.2.0
arc randconfig-001 gcc-8.5.0
arc randconfig-001-20260508 gcc-12.5.0
arc randconfig-001-20260508 gcc-8.5.0
arc randconfig-001-20260509 gcc-9.5.0
arc randconfig-002 gcc-8.5.0
arc randconfig-002-20260508 gcc-12.5.0
arc randconfig-002-20260508 gcc-8.5.0
arc randconfig-002-20260509 gcc-9.5.0
arm allnoconfig gcc-15.2.0
arm allyesconfig clang-16
arm defconfig gcc-15.2.0
arm h3600_defconfig gcc-15.2.0
arm neponset_defconfig gcc-15.2.0
arm randconfig-001 gcc-8.5.0
arm randconfig-001-20260508 gcc-12.5.0
arm randconfig-001-20260508 gcc-8.5.0
arm randconfig-001-20260509 gcc-9.5.0
arm randconfig-002 gcc-8.5.0
arm randconfig-002-20260508 gcc-12.5.0
arm randconfig-002-20260508 gcc-8.5.0
arm randconfig-002-20260509 gcc-9.5.0
arm randconfig-003 gcc-8.5.0
arm randconfig-003-20260508 gcc-12.5.0
arm randconfig-003-20260508 gcc-8.5.0
arm randconfig-003-20260509 gcc-9.5.0
arm randconfig-004 gcc-8.5.0
arm randconfig-004-20260508 gcc-12.5.0
arm randconfig-004-20260508 gcc-8.5.0
arm randconfig-004-20260509 gcc-9.5.0
arm64 allmodconfig clang-19
arm64 allmodconfig clang-23
arm64 allnoconfig gcc-15.2.0
arm64 defconfig gcc-15.2.0
arm64 randconfig-001-20260508 gcc-14.3.0
arm64 randconfig-001-20260509 gcc-10.5.0
arm64 randconfig-002-20260508 gcc-14.3.0
arm64 randconfig-002-20260509 gcc-10.5.0
arm64 randconfig-003-20260508 gcc-14.3.0
arm64 randconfig-003-20260509 gcc-10.5.0
arm64 randconfig-004-20260508 gcc-14.3.0
arm64 randconfig-004-20260509 gcc-10.5.0
csky allmodconfig gcc-15.2.0
csky allnoconfig gcc-15.2.0
csky defconfig gcc-15.2.0
csky randconfig-001-20260508 gcc-14.3.0
csky randconfig-001-20260509 gcc-10.5.0
csky randconfig-002-20260508 gcc-14.3.0
csky randconfig-002-20260509 gcc-10.5.0
hexagon allmodconfig gcc-15.2.0
hexagon allnoconfig gcc-15.2.0
hexagon defconfig gcc-15.2.0
hexagon randconfig-001 gcc-11.5.0
hexagon randconfig-001-20260508 clang-23
hexagon randconfig-001-20260508 gcc-11.5.0
hexagon randconfig-001-20260509 clang-17
hexagon randconfig-002 gcc-11.5.0
hexagon randconfig-002-20260508 clang-23
hexagon randconfig-002-20260508 gcc-11.5.0
hexagon randconfig-002-20260509 clang-17
i386 allmodconfig clang-20
i386 allnoconfig gcc-15.2.0
i386 allyesconfig clang-20
i386 buildonly-randconfig-001 gcc-14
i386 buildonly-randconfig-001-20260508 gcc-14
i386 buildonly-randconfig-001-20260509 gcc-14
i386 buildonly-randconfig-002 gcc-14
i386 buildonly-randconfig-002-20260508 gcc-14
i386 buildonly-randconfig-002-20260509 gcc-14
i386 buildonly-randconfig-003 gcc-14
i386 buildonly-randconfig-003-20260508 gcc-14
i386 buildonly-randconfig-003-20260509 gcc-14
i386 buildonly-randconfig-004 gcc-14
i386 buildonly-randconfig-004-20260508 gcc-14
i386 buildonly-randconfig-004-20260509 gcc-14
i386 buildonly-randconfig-005 gcc-14
i386 buildonly-randconfig-005-20260508 gcc-14
i386 buildonly-randconfig-005-20260509 gcc-14
i386 buildonly-randconfig-006 gcc-14
i386 buildonly-randconfig-006-20260508 gcc-14
i386 buildonly-randconfig-006-20260509 gcc-14
i386 defconfig gcc-15.2.0
i386 randconfig-001 gcc-14
i386 randconfig-001-20260508 gcc-14
i386 randconfig-001-20260509 clang-20
i386 randconfig-002 gcc-14
i386 randconfig-002-20260508 gcc-14
i386 randconfig-002-20260509 clang-20
i386 randconfig-003 gcc-14
i386 randconfig-003-20260508 gcc-14
i386 randconfig-003-20260509 clang-20
i386 randconfig-004 gcc-14
i386 randconfig-004-20260508 gcc-14
i386 randconfig-004-20260509 clang-20
i386 randconfig-005 gcc-14
i386 randconfig-005-20260508 gcc-14
i386 randconfig-005-20260509 clang-20
i386 randconfig-006 gcc-14
i386 randconfig-006-20260508 gcc-14
i386 randconfig-006-20260509 clang-20
i386 randconfig-007 gcc-14
i386 randconfig-007-20260508 gcc-14
i386 randconfig-007-20260509 clang-20
i386 randconfig-011-20260508 gcc-13
i386 randconfig-011-20260509 gcc-14
i386 randconfig-012-20260508 gcc-13
i386 randconfig-012-20260509 gcc-14
i386 randconfig-013-20260508 gcc-13
i386 randconfig-013-20260509 gcc-14
i386 randconfig-014-20260508 gcc-13
i386 randconfig-014-20260509 gcc-14
i386 randconfig-015-20260508 gcc-13
i386 randconfig-015-20260509 gcc-14
i386 randconfig-016-20260508 gcc-13
i386 randconfig-016-20260509 gcc-14
i386 randconfig-017-20260508 gcc-13
i386 randconfig-017-20260509 gcc-14
loongarch allmodconfig clang-19
loongarch allmodconfig clang-23
loongarch allnoconfig gcc-15.2.0
loongarch defconfig clang-19
loongarch randconfig-001 gcc-11.5.0
loongarch randconfig-001-20260508 clang-23
loongarch randconfig-001-20260508 gcc-11.5.0
loongarch randconfig-001-20260509 clang-17
loongarch randconfig-002 gcc-11.5.0
loongarch randconfig-002-20260508 clang-23
loongarch randconfig-002-20260508 gcc-11.5.0
loongarch randconfig-002-20260509 clang-17
m68k allmodconfig gcc-15.2.0
m68k allnoconfig gcc-15.2.0
m68k allyesconfig clang-16
m68k defconfig clang-19
microblaze allnoconfig gcc-15.2.0
microblaze allyesconfig gcc-15.2.0
microblaze defconfig clang-19
mips allmodconfig gcc-15.2.0
mips allnoconfig gcc-15.2.0
mips allyesconfig gcc-15.2.0
mips loongson3_defconfig gcc-15.2.0
mips omega2p_defconfig clang-23
mips rt305x_defconfig clang-23
nios2 allmodconfig clang-23
nios2 allnoconfig clang-23
nios2 allnoconfig gcc-11.5.0
nios2 defconfig clang-19
nios2 randconfig-001 gcc-11.5.0
nios2 randconfig-001-20260508 clang-23
nios2 randconfig-001-20260508 gcc-11.5.0
nios2 randconfig-001-20260509 clang-17
nios2 randconfig-002 gcc-11.5.0
nios2 randconfig-002-20260508 clang-23
nios2 randconfig-002-20260508 gcc-11.5.0
nios2 randconfig-002-20260509 clang-17
openrisc allmodconfig clang-23
openrisc allnoconfig clang-23
openrisc allnoconfig gcc-15.2.0
openrisc defconfig gcc-15.2.0
openrisc simple_smp_defconfig gcc-15.2.0
parisc allmodconfig gcc-15.2.0
parisc allnoconfig clang-23
parisc allnoconfig gcc-15.2.0
parisc allyesconfig clang-19
parisc defconfig gcc-15.2.0
parisc randconfig-001 gcc-9.5.0
parisc randconfig-001-20260508 gcc-9.5.0
parisc randconfig-001-20260509 gcc-11.5.0
parisc randconfig-002 gcc-9.5.0
parisc randconfig-002-20260508 gcc-9.5.0
parisc randconfig-002-20260509 gcc-11.5.0
parisc64 defconfig clang-19
powerpc allmodconfig gcc-15.2.0
powerpc allnoconfig clang-23
powerpc allnoconfig gcc-15.2.0
powerpc holly_defconfig clang-23
powerpc randconfig-001 gcc-9.5.0
powerpc randconfig-001-20260508 gcc-9.5.0
powerpc randconfig-001-20260509 gcc-11.5.0
powerpc randconfig-002 gcc-9.5.0
powerpc randconfig-002-20260508 gcc-9.5.0
powerpc randconfig-002-20260509 gcc-11.5.0
powerpc tqm8555_defconfig gcc-15.2.0
powerpc64 randconfig-001 gcc-9.5.0
powerpc64 randconfig-001-20260508 gcc-9.5.0
powerpc64 randconfig-001-20260509 gcc-11.5.0
powerpc64 randconfig-002 gcc-9.5.0
powerpc64 randconfig-002-20260508 gcc-9.5.0
powerpc64 randconfig-002-20260509 gcc-11.5.0
riscv allmodconfig clang-23
riscv allnoconfig clang-23
riscv allnoconfig gcc-15.2.0
riscv allyesconfig clang-16
riscv defconfig gcc-15.2.0
riscv randconfig-001-20260508 clang-23
riscv randconfig-001-20260509 clang-23
riscv randconfig-002-20260508 clang-23
riscv randconfig-002-20260509 clang-23
s390 allmodconfig clang-19
s390 allnoconfig clang-23
s390 allyesconfig gcc-15.2.0
s390 defconfig gcc-15.2.0
s390 randconfig-001-20260508 clang-23
s390 randconfig-001-20260509 clang-23
s390 randconfig-002-20260508 clang-23
s390 randconfig-002-20260509 clang-23
sh allmodconfig gcc-15.2.0
sh allnoconfig clang-23
sh allnoconfig gcc-15.2.0
sh allyesconfig clang-19
sh apsh4ad0a_defconfig gcc-15.2.0
sh defconfig gcc-14
sh magicpanelr2_defconfig gcc-15.2.0
sh polaris_defconfig gcc-15.2.0
sh randconfig-001-20260508 clang-23
sh randconfig-001-20260509 clang-23
sh randconfig-002-20260508 clang-23
sh randconfig-002-20260509 clang-23
sparc allnoconfig clang-23
sparc allnoconfig gcc-15.2.0
sparc defconfig gcc-15.2.0
sparc randconfig-001 gcc-12
sparc randconfig-001-20260508 gcc-12
sparc randconfig-001-20260509 clang-23
sparc randconfig-002 gcc-12
sparc randconfig-002-20260508 gcc-12
sparc randconfig-002-20260509 clang-23
sparc64 allmodconfig clang-23
sparc64 defconfig gcc-14
sparc64 randconfig-001 gcc-12
sparc64 randconfig-001-20260508 gcc-12
sparc64 randconfig-001-20260509 clang-23
sparc64 randconfig-002 gcc-12
sparc64 randconfig-002-20260508 gcc-12
sparc64 randconfig-002-20260509 clang-23
um allmodconfig clang-19
um allnoconfig clang-23
um allyesconfig gcc-14
um allyesconfig gcc-15.2.0
um defconfig gcc-14
um i386_defconfig gcc-14
um randconfig-001 gcc-12
um randconfig-001-20260508 gcc-12
um randconfig-001-20260509 clang-23
um randconfig-002 gcc-12
um randconfig-002-20260508 gcc-12
um randconfig-002-20260509 clang-23
um x86_64_defconfig gcc-14
x86_64 allmodconfig clang-20
x86_64 allnoconfig clang-20
x86_64 allnoconfig clang-23
x86_64 allyesconfig clang-20
x86_64 buildonly-randconfig-001 gcc-14
x86_64 buildonly-randconfig-001-20260508 gcc-14
x86_64 buildonly-randconfig-002 gcc-14
x86_64 buildonly-randconfig-002-20260508 gcc-14
x86_64 buildonly-randconfig-003 gcc-14
x86_64 buildonly-randconfig-003-20260508 gcc-14
x86_64 buildonly-randconfig-004 gcc-14
x86_64 buildonly-randconfig-004-20260508 gcc-14
x86_64 buildonly-randconfig-005 gcc-14
x86_64 buildonly-randconfig-005-20260508 gcc-14
x86_64 buildonly-randconfig-006 gcc-14
x86_64 buildonly-randconfig-006-20260508 gcc-14
x86_64 defconfig gcc-14
x86_64 kexec clang-20
x86_64 randconfig-001 clang-20
x86_64 randconfig-001-20260508 clang-20
x86_64 randconfig-001-20260509 gcc-14
x86_64 randconfig-002 clang-20
x86_64 randconfig-002-20260508 clang-20
x86_64 randconfig-002-20260509 gcc-14
x86_64 randconfig-003 clang-20
x86_64 randconfig-003-20260508 clang-20
x86_64 randconfig-003-20260509 gcc-14
x86_64 randconfig-004 clang-20
x86_64 randconfig-004-20260508 clang-20
x86_64 randconfig-004-20260509 gcc-14
x86_64 randconfig-005 clang-20
x86_64 randconfig-005-20260508 clang-20
x86_64 randconfig-005-20260509 gcc-14
x86_64 randconfig-006 clang-20
x86_64 randconfig-006-20260508 clang-20
x86_64 randconfig-006-20260509 gcc-14
x86_64 randconfig-011 gcc-14
x86_64 randconfig-011-20260508 gcc-14
x86_64 randconfig-011-20260509 gcc-14
x86_64 randconfig-012 gcc-14
x86_64 randconfig-012-20260508 gcc-14
x86_64 randconfig-012-20260509 gcc-14
x86_64 randconfig-013 gcc-14
x86_64 randconfig-013-20260508 gcc-14
x86_64 randconfig-013-20260509 gcc-14
x86_64 randconfig-014 gcc-14
x86_64 randconfig-014-20260508 gcc-14
x86_64 randconfig-014-20260509 gcc-14
x86_64 randconfig-015 gcc-14
x86_64 randconfig-015-20260508 gcc-14
x86_64 randconfig-015-20260509 gcc-14
x86_64 randconfig-016 gcc-14
x86_64 randconfig-016-20260508 gcc-14
x86_64 randconfig-016-20260509 gcc-14
x86_64 randconfig-071 gcc-14
x86_64 randconfig-071-20260508 gcc-14
x86_64 randconfig-071-20260509 clang-20
x86_64 randconfig-072 gcc-14
x86_64 randconfig-072-20260508 gcc-14
x86_64 randconfig-072-20260509 clang-20
x86_64 randconfig-073 gcc-14
x86_64 randconfig-073-20260508 gcc-14
x86_64 randconfig-073-20260509 clang-20
x86_64 randconfig-074 gcc-14
x86_64 randconfig-074-20260508 gcc-14
x86_64 randconfig-074-20260509 clang-20
x86_64 randconfig-075 gcc-14
x86_64 randconfig-075-20260508 gcc-14
x86_64 randconfig-075-20260509 clang-20
x86_64 randconfig-076 gcc-14
x86_64 randconfig-076-20260508 gcc-14
x86_64 randconfig-076-20260509 clang-20
x86_64 rhel-9.4 clang-20
x86_64 rhel-9.4-bpf gcc-14
x86_64 rhel-9.4-func clang-20
x86_64 rhel-9.4-kselftests clang-20
x86_64 rhel-9.4-kunit gcc-14
x86_64 rhel-9.4-ltp gcc-14
x86_64 rhel-9.4-rust clang-20
xtensa allnoconfig clang-23
xtensa allnoconfig gcc-15.2.0
xtensa allyesconfig clang-23
xtensa randconfig-001 gcc-12
xtensa randconfig-001-20260508 gcc-12
xtensa randconfig-001-20260509 clang-23
xtensa randconfig-002 gcc-12
xtensa randconfig-002-20260508 gcc-12
xtensa randconfig-002-20260509 clang-23
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* Re: [PATCH v3 11/12] mm/memcg: remove no longer used swap cgroup array
From: Chris Li @ 2026-05-08 22:47 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-11-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 8:17 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Kairui Song <kasong@tencent.com>
>
> Now all swap cgroup records are stored in the swap cluster directly,
> the static array is no longer needed.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
> ---
> MAINTAINERS | 1 -
> include/linux/swap_cgroup.h | 47 ------------
> mm/Makefile | 3 -
> mm/internal.h | 1 -
> mm/memcontrol-v1.c | 1 -
> mm/memcontrol.c | 1 -
> mm/swap_cgroup.c | 172 --------------------------------------------
Nice patch stats.
Acked-by: Chris Li <chrisl@kernel.org>
Chris
> mm/swapfile.c | 8 ---
> 8 files changed, 234 deletions(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 76d8291237be..217d98c89275 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -6565,7 +6565,6 @@ F: mm/memcontrol.c
> F: mm/memcontrol-v1.c
> F: mm/memcontrol-v1.h
> F: mm/page_counter.c
> -F: mm/swap_cgroup.c
> F: samples/cgroup/*
> F: tools/testing/selftests/cgroup/memcg_protection.m
> F: tools/testing/selftests/cgroup/test_hugetlb_memcg.c
> diff --git a/include/linux/swap_cgroup.h b/include/linux/swap_cgroup.h
> deleted file mode 100644
> index 91cdf12190a0..000000000000
> --- a/include/linux/swap_cgroup.h
> +++ /dev/null
> @@ -1,47 +0,0 @@
> -/* SPDX-License-Identifier: GPL-2.0 */
> -#ifndef __LINUX_SWAP_CGROUP_H
> -#define __LINUX_SWAP_CGROUP_H
> -
> -#include <linux/swap.h>
> -
> -#if defined(CONFIG_MEMCG) && defined(CONFIG_SWAP)
> -
> -extern void swap_cgroup_record(struct folio *folio, unsigned short id, swp_entry_t ent);
> -extern unsigned short swap_cgroup_clear(swp_entry_t ent, unsigned int nr_ents);
> -extern unsigned short lookup_swap_cgroup_id(swp_entry_t ent);
> -extern int swap_cgroup_swapon(int type, unsigned long max_pages);
> -extern void swap_cgroup_swapoff(int type);
> -
> -#else
> -
> -static inline
> -void swap_cgroup_record(struct folio *folio, unsigned short id, swp_entry_t ent)
> -{
> -}
> -
> -static inline
> -unsigned short swap_cgroup_clear(swp_entry_t ent, unsigned int nr_ents)
> -{
> - return 0;
> -}
> -
> -static inline
> -unsigned short lookup_swap_cgroup_id(swp_entry_t ent)
> -{
> - return 0;
> -}
> -
> -static inline int
> -swap_cgroup_swapon(int type, unsigned long max_pages)
> -{
> - return 0;
> -}
> -
> -static inline void swap_cgroup_swapoff(int type)
> -{
> - return;
> -}
> -
> -#endif
> -
> -#endif /* __LINUX_SWAP_CGROUP_H */
> diff --git a/mm/Makefile b/mm/Makefile
> index 8ad2ab08244e..eff9f9e7e061 100644
> --- a/mm/Makefile
> +++ b/mm/Makefile
> @@ -103,9 +103,6 @@ obj-$(CONFIG_PAGE_COUNTER) += page_counter.o
> obj-$(CONFIG_LIVEUPDATE_MEMFD) += memfd_luo.o
> obj-$(CONFIG_MEMCG_V1) += memcontrol-v1.o
> obj-$(CONFIG_MEMCG) += memcontrol.o vmpressure.o
> -ifdef CONFIG_SWAP
> -obj-$(CONFIG_MEMCG) += swap_cgroup.o
> -endif
> ifdef CONFIG_BPF_SYSCALL
> obj-$(CONFIG_MEMCG) += bpf_memcontrol.o
> endif
> diff --git a/mm/internal.h b/mm/internal.h
> index 9d2fec696bd6..7646ecb9d621 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -17,7 +17,6 @@
> #include <linux/rmap.h>
> #include <linux/swap.h>
> #include <linux/leafops.h>
> -#include <linux/swap_cgroup.h>
> #include <linux/tracepoint-defs.h>
>
> /* Internal core VMA manipulation functions. */
> diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
> index 494e7b9adc60..08be1a752c2e 100644
> --- a/mm/memcontrol-v1.c
> +++ b/mm/memcontrol-v1.c
> @@ -5,7 +5,6 @@
> #include <linux/mm_inline.h>
> #include <linux/pagewalk.h>
> #include <linux/backing-dev.h>
> -#include <linux/swap_cgroup.h>
> #include <linux/eventfd.h>
> #include <linux/poll.h>
> #include <linux/sort.h>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 193c8eb73be7..12165fd32529 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -54,7 +54,6 @@
> #include <linux/vmpressure.h>
> #include <linux/memremap.h>
> #include <linux/mm_inline.h>
> -#include <linux/swap_cgroup.h>
> #include <linux/cpu.h>
> #include <linux/oom.h>
> #include <linux/lockdep.h>
> diff --git a/mm/swap_cgroup.c b/mm/swap_cgroup.c
> deleted file mode 100644
> index de779fed8c21..000000000000
> --- a/mm/swap_cgroup.c
> +++ /dev/null
> @@ -1,172 +0,0 @@
> -// SPDX-License-Identifier: GPL-2.0
> -#include <linux/swap_cgroup.h>
> -#include <linux/vmalloc.h>
> -#include <linux/mm.h>
> -
> -#include <linux/swapops.h> /* depends on mm.h include */
> -
> -static DEFINE_MUTEX(swap_cgroup_mutex);
> -
> -/* Pack two cgroup id (short) of two entries in one swap_cgroup (atomic_t) */
> -#define ID_PER_SC (sizeof(struct swap_cgroup) / sizeof(unsigned short))
> -#define ID_SHIFT (BITS_PER_TYPE(unsigned short))
> -#define ID_MASK (BIT(ID_SHIFT) - 1)
> -struct swap_cgroup {
> - atomic_t ids;
> -};
> -
> -struct swap_cgroup_ctrl {
> - struct swap_cgroup *map;
> -};
> -
> -static struct swap_cgroup_ctrl swap_cgroup_ctrl[MAX_SWAPFILES];
> -
> -static unsigned short __swap_cgroup_id_lookup(struct swap_cgroup *map,
> - pgoff_t offset)
> -{
> - unsigned int shift = (offset % ID_PER_SC) * ID_SHIFT;
> - unsigned int old_ids = atomic_read(&map[offset / ID_PER_SC].ids);
> -
> - BUILD_BUG_ON(!is_power_of_2(ID_PER_SC));
> - BUILD_BUG_ON(sizeof(struct swap_cgroup) != sizeof(atomic_t));
> -
> - return (old_ids >> shift) & ID_MASK;
> -}
> -
> -static unsigned short __swap_cgroup_id_xchg(struct swap_cgroup *map,
> - pgoff_t offset,
> - unsigned short new_id)
> -{
> - unsigned short old_id;
> - struct swap_cgroup *sc = &map[offset / ID_PER_SC];
> - unsigned int shift = (offset % ID_PER_SC) * ID_SHIFT;
> - unsigned int new_ids, old_ids = atomic_read(&sc->ids);
> -
> - do {
> - old_id = (old_ids >> shift) & ID_MASK;
> - new_ids = (old_ids & ~(ID_MASK << shift));
> - new_ids |= ((unsigned int)new_id) << shift;
> - } while (!atomic_try_cmpxchg(&sc->ids, &old_ids, new_ids));
> -
> - return old_id;
> -}
> -
> -/**
> - * swap_cgroup_record - record mem_cgroup for a set of swap entries.
> - * These entries must belong to one single folio, and that folio
> - * must be being charged for swap space (swap out), and these
> - * entries must not have been charged
> - *
> - * @folio: the folio that the swap entry belongs to
> - * @id: mem_cgroup ID to be recorded
> - * @ent: the first swap entry to be recorded
> - */
> -void swap_cgroup_record(struct folio *folio, unsigned short id,
> - swp_entry_t ent)
> -{
> - unsigned int nr_ents = folio_nr_pages(folio);
> - struct swap_cgroup *map;
> - pgoff_t offset, end;
> - unsigned short old;
> -
> - offset = swp_offset(ent);
> - end = offset + nr_ents;
> - map = swap_cgroup_ctrl[swp_type(ent)].map;
> -
> - do {
> - old = __swap_cgroup_id_xchg(map, offset, id);
> - VM_BUG_ON(old);
> - } while (++offset != end);
> -}
> -
> -/**
> - * swap_cgroup_clear - clear mem_cgroup for a set of swap entries.
> - * These entries must be being uncharged from swap. They either
> - * belongs to one single folio in the swap cache (swap in for
> - * cgroup v1), or no longer have any users (slot freeing).
> - *
> - * @ent: the first swap entry to be recorded into
> - * @nr_ents: number of swap entries to be recorded
> - *
> - * Returns the existing old value.
> - */
> -unsigned short swap_cgroup_clear(swp_entry_t ent, unsigned int nr_ents)
> -{
> - pgoff_t offset, end;
> - struct swap_cgroup *map;
> - unsigned short old, iter = 0;
> -
> - offset = swp_offset(ent);
> - end = offset + nr_ents;
> - map = swap_cgroup_ctrl[swp_type(ent)].map;
> -
> - do {
> - old = __swap_cgroup_id_xchg(map, offset, 0);
> - if (!iter)
> - iter = old;
> - VM_BUG_ON(iter != old);
> - } while (++offset != end);
> -
> - return old;
> -}
> -
> -/**
> - * lookup_swap_cgroup_id - lookup mem_cgroup id tied to swap entry
> - * @ent: swap entry to be looked up.
> - *
> - * Returns ID of mem_cgroup at success. 0 at failure. (0 is invalid ID)
> - */
> -unsigned short lookup_swap_cgroup_id(swp_entry_t ent)
> -{
> - struct swap_cgroup_ctrl *ctrl;
> -
> - if (mem_cgroup_disabled())
> - return 0;
> -
> - ctrl = &swap_cgroup_ctrl[swp_type(ent)];
> - return __swap_cgroup_id_lookup(ctrl->map, swp_offset(ent));
> -}
> -
> -int swap_cgroup_swapon(int type, unsigned long max_pages)
> -{
> - struct swap_cgroup *map;
> - struct swap_cgroup_ctrl *ctrl;
> -
> - if (mem_cgroup_disabled())
> - return 0;
> -
> - BUILD_BUG_ON(sizeof(unsigned short) * ID_PER_SC !=
> - sizeof(struct swap_cgroup));
> - map = vzalloc(DIV_ROUND_UP(max_pages, ID_PER_SC) *
> - sizeof(struct swap_cgroup));
> - if (!map)
> - goto nomem;
> -
> - ctrl = &swap_cgroup_ctrl[type];
> - mutex_lock(&swap_cgroup_mutex);
> - ctrl->map = map;
> - mutex_unlock(&swap_cgroup_mutex);
> -
> - return 0;
> -nomem:
> - pr_info("couldn't allocate enough memory for swap_cgroup\n");
> - pr_info("swap_cgroup can be disabled by swapaccount=0 boot option\n");
> - return -ENOMEM;
> -}
> -
> -void swap_cgroup_swapoff(int type)
> -{
> - struct swap_cgroup *map;
> - struct swap_cgroup_ctrl *ctrl;
> -
> - if (mem_cgroup_disabled())
> - return;
> -
> - mutex_lock(&swap_cgroup_mutex);
> - ctrl = &swap_cgroup_ctrl[type];
> - map = ctrl->map;
> - ctrl->map = NULL;
> - mutex_unlock(&swap_cgroup_mutex);
> -
> - vfree(map);
> -}
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index edf4cb36728e..2172920e68d1 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -45,7 +45,6 @@
>
> #include <asm/tlbflush.h>
> #include <linux/leafops.h>
> -#include <linux/swap_cgroup.h>
> #include "swap_table.h"
> #include "internal.h"
> #include "swap.h"
> @@ -3136,8 +3135,6 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
> p->global_cluster = NULL;
> kvfree(zeromap);
> free_swap_cluster_info(cluster_info, maxpages);
> - /* Destroy swap account information */
> - swap_cgroup_swapoff(p->type);
>
> inode = mapping->host;
>
> @@ -3668,10 +3665,6 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
> if (error)
> goto bad_swap_unlock_inode;
>
> - error = swap_cgroup_swapon(si->type, maxpages);
> - if (error)
> - goto bad_swap_unlock_inode;
> -
> /*
> * Use kvmalloc_array instead of bitmap_zalloc as the allocation order might
> * be above MAX_PAGE_ORDER incase of a large swap file.
> @@ -3782,7 +3775,6 @@ SYSCALL_DEFINE2(swapon, const char __user *, specialfile, int, swap_flags)
> si->global_cluster = NULL;
> inode = NULL;
> destroy_swap_extents(si, swap_file);
> - swap_cgroup_swapoff(si->type);
> free_swap_cluster_info(si->cluster_info, si->max);
> si->cluster_info = NULL;
> kvfree(si->zeromap);
>
> --
> 2.53.0
>
>
>
^ permalink raw reply
* Re: [PATCH v3 10/12] mm/memcg, swap: store cgroup id in cluster table directly
From: Chris Li @ 2026-05-08 22:46 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-10-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 2:16 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Kairui Song <kasong@tencent.com>
>
> Drop the usage of the swap_cgroup_ctrl, and use the dynamic cluster
> table instead.
Nice! It takes so many steps to finally drop the static allocated swap
cgroup ctrl array. Thank you for making it happen.
>
> The per-cluster memcg table is 1024 / 512 bytes on most archs, and does
> not need RCU protection: the cgroup data is only read and written under
> the cluster lock. That keeps things simple, lets the allocation use
> plain kmalloc with immediate kfree (no deferred free), and keeps
> fragmentation acceptable.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
Overall looks good, with some nitpick and question follows.
Acked-by: Chris Li <chrisl@kernel.org>
> ---
> include/linux/memcontrol.h | 6 ++++--
> include/linux/swap.h | 8 +++----
> mm/memcontrol-v1.c | 42 +++++++++++++++++++++++-------------
> mm/memcontrol.c | 14 +++++++-----
> mm/swap.h | 4 ++++
> mm/swap_state.c | 6 ++----
> mm/swap_table.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++
> mm/swapfile.c | 35 +++++++++++++++++++-----------
> mm/vmscan.c | 2 +-
> 9 files changed, 128 insertions(+), 43 deletions(-)
>
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index a013f37f24aa..bf1a6e131eca 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -29,6 +29,7 @@ struct obj_cgroup;
> struct page;
> struct mm_struct;
> struct kmem_cache;
> +struct swap_cluster_info;
>
> /* Cgroup-specific page state, on top of universal node page state */
> enum memcg_stat_item {
> @@ -1899,7 +1900,7 @@ static inline void mem_cgroup_exit_user_fault(void)
> current->in_user_fault = 0;
> }
>
> -void __memcg1_swapout(struct folio *folio);
> +void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci);
> void memcg1_swapin(struct folio *folio);
>
> #else /* CONFIG_MEMCG_V1 */
> @@ -1929,7 +1930,8 @@ static inline void mem_cgroup_exit_user_fault(void)
> {
> }
>
> -static inline void __memcg1_swapout(struct folio *folio)
> +static inline void __memcg1_swapout(struct folio *folio,
> + struct swap_cluster_info *ci)
> {
> }
>
> diff --git a/include/linux/swap.h b/include/linux/swap.h
> index f2949f5844a6..57af4647d432 100644
> --- a/include/linux/swap.h
> +++ b/include/linux/swap.h
> @@ -582,12 +582,12 @@ static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> return __mem_cgroup_try_charge_swap(folio);
> }
>
> -extern void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages);
> -static inline void mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)
> +extern void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages);
> +static inline void mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
> {
> if (mem_cgroup_disabled())
> return;
> - __mem_cgroup_uncharge_swap(entry, nr_pages);
> + __mem_cgroup_uncharge_swap(id, nr_pages);
> }
>
> extern long mem_cgroup_get_nr_swap_pages(struct mem_cgroup *memcg);
> @@ -598,7 +598,7 @@ static inline int mem_cgroup_try_charge_swap(struct folio *folio)
> return 0;
> }
>
> -static inline void mem_cgroup_uncharge_swap(swp_entry_t entry,
> +static inline void mem_cgroup_uncharge_swap(unsigned short id,
> unsigned int nr_pages)
> {
> }
> diff --git a/mm/memcontrol-v1.c b/mm/memcontrol-v1.c
> index 36c507d81dc5..494e7b9adc60 100644
> --- a/mm/memcontrol-v1.c
> +++ b/mm/memcontrol-v1.c
> @@ -14,6 +14,7 @@
>
> #include "internal.h"
> #include "swap.h"
> +#include "swap_table.h"
> #include "memcontrol-v1.h"
>
> /*
> @@ -606,14 +607,15 @@ void memcg1_commit_charge(struct folio *folio, struct mem_cgroup *memcg)
> /**
> * __memcg1_swapout - transfer a memsw charge to swap
> * @folio: folio whose memsw charge to transfer
> + * @ci: the locked swap cluster holding the swap entries
> *
> * Transfer the memsw charge of @folio to the swap entry stored in
> * folio->swap.
> *
> - * Context: folio must be isolated, unmapped, locked and is just about
> - * to be freed, and caller must disable IRQs.
> + * Context: folio must be isolated, unmapped, locked and is just about to
> + * be freed, and caller must disable IRQs and hold the swap cluster lock.
> */
> -void __memcg1_swapout(struct folio *folio)
> +void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
> {
> struct mem_cgroup *memcg, *swap_memcg;
> struct obj_cgroup *objcg;
> @@ -646,7 +648,8 @@ void __memcg1_swapout(struct folio *folio)
> swap_memcg = mem_cgroup_private_id_get_online(memcg, nr_entries);
> mod_memcg_state(swap_memcg, MEMCG_SWAP, nr_entries);
>
> - swap_cgroup_record(folio, mem_cgroup_private_id(swap_memcg), folio->swap);
> + __swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_entries,
> + mem_cgroup_private_id(swap_memcg));
>
> folio_unqueue_deferred_split(folio);
> folio->memcg_data = 0;
> @@ -661,8 +664,7 @@ void __memcg1_swapout(struct folio *folio)
> }
>
> /*
> - * Interrupts should be disabled here because the caller holds the
> - * i_pages lock which is taken with interrupts-off. It is
> + * The caller must hold the swap cluster lock with IRQ off. It is
> * important here to have the interrupts disabled because it is the
> * only synchronisation we have for updating the per-CPU variables.
> */
> @@ -677,7 +679,7 @@ void __memcg1_swapout(struct folio *folio)
> }
>
> /**
> - * memcg1_swapin - uncharge swap slot
> + * memcg1_swapin - uncharge swap slot on swapin
> * @folio: folio being swapped in
> *
> * Call this function after successfully adding the charged
> @@ -687,6 +689,10 @@ void __memcg1_swapout(struct folio *folio)
> */
> void memcg1_swapin(struct folio *folio)
> {
> + struct swap_cluster_info *ci;
> + unsigned long nr_pages;
> + unsigned short id;
> +
> VM_WARN_ON_ONCE_FOLIO(!folio_test_swapcache(folio), folio);
> VM_WARN_ON_ONCE_FOLIO(!folio_test_locked(folio), folio);
>
> @@ -702,14 +708,20 @@ void memcg1_swapin(struct folio *folio)
> * correspond 1:1 to page and swap slot lifetimes: we charge the
> * page to memory here, and uncharge swap when the slot is freed.
> */
> - if (do_memsw_account()) {
> - /*
> - * The swap entry might not get freed for a long time,
> - * let's not wait for it. The page already received a
> - * memory+swap charge, drop the swap entry duplicate.
> - */
> - mem_cgroup_uncharge_swap(folio->swap, folio_nr_pages(folio));
> - }
> + if (!do_memsw_account())
> + return;
> +
> + /*
> + * The swap entry might not get freed for a long time,
> + * let's not wait for it. The page already received a
> + * memory+swap charge, drop the swap entry duplicate.
> + */
> + nr_pages = folio_nr_pages(folio);
> + ci = swap_cluster_get_and_lock(folio);
> + id = __swap_cgroup_clear(ci, swp_cluster_offset(folio->swap),
> + nr_pages);
> + swap_cluster_unlock(ci);
> + mem_cgroup_uncharge_swap(id, nr_pages);
> }
>
> void memcg1_uncharge_batch(struct mem_cgroup *memcg, unsigned long pgpgout,
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 641706fa47bf..193c8eb73be7 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -64,6 +64,8 @@
> #include <linux/sched/isolation.h>
> #include <linux/kmemleak.h>
> #include "internal.h"
> +#include "swap.h"
> +#include "swap_table.h"
> #include <net/sock.h>
> #include <net/ip.h>
> #include "slab.h"
> @@ -5462,6 +5464,7 @@ int __init mem_cgroup_init(void)
> int __mem_cgroup_try_charge_swap(struct folio *folio)
> {
> unsigned int nr_pages = folio_nr_pages(folio);
> + struct swap_cluster_info *ci;
> struct page_counter *counter;
> struct mem_cgroup *memcg;
> struct obj_cgroup *objcg;
> @@ -5495,22 +5498,23 @@ int __mem_cgroup_try_charge_swap(struct folio *folio)
> }
> mod_memcg_state(memcg, MEMCG_SWAP, nr_pages);
>
> - swap_cgroup_record(folio, mem_cgroup_private_id(memcg), folio->swap);
> + ci = swap_cluster_get_and_lock(folio);
> + __swap_cgroup_set(ci, swp_cluster_offset(folio->swap), nr_pages,
> + mem_cgroup_private_id(memcg));
> + swap_cluster_unlock(ci);
>
> return 0;
> }
>
> /**
> * __mem_cgroup_uncharge_swap - uncharge swap space
> - * @entry: swap entry to uncharge
> + * @id: cgroup id to uncharge
> * @nr_pages: the amount of swap space to uncharge
> */
> -void __mem_cgroup_uncharge_swap(swp_entry_t entry, unsigned int nr_pages)
> +void __mem_cgroup_uncharge_swap(unsigned short id, unsigned int nr_pages)
> {
> struct mem_cgroup *memcg;
> - unsigned short id;
>
> - id = swap_cgroup_clear(entry, nr_pages);
> rcu_read_lock();
> memcg = mem_cgroup_from_private_id(id);
> if (memcg) {
> diff --git a/mm/swap.h b/mm/swap.h
> index 80c2f1bf7a57..e4ac7dbc1080 100644
> --- a/mm/swap.h
> +++ b/mm/swap.h
> @@ -5,6 +5,7 @@
> #include <linux/atomic.h> /* for atomic_long_t */
> struct mempolicy;
> struct swap_iocb;
> +struct swap_memcg_table;
>
> extern int page_cluster;
>
> @@ -38,6 +39,9 @@ struct swap_cluster_info {
> u8 order;
> atomic_long_t __rcu *table; /* Swap table entries, see mm/swap_table.h */
> unsigned int *extend_table; /* For large swap count, protected by ci->lock */
> +#ifdef CONFIG_MEMCG
> + struct swap_memcg_table *memcg_table; /* Swap table entries' cgroup record */
> +#endif
> struct list_head list;
> };
>
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index 86d517a33a55..71a3f128fcf0 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -176,21 +176,19 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
> if (shadowp && swp_tb_is_shadow(old_tb))
> *shadowp = swp_tb_to_shadow(old_tb);
> if (memcg_id)
> - *memcg_id = lookup_swap_cgroup_id(targ_entry);
> + *memcg_id = __swap_cgroup_get(ci, ci_off);
>
> if (nr == 1)
> return 0;
>
> - targ_entry.val = round_down(targ_entry.val, nr);
> ci_off = round_down(ci_off, nr);
> ci_end = ci_off + nr;
> do {
> old_tb = __swap_table_get(ci, ci_off);
> if (unlikely(swp_tb_is_folio(old_tb) ||
> !__swp_tb_get_count(old_tb) ||
> - (memcg_id && *memcg_id != lookup_swap_cgroup_id(targ_entry))))
> + (memcg_id && *memcg_id != __swap_cgroup_get(ci, ci_off))))
> return -EBUSY;
> - targ_entry.val++;
> } while (++ci_off < ci_end);
>
> return 0;
> diff --git a/mm/swap_table.h b/mm/swap_table.h
> index 8415ffbe2b9c..b2b02ee161b1 100644
> --- a/mm/swap_table.h
> +++ b/mm/swap_table.h
> @@ -11,6 +11,11 @@ struct swap_table {
> atomic_long_t entries[SWAPFILE_CLUSTER];
> };
>
> +/* For storing memcg private id */
> +struct swap_memcg_table {
> + unsigned short id[SWAPFILE_CLUSTER];
> +};
> +
> #define SWP_TABLE_USE_PAGE (sizeof(struct swap_table) == PAGE_SIZE)
>
> /*
> @@ -247,4 +252,53 @@ static inline unsigned long swap_table_get(struct swap_cluster_info *ci,
>
> return swp_tb;
> }
> +
> +#ifdef CONFIG_MEMCG
> +static inline void __swap_cgroup_set(struct swap_cluster_info *ci,
> + unsigned int ci_off, unsigned long nr, unsigned short id)
> +{
> + lockdep_assert_held(&ci->lock);
> + VM_WARN_ON_ONCE(ci_off >= SWAPFILE_CLUSTER);
> + do {
> + ci->memcg_table->id[ci_off++] = id;
Do you need to check the memcg_table is not NULL here? Because this
function is no longer static. Another caller might invoke this when
the cluster hasn't allocated the memcg_table. They shouldn't. We might
want some check and complain here.
> + } while (--nr);
> +}
> +
> +static inline unsigned short __swap_cgroup_get(struct swap_cluster_info *ci,
> + unsigned int ci_off)
> +{
> + lockdep_assert_held(&ci->lock);
> + VM_WARN_ON_ONCE(ci_off >= SWAPFILE_CLUSTER);
> + return ci->memcg_table->id[ci_off];
Here too.
> +}
> +
> +static inline unsigned short __swap_cgroup_clear(struct swap_cluster_info *ci,
> + unsigned int ci_off,
> + unsigned long nr)
> +{
> + unsigned short old = ci->memcg_table->id[ci_off];
Here as well.
Chris
> +
> + __swap_cgroup_set(ci, ci_off, nr, 0);
> + return old;
> +}
> +#else
> +static inline void __swap_cgroup_set(struct swap_cluster_info *ci,
> + unsigned int ci_off, unsigned long nr, unsigned short id)
> +{
> +}
> +
> +static inline unsigned short __swap_cgroup_get(struct swap_cluster_info *ci,
> + unsigned int ci_off)
> +{
> + return 0;
> +}
> +
> +static inline unsigned short __swap_cgroup_clear(struct swap_cluster_info *ci,
> + unsigned int ci_off,
> + unsigned long nr)
> +{
> + return 0;
> +}
> +#endif
> +
> #endif
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 2d16aa89a4fd..edf4cb36728e 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -423,7 +423,12 @@ static void swap_cluster_free_table(struct swap_cluster_info *ci)
> {
> struct swap_table *table;
>
> - table = (struct swap_table *)rcu_dereference_protected(ci->table, true);
> +#ifdef CONFIG_MEMCG
> + kfree(ci->memcg_table);
> + ci->memcg_table = NULL;
> +#endif
> +
> + table = (struct swap_table *)rcu_access_pointer(ci->table);
> if (!table)
> return;
>
> @@ -441,6 +446,7 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
> {
> struct swap_table *table = NULL;
> struct folio *folio;
> + int ret = 0;
>
> /* The cluster must be empty and not on any list during allocation. */
> VM_WARN_ON_ONCE(ci->flags || !cluster_is_empty(ci));
> @@ -458,7 +464,17 @@ static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
> return -ENOMEM;
>
> rcu_assign_pointer(ci->table, table);
> - return 0;
> +
> +#ifdef CONFIG_MEMCG
> + if (!ci->memcg_table)
> + ci->memcg_table = kzalloc(sizeof(*ci->memcg_table), gfp);
> + if (!ci->memcg_table)
> + ret = -ENOMEM;
> +#endif
> + if (ret)
> + swap_cluster_free_table(ci);
> +
> + return ret;
> }
>
> /*
> @@ -483,6 +499,7 @@ static void swap_cluster_assert_empty(struct swap_cluster_info *ci,
> bad_slots++;
> else
> WARN_ON_ONCE(!swp_tb_is_null(swp_tb));
> + WARN_ON_ONCE(__swap_cgroup_get(ci, ci_off));
> } while (++ci_off < ci_end);
>
> WARN_ON_ONCE(bad_slots != (swapoff ? ci->count : 0));
> @@ -1860,12 +1877,10 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
> unsigned int ci_start, unsigned int nr_pages)
> {
> unsigned long old_tb;
> - unsigned int type = si->type;
> unsigned short id = 0, id_cur;
> unsigned int ci_off = ci_start, ci_end = ci_start + nr_pages;
> unsigned long offset = cluster_offset(si, ci);
> unsigned int ci_batch = ci_off;
> - swp_entry_t entry;
>
> VM_WARN_ON(ci->count < nr_pages);
>
> @@ -1883,21 +1898,17 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
> * Uncharge swap slots by memcg in batches. Consecutive
> * slots with the same cgroup id are uncharged together.
> */
> - entry = swp_entry(type, offset + ci_off);
> - id_cur = lookup_swap_cgroup_id(entry);
> + id_cur = __swap_cgroup_clear(ci, ci_off, 1);
> if (id != id_cur) {
> if (id)
> - mem_cgroup_uncharge_swap(swp_entry(type, offset + ci_batch),
> - ci_off - ci_batch);
> + mem_cgroup_uncharge_swap(id, ci_off - ci_batch);
> id = id_cur;
> ci_batch = ci_off;
> }
> } while (++ci_off < ci_end);
>
> - if (id) {
> - mem_cgroup_uncharge_swap(swp_entry(type, offset + ci_batch),
> - ci_off - ci_batch);
> - }
> + if (id)
> + mem_cgroup_uncharge_swap(id, ci_off - ci_batch);
>
> swap_range_free(si, offset + ci_start, nr_pages);
> swap_cluster_assert_empty(ci, ci_start, nr_pages, false);
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 63d06930d8e3..50d87ff58f86 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -739,7 +739,7 @@ static int __remove_mapping(struct address_space *mapping, struct folio *folio,
>
> if (reclaimed && !mapping_exiting(mapping))
> shadow = workingset_eviction(folio, target_memcg);
> - __memcg1_swapout(folio);
> + __memcg1_swapout(folio, ci);
> __swap_cache_del_folio(ci, folio, swap, shadow);
> swap_cluster_unlock_irq(ci);
> } else {
>
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH] selftests/cgroup: Fix incorrect variable check in online_cpus()
From: Tejun Heo @ 2026-05-08 16:51 UTC (permalink / raw)
To: Hongfu Li
Cc: longman, chenridong, hannes, mkoutny, shuah, cgroups,
linux-kselftest, linux-kernel
In-Reply-To: <20260508033453.1425026-1-lihongfu@kylinos.cn>
Hello,
> Hongfu Li (1):
> selftests/cgroup: Fix incorrect variable check in online_cpus()
Applied to cgroup/for-7.2.
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH] cgroup/cpuset: skip hardwall ancestor scan in v2 mode in cpuset_current_node_allowed()
From: Tejun Heo @ 2026-05-08 15:51 UTC (permalink / raw)
To: Chen Wandun; +Cc: longman, chenridong, hannes, mkoutny, cgroups, linux-kernel
In-Reply-To: <20260508062940.4094652-1-chenwandun@lixiang.com>
Hello,
is_in_v2_mode() is also true for v1 mounted with cpuset_v2_mode, where
cpuset.mem_exclusive / cpuset.mem_hardwall are still settable. Would
that be a problem here? cpuset_v2() looks like a tighter fit.
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH v2 2/2] cgroup/cpuset: align DL bandwidth reservation with attach target mask
From: Guopeng Zhang @ 2026-05-08 13:11 UTC (permalink / raw)
To: Waiman Long, Tejun Heo, Michal Koutný, Ingo Molnar,
Peter Zijlstra, Juri Lelli
Cc: Chen Ridong, Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups
In-Reply-To: <4e718145-5af6-40c3-83da-a004904e29d1@redhat.com>
在 2026/5/7 23:52, Waiman Long 写道:
> On 5/7/26 6:33 AM, Guopeng Zhang wrote:
>> cpuset_can_attach() preallocates destination SCHED_DEADLINE bandwidth
>> before the attach commit point, while set_cpus_allowed_dl() later
>> subtracts bandwidth from the source root domain when the task affinity is
>> actually updated.
>>
>> Those two decisions must be made with the same CPU mask.
>> cpuset_can_attach() used the destination cpuset effective mask directly,
>> but cpuset_attach_task() first builds a per-task target mask which is
>> constrained by task_cpu_possible_mask() and, if needed, by walking up the
>> cpuset hierarchy. On asymmetric systems, the actual target mask can
>> therefore be a strict subset of cs->effective_cpus.
>
> The task_cpu_possible_mask() is there for a special class of arm64 CPUs where only some of the cores are able to run legacy 32-bit applications on 64-bit arm CPUs. We can argue how likely that a DL task can be a legacy 32 bit application that is inherently slower than the same application compiled into native 64-bit code. Perhaps we can just disallow such a legacy 32-bit application from moving to a DL scheduling class in the first place.
>
> I am not in favor of the idea of making the cpuset code more complex to support such a corner case which may never be utilized. Could you strip out the task_possible_cpu_mask() part from this patch? We can revisit this with another patch if such a special use case can be useful to support in the future.
>
Thanks for the review.
I agree. The task_cpu_possible_mask() case makes the fix broader and
adds more cpuset-side complexity than needed for this series.
I will drop the cpuset_attach_task() target-mask mirroring from v3 and
keep cpuset_can_attach() using cs->effective_cpus. The updated patch will
only share the root-domain bandwidth-move test with set_cpus_allowed_dl()
and only add a migrating DL task to sum_migrate_dl_bw when that task
actually needs a root-domain bandwidth move.
The task_cpu_possible_mask() corner case can be revisited separately if
there is a real need to support that scenario.
Thanks,
Guopeng
> Cheers,
> Longman
>
>>
>> If the source root domain intersects cs->effective_cpus only on CPUs
>> outside the task's possible mask, can_attach() can skip the destination
>> reservation even though set_cpus_allowed_dl() later sees a real
>> root-domain move and subtracts from the source domain.
>>
>> Extract the root-domain bandwidth-move test used by
>> set_cpus_allowed_dl() into dl_task_needs_bw_move(), and make
>> cpuset_can_attach() compute the same per-task target mask that
>> cpuset_attach_task() applies.
>>
>> Keep nr_migrate_dl_tasks counting all migrating deadline tasks for
>> cpuset DL task accounting. Restrict sum_migrate_dl_bw to the subset of
>> tasks that need destination root-domain bandwidth reservation, because a
>> deadline task can move between cpusets without moving bandwidth between
>> root domains.
>>
>> This keeps the existing per-attach aggregate reservation model; it only
>> changes the per-task mask used to decide which tasks contribute to that
>> aggregate. The broader can_attach()/attach() transaction window is left
>> unchanged.
>>
>> Fixes: 431c69fac05b ("cpuset: Honour task_cpu_possible_mask() in guarantee_online_cpus()")
>> Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>> ---
>> include/linux/sched/deadline.h | 9 +++
>> kernel/cgroup/cpuset-internal.h | 1 +
>> kernel/cgroup/cpuset.c | 97 ++++++++++++++++++++++-----------
>> kernel/sched/deadline.c | 13 ++++-
>> 4 files changed, 86 insertions(+), 34 deletions(-)
>>
^ permalink raw reply
* Re: [PATCH v2 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Guopeng Zhang @ 2026-05-08 13:03 UTC (permalink / raw)
To: Waiman Long, Chen Ridong, Tejun Heo, Michal Koutný,
Ingo Molnar, Peter Zijlstra, Juri Lelli
Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups
In-Reply-To: <ff904e7f-60bd-40ce-818e-b03b47a79e6f@redhat.com>
在 2026/5/8 10:26, Waiman Long 写道:
>
> On 5/7/26 10:14 PM, Chen Ridong wrote:
>>
>> On 2026/5/7 22:31, Waiman Long wrote:
>>> On 5/7/26 6:33 AM, Guopeng Zhang wrote:
>>>> cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
>>>> state in the destination cpuset while walking the taskset.
>>>>
>>>> If a later task_can_attach() or security_task_setscheduler() check
>>>> fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
>>>> and does not call cpuset_cancel_attach() for it. The partially
>>>> accumulated state is then left behind and can be consumed by a later
>>>> attach, corrupting cpuset DL task accounting and pending DL bandwidth
>>>> accounting.
>>>>
>>>> Reset the pending DL migration state before returning from those
>>>> per-task failure paths.
>>>>
>>>> Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
>>>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>>>> ---
>>>> kernel/cgroup/cpuset.c | 8 ++++++--
>>>> 1 file changed, 6 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>>>> index e3a081a07c6d..ae41736399a1 100644
>>>> --- a/kernel/cgroup/cpuset.c
>>>> +++ b/kernel/cgroup/cpuset.c
>>>> @@ -3029,12 +3029,12 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>>>> cgroup_taskset_for_each(task, css, tset) {
>>>> ret = task_can_attach(task);
>>>> if (ret)
>>>> - goto out_unlock;
>>>> + goto out_reset_dl_data;
>>>> if (setsched_check) {
>>>> ret = security_task_setscheduler(task);
>>>> if (ret)
>>>> - goto out_unlock;
>>>> + goto out_reset_dl_data;
>>>> }
>>>> if (dl_task(task)) {
>>>> @@ -3070,6 +3070,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>>>> * changes which zero cpus/mems_allowed.
>>>> */
>>>> cs->attach_in_progress++;
>>>> + goto out_unlock;
>>>> +
>>>> +out_reset_dl_data:
>>>> + reset_migrate_dl_data(cs);
>>>> out_unlock:
>>>> mutex_unlock(&cpuset_mutex);
>>>> return ret;
>>> I would prefer the likely success path be a straight line instead of doing a
>>> goto. IOW, move out_reset_dl_data below return. Other than that, this patch
>>> looks good to me.
>>>
>> I've read the code and found several places that call reset_migrate_dl_data(cs).
>>
>> I think it would be better to call reset_migrate_dl_data(cs) only when we
>> encounter an error, for example:
>>
>> ```
>> static int cpuset_can_attach(struct cgroup_taskset *tset)
>> {
>> ...
>> out_unlock:
>> if (ret)
>> reset_migrate_dl_data(cs);
>> mutex_unlock(&cpuset_mutex);
>> return ret;
>> }
>> ```
>> After that, no other places would need to call reset_migrate_dl_data(cs), right?
>>
> Yes, that should work too.
>
Thanks for the review.
Yes, I will update cpuset_can_attach() to use the common ret-based
cleanup in out_unlock.
Thanks,
Guopeng
> Cheers,
> Longman
^ permalink raw reply
* [PATCH] cgroup/cpuset: skip hardwall ancestor scan in v2 mode in cpuset_current_node_allowed()
From: Chen Wandun @ 2026-05-08 6:29 UTC (permalink / raw)
To: longman, chenridong, tj, hannes, mkoutny; +Cc: cgroups, linux-kernel
In cgroup v2, the non-hardwall fallthrough path in
cpuset_current_node_allowed() always ends up allowing the allocation:
- CS_MEM_EXCLUSIVE and CS_MEM_HARDWALL are v1-only flags, toggled
only via the cpuset.mem_exclusive / cpuset.mem_hardwall files
which do not exist in v2. Neither flag is ever set on any cpuset
(including top_cpuset) in pure v2 mode.
- As a result, nearest_hardwall_ancestor() always walks up to
top_cpuset.
- top_cpuset.mems_allowed is set to node_possible_map in v2 mode,
so node_isset() on it is always true for any valid node.
The whole scan therefore boils down to taking callback_lock, walking
to the root and returning true. Short-circuit it by returning true
directly when is_in_v2_mode() holds, sparing the callback_lock
acquisition and the pointless walk.
Place the short-circuit after the __GFP_HARDWALL check so that the
generic hardwall enforcement for GFP_USER allocations remains in
effect: __GFP_HARDWALL requests still return false when the node is
outside mems_allowed, preserving cpuset.mems constraints for
__alloc_pages() callers (which prepare_alloc_pages() marks
__GFP_HARDWALL unconditionally when cpusets are enabled).
Suggested-by: Michal Koutný <mkoutny@suse.com>
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
---
kernel/cgroup/cpuset.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index a48901a0416a..b539f5b4d21e 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -4231,6 +4231,9 @@ bool cpuset_current_node_allowed(int node, gfp_t gfp_mask)
if (gfp_mask & __GFP_HARDWALL) /* If hardwall request, stop here */
return false;
+ if (is_in_v2_mode())
+ return true;
+
/* Not hardwall and node outside mems_allowed: scan up cpusets */
spin_lock_irqsave(&callback_lock, flags);
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
From: Wandun @ 2026-05-08 6:27 UTC (permalink / raw)
To: Michal Koutný; +Cc: longman, chenridong, tj, hannes, cgroups, linux-kernel
In-Reply-To: <afx1u4kV-2kvgEEf@localhost.localdomain>
On 5/7/26 20:33, Michal Koutný wrote:
> On Thu, May 07, 2026 at 06:54:34PM +0800, Chen Wandun <chenwandun1@gmail.com> wrote:
>> This makes it unreachable in the common case, so dying tasks can get
>> stuck in direct reclaim or even trigger OOM while trying to exit,
>> despite being allowed to allocate from any node.
> (OTOH, the caused OOM could select this task and bypass the hardwall. So
> this should only expedite but no unblock the exit path.)
>
>> Move the PF_EXITING check before __GFP_HARDWALL so that dying tasks
>> can allocate memory from any node to exit quickly, even when cpusets
>> are enabled.
> This makes sense to me on its own (given other hardwall exemptions,
> namely the commit c596d9f320aaf ("cpusets: allow TIF_MEMDIE threads to
> allocate anywhere")).
>
> Acked-by: Michal Koutný <mkoutny@suse.com>
>
>
> At first, I wondered whether this could happen on cpuset v2 -- it can --
> because only per-cpuset hardwalling is absent but the generic logic for
> GFP_USER allocations is still meant to be in place. Nevertheless, it
> occured to me we can spare callback_lock in this function (a separate
> chaneg for cpuset_current_node_allowed()):
>
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -4213,6 +4213,9 @@ bool cpuset_current_node_allowed(int node, gfp_t gfp_mask)
> if (current->flags & PF_EXITING) /* Let dying task have memory */
> return true;
>
> + if (is_in_v2_mode())
> + return true;
> +
Thanks for the suggestion! I'll send a separate patch for this
optimization. Best regards, Wandun
> /* Not hardwall and node outside mems_allowed: scan up cpusets */
> spin_lock_irqsave(&callback_lock, flags);
>
> Regards,
> Michal
^ permalink raw reply
* Re: [PATCH] cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
From: Wandun @ 2026-05-08 6:15 UTC (permalink / raw)
To: Chen Ridong; +Cc: cgroups, linux-kernel, longman, tj, hannes, mkoutny
In-Reply-To: <02352ad2-9c85-4825-82b6-49c6a4b081d8@huaweicloud.com>
On 5/8/26 09:39, Chen Ridong wrote:
>
> On 2026/5/7 18:54, Chen Wandun wrote:
>> Since prepare_alloc_pages() unconditionally adds __GFP_HARDWALL for the
>> fast path when cpusets are enabled, the __GFP_HARDWALL check in
>> cpuset_current_node_allowed() causes the PF_EXITING escape path to be
>> skipped on the first allocation attempt. This makes it unreachable in
>> the common case, so dying tasks can get stuck in direct reclaim or even
>> trigger OOM while trying to exit, despite being allowed to allocate from
>> any node.
>>
>> Move the PF_EXITING check before __GFP_HARDWALL so that dying tasks
>> can allocate memory from any node to exit quickly, even when cpusets
>> are enabled.
>>
>> Also update the function comment to reflect the actual behavior of
>> prepare_alloc_pages() and the corrected check ordering.
>>
>> Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
>> ---
>> kernel/cgroup/cpuset.c | 14 ++++++++------
>> 1 file changed, 8 insertions(+), 6 deletions(-)
>>
>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>> index e3a081a07c6d..a48901a0416a 100644
>> --- a/kernel/cgroup/cpuset.c
>> +++ b/kernel/cgroup/cpuset.c
>> @@ -4176,11 +4176,11 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
>> * current's mems_allowed, yes. If it's not a __GFP_HARDWALL request and this
>> * node is set in the nearest hardwalled cpuset ancestor to current's cpuset,
>> * yes. If current has access to memory reserves as an oom victim, yes.
>> - * Otherwise, no.
>> + * If the current task is PF_EXITING, yes. Otherwise, no.
>> *
>> * GFP_USER allocations are marked with the __GFP_HARDWALL bit,
>> * and do not allow allocations outside the current tasks cpuset
>> - * unless the task has been OOM killed.
>> + * unless the task has been OOM killed or is exiting.
>> * GFP_KERNEL allocations are not so marked, so can escape to the
>> * nearest enclosing hardwalled ancestor cpuset.
>> *
>> @@ -4194,7 +4194,9 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
>> * The first call here from mm/page_alloc:get_page_from_freelist()
>> * has __GFP_HARDWALL set in gfp_mask, enforcing hardwall cpusets,
>> * so no allocation on a node outside the cpuset is allowed (unless
>> - * in interrupt, of course).
>> + * in interrupt, of course). The PF_EXITING check must therefore
>> + * come before the __GFP_HARDWALL check, otherwise a dying task
>> + * would be blocked on the fast path.
>> *
>> * The second pass through get_page_from_freelist() doesn't even call
>> * here for GFP_ATOMIC calls. For those calls, the __alloc_pages()
>> @@ -4204,6 +4206,7 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
>> * in_interrupt - any node ok (current task context irrelevant)
>> * GFP_ATOMIC - any node ok
>> * tsk_is_oom_victim - any node ok
>> + * PF_EXITING - any node ok (let dying task exit quickly)
>> * GFP_KERNEL - any node in enclosing hardwalled cpuset ok
>> * GFP_USER - only nodes in current tasks mems allowed ok.
>> */
>> @@ -4223,11 +4226,10 @@ bool cpuset_current_node_allowed(int node, gfp_t gfp_mask)
>> */
>> if (unlikely(tsk_is_oom_victim(current)))
>> return true;
>> - if (gfp_mask & __GFP_HARDWALL) /* If hardwall request, stop here */
>> - return false;
>> -
>> if (current->flags & PF_EXITING) /* Let dying task have memory */
>> return true;
>> + if (gfp_mask & __GFP_HARDWALL) /* If hardwall request, stop here */
>> + return false;
>>
>> /* Not hardwall and node outside mems_allowed: scan up cpusets */
>> spin_lock_irqsave(&callback_lock, flags);
> Make sense.
>
> BTW, how did you find this issue?
I found this while reviewing the cpuset node-allowed logic during an
investigation into a memory allocation issue (not the root cause of
that investigation).
>
> Reviewed-by: Chen Ridong <chenridong@huaweicloud.com>
>
^ permalink raw reply
* Re: [PATCH v3 09/12] mm, swap: consolidate cluster allocation helpers
From: Chris Li @ 2026-05-08 5:02 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-9-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 2:16 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Kairui Song <kasong@tencent.com>
>
> Swap cluster table management is spread across several narrow
> helpers. As a result, the allocation and fallback sequences are
> open-coded in multiple places.
>
> A few more per-cluster tables will be added soon, so avoid
> duplicating these sequences per table type. Fold the existing
> pairs into cluster-oriented helpers, and rename for consistency.
>
> No functional change, only a few sanity checks are slightly adjusted.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
Acked-by: Chris Li <chrisl@kernel.org>
Chris
> ---
> mm/swapfile.c | 110 ++++++++++++++++++++++++++--------------------------------
> 1 file changed, 49 insertions(+), 61 deletions(-)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 8d3d22c463f3..2d16aa89a4fd 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -411,20 +411,7 @@ static inline unsigned int cluster_offset(struct swap_info_struct *si,
> return cluster_index(si, ci) * SWAPFILE_CLUSTER;
> }
>
> -static struct swap_table *swap_table_alloc(gfp_t gfp)
> -{
> - struct folio *folio;
> -
> - if (!SWP_TABLE_USE_PAGE)
> - return kmem_cache_zalloc(swap_table_cachep, gfp);
> -
> - folio = folio_alloc(gfp | __GFP_ZERO, 0);
> - if (folio)
> - return folio_address(folio);
> - return NULL;
> -}
> -
> -static void swap_table_free_folio_rcu_cb(struct rcu_head *head)
> +static void swap_cluster_free_table_folio_rcu_cb(struct rcu_head *head)
> {
> struct folio *folio;
>
> @@ -432,15 +419,46 @@ static void swap_table_free_folio_rcu_cb(struct rcu_head *head)
> folio_put(folio);
> }
>
> -static void swap_table_free(struct swap_table *table)
> +static void swap_cluster_free_table(struct swap_cluster_info *ci)
> {
> + struct swap_table *table;
> +
> + table = (struct swap_table *)rcu_dereference_protected(ci->table, true);
> + if (!table)
> + return;
> +
> + rcu_assign_pointer(ci->table, NULL);
> if (!SWP_TABLE_USE_PAGE) {
> kmem_cache_free(swap_table_cachep, table);
> return;
> }
>
> call_rcu(&(folio_page(virt_to_folio(table), 0)->rcu_head),
> - swap_table_free_folio_rcu_cb);
> + swap_cluster_free_table_folio_rcu_cb);
> +}
> +
> +static int swap_cluster_alloc_table(struct swap_cluster_info *ci, gfp_t gfp)
> +{
> + struct swap_table *table = NULL;
> + struct folio *folio;
> +
> + /* The cluster must be empty and not on any list during allocation. */
> + VM_WARN_ON_ONCE(ci->flags || !cluster_is_empty(ci));
> + if (rcu_access_pointer(ci->table))
> + return 0;
> +
> + if (SWP_TABLE_USE_PAGE) {
> + folio = folio_alloc(gfp | __GFP_ZERO, 0);
> + if (folio)
> + table = folio_address(folio);
> + } else {
> + table = kmem_cache_zalloc(swap_table_cachep, gfp);
> + }
> + if (!table)
> + return -ENOMEM;
> +
> + rcu_assign_pointer(ci->table, table);
> + return 0;
> }
>
> /*
> @@ -471,27 +489,15 @@ static void swap_cluster_assert_empty(struct swap_cluster_info *ci,
> WARN_ON_ONCE(nr == SWAPFILE_CLUSTER && ci->extend_table);
> }
>
> -static void swap_cluster_free_table(struct swap_cluster_info *ci)
> -{
> - struct swap_table *table;
> -
> - /* Only empty cluster's table is allow to be freed */
> - lockdep_assert_held(&ci->lock);
> - table = (void *)rcu_dereference_protected(ci->table, true);
> - rcu_assign_pointer(ci->table, NULL);
> -
> - swap_table_free(table);
> -}
> -
> /*
> * Allocate swap table for one cluster. Attempt an atomic allocation first,
> * then fallback to sleeping allocation.
> */
> static struct swap_cluster_info *
> -swap_cluster_alloc_table(struct swap_info_struct *si,
> +swap_cluster_populate(struct swap_info_struct *si,
> struct swap_cluster_info *ci)
> {
> - struct swap_table *table;
> + int ret;
>
> /*
> * Only cluster isolation from the allocator does table allocation.
> @@ -502,14 +508,9 @@ swap_cluster_alloc_table(struct swap_info_struct *si,
> lockdep_assert_held(&si->global_cluster_lock);
> lockdep_assert_held(&ci->lock);
>
> - /* The cluster must be free and was just isolated from the free list. */
> - VM_WARN_ON_ONCE(ci->flags || !cluster_is_empty(ci));
> -
> - table = swap_table_alloc(__GFP_HIGH | __GFP_NOMEMALLOC | __GFP_NOWARN);
> - if (table) {
> - rcu_assign_pointer(ci->table, table);
> + if (!swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
> + __GFP_NOWARN))
> return ci;
> - }
>
> /*
> * Try a sleep allocation. Each isolated free cluster may cause
> @@ -521,7 +522,8 @@ swap_cluster_alloc_table(struct swap_info_struct *si,
> spin_unlock(&si->global_cluster_lock);
> local_unlock(&percpu_swap_cluster.lock);
>
> - table = swap_table_alloc(__GFP_HIGH | __GFP_NOMEMALLOC | GFP_KERNEL);
> + ret = swap_cluster_alloc_table(ci, __GFP_HIGH | __GFP_NOMEMALLOC |
> + GFP_KERNEL);
>
> /*
> * Back to atomic context. We might have migrated to a new CPU with a
> @@ -536,20 +538,11 @@ swap_cluster_alloc_table(struct swap_info_struct *si,
> spin_lock(&si->global_cluster_lock);
> spin_lock(&ci->lock);
>
> - /* Nothing except this helper should touch a dangling empty cluster. */
> - if (WARN_ON_ONCE(cluster_table_is_alloced(ci))) {
> - if (table)
> - swap_table_free(table);
> - return ci;
> - }
> -
> - if (!table) {
> + if (ret) {
> move_cluster(si, ci, &si->free_clusters, CLUSTER_FLAG_FREE);
> spin_unlock(&ci->lock);
> return NULL;
> }
> -
> - rcu_assign_pointer(ci->table, table);
> return ci;
> }
>
> @@ -621,12 +614,11 @@ static struct swap_cluster_info *isolate_lock_cluster(
> }
> spin_unlock(&si->lock);
>
> - if (found && !cluster_table_is_alloced(found)) {
> - /* Only an empty free cluster's swap table can be freed. */
> - VM_WARN_ON_ONCE(flags != CLUSTER_FLAG_FREE);
> + /* Cluster's table is freed when and only when it's on the free list. */
> + if (found && flags == CLUSTER_FLAG_FREE) {
> VM_WARN_ON_ONCE(list != &si->free_clusters);
> - VM_WARN_ON_ONCE(!cluster_is_empty(found));
> - return swap_cluster_alloc_table(si, found);
> + VM_WARN_ON_ONCE(cluster_table_is_alloced(found));
> + return swap_cluster_populate(si, found);
> }
>
> return found;
> @@ -769,7 +761,6 @@ static int swap_cluster_setup_bad_slot(struct swap_info_struct *si,
> unsigned int ci_off = offset % SWAPFILE_CLUSTER;
> unsigned long idx = offset / SWAPFILE_CLUSTER;
> struct swap_cluster_info *ci;
> - struct swap_table *table;
> int ret = 0;
>
> /* si->max may got shrunk by swap swap_activate() */
> @@ -790,12 +781,9 @@ static int swap_cluster_setup_bad_slot(struct swap_info_struct *si,
> }
>
> ci = cluster_info + idx;
> - if (!ci->table) {
> - table = swap_table_alloc(GFP_KERNEL);
> - if (!table)
> - return -ENOMEM;
> - rcu_assign_pointer(ci->table, table);
> - }
> + /* Need to allocate swap table first for initial bad slot marking. */
> + if (!ci->count && swap_cluster_alloc_table(ci, GFP_KERNEL))
> + return -ENOMEM;
> spin_lock(&ci->lock);
> /* Check for duplicated bad swap slots. */
> if (__swap_table_xchg(ci, ci_off, SWP_TB_BAD) != SWP_TB_NULL) {
> @@ -2992,7 +2980,7 @@ static void free_swap_cluster_info(struct swap_cluster_info *cluster_info,
> ci = cluster_info + i;
> /* Cluster with bad marks count will have a remaining table */
> spin_lock(&ci->lock);
> - if (rcu_dereference_protected(ci->table, true)) {
> + if (cluster_table_is_alloced(ci)) {
> swap_cluster_assert_empty(ci, 0, SWAPFILE_CLUSTER, true);
> swap_cluster_free_table(ci);
> }
>
> --
> 2.53.0
>
>
^ permalink raw reply
* Re: [PATCH v3 08/12] mm, swap: delay and unify memcg lookup and charging for swapin
From: Chris Li @ 2026-05-08 4:46 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-8-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 2:16 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Kairui Song <kasong@tencent.com>
>
> Instead of checking the cgroup private ID during page table walk in
> swap_pte_batch(), move the memcg lookup into __swap_cache_add_check()
> under the cluster lock.
>
> The first pre-alloc check is speculative and skips the memcg check since
> the post-alloc stable check ensures all slots covered by the folio
> belong to the same memcg. It is very rare for contiguous and aligned
> entries across a contiguous region of a page table of the same process
> or shmem mapping to belong to different memcgs.
>
> This also prepares for recording the memcg info in the cluster's table.
> Also make the order check and fallback more compact.
>
> There should be no user-observable behavior change.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
Acked-by: Chris Li <chrisl@kernel.org>
> ---
> include/linux/memcontrol.h | 6 +++---
> mm/internal.h | 10 +---------
> mm/memcontrol.c | 10 ++++------
> mm/swap_state.c | 28 +++++++++++++++++++---------
> 4 files changed, 27 insertions(+), 27 deletions(-)
>
> diff --git a/include/linux/memcontrol.h b/include/linux/memcontrol.h
> index 7d08128de1fd..a013f37f24aa 100644
> --- a/include/linux/memcontrol.h
> +++ b/include/linux/memcontrol.h
> @@ -646,8 +646,8 @@ static inline int mem_cgroup_charge(struct folio *folio, struct mm_struct *mm,
>
> int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp);
>
> -int mem_cgroup_swapin_charge_folio(struct folio *folio, struct mm_struct *mm,
> - gfp_t gfp, swp_entry_t entry);
> +int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id,
> + struct mm_struct *mm, gfp_t gfp);
>
> void __mem_cgroup_uncharge(struct folio *folio);
>
> @@ -1137,7 +1137,7 @@ static inline int mem_cgroup_charge_hugetlb(struct folio* folio, gfp_t gfp)
> }
>
> static inline int mem_cgroup_swapin_charge_folio(struct folio *folio,
> - struct mm_struct *mm, gfp_t gfp, swp_entry_t entry)
> + unsigned short id, struct mm_struct *mm, gfp_t gfp)
> {
> return 0;
> }
> diff --git a/mm/internal.h b/mm/internal.h
> index 5a2ddcf68e0b..9d2fec696bd6 100644
> --- a/mm/internal.h
> +++ b/mm/internal.h
> @@ -451,24 +451,16 @@ static inline int swap_pte_batch(pte_t *start_ptep, int max_nr, pte_t pte)
> {
> pte_t expected_pte = pte_next_swp_offset(pte);
> const pte_t *end_ptep = start_ptep + max_nr;
> - const softleaf_t entry = softleaf_from_pte(pte);
> pte_t *ptep = start_ptep + 1;
> - unsigned short cgroup_id;
>
> VM_WARN_ON(max_nr < 1);
> - VM_WARN_ON(!softleaf_is_swap(entry));
> + VM_WARN_ON(!softleaf_is_swap(softleaf_from_pte(pte)));
>
> - cgroup_id = lookup_swap_cgroup_id(entry);
> while (ptep < end_ptep) {
> - softleaf_t entry;
> -
> pte = ptep_get(ptep);
>
> if (!pte_same(pte, expected_pte))
> break;
> - entry = softleaf_from_pte(pte);
> - if (lookup_swap_cgroup_id(entry) != cgroup_id)
> - break;
> expected_pte = pte_next_swp_offset(expected_pte);
> ptep++;
> }
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index c7df30ca5aa7..641706fa47bf 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -5062,27 +5062,25 @@ int mem_cgroup_charge_hugetlb(struct folio *folio, gfp_t gfp)
>
> /**
> * mem_cgroup_swapin_charge_folio - Charge a newly allocated folio for swapin.
> - * @folio: folio to charge.
> + * @folio: the folio to charge
> + * @id: memory cgroup id
> * @mm: mm context of the victim
> * @gfp: reclaim mode
> - * @entry: swap entry for which the folio is allocated
> *
> * This function charges a folio allocated for swapin. Please call this before
> * adding the folio to the swapcache.
> *
> * Returns 0 on success. Otherwise, an error code is returned.
> */
> -int mem_cgroup_swapin_charge_folio(struct folio *folio, struct mm_struct *mm,
> - gfp_t gfp, swp_entry_t entry)
> +int mem_cgroup_swapin_charge_folio(struct folio *folio, unsigned short id,
> + struct mm_struct *mm, gfp_t gfp)
> {
> struct mem_cgroup *memcg;
> - unsigned short id;
> int ret;
>
> if (mem_cgroup_disabled())
> return 0;
>
> - id = lookup_swap_cgroup_id(entry);
> rcu_read_lock();
> memcg = mem_cgroup_from_private_id(id);
> if (!memcg || !css_tryget_online(&memcg->css))
> diff --git a/mm/swap_state.c b/mm/swap_state.c
> index 12b290d43e45..86d517a33a55 100644
> --- a/mm/swap_state.c
> +++ b/mm/swap_state.c
> @@ -142,16 +142,20 @@ void *swap_cache_get_shadow(swp_entry_t entry)
> * @ci: The locked swap cluster
> * @targ_entry: The target swap entry to check, will be rounded down by @nr
> * @nr: Number of slots to check, must be a power of 2
> - * @shadowp: Returns the shadow value if one exists in the range.
> + * @shadowp: Returns the shadow value if one exists in the range
> + * @memcg_id: Returns the memory cgroup id, NULL to ignore cgroup check
> *
> * Check if all slots covered by given range have a swap count >= 1.
> - * Retrieves the shadow if there is one.
> + * Retrieves the shadow if there is one. If @memcg_id is not NULL, also
> + * checks if all slots belong to the same cgroup and return the cgroup
> + * private id.
> *
> * Context: Caller must lock the cluster.
> */
> static int __swap_cache_add_check(struct swap_cluster_info *ci,
> swp_entry_t targ_entry,
> - unsigned long nr, void **shadowp)
> + unsigned long nr, void **shadowp,
> + unsigned short *memcg_id)
> {
> unsigned int ci_off, ci_end;
> unsigned long old_tb;
> @@ -169,19 +173,24 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci,
> return -EEXIST;
> if (!__swp_tb_get_count(old_tb))
> return -ENOENT;
> - if (swp_tb_is_shadow(old_tb) && shadowp)
> + if (shadowp && swp_tb_is_shadow(old_tb))
> *shadowp = swp_tb_to_shadow(old_tb);
> + if (memcg_id)
> + *memcg_id = lookup_swap_cgroup_id(targ_entry);
Nitpick: Consider also use a local variable to stare the memcg_id value here.
>
> if (nr == 1)
> return 0;
>
> + targ_entry.val = round_down(targ_entry.val, nr);
> ci_off = round_down(ci_off, nr);
> ci_end = ci_off + nr;
> do {
> old_tb = __swap_table_get(ci, ci_off);
> if (unlikely(swp_tb_is_folio(old_tb) ||
> - !__swp_tb_get_count(old_tb)))
> + !__swp_tb_get_count(old_tb) ||
> + (memcg_id && *memcg_id != lookup_swap_cgroup_id(targ_entry))))
Nitpick: You can use the local variable here to avoid a memory fetch.
Micro optimizations.
Chris
^ permalink raw reply
* Re: [PATCH v3 07/12] mm, swap: support flexible batch freeing of slots in different memcgs
From: Chris Li @ 2026-05-08 4:01 UTC (permalink / raw)
To: kasong
Cc: linux-mm, Andrew Morton, David Hildenbrand, Zi Yan, Baolin Wang,
Barry Song, Hugh Dickins, Kemeng Shi, Nhat Pham, Baoquan He,
Johannes Weiner, Youngjun Park, Chengming Zhou, Roman Gushchin,
Shakeel Butt, Muchun Song, Qi Zheng, linux-kernel, cgroups,
Yosry Ahmed, Lorenzo Stoakes, Dev Jain, Lance Yang, Michal Hocko,
Michal Hocko, Suren Baghdasaryan, Axel Rasmussen
In-Reply-To: <20260421-swap-table-p4-v3-7-2f23759a76bc@tencent.com>
On Tue, Apr 21, 2026 at 7:16 AM Kairui Song via B4 Relay
<devnull+kasong.tencent.com@kernel.org> wrote:
>
> From: Kairui Song <kasong@tencent.com>
>
> Instead of requiring the caller to ensure all slots are in the same
> memcg, make the function handle different memcgs at once.
>
> This is both a micro optimization and required for removing the memcg
> lookup in the page table layer, so it can be unified at the swap layer.
>
> We are not removing the memcg lookup in the page table in this commit.
> It has to be done after the memcg lookup is deferred to the swap layer.
>
> Signed-off-by: Kairui Song <kasong@tencent.com>
Overall, it looks good. Some nitpicks follow.
Acked-by: Chris Li <chrisl@kernel.org>
> ---
> mm/swapfile.c | 33 +++++++++++++++++++++++++++++----
> 1 file changed, 29 insertions(+), 4 deletions(-)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index e1ad77a69e54..8d3d22c463f3 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -1872,21 +1872,46 @@ void __swap_cluster_free_entries(struct swap_info_struct *si,
> unsigned int ci_start, unsigned int nr_pages)
> {
> unsigned long old_tb;
> + unsigned int type = si->type;
> + unsigned short id = 0, id_cur;
Nitpick: I'm tempted to rename a few variables to improve my
understanding. Feel free to keep it as it is.
id -> batch_id
> unsigned int ci_off = ci_start, ci_end = ci_start + nr_pages;
> - unsigned long offset = cluster_offset(si, ci) + ci_start;
> + unsigned long offset = cluster_offset(si, ci);
Nitpick: offset -> ci_offset. This is the fixed offset of the ci which
is a fixed in the loop.
> + unsigned int ci_batch = ci_off;
Nitpick: ci_batch -> batch_off, this one go with the batch_id.
> + swp_entry_t entry;
>
> VM_WARN_ON(ci->count < nr_pages);
>
> ci->count -= nr_pages;
> do {
> old_tb = __swap_table_get(ci, ci_off);
> - /* Release the last ref, or after swap cache is dropped */
> + /*
> + * Freeing is done after release of the last swap count
> + * ref, or after swap cache is dropped
> + */
> VM_WARN_ON(!swp_tb_is_shadow(old_tb) || __swp_tb_get_count(old_tb) > 1);
> __swap_table_set(ci, ci_off, null_to_swp_tb());
> +
> + /*
> + * Uncharge swap slots by memcg in batches. Consecutive
> + * slots with the same cgroup id are uncharged together.
> + */
> + entry = swp_entry(type, offset + ci_off);
Nitpick: This line confused me a bit. Two offsets are mentioned here:
"offset + ci_offset". One would assume that ci_offset is the offset of
the ci, and the offset is the incremental one. It is the other way
around.
> + id_cur = lookup_swap_cgroup_id(entry);
> + if (id != id_cur) {
> + if (id)
> + mem_cgroup_uncharge_swap(swp_entry(type, offset + ci_batch),
> + ci_off - ci_batch);
With the above rename, this become:
"... swp_entry(type, ci_offset + batch_off)," ; This combined the
offset turn into the swap entry.
"ci_off - batch_off". That is the running length from the beginning of batch.
> + id = id_cur;
> + ci_batch = ci_off;
> + }
> } while (++ci_off < ci_end);
>
> - mem_cgroup_uncharge_swap(swp_entry(si->type, offset), nr_pages);
> - swap_range_free(si, offset, nr_pages);
> + if (id) {
This becomes `if (batch_id)`, meaning if we have pending batching, we
flush the current batch.
Chris
^ permalink raw reply
* [PATCH] selftests/cgroup: Fix incorrect variable check in online_cpus()
From: Hongfu Li @ 2026-05-08 3:34 UTC (permalink / raw)
To: longman, chenridong, tj, hannes, mkoutny, shuah
Cc: cgroups, linux-kselftest, linux-kernel, Hongfu Li
"OFFLINE_CPUS" is a literal string that is always non-empty. It should
be "$OFFLINE_CPUS" to check the variable's value instead.
Signed-off-by: Hongfu Li <lihongfu@kylinos.cn>
---
tools/testing/selftests/cgroup/test_cpuset_prs.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index a56f4153c64d..df89ddfa073f 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -617,7 +617,7 @@ set_ctrl_state_noerr()
online_cpus()
{
- [[ -n "OFFLINE_CPUS" ]] && {
+ [[ -n "$OFFLINE_CPUS" ]] && {
for C in $OFFLINE_CPUS
do
write_cpu_online ${C}=1
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v2 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Chen Ridong @ 2026-05-08 2:14 UTC (permalink / raw)
To: Waiman Long, Guopeng Zhang, Tejun Heo, Michal Koutný,
Ingo Molnar, Peter Zijlstra, Juri Lelli
Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups
In-Reply-To: <6410d11c-1d8a-4e72-ac22-43058027b304@redhat.com>
On 2026/5/7 22:31, Waiman Long wrote:
> On 5/7/26 6:33 AM, Guopeng Zhang wrote:
>> cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
>> state in the destination cpuset while walking the taskset.
>>
>> If a later task_can_attach() or security_task_setscheduler() check
>> fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
>> and does not call cpuset_cancel_attach() for it. The partially
>> accumulated state is then left behind and can be consumed by a later
>> attach, corrupting cpuset DL task accounting and pending DL bandwidth
>> accounting.
>>
>> Reset the pending DL migration state before returning from those
>> per-task failure paths.
>>
>> Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>> ---
>> kernel/cgroup/cpuset.c | 8 ++++++--
>> 1 file changed, 6 insertions(+), 2 deletions(-)
>>
>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>> index e3a081a07c6d..ae41736399a1 100644
>> --- a/kernel/cgroup/cpuset.c
>> +++ b/kernel/cgroup/cpuset.c
>> @@ -3029,12 +3029,12 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>> cgroup_taskset_for_each(task, css, tset) {
>> ret = task_can_attach(task);
>> if (ret)
>> - goto out_unlock;
>> + goto out_reset_dl_data;
>> if (setsched_check) {
>> ret = security_task_setscheduler(task);
>> if (ret)
>> - goto out_unlock;
>> + goto out_reset_dl_data;
>> }
>> if (dl_task(task)) {
>> @@ -3070,6 +3070,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>> * changes which zero cpus/mems_allowed.
>> */
>> cs->attach_in_progress++;
>> + goto out_unlock;
>> +
>> +out_reset_dl_data:
>> + reset_migrate_dl_data(cs);
>> out_unlock:
>> mutex_unlock(&cpuset_mutex);
>> return ret;
>
> I would prefer the likely success path be a straight line instead of doing a
> goto. IOW, move out_reset_dl_data below return. Other than that, this patch
> looks good to me.
>
I've read the code and found several places that call reset_migrate_dl_data(cs).
I think it would be better to call reset_migrate_dl_data(cs) only when we
encounter an error, for example:
```
static int cpuset_can_attach(struct cgroup_taskset *tset)
{
...
out_unlock:
if (ret)
reset_migrate_dl_data(cs);
mutex_unlock(&cpuset_mutex);
return ret;
}
```
After that, no other places would need to call reset_migrate_dl_data(cs), right?
--
Best regards,
Ridong
^ permalink raw reply
* Re: [PATCH v2 1/2] cgroup/cpuset: reset DL migration state on can_attach() failure
From: Waiman Long @ 2026-05-08 2:26 UTC (permalink / raw)
To: Chen Ridong, Guopeng Zhang, Tejun Heo, Michal Koutný,
Ingo Molnar, Peter Zijlstra, Juri Lelli
Cc: Johannes Weiner, Vincent Guittot, Dietmar Eggemann,
Steven Rostedt, Ben Segall, Mel Gorman, Valentin Schneider,
K Prateek Nayak, Gabriele Monaco, Will Deacon, linux-kernel,
cgroups
In-Reply-To: <5d69e8bb-c925-4de2-8d50-0880b23864e0@huaweicloud.com>
On 5/7/26 10:14 PM, Chen Ridong wrote:
>
> On 2026/5/7 22:31, Waiman Long wrote:
>> On 5/7/26 6:33 AM, Guopeng Zhang wrote:
>>> cpuset_can_attach() accumulates temporary SCHED_DEADLINE migration
>>> state in the destination cpuset while walking the taskset.
>>>
>>> If a later task_can_attach() or security_task_setscheduler() check
>>> fails, cgroup_migrate_execute() treats cpuset as the failing subsystem
>>> and does not call cpuset_cancel_attach() for it. The partially
>>> accumulated state is then left behind and can be consumed by a later
>>> attach, corrupting cpuset DL task accounting and pending DL bandwidth
>>> accounting.
>>>
>>> Reset the pending DL migration state before returning from those
>>> per-task failure paths.
>>>
>>> Fixes: 2ef269ef1ac0 ("cgroup/cpuset: Free DL BW in case can_attach() fails")
>>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>>> ---
>>> kernel/cgroup/cpuset.c | 8 ++++++--
>>> 1 file changed, 6 insertions(+), 2 deletions(-)
>>>
>>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>>> index e3a081a07c6d..ae41736399a1 100644
>>> --- a/kernel/cgroup/cpuset.c
>>> +++ b/kernel/cgroup/cpuset.c
>>> @@ -3029,12 +3029,12 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>>> cgroup_taskset_for_each(task, css, tset) {
>>> ret = task_can_attach(task);
>>> if (ret)
>>> - goto out_unlock;
>>> + goto out_reset_dl_data;
>>> if (setsched_check) {
>>> ret = security_task_setscheduler(task);
>>> if (ret)
>>> - goto out_unlock;
>>> + goto out_reset_dl_data;
>>> }
>>> if (dl_task(task)) {
>>> @@ -3070,6 +3070,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
>>> * changes which zero cpus/mems_allowed.
>>> */
>>> cs->attach_in_progress++;
>>> + goto out_unlock;
>>> +
>>> +out_reset_dl_data:
>>> + reset_migrate_dl_data(cs);
>>> out_unlock:
>>> mutex_unlock(&cpuset_mutex);
>>> return ret;
>> I would prefer the likely success path be a straight line instead of doing a
>> goto. IOW, move out_reset_dl_data below return. Other than that, this patch
>> looks good to me.
>>
> I've read the code and found several places that call reset_migrate_dl_data(cs).
>
> I think it would be better to call reset_migrate_dl_data(cs) only when we
> encounter an error, for example:
>
> ```
> static int cpuset_can_attach(struct cgroup_taskset *tset)
> {
> ...
> out_unlock:
> if (ret)
> reset_migrate_dl_data(cs);
> mutex_unlock(&cpuset_mutex);
> return ret;
> }
> ```
> After that, no other places would need to call reset_migrate_dl_data(cs), right?
>
Yes, that should work too.
Cheers,
Longman
^ permalink raw reply
* Re: [PATCH] cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
From: Chen Ridong @ 2026-05-08 1:39 UTC (permalink / raw)
To: Chen Wandun, longman, tj, hannes, mkoutny; +Cc: cgroups, linux-kernel
In-Reply-To: <20260507105434.3266234-1-chenwandun@lixiang.com>
On 2026/5/7 18:54, Chen Wandun wrote:
> Since prepare_alloc_pages() unconditionally adds __GFP_HARDWALL for the
> fast path when cpusets are enabled, the __GFP_HARDWALL check in
> cpuset_current_node_allowed() causes the PF_EXITING escape path to be
> skipped on the first allocation attempt. This makes it unreachable in
> the common case, so dying tasks can get stuck in direct reclaim or even
> trigger OOM while trying to exit, despite being allowed to allocate from
> any node.
>
> Move the PF_EXITING check before __GFP_HARDWALL so that dying tasks
> can allocate memory from any node to exit quickly, even when cpusets
> are enabled.
>
> Also update the function comment to reflect the actual behavior of
> prepare_alloc_pages() and the corrected check ordering.
>
> Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
> ---
> kernel/cgroup/cpuset.c | 14 ++++++++------
> 1 file changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index e3a081a07c6d..a48901a0416a 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -4176,11 +4176,11 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
> * current's mems_allowed, yes. If it's not a __GFP_HARDWALL request and this
> * node is set in the nearest hardwalled cpuset ancestor to current's cpuset,
> * yes. If current has access to memory reserves as an oom victim, yes.
> - * Otherwise, no.
> + * If the current task is PF_EXITING, yes. Otherwise, no.
> *
> * GFP_USER allocations are marked with the __GFP_HARDWALL bit,
> * and do not allow allocations outside the current tasks cpuset
> - * unless the task has been OOM killed.
> + * unless the task has been OOM killed or is exiting.
> * GFP_KERNEL allocations are not so marked, so can escape to the
> * nearest enclosing hardwalled ancestor cpuset.
> *
> @@ -4194,7 +4194,9 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
> * The first call here from mm/page_alloc:get_page_from_freelist()
> * has __GFP_HARDWALL set in gfp_mask, enforcing hardwall cpusets,
> * so no allocation on a node outside the cpuset is allowed (unless
> - * in interrupt, of course).
> + * in interrupt, of course). The PF_EXITING check must therefore
> + * come before the __GFP_HARDWALL check, otherwise a dying task
> + * would be blocked on the fast path.
> *
> * The second pass through get_page_from_freelist() doesn't even call
> * here for GFP_ATOMIC calls. For those calls, the __alloc_pages()
> @@ -4204,6 +4206,7 @@ static struct cpuset *nearest_hardwall_ancestor(struct cpuset *cs)
> * in_interrupt - any node ok (current task context irrelevant)
> * GFP_ATOMIC - any node ok
> * tsk_is_oom_victim - any node ok
> + * PF_EXITING - any node ok (let dying task exit quickly)
> * GFP_KERNEL - any node in enclosing hardwalled cpuset ok
> * GFP_USER - only nodes in current tasks mems allowed ok.
> */
> @@ -4223,11 +4226,10 @@ bool cpuset_current_node_allowed(int node, gfp_t gfp_mask)
> */
> if (unlikely(tsk_is_oom_victim(current)))
> return true;
> - if (gfp_mask & __GFP_HARDWALL) /* If hardwall request, stop here */
> - return false;
> -
> if (current->flags & PF_EXITING) /* Let dying task have memory */
> return true;
> + if (gfp_mask & __GFP_HARDWALL) /* If hardwall request, stop here */
> + return false;
>
> /* Not hardwall and node outside mems_allowed: scan up cpusets */
> spin_lock_irqsave(&callback_lock, flags);
Make sense.
BTW, how did you find this issue?
Reviewed-by: Chen Ridong <chenridong@huaweicloud.com>
--
Best regards,
Ridong
^ permalink raw reply
* Re: [PATCH v3] blk-cgroup: fix leaks and online flag on radix_tree_insert failure
From: Tejun Heo @ 2026-05-08 0:27 UTC (permalink / raw)
To: Tao Cui; +Cc: axboe, cgroups, josef
In-Reply-To: <20260507061229.57466-1-cuitao@kylinos.cn>
On Thu, May 07, 2026 at 02:12:29PM +0800, Tao Cui wrote:
> When radix_tree_insert() fails in blkg_create(), the error path has two
> issues:
>
> 1. blkg->online is set to true unconditionally, even when the blkg was
> never fully inserted. Move the assignment inside the success block.
>
> 2. The error path calls blkg_put() without first calling
> percpu_ref_kill(). Because the refcount is still in percpu mode,
> percpu_ref_put() only does this_cpu_sub() without checking for zero,
> so blkg_release() is never triggered. This permanently leaks the
> blkg memory, its percpu iostat, policy data, the parent blkg
> reference, and the cgroup css reference — the latter preventing the
> cgroup from ever being destroyed.
>
> Fix by replacing blkg_put() with percpu_ref_kill(), matching the pattern
> used in blkg_destroy().
>
> Signed-off-by: Tao Cui <cuitao@kylinos.cn>
Acked-by: Tejun Heo <tj@kernel.org>
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH] cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
From: Tejun Heo @ 2026-05-07 22:00 UTC (permalink / raw)
To: Chen Wandun, Chen Wandun
Cc: longman, chenridong, hannes, mkoutny, cgroups, linux-kernel
In-Reply-To: <20260507105434.3266234-1-chenwandun@lixiang.com>
Hello,
> Chen Wandun (1):
> cgroup/cpuset: move PF_EXITING check before __GFP_HARDWALL in cpuset_current_node_allowed()
Applied to cgroup/for-7.1-fixes.
Thanks.
--
tejun
^ permalink raw reply
* Re: [RFC PATCH v5 20/29] sched/deadline: Allow deeper hierarchies of RT cgroups
From: luca abeni @ 2026-05-07 16:44 UTC (permalink / raw)
To: Peter Zijlstra
Cc: Tejun Heo, Yuri Andriaccio, Ingo Molnar, Juri Lelli,
Vincent Guittot, Dietmar Eggemann, Steven Rostedt, Ben Segall,
Mel Gorman, Valentin Schneider, linux-kernel, Yuri Andriaccio,
hannes, mkoutny, cgroups
In-Reply-To: <20260507105331.GQ1026330@noisy.programming.kicks-ass.net>
Hi,
On Thu, 7 May 2026 12:53:31 +0200
Peter Zijlstra <peterz@infradead.org> wrote:
[...]
> > - This has the same problem with cgroup1's rt cgroup sched support
> > where there is no way to have a permissive default configuration,
> > which means that users who don't really care about distributing rt
> > shares hierarchically would get blocked from running rt processes
> > by default, which basically forces distros to disable rt cgroup
> > sched support. This is not new but it'd be a shame to put in all
> > the work and the end result is that most people don't even have
> > access to the feature.
>
> Right, but cgroup-v2 allows enabling/disabling specific controllers
> for a (sub)-hierarchy, right? So if the controller is not enabled (by
> default), it will fall back to putting the tasks in whatever parent
> does have it on, and by default the root group would have and would
> accept tasks.
If I understand well, this is similar to what I was thinking about:
having a default that allows creating FIFO/RR tasks (and execute them
without runtime control - so, without being served by a dl server)
> Additionally, I think we want a flag to allow non-priv tasks to use RT
> inside the controller -- after all, these tasks would be subject to
> strict bandwidth controls and cannot burn the system like
> unbounded/root FIFO tasks can.
This is something Yuri and I wanted to propose as a follow-up patch,
once there is an agreement on the patchset (should be a pretty simple
change :)
Luca
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox