Skip to contentSkip to conversation

cgroup/cpuset: Fix partition CPU ownership and isolation accounting

PATCH Superseded Guopeng Zhang 3 participants 31 messages Aug 20 – Sep 10 1 message not in the archive

Subsystems (3)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore
#0 lore raw

cover cgroup/cpuset: Fix partition CPU ownership and isolation accounting

From: Guopeng Zhang <[email protected]>

update_prstate() uses effective_xcpus for isolation accounting and housekeeping checks when switching a partition between root and isolated. The mask also includes CPUs granted to valid child partitions. Such a type change can alter isolation accounting for child-owned CPUs or fail a housekeeping check because of a CPU the parent does not own.

A rejected type change can leave CPUs assigned to the invalidated partition instead of returning them to the partition that owns the invalidated subtree. When cpuset.cpus changes, child validation can use the old parent mask, and a remote update can observe stale cpus_allowed when it revisits a cpuset. Direct and propagated invalidation can leave isolated_cpus reflecting the released partition's type instead of the type of the partition receiving its CPUs. Releasing an isolated partition can clear boot-isolated CPUs from isolated_cpus.

Limit type-change checks and accounting to CPUs owned directly by the partition. Preserve boot-isolated CPUs when a partition is released, return CPUs after rejected type changes, validate children against the new parent mask, and publish cpus_allowed before a partition update can revisit the cpuset. Fix isolation accounting during direct and propagated invalidation.

Patch 1 removes an invalid selftest comparison that makes test_cpuset_prs.sh fail when boot-time domain isolation is configured. Each kernel fix is followed by a focused selftest update.

Guopeng Zhang (17):
selftests/cgroup: Drop invalid boot isolation comparison cgroup/cpuset: Preserve boot-isolated CPUs on partition release selftests/cgroup: Test boot-isolated CPU partition release cgroup/cpuset: Limit type-change accounting to owned CPUs selftests/cgroup: Test isolated CPU accounting on type changes cgroup/cpuset: Validate type changes against owned CPUs selftests/cgroup: Test type-change validation with child-owned CPUs cgroup/cpuset: Release CPUs when a type change is rejected selftests/cgroup: Test rejected partition type changes cgroup/cpuset: Fix isolated accounting on direct child invalidation selftests/cgroup: Test isolation accounting on direct child
invalidation
cgroup/cpuset: Invalidate children outside the new CPU mask selftests/cgroup: Test child invalidation after shrinking cpuset.cpus cgroup/cpuset: Publish cpus_allowed before partition updates selftests/cgroup: Test shrinking cpuset.cpus in a remote partition cgroup/cpuset: Fix isolated accounting on propagated invalidation selftests/cgroup: Test isolation accounting on propagated invalidation

 kernel/cgroup/cpuset.c                        | 162 ++++++++++--
 .../selftests/cgroup/test_cpuset_prs.sh       | 231 +++++++++++++++++-
 2 files changed, 361 insertions(+), 32 deletions(-)

base-commit: 6a746cd265aed59107ebdaa9ce039bb832922969

Signature · 1 line
2.43.0
#1 lore raw

PATCH 1/17 selftests/cgroup: Drop invalid boot isolation comparison

From: Guopeng Zhang <[email protected]>

check_isolcpus() clears ISOLCPUS before rebuilding it from sched domain data. Comparing that empty value with /sys/devices/system/cpu/isolated makes the test fail whenever isolcpus=domain is present.

The cpuset.cpus.isolated check above and the sched domain check below already validate their respective interfaces. Remove the comparison and the unused HKICPUS read.

Fixes 6df415aa46ec ("cgroup/cpuset: Defer housekeeping_update() calls from CPU hotplug to workqueue")
---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 6 ------
 1 file changed, 6 deletions(-)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +0 -6
index da8f7b920178..fdb3185570d4 100755
@@ -797,7 +797,6 @@ check_isolcpus()
 	EXPECTED_ISOLCPUS=$1
 	ISCPUS=${CGROUP2}/cpuset.cpus.isolated
 	ISOLCPUS=$(cat $ISCPUS)
-	HKICPUS=$(cat /sys/devices/system/cpu/isolated)
 	LASTISOLCPU=
 	SCHED_DOMAINS=/sys/kernel/debug/sched/domains
 	if [[ $EXPECTED_ISOLCPUS = . ]]
@@ -835,11 +834,6 @@ check_isolcpus()
 	ISOLCPUS=
 	EXPECTED_ISOLCPUS=$EXPECTED_SDOMAIN
 
-	#
-	# The inverse of HK_TYPE_DOMAIN cpumask in $HKICPUS should match $ISOLCPUS
-	#
-	[[ "$ISOLCPUS" != "$HKICPUS" ]] && return 1
-
 	#
 	# Use the sched domain in debugfs to check isolated CPUs, if available
 	#
Signature · 1 line
2.43.0

A message that is not in the archive (sent off-list or not yet received): [email protected]

#3 lore raw

Re: [PATCH 01/17] selftests/cgroup: Drop invalid boot isolation comparison

Quoted 33 lines · 在 2026/8/20 20:53, [email protected] 写道:

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Removing the check for /sys/devices/system/cpu/isolated drops critical test coverage for the dynamically updated housekeeping mask. --

--- Patch [1]: [PATCH 01/17] selftests/cgroup: Drop invalid boot isolation comparison --- commit 9327a9b65fdcaad904f3dc133a330232cb860c67
Author: Guopeng Zhang <[email protected]>
selftests/cgroup: Drop invalid boot isolation comparison

This commit removes the comparison between the cgroup cpuset.cpus.isolated mask and the housekeeping mask at /sys/devices/system/cpu/isolated in the test_cpuset_prs.sh selftest. The change is intended to fix test failures when isolcpus=domain is present by removing the HKICPUS read and comparison.

Quoted 4 lines
tools/testing/selftests/cgroup/test_cpuset_prs.sh +0 -0
index da8f7b9201784..fdb3185570d40 100755

[ ... ]

tools/testing/selftests/cgroup/test_cpuset_prs.sh check_isolcpus() · line 842

Quoted 9 lines
tools/testing/selftests/cgroup/test_cpuset_prs.sh +0 -5
@@ -835,11 +834,6 @@ check_isolcpus()
 	ISOLCPUS=
 	EXPECTED_ISOLCPUS=$EXPECTED_SDOMAIN
 
-	#
-	# The inverse of HK_TYPE_DOMAIN cpumask in $HKICPUS should match $ISOLCPUS
-	#
-	[[ "$ISOLCPUS" != "$HKICPUS" ]] && return 1
-

[Severity: Medium]
Does removing this check completely drop critical test coverage for the dynamically updated housekeeping mask?

Quoted 8 lines

Commit 6df415aa46ec specifically deferred housekeeping_update() execution to a workqueue. By removing this comparison, the test no longer verifies that those deferred updates correctly apply the isolated CPUs to the housekeeping subsystem.

Instead of removing the check entirely, could we fix it by re-reading HKICPUS inside a retry loop and comparing it against EXPECTED_ISOLCPUS?

This sysfs file cannot observe the dynamically updated housekeeping mask. print_cpus_isolated() reports the HK_TYPE_DOMAIN_BOOT mask, while housekeeping_update() updates the HK_TYPE_DOMAIN mask. Therefore, re-reading HKICPUS would still only return the boot-time isolated CPUs and would not validate dynamically isolated cpuset CPUs.

The dynamic isolation state is verified through cpuset.cpus.isolated and the scheduler domains, which reflect the HK_TYPE_DOMAIN update. Therefore, removing this comparison is intentional.

Thanks,
Guopeng

Quoted 4 lines
Quoted 3 lines
tools/testing/selftests/cgroup/test_cpuset_prs.sh +0 -0
 	#
 	# Use the sched domain in debugfs to check isolated CPUs, if available
 	#
#4 lore raw

PATCH 2/17 cgroup/cpuset: Preserve boot-isolated CPUs on partition release

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

isolated_cpus tracks CPUs isolated with isolcpus= as well as CPUs in isolated cpuset partitions. When an isolated partition is released, isolated_cpus_update() removes its whole CPU mask. This also clears CPUs which were already isolated at boot.

Update isolated_cpus one CPU at a time and keep CPUs outside the boot-time domain housekeeping mask isolated.

Fixes c188f33c864e ("cgroup/cpuset: Account for boot time isolated CPUs")
---
 kernel/cgroup/cpuset.c | 39 +++++++++++++++++++++++++++++----------
 1 file changed, 29 insertions(+), 10 deletions(-)
kernel/cgroup/cpuset.c +29 -10
index d100634fa12b..2538faac9aba 100644
@@ -1259,6 +1259,28 @@ static void reset_partition_data(struct cpuset *cs)
 		cpumask_copy(cs->effective_cpus, parent->effective_cpus);
 }
 
+/* Return true if isolated_cpus changes. */
+static bool isolated_cpu_update(int new_prs, int cpu)
+{
+	lockdep_assert_held(&callback_lock);
+	lockdep_assert_held(&cpuset_mutex);
+
+	if (new_prs == PRS_ISOLATED) {
+		if (cpumask_test_cpu(cpu, isolated_cpus))
+			return false;
+		cpumask_set_cpu(cpu, isolated_cpus);
+		return true;
+	}
+
+	/* CPUs isolated at boot must remain isolated. */
+	if (!cpumask_test_cpu(cpu,
+			      housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT)) ||
+	    !cpumask_test_cpu(cpu, isolated_cpus))
+		return false;
+	cpumask_clear_cpu(cpu, isolated_cpus);
+	return true;
+}
+
 /*
  * isolated_cpus_update - Update the isolated_cpus mask
  * @old_prs: old partition_root_state
@@ -1267,19 +1289,16 @@ static void reset_partition_data(struct cpuset *cs)
  */
 static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus)
 {
+	bool updated = false;
+	int cpu;
+
 	WARN_ON_ONCE(old_prs == new_prs);
 	lockdep_assert_held(&callback_lock);
 	lockdep_assert_held(&cpuset_mutex);
-	if (new_prs == PRS_ISOLATED) {
-		if (cpumask_subset(xcpus, isolated_cpus))
-			return;
-		cpumask_or(isolated_cpus, isolated_cpus, xcpus);
-	} else {
-		if (!cpumask_intersects(xcpus, isolated_cpus))
-			return;
-		cpumask_andnot(isolated_cpus, isolated_cpus, xcpus);
-	}
-	update_housekeeping = true;
+	for_each_cpu(cpu, xcpus)
+		updated |= isolated_cpu_update(new_prs, cpu);
+	if (updated)
+		update_housekeeping = true;
 }
 
 /*
Signature · 1 line
2.43.0
#5 lore raw

PATCH 3/17 selftests/cgroup: Test boot-isolated CPU partition release

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Put a CPU isolated at boot into an isolated partition, change the partition back to member and check that the CPU remains isolated.

---
 .../selftests/cgroup/test_cpuset_prs.sh       | 58 +++++++++++++++++++
 1 file changed, 58 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +58 -0
index fdb3185570d4..131d8b4551ef 100755
@@ -1155,6 +1155,63 @@ test_isolated()
 	pause 0.05
 }
 
+#
+# Select an online CPU isolated from scheduler domains at boot.
+# $1: test name used in the skip message
+#
+get_boot_isolated_cpu()
+{
+	TEST_NAME=$1
+	BOOT_ISOLATED_FILE=/sys/devices/system/cpu/isolated
+
+	[[ -r $BOOT_ISOLATED_FILE ]] || {
+		echo "$TEST_NAME test SKIPPED: boot isolation state unavailable"
+		return 1
+	}
+	BOOT_CPUS=$(cat $BOOT_ISOLATED_FILE)
+	[[ -n "$BOOT_CPUS" ]] || {
+		echo "$TEST_NAME test SKIPPED: no boot-isolated CPU"
+		return 1
+	}
+
+	BOOT_CPU=$(echo "$BOOT_CPUS" | sed -e 's/[,-].*//')
+	CPU_ONLINE=/sys/devices/system/cpu/cpu${BOOT_CPU}/online
+	[[ ! -e $CPU_ONLINE || $(cat $CPU_ONLINE) -eq 1 ]] || {
+		echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is offline"
+		return 1
+	}
+}
+
+#
+# A CPU isolated at boot must stay isolated after it is released by a dynamic
+# isolated partition.
+#
+test_boot_isolated()
+{
+	TEST_NAME="Boot-isolated CPU partition release"
+	get_boot_isolated_cpu "$TEST_NAME" || return 0
+	echo "Running $TEST_NAME test ..."
+
+	cd $CGROUP2/test
+	echo member > cpuset.cpus.partition
+	echo $BOOT_CPU > cpuset.cpus
+	[[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
+		echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
+		echo "" > cpuset.cpus
+		cd $CGROUP2
+		return 0
+	}
+	test_partition isolated
+	test_partition member
+	check_isolcpus "." || {
+		echo "Boot-isolated CPU $BOOT_CPU was lost after partition release"
+		exit 1
+	}
+	echo "" > cpuset.cpus
+	cd $CGROUP2
+	echo "$TEST_NAME test PASSED."
+}
+
 #
 # Wait for inotify event for the given file and read it
 # $1: cgroup file to wait for
@@ -1226,5 +1283,6 @@ trap cleanup 0 2 3 6
 run_state_test TEST_MATRIX
 run_remote_state_test REMOTE_TEST_MATRIX
 test_isolated
+test_boot_isolated
 test_inotify
 echo "All tests PASSED."
Signature · 1 line
2.43.0
#6 lore raw

PATCH 4/17 cgroup/cpuset: Limit type-change accounting to owned CPUs

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

effective_xcpus includes CPUs granted to valid child partitions. Passing the whole mask to isolated_cpus_update() during a root-to-isolated or isolated-to-root change applies the parent's new state to child-owned CPUs as well.

Build a mask of CPUs owned by the partition by subtracting the effective_xcpus of valid children. Use this mask when updating isolated_cpus for a partition type change.

Fixes 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")
---
 kernel/cgroup/cpuset.c | 30 ++++++++++++++++++++++++++++--
 1 file changed, 28 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +28 -2
index 2538faac9aba..468272baadb2 100644
@@ -2155,6 +2155,30 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
 	rcu_read_unlock();
 }
 
+/*
+ * Compute CPUs owned directly by a partition.
+ *
+ * effective_xcpus includes CPUs granted to valid child partitions. Exclude
+ * those CPUs when changing only this partition's type.
+ */
+static void compute_partition_owned_cpumask(struct cpuset *cs,
+					    struct cpumask *owned_cpus)
+{
+	struct cgroup_subsys_state *css;
+	struct cpuset *child;
+
+	lockdep_assert_held(&cpuset_mutex);
+	cpumask_copy(owned_cpus, cs->effective_xcpus);
+
+	rcu_read_lock();
+	cpuset_for_each_child(child, css, cs) {
+		if (is_partition_valid(child))
+			cpumask_andnot(owned_cpus, owned_cpus,
+				       child->effective_xcpus);
+	}
+	rcu_read_unlock();
+}
+
 /*
  * update_cpumasks_hier - Update effective cpumasks and tasks in the subtree
  * @cs:  the cpuset to consider
@@ -2990,8 +3014,10 @@ static int update_prstate(struct cpuset *cs, int new_prs)
 	} else if (old_prs && new_prs) {
 		/*
 		 * A change in load balance state only, no change in cpumasks.
-		 * Need to update isolated_cpus.
+		 * Need to update isolated_cpus for CPUs owned by this partition,
+		 * excluding CPUs distributed to valid child partitions.
 		 */
+		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
 		if (((new_prs == PRS_ISOLATED) &&
 		     !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||
 		    prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))
@@ -3030,7 +3056,7 @@ static int update_prstate(struct cpuset *cs, int new_prs)
 	if (!is_partition_valid(cs))
 		reset_partition_data(cs);
 	else if (isolcpus_updated)
-		isolated_cpus_update(old_prs, new_prs, cs->effective_xcpus);
+		isolated_cpus_update(old_prs, new_prs, tmpmask.new_cpus);
 	spin_unlock_irq(&callback_lock);
 
 	/* Force update if switching back to member & update effective_xcpus */
Signature · 1 line
2.43.0
maintainer
#7 lore raw

Re: [PATCH 04/17] cgroup/cpuset: Limit type-change accounting to owned CPUs

Quoted 65 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

effective_xcpus includes CPUs granted to valid child partitions. Passing the whole mask to isolated_cpus_update() during a root-to-isolated or isolated-to-root change applies the parent's new state to child-owned CPUs as well.

Build a mask of CPUs owned by the partition by subtracting the effective_xcpus of valid children. Use this mask when updating isolated_cpus for a partition type change.

Fixes 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")
---
  kernel/cgroup/cpuset.c | 30 ++++++++++++++++++++++++++++--
  1 file changed, 28 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +27 -1
index 2538faac9aba..468272baadb2 100644
@@ -2155,6 +2155,30 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
  	rcu_read_unlock();
  }
 
+/*
+ * Compute CPUs owned directly by a partition.
+ *
+ * effective_xcpus includes CPUs granted to valid child partitions. Exclude
+ * those CPUs when changing only this partition's type.
+ */
+static void compute_partition_owned_cpumask(struct cpuset *cs,
+					    struct cpumask *owned_cpus)
+{
+	struct cgroup_subsys_state *css;
+	struct cpuset *child;
+
+	lockdep_assert_held(&cpuset_mutex);
+	cpumask_copy(owned_cpus, cs->effective_xcpus);
+
+	rcu_read_lock();
+	cpuset_for_each_child(child, css, cs) {
+		if (is_partition_valid(child))
+			cpumask_andnot(owned_cpus, owned_cpus,
+				       child->effective_xcpus);
+	}
+	rcu_read_unlock();
+}
+
  /*
   * update_cpumasks_hier - Update effective cpumasks and tasks in the subtree
   * @cs:  the cpuset to consider
@@ -2990,8 +3014,10 @@ static int update_prstate(struct cpuset *cs, int new_prs)
  	} else if (old_prs && new_prs) {
  		/*
  		 * A change in load balance state only, no change in cpumasks.
-		 * Need to update isolated_cpus.
+		 * Need to update isolated_cpus for CPUs owned by this partition,
+		 * excluding CPUs distributed to valid child partitions.
  		 */
+		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
  		if (((new_prs == PRS_ISOLATED) &&
  		     !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||
  		    prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))

kernel/cgroup/cpuset.c static int update_prstate(struct cpuset *cs, int new_prs) · line 3062

Quoted 8 lines
kernel/cgroup/cpuset.c +1 -1
@@ -3030,7 +3056,7 @@ static int update_prstate(struct cpuset *cs, int new_prs)
  	if (!is_partition_valid(cs))
  		reset_partition_data(cs);
  	else if (isolcpus_updated)
-		isolated_cpus_update(old_prs, new_prs, cs->effective_xcpus);
+		isolated_cpus_update(old_prs, new_prs, tmpmask.new_cpus);
  	spin_unlock_irq(&callback_lock);
 
  	/* Force update if switching back to member & update effective_xcpus */

Your use of tmpmask.new_cpus in isolated_cpus_update() can be problematic. isolcpus_updated can be set when an isolated partition is enabled or disabled. In both cases, tmpmask.new_cpus can be used temporarily. So the content of this temporary cpumask may not be what we want to pass into isolated_cpus_update(). I will suggest you only use tmpmask.new_cpus if it is determined to be coming from partition state transition instead of enabling/disabling of partition.

Cheers,
Longman

#8 lore raw

Re: [PATCH 04/17] cgroup/cpuset: Limit type-change accounting to owned CPUs

Quoted 66 lines · Waiman Long
Quoted 65 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

effective_xcpus includes CPUs granted to valid child partitions. Passing the whole mask to isolated_cpus_update() during a root-to-isolated or isolated-to-root change applies the parent's new state to child-owned CPUs as well.

Build a mask of CPUs owned by the partition by subtracting the effective_xcpus of valid children. Use this mask when updating isolated_cpus for a partition type change.

Fixes 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")

---
  kernel/cgroup/cpuset.c | 30 ++++++++++++++++++++++++++++--

  1 file changed, 28 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +0 -0
index 2538faac9aba..468272baadb2 100644
@@ -2155,6 +2155,30 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
       rcu_read_unlock();
   }

  +/*
+ * Compute CPUs owned directly by a partition.
+ *
+ * effective_xcpus includes CPUs granted to valid child partitions. Exclude
+ * those CPUs when changing only this partition's type.
+ */

kernel/cgroup/cpuset.c +18 -0
+static void compute_partition_owned_cpumask(struct cpuset *cs,
+                        struct cpumask *owned_cpus)
+{
+    struct cgroup_subsys_state *css;
+    struct cpuset *child;
+
+    lockdep_assert_held(&cpuset_mutex);
+    cpumask_copy(owned_cpus, cs->effective_xcpus);
+
+    rcu_read_lock();
+    cpuset_for_each_child(child, css, cs) {
+        if (is_partition_valid(child))
+            cpumask_andnot(owned_cpus, owned_cpus,
+                       child->effective_xcpus);
+    }
+    rcu_read_unlock();
+}
+

  /*
   * update_cpumasks_hier - Update effective cpumasks and tasks in the subtree
   * @cs:  the cpuset to consider

kernel/cgroup/cpuset.c +3 -1
@@ -2990,8 +3014,10 @@ static int update_prstate(struct cpuset *cs, int new_prs)
       } else if (old_prs && new_prs) {
           /*
            * A change in load balance state only, no change in cpumasks.
-         * Need to update isolated_cpus.
+         * Need to update isolated_cpus for CPUs owned by this partition,
+         * excluding CPUs distributed to valid child partitions.
            */
+        compute_partition_owned_cpumask(cs, tmpmask.new_cpus);

          if (((new_prs == PRS_ISOLATED) && !isolated_cpus_can_update(cs->effective_xcpus, NULL)) || prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))

kernel/cgroup/cpuset.c +0 -0
@@ -3030,7 +3056,7 @@ static int update_prstate(struct cpuset *cs, int new_prs)
       if (!is_partition_valid(cs))
Quoted 9 lines

kernel/cgroup/cpuset.c static int update_prstate(struct cpuset *cs, int new_prs) · line 3062

Quoted 6 lines
kernel/cgroup/cpuset.c +1 -1
           reset_partition_data(cs);
       else if (isolcpus_updated)
-        isolated_cpus_update(old_prs, new_prs, cs->effective_xcpus);
+        isolated_cpus_update(old_prs, new_prs, tmpmask.new_cpus);
       spin_unlock_irq(&callback_lock);
         /* Force update if switching back to member & update effective_xcpus */

Your use of tmpmask.new_cpus in isolated_cpus_update() can be problematic. isolcpus_updated can be set when an isolated partition is enabled or disabled. In both cases, tmpmask.new_cpus can be used temporarily. So the content of this temporary cpumask may not be what we want to pass into isolated_cpus_update(). I will suggest you only use tmpmask.new_cpus if it is determined to be coming from partition state transition instead of enabling/disabling of partition.

Thanks for the review, Longman.

I followed your suggestion and checked update_prstate() again.

isolcpus_updated starts as false and is set only in the old_prs && new_prs branch, after tmpmask.new_cpus has been filled with the CPUs owned directly by the partition. That branch handles only root-to-isolated and isolated-to-root transitions. The member-to-partition and partition-to-member paths do not set the flag.

Those enable and disable paths update isolated_cpus separately. For a local partition, the path is:

        update_parent_effective_cpumask()
          -> partition_xcpus_add()/partition_xcpus_del()
             -> isolated_cpus_update()

For a remote partition, the path is:

        remote_partition_enable()/remote_partition_disable()
          -> partition_xcpus_add()/partition_xcpus_del()
             -> isolated_cpus_update()

There is no write to tmpmask.new_cpus between compute_partition_owned_cpumask() and the isolated_cpus_update() call guarded by isolcpus_updated. As far as I can tell, tmpmask.new_cpus therefore still contains the directly owned CPUs whenever the flag is set.

Please let me know if I have overlooked another path.

Thanks,
Guopeng

Quoted 2 lines

Cheers,
Longman

#9 lore raw

PATCH 5/17 selftests/cgroup: Test isolated CPU accounting on type changes

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Switch a parent between root and isolated while a child has the opposite type. Check that CPUs owned by the child keep their isolation state.

---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 2 ++
 1 file changed, 2 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +2 -0
index 131d8b4551ef..9a1cce4807b4 100755
@@ -224,6 +224,8 @@ TEST_MATRIX=(
 	"   C0-1     .      .   C2-3:P1   .      .      .     C2     0 "
 	"   C0-1     .      .   C2-3:P1   .      .      .    C4-5    0 B1:4-5"
 	"  C0-3:P1 C2-3:P1  .      .      .      .      .      .     0 A1:0-1|A2:2-3|XA2:2-3"
+	"  C0-3:P1 C2-3:P1  .      .     P2      .      .      .     0 A1:0-1|A2:2-3 A1:P2|A2:P1 0-1"
+	"  C0-3:P2 C2-3:P2  .      .     P1      .      .      .     0 A1:0-1|A2:2-3 A1:P1|A2:P2 2-3"
 	"  C0-3:P1 C2-3:P1  .      .     C1-3    .      .      .     0 A1:1|A2:2-3|XA2:2-3"
 	"  C2-3:P1  C3:P1   .      .     C3      .      .      .     0 A1:|A2:3|XA2:3 A1:P1|A2:P1"
 	"  C2-3:P1  C3:P1   .      .     C3      P0     .      .     0 A1:3|A2:3 A1:P1|A2:P0"
Signature · 1 line
2.43.0
#10 lore raw

PATCH 6/17 cgroup/cpuset: Validate type changes against owned CPUs

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_prstate() checks effective_xcpus against the housekeeping masks before changing a partition between root and isolated. effective_xcpus also contains CPUs granted to valid child partitions, although those CPUs are not affected by the parent's type change. A child-owned CPU can therefore reject an otherwise valid change.

Run the nohz_full and boot-domain housekeeping checks against the mask of CPUs owned by the partition instead.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
Fixes b1034a690129 ("cgroup/cpuset: Ensure domain isolated CPUs stay in root or isolated partition")
---
 kernel/cgroup/cpuset.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +2 -2
index 468272baadb2..853942c1afeb 100644
@@ -3019,8 +3019,8 @@ static int update_prstate(struct cpuset *cs, int new_prs)
 		 */
 		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
 		if (((new_prs == PRS_ISOLATED) &&
-		     !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||
-		    prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))
+		     !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
+		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
 			err = PERR_HKEEPING;
 		else
 			isolcpus_updated = true;
Signature · 1 line
2.43.0
maintainer
#11 lore raw

Re: [PATCH 06/17] cgroup/cpuset: Validate type changes against owned CPUs

Quoted 23 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_prstate() checks effective_xcpus against the housekeeping masks before changing a partition between root and isolated. effective_xcpus also contains CPUs granted to valid child partitions, although those CPUs are not affected by the parent's type change. A child-owned CPU can therefore reject an otherwise valid change.

Run the nohz_full and boot-domain housekeeping checks against the mask of CPUs owned by the partition instead.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
Fixes b1034a690129 ("cgroup/cpuset: Ensure domain isolated CPUs stay in root or isolated partition")
---
  kernel/cgroup/cpuset.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +0 -0
index 468272baadb2..853942c1afeb 100644

kernel/cgroup/cpuset.c static int update_prstate(struct cpuset *cs, int new_prs) · line 3026

Quoted 10 lines
kernel/cgroup/cpuset.c +2 -2
@@ -3019,8 +3019,8 @@ static int update_prstate(struct cpuset *cs, int new_prs)
  		 */
  		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
  		if (((new_prs == PRS_ISOLATED) &&
-		     !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||
-		    prstate_housekeeping_conflict(new_prs, cs->effective_xcpus))
+		     !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
+		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
  			err = PERR_HKEEPING;
  		else
  			isolcpus_updated = true;

This can be merged into patch 4. This is basically the same change as in patch 4.

Cheers,
Longman

#12 lore raw

Re: [PATCH 06/17] cgroup/cpuset: Validate type changes against owned CPUs

Quoted 28 lines · Waiman Long
Quoted 27 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_prstate() checks effective_xcpus against the housekeeping masks before changing a partition between root and isolated. effective_xcpus also contains CPUs granted to valid child partitions, although those CPUs are not affected by the parent's type change. A child-owned CPU can therefore reject an otherwise valid change.

Run the nohz_full and boot-domain housekeeping checks against the mask of CPUs owned by the partition instead.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
Fixes b1034a690129 ("cgroup/cpuset: Ensure domain isolated CPUs stay in root or isolated partition")

---
  kernel/cgroup/cpuset.c | 4 ++--

  1 file changed, 2 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +0 -0
index 468272baadb2..853942c1afeb 100644
@@ -3019,8 +3019,8 @@ static int update_prstate(struct cpuset *cs, int new_prs)
            */
           compute_partition_owned_cpumask(cs, tmpmask.new_cpus);

          if (((new_prs == PRS_ISOLATED) &&
-             !isolated_cpus_can_update(cs->effective_xcpus, NULL)) ||

Quoted 9 lines
Quoted 6 lines

-            prstate_housekeeping_conflict(new_prs, cs->effective_xcpus)) +             !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) || +            prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) err = PERR_HKEEPING;
          else
              isolcpus_updated = true;

This can be merged into patch 4. This is basically the same change as in patch 4.

That makes sense. I have folded the two changes together in v2.

Thanks,
Guopeng

#13 lore raw

PATCH 7/17 selftests/cgroup: Test type-change validation with child-owned CPUs

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Create an isolated child which owns a CPU isolated at boot, then change its parent from isolated to root. Check that the change succeeds without altering the child or its isolated CPU.

---
 .../selftests/cgroup/test_cpuset_prs.sh       | 47 +++++++++++++++++++
 1 file changed, 47 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +47 -0
index 9a1cce4807b4..bc8dbb724d5c 100755
@@ -1214,6 +1214,52 @@ test_boot_isolated()
 	echo "$TEST_NAME test PASSED."
 }
 
+#
+# A parent's type change must validate only CPUs owned directly by the parent,
+# not a boot-isolated CPU owned by a valid isolated child partition.
+#
+test_owned_cpus_housekeeping()
+{
+	TEST_NAME="Child-owned CPU type change"
+	get_boot_isolated_cpu "$TEST_NAME" || return 0
+	echo "Running $TEST_NAME test ..."
+
+	cd $CGROUP2/test
+	echo member > cpuset.cpus.partition
+	echo +cpuset > cgroup.subtree_control
+	echo 2,$BOOT_CPU > cpuset.cpus
+	[[ $(cat cpuset.cpus.effective) = "2,$BOOT_CPU" ]] || {
+		echo "$TEST_NAME test SKIPPED: CPUs 2,$BOOT_CPU are unavailable"
+		echo "" > cpuset.cpus
+		cd $CGROUP2
+		return 0
+	}
+	test_partition isolated
+	mkdir A1
+	cd A1
+	echo $BOOT_CPU > cpuset.cpus
+	test_partition isolated
+	cd ..
+	test_effective_cpus 2
+	test_partition root
+	[[ $(cat A1/cpuset.cpus.partition) = isolated ]] || {
+		echo "Child partition changed during parent type change"
+		exit 1
+	}
+	check_isolcpus "." || {
+		echo "Parent type change corrupted child isolation"
+		exit 1
+	}
+	cd A1
+	test_partition member
+	cd ..
+	rmdir A1
+	test_partition member
+	echo "" > cpuset.cpus
+	cd $CGROUP2
+	echo "$TEST_NAME test PASSED."
+}
+
 #
 # Wait for inotify event for the given file and read it
 # $1: cgroup file to wait for
@@ -1286,5 +1332,6 @@ run_state_test TEST_MATRIX
 run_remote_state_test REMOTE_TEST_MATRIX
 test_isolated
 test_boot_isolated
+test_owned_cpus_housekeeping
 test_inotify
 echo "All tests PASSED."
Signature · 1 line
2.43.0
maintainer
#14 lore raw

Re: [PATCH 07/17] selftests/cgroup: Test type-change validation with child-owned CPUs

Quoted 24 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Create an isolated child which owns a CPU isolated at boot, then change its parent from isolated to root. Check that the change succeeds without altering the child or its isolated CPU.

---
  .../selftests/cgroup/test_cpuset_prs.sh       | 47 +++++++++++++++++++
  1 file changed, 47 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +5 -0
index 9a1cce4807b4..bc8dbb724d5c 100755
@@ -1214,6 +1214,52 @@ test_boot_isolated()
  	echo "$TEST_NAME test PASSED."
  }
 
+#
+# A parent's type change must validate only CPUs owned directly by the parent,
+# not a boot-isolated CPU owned by a valid isolated child partition.
+#
+test_owned_cpus_housekeeping()

tools/testing/selftests/cgroup/test_cpuset_prs.sh test_boot_isolated() · line 1231

Quoted 10 lines
tools/testing/selftests/cgroup/test_cpuset_prs.sh +10 -0
+{
+	TEST_NAME="Child-owned CPU type change"
+	get_boot_isolated_cpu "$TEST_NAME" || return 0
+	echo "Running $TEST_NAME test ..."
+
+	cd $CGROUP2/test
+	echo member > cpuset.cpus.partition
+	echo +cpuset > cgroup.subtree_control
+	echo 2,$BOOT_CPU > cpuset.cpus
+	[[ $(cat cpuset.cpus.effective) = "2,$BOOT_CPU" ]] || {

You are using the same BOOT_CPU set up in get_boot_isolated_cpu() in patch 3. That creates an hidden dependency on the order of the tests. I will suggest that you set up BOOT_CPU and  BOOT_CPUS as part of the initialization so the data is available to all the tests that want to use it.

Cheers,
Longman

#15 lore raw

Re: [PATCH 07/17] selftests/cgroup: Test type-change validation with child-owned CPUs

Quoted 28 lines · Waiman Long
Quoted 27 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Create an isolated child which owns a CPU isolated at boot, then change its parent from isolated to root. Check that the change succeeds without altering the child or its isolated CPU.

---
  .../selftests/cgroup/test_cpuset_prs.sh       | 47 +++++++++++++++++++

  1 file changed, 47 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +0 -0
index 9a1cce4807b4..bc8dbb724d5c 100755
@@ -1214,6 +1214,52 @@ test_boot_isolated()

      echo "$TEST_NAME test PASSED."
  }
  +#

tools/testing/selftests/cgroup/test_cpuset_prs.sh +8 -0
+# A parent's type change must validate only CPUs owned directly by the parent,
+# not a boot-isolated CPU owned by a valid isolated child partition.
+#
+test_owned_cpus_housekeeping()
+{
+    TEST_NAME="Child-owned CPU type change"
+    get_boot_isolated_cpu "$TEST_NAME" || return 0
+    echo "Running $TEST_NAME test ..."
Quoted 9 lines
Quoted 6 lines

+
+    cd $CGROUP2/test
+    echo member > cpuset.cpus.partition
+    echo +cpuset > cgroup.subtree_control
+    echo 2,$BOOT_CPU > cpuset.cpus
+    [[ $(cat cpuset.cpus.effective) = "2,$BOOT_CPU" ]] || {

You are using the same BOOT_CPU set up in get_boot_isolated_cpu() in patch 3. That creates an hidden dependency on the order of the tests. I will suggest that you set up BOOT_CPU and  BOOT_CPUS as part of the initialization so the data is available to all the tests that want to use it.

That makes sense. I will move the initialization of BOOT_CPUS and BOOT_CPU to the setup stage in v2.

Thanks,
Guopeng

#16 lore raw

PATCH 8/17 cgroup/cpuset: Release CPUs when a type change is rejected

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When a housekeeping check rejects a root-to-isolated or isolated-to-root change, update_prstate() marks the partition invalid but leaves its effective_xcpus allocated. Those CPUs remain missing from the partition which owns the invalidated subtree.

Disable a rejected remote partition with remote_partition_disable(). For a local partition, return effective_xcpus to its parent before recording the invalid state. Use the complete mask because descendants are invalidated along with the parent and their CPUs return at the same time.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
---
 kernel/cgroup/cpuset.c | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +13 -2
index 853942c1afeb..a616012927be 100644
@@ -3020,10 +3020,21 @@ static int update_prstate(struct cpuset *cs, int new_prs)
 		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
 		if (((new_prs == PRS_ISOLATED) &&
 		     !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
-		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
+		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) {
 			err = PERR_HKEEPING;
-		else
+			if (is_remote_partition(cs)) {
+				WRITE_ONCE(cs->prs_err, err);
+				remote_partition_disable(cs, &tmpmask);
+			} else {
+				spin_lock_irq(&callback_lock);
+				partition_xcpus_del(old_prs, parent, cs->effective_xcpus);
+				spin_unlock_irq(&callback_lock);
+				cpuset_update_tasks_cpumask(parent, tmpmask.new_cpus);
+				update_sibling_cpumasks(parent, cs, &tmpmask);
+			}
+		} else {
 			isolcpus_updated = true;
+		}
 	} else {
 		/*
 		 * Switching back to member is always allowed even if it
Signature · 1 line
2.43.0
maintainer
#17 lore raw

Re: [PATCH 08/17] cgroup/cpuset: Release CPUs when a type change is rejected

Quoted 24 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When a housekeeping check rejects a root-to-isolated or isolated-to-root change, update_prstate() marks the partition invalid but leaves its effective_xcpus allocated. Those CPUs remain missing from the partition which owns the invalidated subtree.

Disable a rejected remote partition with remote_partition_disable(). For a local partition, return effective_xcpus to its parent before recording the invalid state. Use the complete mask because descendants are invalidated along with the parent and their CPUs return at the same time.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")
---
  kernel/cgroup/cpuset.c | 15 +++++++++++++--
  1 file changed, 13 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +0 -0
index 853942c1afeb..a616012927be 100644

kernel/cgroup/cpuset.c static int update_prstate(struct cpuset *cs, int new_prs) · line 3026

Quoted 9 lines
kernel/cgroup/cpuset.c +3 -2
@@ -3020,10 +3020,21 @@ static int update_prstate(struct cpuset *cs, int new_prs)
  		compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
  		if (((new_prs == PRS_ISOLATED) &&
  		     !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) ||
-		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus))
+		    prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) {
  			err = PERR_HKEEPING;
-		else
+			if (is_remote_partition(cs)) {
+				WRITE_ONCE(cs->prs_err, err);

This WRITE_ONCE() is not necessary as it will be done later in the function.

kernel/cgroup/cpuset.c static int update_prstate(struct cpuset *cs, int new_prs) · line 3034

Quoted 8 lines
kernel/cgroup/cpuset.c +8 -0
+				remote_partition_disable(cs, &tmpmask);
+			} else {
+				spin_lock_irq(&callback_lock);
+				partition_xcpus_del(old_prs, parent, cs->effective_xcpus);
+				spin_unlock_irq(&callback_lock);
+				cpuset_update_tasks_cpumask(parent, tmpmask.new_cpus);
+				update_sibling_cpumasks(parent, cs, &tmpmask);
+			}

This code to disable the partition is duplicating what is being done in the last else clause of the outer if, but in a somewhat different way. That is not good programming practice. You can either jump to the partition disabling code below or separate out the partition disabling code controlled by a separate control flag and set the flag to do it.

Cheers,
Longman

Quoted 6 lines
kernel/cgroup/cpuset.c +2 -0
+		} else {
  			isolcpus_updated = true;
+		}
  	} else {
  		/*
  		 * Switching back to member is always allowed even if it
#18 lore raw

Re: [PATCH 08/17] cgroup/cpuset: Release CPUs when a type change is rejected

Quoted 44 lines · Waiman Long
Quoted 25 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When a housekeeping check rejects a root-to-isolated or isolated-to-root change, update_prstate() marks the partition invalid but leaves its effective_xcpus allocated. Those CPUs remain missing from the partition which owns the invalidated subtree.

Disable a rejected remote partition with remote_partition_disable(). For a local partition, return effective_xcpus to its parent before recording the invalid state. Use the complete mask because descendants are invalidated along with the parent and their CPUs return at the same time.

Fixes 103b08709e8a ("cgroup/cpuset: Fail if isolated and nohz_full don't leave any housekeeping")

---
  kernel/cgroup/cpuset.c | 15 +++++++++++++--

  1 file changed, 13 insertions(+), 2 deletions(-)
kernel/cgroup/cpuset.c +0 -0
index 853942c1afeb..a616012927be 100644
@@ -3020,10 +3020,21 @@ static int update_prstate(struct cpuset *cs, int new_prs)
           compute_partition_owned_cpumask(cs, tmpmask.new_cpus);
Quoted 8 lines

          if (((new_prs == PRS_ISOLATED) && !isolated_cpus_can_update(tmpmask.new_cpus, NULL)) || -            prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) +            prstate_housekeeping_conflict(new_prs, tmpmask.new_cpus)) { err = PERR_HKEEPING;
-        else
+            if (is_remote_partition(cs)) {
+                WRITE_ONCE(cs->prs_err, err);

This WRITE_ONCE() is not necessary as it will be done later in the function.

Quoted 8 lines

+                remote_partition_disable(cs, &tmpmask);
+            } else {
+                spin_lock_irq(&callback_lock);
+                partition_xcpus_del(old_prs, parent, cs->effective_xcpus); +                spin_unlock_irq(&callback_lock);
+                cpuset_update_tasks_cpumask(parent, tmpmask.new_cpus); +                update_sibling_cpumasks(parent, cs, &tmpmask); +            }

Quoted 2 lines

This code to disable the partition is duplicating what is being done in the last else clause of the outer if, but in a somewhat different way. That is not good programming practice. You can either jump to the partition disabling code below or separate out the partition disabling code controlled by a separate control flag and set the flag to do it.

Agreed. I removed the redundant WRITE_ONCE() and used a disable_partition flag to share the existing partition-disable path in v2.

Thanks,
Guopeng

Quoted 10 lines

Cheers,
Longman

Quoted 6 lines

+        } else {
              isolcpus_updated = true;
+        }
      } else {
          /*
           * Switching back to member is always allowed even if it

#19 lore raw

PATCH 9/17 selftests/cgroup: Test rejected partition type changes

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Exercise housekeeping rejection for local and remote partition type changes. Check the invalid state, the CPUs returned to the top cpuset and the boot-time isolation state of those CPUs.

---
 .../selftests/cgroup/test_cpuset_prs.sh       | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +93 -0
index bc8dbb724d5c..35c19738aaa4 100755
@@ -1260,6 +1260,97 @@ test_owned_cpus_housekeeping()
 	echo "$TEST_NAME test PASSED."
 }
 
+#
+# A local type change rejected by the boot housekeeping constraint must release
+# the partition CPUs and preserve their boot-isolated state.
+#
+test_partition_type_failure()
+{
+	TEST_NAME="Rejected local partition type change"
+	get_boot_isolated_cpu "$TEST_NAME" || return 0
+	echo "Running $TEST_NAME test ..."
+
+	cd $CGROUP2/test
+	echo member > cpuset.cpus.partition
+	echo $BOOT_CPU > cpuset.cpus
+	[[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
+		echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
+		echo "" > cpuset.cpus
+		cd $CGROUP2
+		return 0
+	}
+	test_partition isolated
+	echo root > cpuset.cpus.partition
+	grep -q '^root invalid (partition config conflicts with housekeeping setup)$' \
+		cpuset.cpus.partition || {
+		echo "Partition type failure did not produce the expected state"
+		exit 1
+	}
+	[[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+		echo "Partition type failure did not release CPU $BOOT_CPU"
+		exit 1
+	}
+	check_isolcpus "." || {
+		echo "Partition type failure lost boot-isolated CPU $BOOT_CPU"
+		exit 1
+	}
+	echo member > cpuset.cpus.partition
+	echo "" > cpuset.cpus
+	cd $CGROUP2
+	echo "$TEST_NAME test PASSED."
+}
+
+#
+# Exercise the same rejected type change for a remote partition.
+#
+test_remote_partition_type_failure()
+{
+	TEST_NAME="Rejected remote partition type change"
+	get_boot_isolated_cpu "$TEST_NAME" || return 0
+	echo "Running $TEST_NAME test ..."
+
+	cd $CGROUP2/test
+	echo member > cpuset.cpus.partition
+	echo +cpuset > cgroup.subtree_control
+	echo $BOOT_CPU > cpuset.cpus
+	echo $BOOT_CPU > cpuset.cpus.exclusive
+	[[ $(cat cpuset.cpus.effective) = "$BOOT_CPU" ]] || {
+		echo "$TEST_NAME test SKIPPED: CPU $BOOT_CPU is unavailable"
+		echo "" > cpuset.cpus.exclusive
+		echo "" > cpuset.cpus
+		cd $CGROUP2
+		return 0
+	}
+	mkdir A1
+	cd A1
+	echo $BOOT_CPU > cpuset.cpus
+	echo $BOOT_CPU > cpuset.cpus.exclusive
+	test_partition isolated
+	echo root > cpuset.cpus.partition
+	grep -q '^root invalid (partition config conflicts with housekeeping setup)$' \
+		cpuset.cpus.partition || {
+		echo "Remote type failure did not produce the expected state"
+		exit 1
+	}
+	[[ $(cat $CGROUP2/cpuset.cpus.effective) = "$CPULIST" ]] || {
+		echo "Remote type failure did not release CPU $BOOT_CPU"
+		exit 1
+	}
+	check_isolcpus "." || {
+		echo "Remote type failure lost boot-isolated CPU $BOOT_CPU"
+		exit 1
+	}
+	echo member > cpuset.cpus.partition
+	echo "" > cpuset.cpus.exclusive
+	echo "" > cpuset.cpus
+	cd ..
+	rmdir A1
+	echo "" > cpuset.cpus.exclusive
+	echo "" > cpuset.cpus
+	cd $CGROUP2
+	echo "$TEST_NAME test PASSED."
+}
+
 #
 # Wait for inotify event for the given file and read it
 # $1: cgroup file to wait for
@@ -1333,5 +1424,7 @@ run_remote_state_test REMOTE_TEST_MATRIX
 test_isolated
 test_boot_isolated
 test_owned_cpus_housekeeping
+test_partition_type_failure
+test_remote_partition_type_failure
 test_inotify
 echo "All tests PASSED."
Signature · 1 line
2.43.0
#20 lore raw

PATCH 10/17 cgroup/cpuset: Fix isolated accounting on direct child invalidation

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

compute_partition_effective_cpumask() can invalidate a child whose CPUs no longer fit in the parent or would leave a populated parent without an active CPU. make_partition_invalid() changes the child state without updating isolated_cpus for the CPUs returned by the child.

Keep the unfiltered result of compute_excpus() so that offline CPUs are included in isolation accounting. For each CPU released by the child, use the parent's state when the parent remains valid and owns the CPU. Otherwise use the state of the nearest valid partition ancestor.

Fixes 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")
---
 kernel/cgroup/cpuset.c | 43 +++++++++++++++++++++++++++++++++++-------
 1 file changed, 36 insertions(+), 7 deletions(-)
kernel/cgroup/cpuset.c +36 -7
index a616012927be..b9faadf4af6d 100644
@@ -1281,6 +1281,17 @@ static bool isolated_cpu_update(int new_prs, int cpu)
 	return true;
 }
 
+/* Return the valid partition that owns CPUs released by @cs. */
+static struct cpuset *partition_owner(struct cpuset *cs)
+{
+	struct cpuset *owner = parent_cs(cs);
+
+	lockdep_assert_held(&cpuset_mutex);
+	while (!is_partition_valid(owner))
+		owner = parent_cs(owner);
+	return owner;
+}
+
 /*
  * isolated_cpus_update - Update the isolated_cpus mask
  * @old_prs: old partition_root_state
@@ -2089,6 +2100,7 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
  * compute_partition_effective_cpumask - compute effective_cpus for partition
  * @cs: partition root cpuset
  * @new_ecpus: previously computed effective_cpus to be updated
+ * @new_xcpus: scratch mask for the new effective_xcpus
  *
  * Compute the effective_cpus of a partition root by scanning effective_xcpus
  * of child partition roots and excluding their effective_xcpus.
@@ -2102,7 +2114,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
  * Note that rcu_read_lock() is assumed to be held.
  */
 static void compute_partition_effective_cpumask(struct cpuset *cs,
-						struct cpumask *new_ecpus)
+						struct cpumask *new_ecpus,
+						struct cpumask *new_xcpus)
 {
 	struct cgroup_subsys_state *css;
 	struct cpuset *child;
@@ -2116,8 +2129,8 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
 	 *  2) All the effective_cpus will be used up and cp
 	 *     has tasks
 	 */
-	compute_excpus(cs, new_ecpus);
-	cpumask_and(new_ecpus, new_ecpus, cpu_active_mask);
+	compute_excpus(cs, new_xcpus);
+	cpumask_and(new_ecpus, new_xcpus, cpu_active_mask);
 
 	rcu_read_lock();
 	cpuset_for_each_child(child, css, cs) {
@@ -2139,11 +2152,27 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
 
 		if (child->prs_err) {
 			int old_prs = child->partition_root_state;
+			int parent_prs = cs->partition_root_state;
+			int owner_prs = partition_owner(cs)->partition_root_state;
+			int cpu;
 
 			/*
-			 * Invalidate child partition
+			 * CPUs in the parent's new exclusive mask return to the
+			 * parent. The other CPUs have already been released to the
+			 * partition that owns the parent. Account both destinations
+			 * before invalidating the child.
 			 */
 			spin_lock_irq(&callback_lock);
+			for_each_cpu(cpu, child->effective_xcpus) {
+				int new_prs = parent_prs > 0 &&
+					      cpumask_test_cpu(cpu, new_xcpus)
+					      ? parent_prs : owner_prs;
+
+				if (old_prs == new_prs)
+					continue;
+				if (isolated_cpu_update(new_prs, cpu))
+					update_housekeeping = true;
+			}
 			make_partition_invalid(child);
 			spin_unlock_irq(&callback_lock);
 			notify_partition_change(child, old_prs);
@@ -2231,7 +2260,7 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp,
 		}
 
 		if (remote || (is_partition_valid(parent) && is_partition_valid(cp)))
-			compute_partition_effective_cpumask(cp, tmp->new_cpus);
+			compute_partition_effective_cpumask(cp, tmp->new_cpus, tmp->addmask);
 		else
 			compute_effective_cpumask(tmp->new_cpus, cp, parent);
 
@@ -3996,7 +4025,7 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
 	 */
 	remote = is_remote_partition(cs);
 	if (remote || (is_partition_valid(cs) && is_partition_valid(parent)))
-		compute_partition_effective_cpumask(cs, &new_cpus);
+		compute_partition_effective_cpumask(cs, &new_cpus, tmp->addmask);
 
 	if (remote && (cpumask_empty(subpartitions_cpus) ||
 			(cpumask_empty(&new_cpus) &&
@@ -4031,7 +4060,7 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
 	if (partcmd >= 0) {
 		update_parent_effective_cpumask(cs, partcmd, NULL, tmp);
 		if ((partcmd == partcmd_invalidate) || is_partition_valid(cs)) {
-			compute_partition_effective_cpumask(cs, &new_cpus);
+			compute_partition_effective_cpumask(cs, &new_cpus, tmp->addmask);
 			cpuset_force_rebuild();
 		}
 	}
Signature · 1 line
2.43.0
#21 lore raw

PATCH 11/17 selftests/cgroup: Test isolation accounting on direct child invalidation

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Offline all but one CPU in a root child below an isolated parent. Check the invalid child state, the effective CPU masks and isolated accounting for the released offline CPUs.

---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 5 +++++
 1 file changed, 5 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +5 -0
index 35c19738aaa4..fb11a3ab0f35 100755
@@ -330,6 +330,11 @@ TEST_MATRIX=(
 				   .      .     X4      .      .     0 A1:1-3|A2:1-3|A3:2-3|XA2:|XA3: A1:P2|A2:P-2|A3:P-2 1-3"
 	" C0-3:X1-3:P2 C1-3:X2-3:P2 C2-3:X3:P2 \
 				   .      .    C4:X     .      .     0 A1:1-3|A2:1-3|A3:2-3|XA2:|XA3: A1:P2|A2:P-2|A3:P-2 1-3"
+
+	# Direct invalidation returns child CPUs to the parent or its owner
+	" C1-4:P2  C3-4:P1  .      . T:O1=0:O2=0:O4=0 . .      . \
+					   0 A1:3|A2:3 A1:P2|A2:P-1 1-4"
+
 	# Local partition CPU change tests
 	" C0-5:P2  C4-5:P1  .      .      .    C3-5     .      .     0 A1:0-2|A2:3-5 A1:P2|A2:P1 0-2"
 	" C0-5:P2  C4-5:P1  .      .    C1-5     .      .      .     0 A1:1-3|A2:4-5 A1:P2|A2:P1 1-3"
Signature · 1 line
2.43.0
#22 lore raw

PATCH 12/17 cgroup/cpuset: Invalidate children outside the new CPU mask

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When cpuset.cpus changes, compute_partition_effective_cpumask() builds a new exclusive mask but checks child partitions against cs->effective_xcpus. That field still contains the old mask, so a child that no longer fits can remain valid.

Use new_xcpus for the check. A later partcmd_update() may revisit the newly invalid child. Report PERR_INVCPUS if the child CPUs are outside the parent effective exclusive mask so that this visit does not make the child valid again.

Fixes 0c7f293efc87 ("cgroup/cpuset: Add cpuset.cpus.exclusive.effective for v2")
---
 kernel/cgroup/cpuset.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)
kernel/cgroup/cpuset.c +7 -3
index b9faadf4af6d..b3e749ede7d1 100644
@@ -1987,12 +1987,16 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
 				adding = cpumask_and(tmp->addmask,
 						     cs->effective_xcpus,
 						     parent->effective_xcpus);
-		} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&
-			   cpumask_subset(xcpus, parent->effective_xcpus)) {
+		} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus)) {
 			struct cgroup_subsys_state *css;
 			struct cpuset *child;
 			bool exclusive = true;
 
+			if (!cpumask_subset(xcpus, parent->effective_xcpus)) {
+				part_error = PERR_INVCPUS;
+				goto write_error;
+			}
+
 			/*
 			 * Convert invalid partition to valid has to
 			 * pass the cpu exclusivity test.
@@ -2144,7 +2148,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
 		WARN_ON_ONCE(is_remote_partition(child));
 		WRITE_ONCE(child->prs_err, 0);
 		if (!cpumask_subset(child->effective_xcpus,
-				    cs->effective_xcpus))
+				    new_xcpus))
 			WRITE_ONCE(child->prs_err, PERR_INVCPUS);
 		else if (populated &&
 			 cpumask_subset(new_ecpus, child->effective_xcpus))
Signature · 1 line
2.43.0
maintainer
#23 lore raw

Re: [PATCH 12/17] cgroup/cpuset: Invalidate children outside the new CPU mask

Quoted 28 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When cpuset.cpus changes, compute_partition_effective_cpumask() builds a new exclusive mask but checks child partitions against cs->effective_xcpus. That field still contains the old mask, so a child that no longer fits can remain valid.

Use new_xcpus for the check. A later partcmd_update() may revisit the newly invalid child. Report PERR_INVCPUS if the child CPUs are outside the parent effective exclusive mask so that this visit does not make the child valid again.

Fixes 0c7f293efc87 ("cgroup/cpuset: Add cpuset.cpus.exclusive.effective for v2")
---
  kernel/cgroup/cpuset.c | 10 +++++++---
  1 file changed, 7 insertions(+), 3 deletions(-)
kernel/cgroup/cpuset.c +0 -2
index b9faadf4af6d..b3e749ede7d1 100644
@@ -1987,12 +1987,16 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
  				adding = cpumask_and(tmp->addmask,
  						     cs->effective_xcpus,
  						     parent->effective_xcpus);
-		} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&
-			   cpumask_subset(xcpus, parent->effective_xcpus)) {

kernel/cgroup/cpuset.c static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, · line 1999

Quoted 10 lines
kernel/cgroup/cpuset.c +6 -0
+		} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus)) {
  			struct cgroup_subsys_state *css;
  			struct cpuset *child;
  			bool exclusive = true;
 
+			if (!cpumask_subset(xcpus, parent->effective_xcpus)) {
+				part_error = PERR_INVCPUS;
+				goto write_error;
+			}
+

A invalid partition means part_error should be set to some error code. This change only makes sure the error code is PERR_INVCPUS. Other than that, I don't see any other tangible change here.

kernel/cgroup/cpuset.c static void compute_partition_effective_cpumask(struct cpuset *cs, · line 2154

Quoted 12 lines
kernel/cgroup/cpuset.c +1 -1
  			/*
  			 * Convert invalid partition to valid has to
  			 * pass the cpu exclusivity test.
@@ -2144,7 +2148,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
  		WARN_ON_ONCE(is_remote_partition(child));
  		WRITE_ONCE(child->prs_err, 0);
  		if (!cpumask_subset(child->effective_xcpus,
-				    cs->effective_xcpus))
+				    new_xcpus))
  			WRITE_ONCE(child->prs_err, PERR_INVCPUS);
  		else if (populated &&
  			 cpumask_subset(new_ecpus, child->effective_xcpus))

This hunk should probably be grouped into the same patch making change to compute_partition_effective_cpumask().

Cheers,
Longman

#24 lore raw

Re: [PATCH 12/17] cgroup/cpuset: Invalidate children outside the new CPU mask

Quoted 31 lines · Waiman Long
Quoted 30 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

When cpuset.cpus changes, compute_partition_effective_cpumask() builds a new exclusive mask but checks child partitions against cs->effective_xcpus. That field still contains the old mask, so a child that no longer fits can remain valid.

Use new_xcpus for the check. A later partcmd_update() may revisit the newly invalid child. Report PERR_INVCPUS if the child CPUs are outside the parent effective exclusive mask so that this visit does not make the child valid again.

Fixes 0c7f293efc87 ("cgroup/cpuset: Add cpuset.cpus.exclusive.effective for v2")

---
  kernel/cgroup/cpuset.c | 10 +++++++---

  1 file changed, 7 insertions(+), 3 deletions(-)
kernel/cgroup/cpuset.c +1 -2
index b9faadf4af6d..b3e749ede7d1 100644
@@ -1987,12 +1987,16 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
                   adding = cpumask_and(tmp->addmask,
                                cs->effective_xcpus,
                                parent->effective_xcpus);
-        } else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&
-               cpumask_subset(xcpus, parent->effective_xcpus)) {
+        } else if (is_partition_invalid(cs) && !cpumask_empty(xcpus)) {
               struct cgroup_subsys_state *css;
Quoted 8 lines

kernel/cgroup/cpuset.c static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, · line 1999

Quoted 7 lines
kernel/cgroup/cpuset.c +4 -0
               struct cpuset *child;
               bool exclusive = true;
   +            if (!cpumask_subset(xcpus, parent->effective_xcpus)) {
+                part_error = PERR_INVCPUS;
+                goto write_error;
+            }
+

A invalid partition means part_error should be set to some error code. This change only makes sure the error code is PERR_INVCPUS. Other than that, I don't see any other tangible change here.

Thanks, Longman.

I think there is a functional difference here. part_error is a local variable initialized to PERR_NONE on every call to update_parent_effective_cpumask(); it is not initialized from cs->prs_err.

With the current condition:

} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&

	   cpumask_subset(xcpus, parent->effective_xcpus)) {

an invalid partition whose CPUs are no longer a subset of the parent's effective exclusive mask skips this branch entirely, leaving part_error as PERR_NONE.

The later state transition then does:

case PRS_INVALID_ROOT:
case PRS_INVALID_ISOLATED:

	if (!part_error)
		new_prs = -old_prs;
	break;

so the partition can be changed back to a valid state.

Therefore, this change does more than set the error code to PERR_INVCPUS; it also prevents an out-of-mask invalid partition from being made valid again.

Quoted 6 lines
Quoted 6 lines
kernel/cgroup/cpuset.c +0 -0
               /*
                * Convert invalid partition to valid has to
                * pass the cpu exclusivity test.
@@ -2144,7 +2148,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs,
           WARN_ON_ONCE(is_remote_partition(child));
           WRITE_ONCE(child->prs_err, 0);
Quoted 8 lines
Quoted 6 lines
kernel/cgroup/cpuset.c +1 -1
           if (!cpumask_subset(child->effective_xcpus,
-                    cs->effective_xcpus))
+                    new_xcpus))
               WRITE_ONCE(child->prs_err, PERR_INVCPUS);

          else if (populated && cpumask_subset(new_ecpus, child->effective_xcpus))

This hunk should probably be grouped into the same patch making change to compute_partition_effective_cpumask().

I have included these changes in v4 and moved both changes from this patch into the earlier patch that modifies compute_partition_effective_cpumask().

I have also temporarily dropped all selftest patches, including those posted in v3, as the current test design needs some rework.

Thanks,
Guopeng

#25 lore raw

PATCH 13/17 selftests/cgroup: Test child invalidation after shrinking cpuset.cpus

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Shrink a parent CPU mask in root/root, root/isolated and isolated/root configurations. Check that the child becomes invalid and that effective and isolated CPU masks reflect the new ownership.

---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 5 +++++
 1 file changed, 5 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +5 -0
index fb11a3ab0f35..7345e89e22b7 100755
@@ -331,6 +331,11 @@ TEST_MATRIX=(
 	" C0-3:X1-3:P2 C1-3:X2-3:P2 C2-3:X3:P2 \
 				   .      .    C4:X     .      .     0 A1:1-3|A2:1-3|A3:2-3|XA2:|XA3: A1:P2|A2:P-2|A3:P-2 1-3"
 
+	# A child outside its parent's new CPU mask becomes invalid
+	" C0-3:P1  C2-3:P1  .      .    C0-2     .      .      .     0 A1:0-2|A2:2 A1:P1|A2:P-1"
+	" C0-3:P1  C2-3:P2  .      .    C0-2     .      .      .     0 A1:0-2|A2:2 A1:P1|A2:P-2 ."
+	" C0-3:P2  C2-3:P1  .      .    C0-2     .      .      .     0 A1:0-2|A2:2 A1:P2|A2:P-1 0-2"
+
 	# Direct invalidation returns child CPUs to the parent or its owner
 	" C1-4:P2  C3-4:P1  .      . T:O1=0:O2=0:O4=0 . .      . \
 					   0 A1:3|A2:3 A1:P2|A2:P-1 1-4"
Signature · 1 line
2.43.0
#26 lore raw

PATCH 14/17 cgroup/cpuset: Publish cpus_allowed before partition updates

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_cpumask() calls partition_cpus_change() before copying the new cpus_allowed mask. A remote partition update can propagate through an ancestor and revisit the cpuset while the old mask is still visible. The second visit then adds back CPUs that the first visit released, leaving them allocated after the cpuset is removed.

Copy cpus_allowed before partition_cpus_change(). All checks and allocations that can fail have completed by this point, and cpuset_mutex remains held for the rest of the update, so the early copy needs no rollback. Keep effective_xcpus unchanged until afterward so the partition code can still calculate the old-to-new difference.

Fixes f62a5d39368e ("cgroup/cpuset: Remove remote_partition_check() & make update_cpumasks_hier() handle remote partition")
---
 kernel/cgroup/cpuset.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)
kernel/cgroup/cpuset.c +8 -1
index b3e749ede7d1..9e13fc962f41 100644
@@ -2555,10 +2555,17 @@ static int update_cpumask(struct cpuset *cs, struct cpuset *trialcs,
 	 */
 	force = !cpumask_equal(cs->effective_xcpus, trialcs->effective_xcpus);
 
+	/*
+	 * remote_cpus_update() can propagate through an ancestor and revisit
+	 * this cpuset. Make sure that it sees the new configured CPU mask.
+	 */
+	spin_lock_irq(&callback_lock);
+	cpumask_copy(cs->cpus_allowed, trialcs->cpus_allowed);
+	spin_unlock_irq(&callback_lock);
+
 	partition_cpus_change(cs, trialcs, &tmp);
 
 	spin_lock_irq(&callback_lock);
-	cpumask_copy(cs->cpus_allowed, trialcs->cpus_allowed);
 	cpumask_copy(cs->effective_xcpus, trialcs->effective_xcpus);
 	if ((old_prs > 0) && !is_partition_valid(cs))
 		reset_partition_data(cs);
Signature · 1 line
2.43.0
#27 lore raw

PATCH 15/17 selftests/cgroup: Test shrinking cpuset.cpus in a remote partition

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

Cover remote partition shrink with and without a local isolated child. Check that removed CPUs return to the top cpuset and that child invalidation preserves isolated CPU accounting.

---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 9 +++++++++
 1 file changed, 9 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +9 -0
index 7345e89e22b7..ad01016ddc8d 100755
@@ -520,6 +520,15 @@ REMOTE_TEST_MATRIX=(
 	"  C1-4:P1   .   C1-2:P1  C2-4:P2  .       .  \
 	      .      .     .       C1-2    .       .     p1:3-4|c11:1-2|c12:3-4 \
 							 p1:P1|c11:P1|c12:P-2"
+	# Shrinking a remote partition must return removed CPUs to the top
+	# partition even when it has no child partitions.
+	"  C1-4:P1   .      .       .       .       .  \
+	    C1-3     .      .       .       .       .     p1:1-3 p1:P1 ."
+	# A local child invalidated by a remote parent's CPU change must return
+	# isolated accounting to the remote parent or the top partition.
+	"  C1-4:P1   .   C3-4:P2   .       .       .  \
+	    C1-3     .     .       .       .       .     p1:1-3|c11:3 \
+							 p1:P1|c11:P-2 ."
 	# Cpusets with empty cpuset.cpus should inherit parent's effective_cpus
 	"  C1-4:P1 C5-6   C1-2     .       C5      .  \
 	      .      P1    P1      .       .       .     p1:3-4|p2:5-6|c11:1-2|c12:3-4|c21:5|c22:5-6 \
Signature · 1 line
2.43.0
#28 lore raw

PATCH 16/17 cgroup/cpuset: Fix isolated accounting on propagated invalidation

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_cpumasks_hier() invalidates a local partition when its parent becomes a member or an invalid partition. Its CPUs return to the nearest valid partition ancestor, but isolated_cpus still reflects the old partition type.

Update isolated_cpus before resetting a local partition whose state changes from valid to invalid below an invalid parent. Remote partitions are excluded because remote_partition_disable() already updates their isolated CPU accounting.

Fixes 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions")
---
 kernel/cgroup/cpuset.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)
kernel/cgroup/cpuset.c +14 -0
index 9e13fc962f41..4af8e348e838 100644
@@ -2236,9 +2236,12 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp,
 	cpuset_for_each_descendant_pre(cp, pos_css, cs) {
 		struct cpuset *parent = parent_cs(cp);
 		bool remote = is_remote_partition(cp);
+		bool was_remote = remote;
 		bool update_parent = false;
+		int owner_prs;
 
 		old_prs = new_prs = cp->partition_root_state;
+		owner_prs = old_prs;
 
 		/*
 		 * For child remote partition root (!= cs), we need to call
@@ -2338,7 +2341,18 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp,
 			new_prs = cp->partition_root_state;
 		}
 
+		/*
+		 * With no valid parent partition left, this partition's CPUs
+		 * return to the nearest valid partition ancestor.
+		 */
+		if (!was_remote && old_prs > 0 && new_prs < 0 &&
+		    !is_partition_valid(parent))
+			owner_prs = partition_owner(cp)->partition_root_state;
+
 		spin_lock_irq(&callback_lock);
+		if (old_prs != owner_prs)
+			isolated_cpus_update(old_prs, owner_prs,
+					     cp->effective_xcpus);
 		cpumask_copy(cp->effective_cpus, tmp->new_cpus);
 		cp->partition_root_state = new_prs;
 		/*
Signature · 1 line
2.43.0
#29 lore raw

PATCH 17/17 selftests/cgroup: Test isolation accounting on propagated invalidation

In reply to Guopeng Zhang

From: Guopeng Zhang <[email protected]>

State-matrix cases exercise a child invalidated by its parent and a nested partition invalidated through an already invalid parent.

---
 tools/testing/selftests/cgroup/test_cpuset_prs.sh | 6 ++++++
 1 file changed, 6 insertions(+)
tools/testing/selftests/cgroup/test_cpuset_prs.sh +6 -0
index ad01016ddc8d..89f54c689336 100755
@@ -340,6 +340,12 @@ TEST_MATRIX=(
 	" C1-4:P2  C3-4:P1  .      . T:O1=0:O2=0:O4=0 . .      . \
 					   0 A1:3|A2:3 A1:P2|A2:P-1 1-4"
 
+	# Invalidation propagated from a parent returns all descendant CPUs
+	" C0-3:P1  C2-3:P2  .      .      P0      .      .      . \
+					   0 A1:0-3|A2:2-3 A1:P0|A2:P-2 ."
+	" C0-5:P2  C2-5:P2 C4-5:P1 .      .       P0     .      . \
+					   0 A1:0-5|A2:2-5|A3:4-5 A1:P2|A2:P0|A3:P-1 0-5"
+
 	# Local partition CPU change tests
 	" C0-5:P2  C4-5:P1  .      .      .    C3-5     .      .     0 A1:0-2|A2:3-5 A1:P2|A2:P1 0-2"
 	" C0-5:P2  C4-5:P1  .      .    C1-5     .      .      .     0 A1:1-3|A2:4-5 A1:P2|A2:P1 1-3"
Signature · 1 line
2.43.0
reviewer
#30 lore raw
In reply to Guopeng Zhang
Quoted 48 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_prstate() uses effective_xcpus for isolation accounting and housekeeping checks when switching a partition between root and isolated. The mask also includes CPUs granted to valid child partitions. Such a type change can alter isolation accounting for child-owned CPUs or fail a housekeeping check because of a CPU the parent does not own.

A rejected type change can leave CPUs assigned to the invalidated partition instead of returning them to the partition that owns the invalidated subtree. When cpuset.cpus changes, child validation can use the old parent mask, and a remote update can observe stale cpus_allowed when it revisits a cpuset. Direct and propagated invalidation can leave isolated_cpus reflecting the released partition's type instead of the type of the partition receiving its CPUs. Releasing an isolated partition can clear boot-isolated CPUs from isolated_cpus.

Limit type-change checks and accounting to CPUs owned directly by the partition. Preserve boot-isolated CPUs when a partition is released, return CPUs after rejected type changes, validate children against the new parent mask, and publish cpus_allowed before a partition update can revisit the cpuset. Fix isolation accounting during direct and propagated invalidation.

Patch 1 removes an invalid selftest comparison that makes test_cpuset_prs.sh fail when boot-time domain isolation is configured. Each kernel fix is followed by a focused selftest update.

Guopeng Zhang (17):
selftests/cgroup: Drop invalid boot isolation comparison cgroup/cpuset: Preserve boot-isolated CPUs on partition release selftests/cgroup: Test boot-isolated CPU partition release cgroup/cpuset: Limit type-change accounting to owned CPUs selftests/cgroup: Test isolated CPU accounting on type changes cgroup/cpuset: Validate type changes against owned CPUs selftests/cgroup: Test type-change validation with child-owned CPUs cgroup/cpuset: Release CPUs when a type change is rejected selftests/cgroup: Test rejected partition type changes cgroup/cpuset: Fix isolated accounting on direct child invalidation selftests/cgroup: Test isolation accounting on direct child
invalidation
cgroup/cpuset: Invalidate children outside the new CPU mask selftests/cgroup: Test child invalidation after shrinking cpuset.cpus cgroup/cpuset: Publish cpus_allowed before partition updates selftests/cgroup: Test shrinking cpuset.cpus in a remote partition cgroup/cpuset: Fix isolated accounting on propagated invalidation selftests/cgroup: Test isolation accounting on propagated invalidation

Quoted 6 lines
  kernel/cgroup/cpuset.c                        | 162 ++++++++++--
  .../selftests/cgroup/test_cpuset_prs.sh       | 231 +++++++++++++++++-
  2 files changed, 361 insertions(+), 32 deletions(-)

base-commit: 6a746cd265aed59107ebdaa9ce039bb832922969

Hi Guopeng,

This is a fairly large series, which may make the review process challenging. I would suggest splitting it into smaller, more focused patches. It would also be very helpful if you could provide a reproducer for each bug you are trying to fix, so that we can verify the issue and the fix more effectively.

Signature · 2 lines
Best regards
Ridong
#31 lore raw
Quoted 56 lines · Ridong Chen
Quoted 53 lines · Guopeng Zhang

From: Guopeng Zhang <[email protected]>

update_prstate() uses effective_xcpus for isolation accounting and housekeeping checks when switching a partition between root and isolated. The mask also includes CPUs granted to valid child partitions. Such a type change can alter isolation accounting for child-owned CPUs or fail a housekeeping check because of a CPU the parent does not own.

A rejected type change can leave CPUs assigned to the invalidated partition instead of returning them to the partition that owns the invalidated subtree. When cpuset.cpus changes, child validation can use the old parent mask, and a remote update can observe stale cpus_allowed when it revisits a cpuset. Direct and propagated invalidation can leave isolated_cpus reflecting the released partition's type instead of the type of the partition receiving its CPUs. Releasing an isolated partition can clear boot-isolated CPUs from isolated_cpus.

Limit type-change checks and accounting to CPUs owned directly by the partition. Preserve boot-isolated CPUs when a partition is released, return CPUs after rejected type changes, validate children against the new parent mask, and publish cpus_allowed before a partition update can revisit the cpuset. Fix isolation accounting during direct and propagated invalidation.

Patch 1 removes an invalid selftest comparison that makes test_cpuset_prs.sh fail when boot-time domain isolation is configured. Each kernel fix is followed by a focused selftest update.

Guopeng Zhang (17):
   selftests/cgroup: Drop invalid boot isolation comparison cgroup/cpuset: Preserve boot-isolated CPUs on partition release selftests/cgroup: Test boot-isolated CPU partition release cgroup/cpuset: Limit type-change accounting to owned CPUs selftests/cgroup: Test isolated CPU accounting on type changes cgroup/cpuset: Validate type changes against owned CPUs selftests/cgroup: Test type-change validation with child-owned CPUs cgroup/cpuset: Release CPUs when a type change is rejected selftests/cgroup: Test rejected partition type changes cgroup/cpuset: Fix isolated accounting on direct child invalidation selftests/cgroup: Test isolation accounting on direct child invalidation
   cgroup/cpuset: Invalidate children outside the new CPU mask selftests/cgroup: Test child invalidation after shrinking cpuset.cpus cgroup/cpuset: Publish cpus_allowed before partition updates selftests/cgroup: Test shrinking cpuset.cpus in a remote partition cgroup/cpuset: Fix isolated accounting on propagated invalidation selftests/cgroup: Test isolation accounting on propagated invalidation

  kernel/cgroup/cpuset.c                        | 162 ++++++++++-- .../selftests/cgroup/test_cpuset_prs.sh       | 231 +++++++++++++++++-

  2 files changed, 361 insertions(+), 32 deletions(-)
Quoted 6 lines
Quoted 1 line

base-commit: 6a746cd265aed59107ebdaa9ce039bb832922969

Hi Guopeng,

This is a fairly large series, which may make the review process challenging. I would suggest splitting it into smaller, more focused patches. It would also be very helpful if you could provide a reproducer for each bug you are trying to fix, so that we can verify the issue and the fix more effectively.

Hi Ridong,

Thanks for the suggestion. The current series is indeed fairly large, which makes the individual issues harder to review.

I initially kept these patches in one series because some of the later fixes depend on changes introduced by earlier patches. However, while preserving the necessary dependencies, I can group the fixes by issue, split them into several smaller, more focused series, and submit them separately.

I will also add a reproducer to the changelog of each relevant patch, together with the actual behavior before the fix and the expected behavior.

Sashiko has raised a few additional points that I am still reviewing. I will go through those first, and then reorganize and split the series for the next submission.

Thanks,
Guopeng