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


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox