* [PATCH for-7.4-fixes v4 0/2] cpuset: preparatory fixes and cleanups for partition rework
@ 2026-11-12 3:19 Ridong Chen
2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent Ridong Chen
2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface Ridong Chen
0 siblings, 2 replies; 7+ messages in thread
From: Ridong Chen @ 2026-11-12 3:19 UTC (permalink / raw)
To: Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný,
Shuah Khan
Cc: Ridong Chen, cgroups, linux-kernel, linux-kselftest,
Guopeng Zhang, Ridong Chen
From: Ridong Chen <chenridong@xiaomi.com>
This series is part of the cpuset local partition rework [1], which I
started while at Huawei and am now continuing at Xiaomi.
Patches 1 and 2 of v3 have been merged into cgroup/for-7.4-fixes. This
version only has the remaining patches 3 and 4 of v3, rebased onto
cgroup/for-7.4-fixes.
Both patches include a bug fix besides the restructuring (Waiman):
1) settle the isolation state of CPUs given back to the parent in
partition_xcpus_del(). This fixes the isolated CPU leak reported by
Guopeng [2], where an isolated partition is invalidated after its
parent root partition has become invalid and its CPUs are left in
isolated_cpus.
2) generalize validate_partition() so it can be used for both local
and remote partitions. This fixes a false PERR_NOCPUS error when
expanding a remote partition, where the no CPU left for tasks check
is done against the cgroup parent instead of top_cpuset.
Patch 2 adds a test case to test_cpuset_prs.sh for the bug it fixes.
test_cpuset_prs.sh passes with this series applied.
[1] https://lore.kernel.org/all/20261101114206.2434398-1-ridong.chen@linux.dev/
[2] https://lore.kernel.org/all/20261105130344.664336-4-guopeng.zhang@linux.dev/
Changes in v4:
- Drop patches 1 and 2, which have been merged.
- Rebase onto cgroup/for-7.4-fixes, which includes commit 56681bc448a8
("cgroup/cpuset: Correctly set isolated_cpus when switching partition state").
- Patch 1: describe the isolated CPU leak in the commit message, add
Fixes, Reported-by and Closes tags (Waiman, Guopeng).
- Patch 2: describe the remote partition expansion fix in the commit
message, add a Fixes tag and a test case for it (Waiman).
- Link to v3: https://lore.kernel.org/all/20261107120100.3587595-1-ridong.chen@linux.dev/
Ridong Chen (2):
cpuset: settle isolation state when CPUs are given back to parent
cpuset: generalize the validate_partition() interface
kernel/cgroup/cpuset.c | 217 +++++++-----------
.../selftests/cgroup/test_cpuset_prs.sh | 5 +
2 files changed, 83 insertions(+), 139 deletions(-)
base-commit: 56681bc448a80bb2473026b41c44c133766bc38d
--
2.34.1
^ permalink raw reply [flat|nested] 7+ messages in thread* [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent 2026-11-12 3:19 [PATCH for-7.4-fixes v4 0/2] cpuset: preparatory fixes and cleanups for partition rework Ridong Chen @ 2026-11-12 3:19 ` Ridong Chen 2026-11-12 8:33 ` Guopeng Zhang 2026-11-12 19:14 ` Waiman Long 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface Ridong Chen 1 sibling, 2 replies; 7+ messages in thread From: Ridong Chen @ 2026-11-12 3:19 UTC (permalink / raw) To: Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: Ridong Chen, cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen From: Ridong Chen <chenridong@xiaomi.com> partition_xcpus_del() only updates isolated_cpus when the partition that gives back the CPUs has a different state than its parent. CPUs that were further distributed to descendant isolated partitions are not taken into account, so isolated_cpus_release() has to be called afterwards in several places to walk up the ancestors and fix isolated_cpus. This misses some cases and leaks isolated CPUs, as reported by Guopeng. An isolated partition can be invalidated after its parent has already become invalid, and its CPUs are left in isolated_cpus. For example, on a 32-CPU system with all CPUs online and without isolcpus= or nohz_full= boot parameters: cd /sys/fs/cgroup echo +cpuset > cgroup.subtree_control mkdir A echo 0,2-31 > A/cpuset.cpus echo root > A/cpuset.cpus.partition echo +cpuset > A/cgroup.subtree_control mkdir A/B echo 2-3 > A/B/cpuset.cpus echo isolated > A/B/cpuset.cpus.partition cat cpuset.cpus.isolated 2-3 echo 0 > /sys/devices/system/cpu/cpu1/online cat A/B/cpuset.cpus.partition isolated invalid cat cpuset.cpus.isolated 2-3 When A is invalidated, partition_xcpus_del() gives CPUs 0,2-31 back to top cpuset. Both A and top cpuset are root partitions, so isolated_cpus is left untouched although CPUs 2-3 were distributed to the isolated B. B is then invalidated with an invalid parent, and nothing releases CPUs 2-3 either. Make partition_xcpus_del() always set the isolation state of the given back CPUs to the state of the parent that receives them. As the effective_xcpus of a partition covers the CPUs distributed to its descendants, the isolation state of those CPUs is settled at the same time. Boot-time isolated CPUs are still preserved by isolated_cpu_update(). compute_partition_effective_cpumask() invalidates a child partition without going through partition_xcpus_del(), so do the same there: the CPUs of the invalidated child take the isolation state of its parent. As the parent may be shrunk and its effective_xcpus not updated yet, only do so for the CPUs the parent still owns. This is to keep the isolation state correct for now, the in-place invalidation here will be removed later in the partition rework. isolated_cpus_update() assumes the old and new states are different, which no longer holds now that partition_xcpus_del() proceeds regardless of the states. Drop its @old_prs argument and the WARN_ON_ONCE() so that it simply sets the isolation state of the given CPUs, and use it everywhere. The state check in partition_xcpus_add() is dropped as well, as setting the state is a no-op when the states are the same. isolated_cpus_release() is no longer needed and is removed. Fixes: 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions") Reported-by: Guopeng Zhang <zhangguopeng@kylinos.cn> Closes: https://lore.kernel.org/all/20261105130344.664336-4-guopeng.zhang@linux.dev/ Signed-off-by: Ridong Chen <chenridong@xiaomi.com> --- kernel/cgroup/cpuset.c | 98 ++++++++---------------------------------- 1 file changed, 19 insertions(+), 79 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index 06ceeb0ac8c0..c7d4cfb615b7 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1244,17 +1244,17 @@ static bool isolated_cpu_update(int new_prs, int cpu) } /* - * isolated_cpus_update - Update the isolated_cpus mask - * @old_prs: old partition_root_state - * @new_prs: new partition_root_state - * @xcpus: exclusive CPUs with state change + * isolated_cpus_update - Set the isolation state of @xcpus + * @new_prs: partition root state that @xcpus will belong to + * @xcpus: exclusive CPUs whose isolation state may change + * + * CPUs isolated at boot time are kept isolated. */ -static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus) +static void isolated_cpus_update(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); for_each_cpu(cpu, xcpus) @@ -1263,45 +1263,6 @@ static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus update_housekeeping = true; } -/* - * isolated_cpus_release - Remove the relevant CPUs in @xcpus from isolated_cpus - * @cs: the current cpuset - * @xcpus: the set of effective xcpus to be considered for removal - * - * This function is called when an isolated partition is invalidated or when - * some of its effective xcpus are to be removed from isolated_cpus due to - * changes in parent cpuset. The given @xcpus must be modifiable as the function - * will update its value to determine the right set of CPUs to be removed. - */ -static void isolated_cpus_release(struct cpuset *cs, struct cpumask *xcpus) -{ - struct cpuset *ancestor = parent_cs(cs); - struct cpuset *iancestor = NULL; - - /* - * Search the furthest isolated partition root ancestor available - * until top_cpuset or a remote partition root is hit. - */ - for (; ancestor; ancestor = parent_cs(ancestor)) { - if (ancestor->partition_root_state == PRS_ISOLATED) - iancestor = ancestor; - if (is_remote_partition(ancestor)) - break; - } - - /* - * If a furthest isolated partition acnestor is found, use it to - * determine which of the CPUs in @xcpus should be removed from - * isolated_cpus. Otherwise, all of them will be removed. - */ - if (iancestor && !cpumask_andnot(xcpus, xcpus, iancestor->effective_xcpus)) - return; /* No CPU should be removed from isolated_cpus */ - - spin_lock_irq(&callback_lock); - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, xcpus); - spin_unlock_irq(&callback_lock); -} - /* * partition_xcpus_add - Add new exclusive CPUs to partition * @new_prs: new partition_root_state @@ -1325,9 +1286,7 @@ static void partition_xcpus_add(int new_prs, struct cpuset *parent, if (parent == &top_cpuset) cpumask_or(subpartitions_cpus, subpartitions_cpus, xcpus); - if (new_prs != parent->partition_root_state) - isolated_cpus_update(parent->partition_root_state, new_prs, - xcpus); + isolated_cpus_update(new_prs, xcpus); cpumask_andnot(parent->effective_cpus, parent->effective_cpus, xcpus); } @@ -1355,9 +1314,8 @@ static void partition_xcpus_del(int old_prs, struct cpuset *parent, if (parent == &top_cpuset) cpumask_andnot(subpartitions_cpus, subpartitions_cpus, xcpus); - if (old_prs != parent->partition_root_state) - isolated_cpus_update(old_prs, parent->partition_root_state, - xcpus); + /* CPUs given back, including those of descendants, take parent's state */ + isolated_cpus_update(parent->partition_root_state, xcpus); cpumask_or(parent->effective_cpus, parent->effective_cpus, xcpus); cpumask_and(parent->effective_cpus, parent->effective_cpus, cpu_active_mask); @@ -1612,6 +1570,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, struct cgroup_subsys_state *css; struct cpuset *child; bool populated = partition_is_populated(cs, NULL); + static cpumask_t xcpus; /* Protected by cpuset_mutex */ /* * Check child partition roots to see if they should be invalidated @@ -1640,14 +1599,15 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, int parent_prs = cs->partition_root_state; /* - * Invalidate child partition and update isolated_cpus - * if necessary. + * Invalidate child partition. Its CPUs still owned by + * cs take the isolation state of cs. effective_xcpus + * of cs may not be updated yet, so recompute it. */ spin_lock_irq(&callback_lock); WRITE_ONCE(child->prs_err, PERR_NOCPUS); - if ((old_prs == PRS_ISOLATED) && (parent_prs == PRS_ROOT)) - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, - child->effective_xcpus); + compute_excpus(cs, &xcpus); + cpumask_and(&xcpus, &xcpus, child->effective_xcpus); + isolated_cpus_update(parent_prs, &xcpus); child->partition_root_state = -old_prs; spin_unlock_irq(&callback_lock); notify_partition_change(child, old_prs); @@ -2090,22 +2050,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, * * A partition error happens when parent has tasks and all * its effective CPUs will have to be distributed out. - * - * For an isolated partition with a shrunk root partition parent - * we may need to return some or all of its isolated CPUs. */ nocpu |= cpumask_empty(tmp->delmask); - if ((parent_prs == PRS_ROOT) && (old_prs == PRS_ISOLATED)) { - cpumask_clear(tmp->addmask); - if (nocpu) - cpumask_copy(tmp->addmask, cs->effective_xcpus); - else if (!cpumask_subset(cs->effective_xcpus, parent->effective_xcpus)) - cpumask_andnot(tmp->addmask, cs->effective_xcpus, - parent->effective_xcpus); - if (!cpumask_empty(tmp->addmask)) - isolated_cpus_release(cs, tmp->addmask); - /* Expansion of parent's effective_xcpus is handled below */ - } if (nocpu) { part_error = PERR_NOCPUS; if (is_partition_valid(cs)) @@ -2345,18 +2291,12 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp, /* * When parent is not a valid partition root, * child partition roots become invalid too. - * - * For a to be invalidated isolated partition, - * call return_isolated_cpus() to remove the - * relevant CPUs from isolated_cpus. + * The isolation state of their CPUs has been + * settled when the parent gave them back. */ if (is_partition_valid(cp)) new_prs = -old_prs; - if (old_prs == PRS_ISOLATED) { - cpumask_copy(tmp->delmask, cp->effective_xcpus); - isolated_cpus_release(cp, tmp->delmask); - } WRITE_ONCE(cp->prs_err, is_partition_invalid(parent) ? PERR_INVPARENT : PERR_NOTPART); @@ -3132,7 +3072,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, tmpmask.new_cpus); + isolated_cpus_update(new_prs, tmpmask.new_cpus); spin_unlock_irq(&callback_lock); /* Force update if partition is disabled & update effective_xcpus */ -- 2.34.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent Ridong Chen @ 2026-11-12 8:33 ` Guopeng Zhang 2026-11-12 19:14 ` Waiman Long 1 sibling, 0 replies; 7+ messages in thread From: Guopeng Zhang @ 2026-11-12 8:33 UTC (permalink / raw) To: Ridong Chen, Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen 在 2026/11/12 11:19, Ridong Chen 写道: > From: Ridong Chen <chenridong@xiaomi.com> > > partition_xcpus_del() only updates isolated_cpus when the partition that > gives back the CPUs has a different state than its parent. CPUs that were > further distributed to descendant isolated partitions are not taken into > account, so isolated_cpus_release() has to be called afterwards in > several places to walk up the ancestors and fix isolated_cpus. > > This misses some cases and leaks isolated CPUs, as reported by Guopeng. > An isolated partition can be invalidated after its parent has already > become invalid, and its CPUs are left in isolated_cpus. For example, on a > 32-CPU system with all CPUs online and without isolcpus= or nohz_full= > boot parameters: > > cd /sys/fs/cgroup > echo +cpuset > cgroup.subtree_control > mkdir A > echo 0,2-31 > A/cpuset.cpus > echo root > A/cpuset.cpus.partition > echo +cpuset > A/cgroup.subtree_control > mkdir A/B > echo 2-3 > A/B/cpuset.cpus > echo isolated > A/B/cpuset.cpus.partition > cat cpuset.cpus.isolated > 2-3 > echo 0 > /sys/devices/system/cpu/cpu1/online > cat A/B/cpuset.cpus.partition > isolated invalid > cat cpuset.cpus.isolated > 2-3 > > When A is invalidated, partition_xcpus_del() gives CPUs 0,2-31 back to > top cpuset. Both A and top cpuset are root partitions, so isolated_cpus > is left untouched although CPUs 2-3 were distributed to the isolated B. > B is then invalidated with an invalid parent, and nothing releases CPUs > 2-3 either. > > Make partition_xcpus_del() always set the isolation state of the given > back CPUs to the state of the parent that receives them. As the > effective_xcpus of a partition covers the CPUs distributed to its > descendants, the isolation state of those CPUs is settled at the same > time. Boot-time isolated CPUs are still preserved by isolated_cpu_update(). > > compute_partition_effective_cpumask() invalidates a child partition > without going through partition_xcpus_del(), so do the same there: the > CPUs of the invalidated child take the isolation state of its parent. > > As the parent may be shrunk and its effective_xcpus not updated yet, > only do so for the CPUs the parent still owns. This is to keep the > isolation state correct for now, the in-place invalidation here will be > removed later in the partition rework. > > isolated_cpus_update() assumes the old and new states are different, > which no longer holds now that partition_xcpus_del() proceeds regardless > of the states. Drop its @old_prs argument and the WARN_ON_ONCE() so that > it simply sets the isolation state of the given CPUs, and use it > everywhere. The state check in partition_xcpus_add() is dropped as well, > as setting the state is a no-op when the states are the same. > > isolated_cpus_release() is no longer needed and is removed. > > Fixes: 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions") > Reported-by: Guopeng Zhang <zhangguopeng@kylinos.cn> > Closes: https://lore.kernel.org/all/20261105130344.664336-4-guopeng.zhang@linux.dev/ > Signed-off-by: Ridong Chen <chenridong@xiaomi.com> > --- > kernel/cgroup/cpuset.c | 98 ++++++++---------------------------------- > 1 file changed, 19 insertions(+), 79 deletions(-) > > diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c > index 06ceeb0ac8c0..c7d4cfb615b7 100644 > --- a/kernel/cgroup/cpuset.c > +++ b/kernel/cgroup/cpuset.c > @@ -1244,17 +1244,17 @@ static bool isolated_cpu_update(int new_prs, int cpu) > } > > /* > - * isolated_cpus_update - Update the isolated_cpus mask > - * @old_prs: old partition_root_state > - * @new_prs: new partition_root_state > - * @xcpus: exclusive CPUs with state change > + * isolated_cpus_update - Set the isolation state of @xcpus > + * @new_prs: partition root state that @xcpus will belong to > + * @xcpus: exclusive CPUs whose isolation state may change > + * > + * CPUs isolated at boot time are kept isolated. > */ > -static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus) > +static void isolated_cpus_update(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); > for_each_cpu(cpu, xcpus) > @@ -1263,45 +1263,6 @@ static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus > update_housekeeping = true; > } > > -/* > - * isolated_cpus_release - Remove the relevant CPUs in @xcpus from isolated_cpus > - * @cs: the current cpuset > - * @xcpus: the set of effective xcpus to be considered for removal > - * > - * This function is called when an isolated partition is invalidated or when > - * some of its effective xcpus are to be removed from isolated_cpus due to > - * changes in parent cpuset. The given @xcpus must be modifiable as the function > - * will update its value to determine the right set of CPUs to be removed. > - */ > -static void isolated_cpus_release(struct cpuset *cs, struct cpumask *xcpus) > -{ > - struct cpuset *ancestor = parent_cs(cs); > - struct cpuset *iancestor = NULL; > - > - /* > - * Search the furthest isolated partition root ancestor available > - * until top_cpuset or a remote partition root is hit. > - */ > - for (; ancestor; ancestor = parent_cs(ancestor)) { > - if (ancestor->partition_root_state == PRS_ISOLATED) > - iancestor = ancestor; > - if (is_remote_partition(ancestor)) > - break; > - } > - > - /* > - * If a furthest isolated partition acnestor is found, use it to > - * determine which of the CPUs in @xcpus should be removed from > - * isolated_cpus. Otherwise, all of them will be removed. > - */ > - if (iancestor && !cpumask_andnot(xcpus, xcpus, iancestor->effective_xcpus)) > - return; /* No CPU should be removed from isolated_cpus */ > - > - spin_lock_irq(&callback_lock); > - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, xcpus); > - spin_unlock_irq(&callback_lock); > -} > - > /* > * partition_xcpus_add - Add new exclusive CPUs to partition > * @new_prs: new partition_root_state > @@ -1325,9 +1286,7 @@ static void partition_xcpus_add(int new_prs, struct cpuset *parent, > if (parent == &top_cpuset) > cpumask_or(subpartitions_cpus, subpartitions_cpus, xcpus); > > - if (new_prs != parent->partition_root_state) > - isolated_cpus_update(parent->partition_root_state, new_prs, > - xcpus); > + isolated_cpus_update(new_prs, xcpus); > > cpumask_andnot(parent->effective_cpus, parent->effective_cpus, xcpus); > } > @@ -1355,9 +1314,8 @@ static void partition_xcpus_del(int old_prs, struct cpuset *parent, > if (parent == &top_cpuset) > cpumask_andnot(subpartitions_cpus, subpartitions_cpus, xcpus); > > - if (old_prs != parent->partition_root_state) > - isolated_cpus_update(old_prs, parent->partition_root_state, > - xcpus); > + /* CPUs given back, including those of descendants, take parent's state */ > + isolated_cpus_update(parent->partition_root_state, xcpus); > > cpumask_or(parent->effective_cpus, parent->effective_cpus, xcpus); > cpumask_and(parent->effective_cpus, parent->effective_cpus, cpu_active_mask); > @@ -1612,6 +1570,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, > struct cgroup_subsys_state *css; > struct cpuset *child; > bool populated = partition_is_populated(cs, NULL); > + static cpumask_t xcpus; /* Protected by cpuset_mutex */ > > /* > * Check child partition roots to see if they should be invalidated > @@ -1640,14 +1599,15 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, > int parent_prs = cs->partition_root_state; > > /* > - * Invalidate child partition and update isolated_cpus > - * if necessary. > + * Invalidate child partition. Its CPUs still owned by > + * cs take the isolation state of cs. effective_xcpus > + * of cs may not be updated yet, so recompute it. > */ > spin_lock_irq(&callback_lock); > WRITE_ONCE(child->prs_err, PERR_NOCPUS); > - if ((old_prs == PRS_ISOLATED) && (parent_prs == PRS_ROOT)) > - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, > - child->effective_xcpus); > + compute_excpus(cs, &xcpus); > + cpumask_and(&xcpus, &xcpus, child->effective_xcpus); > + isolated_cpus_update(parent_prs, &xcpus); > child->partition_root_state = -old_prs; > spin_unlock_irq(&callback_lock); > notify_partition_change(child, old_prs); > @@ -2090,22 +2050,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, > * > * A partition error happens when parent has tasks and all > * its effective CPUs will have to be distributed out. > - * > - * For an isolated partition with a shrunk root partition parent > - * we may need to return some or all of its isolated CPUs. > */ > nocpu |= cpumask_empty(tmp->delmask); > - if ((parent_prs == PRS_ROOT) && (old_prs == PRS_ISOLATED)) { > - cpumask_clear(tmp->addmask); > - if (nocpu) > - cpumask_copy(tmp->addmask, cs->effective_xcpus); > - else if (!cpumask_subset(cs->effective_xcpus, parent->effective_xcpus)) > - cpumask_andnot(tmp->addmask, cs->effective_xcpus, > - parent->effective_xcpus); > - if (!cpumask_empty(tmp->addmask)) > - isolated_cpus_release(cs, tmp->addmask); > - /* Expansion of parent's effective_xcpus is handled below */ > - } > if (nocpu) { > part_error = PERR_NOCPUS; > if (is_partition_valid(cs)) > @@ -2345,18 +2291,12 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp, > /* > * When parent is not a valid partition root, > * child partition roots become invalid too. > - * > - * For a to be invalidated isolated partition, > - * call return_isolated_cpus() to remove the > - * relevant CPUs from isolated_cpus. > + * The isolation state of their CPUs has been > + * settled when the parent gave them back. > */ > if (is_partition_valid(cp)) > new_prs = -old_prs; > > - if (old_prs == PRS_ISOLATED) { > - cpumask_copy(tmp->delmask, cp->effective_xcpus); > - isolated_cpus_release(cp, tmp->delmask); > - } > WRITE_ONCE(cp->prs_err, > is_partition_invalid(parent) > ? PERR_INVPARENT : PERR_NOTPART); > @@ -3132,7 +3072,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, tmpmask.new_cpus); > + isolated_cpus_update(new_prs, tmpmask.new_cpus); > spin_unlock_irq(&callback_lock); > > /* Force update if partition is disabled & update effective_xcpus */ Reviewed-by: Guopeng Zhang <zhangguopeng@kylinos.cn> Thanks, Guopeng ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent Ridong Chen 2026-11-12 8:33 ` Guopeng Zhang @ 2026-11-12 19:14 ` Waiman Long 1 sibling, 0 replies; 7+ messages in thread From: Waiman Long @ 2026-11-12 19:14 UTC (permalink / raw) To: Ridong Chen, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen On 11/11/26 10:19 PM, Ridong Chen wrote: > From: Ridong Chen <chenridong@xiaomi.com> > > partition_xcpus_del() only updates isolated_cpus when the partition that > gives back the CPUs has a different state than its parent. CPUs that were > further distributed to descendant isolated partitions are not taken into > account, so isolated_cpus_release() has to be called afterwards in > several places to walk up the ancestors and fix isolated_cpus. > > This misses some cases and leaks isolated CPUs, as reported by Guopeng. > An isolated partition can be invalidated after its parent has already > become invalid, and its CPUs are left in isolated_cpus. For example, on a > 32-CPU system with all CPUs online and without isolcpus= or nohz_full= > boot parameters: > > cd /sys/fs/cgroup > echo +cpuset > cgroup.subtree_control > mkdir A > echo 0,2-31 > A/cpuset.cpus > echo root > A/cpuset.cpus.partition > echo +cpuset > A/cgroup.subtree_control > mkdir A/B > echo 2-3 > A/B/cpuset.cpus > echo isolated > A/B/cpuset.cpus.partition > cat cpuset.cpus.isolated > 2-3 > echo 0 > /sys/devices/system/cpu/cpu1/online > cat A/B/cpuset.cpus.partition > isolated invalid > cat cpuset.cpus.isolated > 2-3 > > When A is invalidated, partition_xcpus_del() gives CPUs 0,2-31 back to > top cpuset. Both A and top cpuset are root partitions, so isolated_cpus > is left untouched although CPUs 2-3 were distributed to the isolated B. > B is then invalidated with an invalid parent, and nothing releases CPUs > 2-3 either. > > Make partition_xcpus_del() always set the isolation state of the given > back CPUs to the state of the parent that receives them. As the > effective_xcpus of a partition covers the CPUs distributed to its > descendants, the isolation state of those CPUs is settled at the same > time. Boot-time isolated CPUs are still preserved by isolated_cpu_update(). > > compute_partition_effective_cpumask() invalidates a child partition > without going through partition_xcpus_del(), so do the same there: the > CPUs of the invalidated child take the isolation state of its parent. > > As the parent may be shrunk and its effective_xcpus not updated yet, > only do so for the CPUs the parent still owns. This is to keep the > isolation state correct for now, the in-place invalidation here will be > removed later in the partition rework. > > isolated_cpus_update() assumes the old and new states are different, > which no longer holds now that partition_xcpus_del() proceeds regardless > of the states. Drop its @old_prs argument and the WARN_ON_ONCE() so that > it simply sets the isolation state of the given CPUs, and use it > everywhere. The state check in partition_xcpus_add() is dropped as well, > as setting the state is a no-op when the states are the same. > > isolated_cpus_release() is no longer needed and is removed. > > Fixes: 11e5f407b64a ("cgroup/cpuset: Keep track of CPUs in isolated partitions") > Reported-by: Guopeng Zhang <zhangguopeng@kylinos.cn> > Closes: https://lore.kernel.org/all/20261105130344.664336-4-guopeng.zhang@linux.dev/ > Signed-off-by: Ridong Chen <chenridong@xiaomi.com> > --- > kernel/cgroup/cpuset.c | 98 ++++++++---------------------------------- > 1 file changed, 19 insertions(+), 79 deletions(-) > > diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c > index 06ceeb0ac8c0..c7d4cfb615b7 100644 > --- a/kernel/cgroup/cpuset.c > +++ b/kernel/cgroup/cpuset.c > @@ -1244,17 +1244,17 @@ static bool isolated_cpu_update(int new_prs, int cpu) > } > > /* > - * isolated_cpus_update - Update the isolated_cpus mask > - * @old_prs: old partition_root_state > - * @new_prs: new partition_root_state > - * @xcpus: exclusive CPUs with state change > + * isolated_cpus_update - Set the isolation state of @xcpus > + * @new_prs: partition root state that @xcpus will belong to > + * @xcpus: exclusive CPUs whose isolation state may change > + * > + * CPUs isolated at boot time are kept isolated. > */ > -static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus) > +static void isolated_cpus_update(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); > for_each_cpu(cpu, xcpus) > @@ -1263,45 +1263,6 @@ static void isolated_cpus_update(int old_prs, int new_prs, struct cpumask *xcpus > update_housekeeping = true; > } > > -/* > - * isolated_cpus_release - Remove the relevant CPUs in @xcpus from isolated_cpus > - * @cs: the current cpuset > - * @xcpus: the set of effective xcpus to be considered for removal > - * > - * This function is called when an isolated partition is invalidated or when > - * some of its effective xcpus are to be removed from isolated_cpus due to > - * changes in parent cpuset. The given @xcpus must be modifiable as the function > - * will update its value to determine the right set of CPUs to be removed. > - */ > -static void isolated_cpus_release(struct cpuset *cs, struct cpumask *xcpus) > -{ > - struct cpuset *ancestor = parent_cs(cs); > - struct cpuset *iancestor = NULL; > - > - /* > - * Search the furthest isolated partition root ancestor available > - * until top_cpuset or a remote partition root is hit. > - */ > - for (; ancestor; ancestor = parent_cs(ancestor)) { > - if (ancestor->partition_root_state == PRS_ISOLATED) > - iancestor = ancestor; > - if (is_remote_partition(ancestor)) > - break; > - } > - > - /* > - * If a furthest isolated partition acnestor is found, use it to > - * determine which of the CPUs in @xcpus should be removed from > - * isolated_cpus. Otherwise, all of them will be removed. > - */ > - if (iancestor && !cpumask_andnot(xcpus, xcpus, iancestor->effective_xcpus)) > - return; /* No CPU should be removed from isolated_cpus */ > - > - spin_lock_irq(&callback_lock); > - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, xcpus); > - spin_unlock_irq(&callback_lock); > -} > - > /* > * partition_xcpus_add - Add new exclusive CPUs to partition > * @new_prs: new partition_root_state > @@ -1325,9 +1286,7 @@ static void partition_xcpus_add(int new_prs, struct cpuset *parent, > if (parent == &top_cpuset) > cpumask_or(subpartitions_cpus, subpartitions_cpus, xcpus); > > - if (new_prs != parent->partition_root_state) > - isolated_cpus_update(parent->partition_root_state, new_prs, > - xcpus); > + isolated_cpus_update(new_prs, xcpus); > > cpumask_andnot(parent->effective_cpus, parent->effective_cpus, xcpus); > } > @@ -1355,9 +1314,8 @@ static void partition_xcpus_del(int old_prs, struct cpuset *parent, > if (parent == &top_cpuset) > cpumask_andnot(subpartitions_cpus, subpartitions_cpus, xcpus); > > - if (old_prs != parent->partition_root_state) > - isolated_cpus_update(old_prs, parent->partition_root_state, > - xcpus); > + /* CPUs given back, including those of descendants, take parent's state */ > + isolated_cpus_update(parent->partition_root_state, xcpus); > > cpumask_or(parent->effective_cpus, parent->effective_cpus, xcpus); > cpumask_and(parent->effective_cpus, parent->effective_cpus, cpu_active_mask); > @@ -1612,6 +1570,7 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, > struct cgroup_subsys_state *css; > struct cpuset *child; > bool populated = partition_is_populated(cs, NULL); > + static cpumask_t xcpus; /* Protected by cpuset_mutex */ > > /* > * Check child partition roots to see if they should be invalidated > @@ -1640,14 +1599,15 @@ static void compute_partition_effective_cpumask(struct cpuset *cs, > int parent_prs = cs->partition_root_state; > > /* > - * Invalidate child partition and update isolated_cpus > - * if necessary. > + * Invalidate child partition. Its CPUs still owned by > + * cs take the isolation state of cs. effective_xcpus > + * of cs may not be updated yet, so recompute it. > */ > spin_lock_irq(&callback_lock); > WRITE_ONCE(child->prs_err, PERR_NOCPUS); > - if ((old_prs == PRS_ISOLATED) && (parent_prs == PRS_ROOT)) > - isolated_cpus_update(PRS_ISOLATED, PRS_ROOT, > - child->effective_xcpus); > + compute_excpus(cs, &xcpus); > + cpumask_and(&xcpus, &xcpus, child->effective_xcpus); > + isolated_cpus_update(parent_prs, &xcpus); > child->partition_root_state = -old_prs; > spin_unlock_irq(&callback_lock); > notify_partition_change(child, old_prs); > @@ -2090,22 +2050,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, > * > * A partition error happens when parent has tasks and all > * its effective CPUs will have to be distributed out. > - * > - * For an isolated partition with a shrunk root partition parent > - * we may need to return some or all of its isolated CPUs. > */ > nocpu |= cpumask_empty(tmp->delmask); > - if ((parent_prs == PRS_ROOT) && (old_prs == PRS_ISOLATED)) { > - cpumask_clear(tmp->addmask); > - if (nocpu) > - cpumask_copy(tmp->addmask, cs->effective_xcpus); > - else if (!cpumask_subset(cs->effective_xcpus, parent->effective_xcpus)) > - cpumask_andnot(tmp->addmask, cs->effective_xcpus, > - parent->effective_xcpus); > - if (!cpumask_empty(tmp->addmask)) > - isolated_cpus_release(cs, tmp->addmask); > - /* Expansion of parent's effective_xcpus is handled below */ > - } > if (nocpu) { > part_error = PERR_NOCPUS; > if (is_partition_valid(cs)) > @@ -2345,18 +2291,12 @@ static void update_cpumasks_hier(struct cpuset *cs, struct tmpmasks *tmp, > /* > * When parent is not a valid partition root, > * child partition roots become invalid too. > - * > - * For a to be invalidated isolated partition, > - * call return_isolated_cpus() to remove the > - * relevant CPUs from isolated_cpus. > + * The isolation state of their CPUs has been > + * settled when the parent gave them back. > */ > if (is_partition_valid(cp)) > new_prs = -old_prs; > > - if (old_prs == PRS_ISOLATED) { > - cpumask_copy(tmp->delmask, cp->effective_xcpus); > - isolated_cpus_release(cp, tmp->delmask); > - } > WRITE_ONCE(cp->prs_err, > is_partition_invalid(parent) > ? PERR_INVPARENT : PERR_NOTPART); > @@ -3132,7 +3072,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, tmpmask.new_cpus); > + isolated_cpus_update(new_prs, tmpmask.new_cpus); > spin_unlock_irq(&callback_lock); > > /* Force update if partition is disabled & update effective_xcpus */ LGTM Reviewed-by: Waiman Long <longman@redhat.com> ^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface 2026-11-12 3:19 [PATCH for-7.4-fixes v4 0/2] cpuset: preparatory fixes and cleanups for partition rework Ridong Chen 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent Ridong Chen @ 2026-11-12 3:19 ` Ridong Chen 2026-11-12 8:51 ` Guopeng Zhang 2026-11-12 19:15 ` Waiman Long 1 sibling, 2 replies; 7+ messages in thread From: Ridong Chen @ 2026-11-12 3:19 UTC (permalink / raw) To: Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: Ridong Chen, cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen From: Ridong Chen <chenridong@xiaomi.com> Refactor validate_partition() to take the parent partition, the new partition root state, the new effective exclusive CPUs and the CPUs to be deleted, instead of a trial cpuset. A NULL parent stands for a remote partition, whose CPUs come from top_cpuset. This makes the interface flexible enough to handle both local and remote partition validation. This also fixes a false PERR_NOCPUS error when expanding a remote partition. The no CPU left for tasks check in partition_cpus_change() is done against the cgroup parent, but the CPUs of a remote partition come from top_cpuset, not from its parent. For example, on a 4-CPU system: echo 2-3 > P/cpuset.cpus echo 2-3 > P/cpuset.cpus.exclusive echo 2-3 > P/C/cpuset.cpus echo 2 > P/C/cpuset.cpus.exclusive echo root > P/C/cpuset.cpus.partition (a task in P) echo 2-3 > P/C/cpuset.cpus.exclusive cat P/C/cpuset.cpus.partition root invalid (Parent is not a partition root) The effective_cpus of P is 3, a subset of the new exclusive CPUs 2-3 of C, and P has a task, so the expansion is rejected. However, P is not a partition root and does not give any CPU to C, and top_cpuset still has CPUs 0-1 for the task. Enabling C directly with exclusive CPUs 2-3 succeeds, as remote_partition_enable() checks top_cpuset instead. The no CPU left for tasks check now uses the given parent instead of parent_cs(cs), and top_cpuset for a remote partition, as that is where its CPUs are taken from. The housekeeping conflict check is folded into validate_partition(). It is skipped when the parent is not a valid partition root, as @cs can't be a valid local partition in that case. Use the new interface for local partition enablement in update_parent_effective_cpumask() and in partition_cpus_change(), replacing the open-coded checks there. The order of the checks is now the same for both paths: empty CPUs, housekeeping conflict, then no CPU left for tasks. Add a test case to test_cpuset_prs.sh for the remote partition expansion above. Fixes: 181c8e091aae ("cgroup/cpuset: Introduce remote partition") Signed-off-by: Ridong Chen <chenridong@xiaomi.com> --- kernel/cgroup/cpuset.c | 119 +++++++++--------- .../selftests/cgroup/test_cpuset_prs.sh | 5 + 2 files changed, 64 insertions(+), 60 deletions(-) diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c index c7d4cfb615b7..7e374210c123 100644 --- a/kernel/cgroup/cpuset.c +++ b/kernel/cgroup/cpuset.c @@ -1414,6 +1414,48 @@ static bool prstate_housekeeping_conflict(int new_prs, int parent_prs, return res; } +/** + * validate_partition - Validate a cpuset partition configuration + * @cs: The cpuset to validate + * @parent: Parent partition of @cs, NULL for remote partition + * @new_prs: The proposed new partition root state + * @new_excpus: The new effective exclusive CPUs mask to validate + * @del: exclusive CPUs to be deleted from @cs, can be NULL + * + * A remote partition gets its CPUs from top_cpuset, so top_cpuset is used + * as the parent when @parent is NULL. + * + * The housekeeping check is skipped if @parent is not a valid partition + * root as @cs can't be a valid local partition in that case. + * + * Return: PRS error code (0 if valid, non-zero error code if invalid) + */ +static enum prs_errcode validate_partition(struct cpuset *cs, + struct cpuset *parent, int new_prs, + struct cpumask *new_excpus, + struct cpumask *del) +{ + int parent_prs; + + if (new_prs == PRS_MEMBER) + return PERR_NONE; + + if (cpumask_empty(new_excpus)) + return PERR_INVCPUS; + + if (!parent) + parent = &top_cpuset; + parent_prs = parent->partition_root_state; + if (parent_prs > 0 && + prstate_housekeeping_conflict(new_prs, parent_prs, new_excpus, del)) + return PERR_HKEEPING; + + if (tasks_nocpu_error(parent, cs, new_excpus)) + return PERR_NOCPUS; + + return PERR_NONE; +} + /* * cpuset_update_sd_hk_unlock - Rebuild sched domains, update HK & unlock * @@ -1934,18 +1976,10 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, if ((cmd == partcmd_enable) || (cmd == partcmd_enablei)) { new_prs = (cmd == partcmd_enable) ? PRS_ROOT : PRS_ISOLATED; - /* - * Enabling partition root is not allowed if its - * effective_xcpus is empty. - */ - if (cpumask_empty(tmp->delmask)) - return PERR_INVCPUS; - - if (prstate_housekeeping_conflict(new_prs, parent_prs, tmp->delmask, NULL)) - return PERR_HKEEPING; - - if (nocpu) - return PERR_NOCPUS; + part_error = validate_partition(cs, parent, new_prs, + tmp->delmask, NULL); + if (part_error) + return part_error; /* * This function will only be called when all the preliminary @@ -2428,32 +2462,6 @@ static int parse_cpuset_cpulist(const char *buf, struct cpumask *out_mask) return 0; } -/** - * validate_partition - Validate a cpuset partition configuration - * @cs: The cpuset to validate - * @trialcs: The trial cpuset containing proposed configuration changes - * - * If any validation check fails, the appropriate error code is set in the - * cpuset's prs_err field. - * - * Return: PRS error code (0 if valid, non-zero error code if invalid) - */ -static enum prs_errcode validate_partition(struct cpuset *cs, struct cpuset *trialcs) -{ - struct cpuset *parent = parent_cs(cs); - - if (cs_is_member(trialcs)) - return PERR_NONE; - - if (cpumask_empty(trialcs->effective_xcpus)) - return PERR_INVCPUS; - - if (tasks_nocpu_error(parent, cs, trialcs->effective_xcpus)) - return PERR_NOCPUS; - - return PERR_NONE; -} - /** * partition_cpus_change - Handle partition state changes due to CPU mask updates * @cs: The target cpuset being modified @@ -2467,33 +2475,24 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs, struct tmpmasks *tmp) { enum prs_errcode prs_err; - int parent_prs = is_remote_partition(cs) - ? PRS_ROOT : parent_cs(cs)->partition_root_state; + struct cpuset *parent = is_remote_partition(cs) ? NULL : parent_cs(cs); if (cs_is_member(cs)) return; - prs_err = validate_partition(cs, trialcs); - if (!prs_err && (parent_prs > 0) && - !cpumask_empty(trialcs->effective_xcpus)) { - int prs = cs->partition_root_state; - - /* - * Check for housekeeping conflict by passing the whole - * effective_xcpus as well as those CPUs that are to be - * removed from cs as delmask. - */ - if (prs > 0) - cpumask_andnot(tmp->delmask, cs->effective_xcpus, - trialcs->effective_xcpus); - else - cpumask_clear(tmp->delmask); - - if (prstate_housekeeping_conflict(prs, parent_prs, - trialcs->effective_xcpus, tmp->delmask)) - prs_err = PERR_HKEEPING; - } + /* + * Check for housekeeping conflict by passing the whole + * effective_xcpus as well as those CPUs that are to be + * removed from cs as delmask. + */ + if (is_partition_valid(cs)) + cpumask_andnot(tmp->delmask, cs->effective_xcpus, + trialcs->effective_xcpus); + else + cpumask_clear(tmp->delmask); + prs_err = validate_partition(cs, parent, trialcs->partition_root_state, + trialcs->effective_xcpus, tmp->delmask); if (prs_err) { WRITE_ONCE(cs->prs_err, prs_err); trialcs->prs_err = prs_err; diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh index 1e9e6b4c6f7b..1397ec428c46 100755 --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh @@ -707,6 +707,11 @@ REMOTE_TEST_MATRIX=( " C1-4:P1 C5-6 C1-2 . C5 . \ . P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \ p1:P1|p2:P1|c11:P1" + # Expanding a remote partition should check top cpuset, not its member + # parent, for the no CPU left for tasks error. + " CX2-3:T . C2-3:X2:P1 . . . \ + . . X2-3 . . . p1:1,4-7|c11:2-3 \ + p1:P0|c11:P1" ) # -- 2.34.1 ^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface Ridong Chen @ 2026-11-12 8:51 ` Guopeng Zhang 2026-11-12 19:15 ` Waiman Long 1 sibling, 0 replies; 7+ messages in thread From: Guopeng Zhang @ 2026-11-12 8:51 UTC (permalink / raw) To: Ridong Chen, Waiman Long, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen 在 2026/11/12 11:19, Ridong Chen 写道: > From: Ridong Chen <chenridong@xiaomi.com> > > Refactor validate_partition() to take the parent partition, the new > partition root state, the new effective exclusive CPUs and the CPUs to > be deleted, instead of a trial cpuset. A NULL parent stands for a > remote partition, whose CPUs come from top_cpuset. This makes the > interface flexible enough to handle both local and remote partition > validation. > > This also fixes a false PERR_NOCPUS error when expanding a remote > partition. The no CPU left for tasks check in partition_cpus_change() > is done against the cgroup parent, but the CPUs of a remote partition > come from top_cpuset, not from its parent. For example, on a 4-CPU > system: > > echo 2-3 > P/cpuset.cpus > echo 2-3 > P/cpuset.cpus.exclusive > echo 2-3 > P/C/cpuset.cpus > echo 2 > P/C/cpuset.cpus.exclusive > echo root > P/C/cpuset.cpus.partition > (a task in P) > echo 2-3 > P/C/cpuset.cpus.exclusive > cat P/C/cpuset.cpus.partition > root invalid (Parent is not a partition root) > > The effective_cpus of P is 3, a subset of the new exclusive CPUs 2-3 of > C, and P has a task, so the expansion is rejected. However, P is not a > partition root and does not give any CPU to C, and top_cpuset still has > CPUs 0-1 for the task. Enabling C directly with exclusive CPUs 2-3 > succeeds, as remote_partition_enable() checks top_cpuset instead. > > The no CPU left for tasks check now uses the given parent instead of > parent_cs(cs), and top_cpuset for a remote partition, as that is where > its CPUs are taken from. > > The housekeeping conflict check is folded into validate_partition(). It > is skipped when the parent is not a valid partition root, as @cs can't > be a valid local partition in that case. > > Use the new interface for local partition enablement in > update_parent_effective_cpumask() and in partition_cpus_change(), > replacing the open-coded checks there. The order of the checks is now > the same for both paths: empty CPUs, housekeeping conflict, then no CPU > left for tasks. > > Add a test case to test_cpuset_prs.sh for the remote partition > expansion above. > > Fixes: 181c8e091aae ("cgroup/cpuset: Introduce remote partition") > Signed-off-by: Ridong Chen <chenridong@xiaomi.com> > --- > kernel/cgroup/cpuset.c | 119 +++++++++--------- > .../selftests/cgroup/test_cpuset_prs.sh | 5 + > 2 files changed, 64 insertions(+), 60 deletions(-) > > diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c > index c7d4cfb615b7..7e374210c123 100644 > --- a/kernel/cgroup/cpuset.c > +++ b/kernel/cgroup/cpuset.c > @@ -1414,6 +1414,48 @@ static bool prstate_housekeeping_conflict(int new_prs, int parent_prs, > return res; > } > > +/** > + * validate_partition - Validate a cpuset partition configuration > + * @cs: The cpuset to validate > + * @parent: Parent partition of @cs, NULL for remote partition > + * @new_prs: The proposed new partition root state > + * @new_excpus: The new effective exclusive CPUs mask to validate > + * @del: exclusive CPUs to be deleted from @cs, can be NULL > + * > + * A remote partition gets its CPUs from top_cpuset, so top_cpuset is used > + * as the parent when @parent is NULL. > + * > + * The housekeeping check is skipped if @parent is not a valid partition > + * root as @cs can't be a valid local partition in that case. > + * > + * Return: PRS error code (0 if valid, non-zero error code if invalid) > + */ > +static enum prs_errcode validate_partition(struct cpuset *cs, > + struct cpuset *parent, int new_prs, > + struct cpumask *new_excpus, > + struct cpumask *del) > +{ > + int parent_prs; > + > + if (new_prs == PRS_MEMBER) > + return PERR_NONE; > + > + if (cpumask_empty(new_excpus)) > + return PERR_INVCPUS; > + > + if (!parent) > + parent = &top_cpuset; > + parent_prs = parent->partition_root_state; > + if (parent_prs > 0 && > + prstate_housekeeping_conflict(new_prs, parent_prs, new_excpus, del)) > + return PERR_HKEEPING; > + > + if (tasks_nocpu_error(parent, cs, new_excpus)) > + return PERR_NOCPUS; > + > + return PERR_NONE; > +} > + > /* > * cpuset_update_sd_hk_unlock - Rebuild sched domains, update HK & unlock > * > @@ -1934,18 +1976,10 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, > if ((cmd == partcmd_enable) || (cmd == partcmd_enablei)) { > new_prs = (cmd == partcmd_enable) ? PRS_ROOT : PRS_ISOLATED; > > - /* > - * Enabling partition root is not allowed if its > - * effective_xcpus is empty. > - */ > - if (cpumask_empty(tmp->delmask)) > - return PERR_INVCPUS; > - > - if (prstate_housekeeping_conflict(new_prs, parent_prs, tmp->delmask, NULL)) > - return PERR_HKEEPING; > - > - if (nocpu) > - return PERR_NOCPUS; > + part_error = validate_partition(cs, parent, new_prs, > + tmp->delmask, NULL); > + if (part_error) > + return part_error; > > /* > * This function will only be called when all the preliminary > @@ -2428,32 +2462,6 @@ static int parse_cpuset_cpulist(const char *buf, struct cpumask *out_mask) > return 0; > } > > -/** > - * validate_partition - Validate a cpuset partition configuration > - * @cs: The cpuset to validate > - * @trialcs: The trial cpuset containing proposed configuration changes > - * > - * If any validation check fails, the appropriate error code is set in the > - * cpuset's prs_err field. > - * > - * Return: PRS error code (0 if valid, non-zero error code if invalid) > - */ > -static enum prs_errcode validate_partition(struct cpuset *cs, struct cpuset *trialcs) > -{ > - struct cpuset *parent = parent_cs(cs); > - > - if (cs_is_member(trialcs)) > - return PERR_NONE; > - > - if (cpumask_empty(trialcs->effective_xcpus)) > - return PERR_INVCPUS; > - > - if (tasks_nocpu_error(parent, cs, trialcs->effective_xcpus)) > - return PERR_NOCPUS; > - > - return PERR_NONE; > -} > - > /** > * partition_cpus_change - Handle partition state changes due to CPU mask updates > * @cs: The target cpuset being modified > @@ -2467,33 +2475,24 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs, > struct tmpmasks *tmp) > { > enum prs_errcode prs_err; > - int parent_prs = is_remote_partition(cs) > - ? PRS_ROOT : parent_cs(cs)->partition_root_state; > + struct cpuset *parent = is_remote_partition(cs) ? NULL : parent_cs(cs); > > if (cs_is_member(cs)) > return; > > - prs_err = validate_partition(cs, trialcs); > - if (!prs_err && (parent_prs > 0) && > - !cpumask_empty(trialcs->effective_xcpus)) { > - int prs = cs->partition_root_state; > - > - /* > - * Check for housekeeping conflict by passing the whole > - * effective_xcpus as well as those CPUs that are to be > - * removed from cs as delmask. > - */ > - if (prs > 0) > - cpumask_andnot(tmp->delmask, cs->effective_xcpus, > - trialcs->effective_xcpus); > - else > - cpumask_clear(tmp->delmask); > - > - if (prstate_housekeeping_conflict(prs, parent_prs, > - trialcs->effective_xcpus, tmp->delmask)) > - prs_err = PERR_HKEEPING; > - } > + /* > + * Check for housekeeping conflict by passing the whole > + * effective_xcpus as well as those CPUs that are to be > + * removed from cs as delmask. > + */ > + if (is_partition_valid(cs)) > + cpumask_andnot(tmp->delmask, cs->effective_xcpus, > + trialcs->effective_xcpus); > + else > + cpumask_clear(tmp->delmask); > > + prs_err = validate_partition(cs, parent, trialcs->partition_root_state, > + trialcs->effective_xcpus, tmp->delmask); > if (prs_err) { > WRITE_ONCE(cs->prs_err, prs_err); > trialcs->prs_err = prs_err; > diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh > index 1e9e6b4c6f7b..1397ec428c46 100755 > --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh > +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh > @@ -707,6 +707,11 @@ REMOTE_TEST_MATRIX=( > " C1-4:P1 C5-6 C1-2 . C5 . \ > . P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \ > p1:P1|p2:P1|c11:P1" > + # Expanding a remote partition should check top cpuset, not its member > + # parent, for the no CPU left for tasks error. > + " CX2-3:T . C2-3:X2:P1 . . . \ > + . . X2-3 . . . p1:1,4-7|c11:2-3 \ > + p1:P0|c11:P1" > ) > > # Reviewed-by: Guopeng Zhang <zhangguopeng@kylinos.cn> Thanks, Guopeng ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface Ridong Chen 2026-11-12 8:51 ` Guopeng Zhang @ 2026-11-12 19:15 ` Waiman Long 1 sibling, 0 replies; 7+ messages in thread From: Waiman Long @ 2026-11-12 19:15 UTC (permalink / raw) To: Ridong Chen, Tejun Heo, Johannes Weiner, Michal Koutný, Shuah Khan Cc: cgroups, linux-kernel, linux-kselftest, Guopeng Zhang, Ridong Chen On 11/11/26 10:19 PM, Ridong Chen wrote: > From: Ridong Chen <chenridong@xiaomi.com> > > Refactor validate_partition() to take the parent partition, the new > partition root state, the new effective exclusive CPUs and the CPUs to > be deleted, instead of a trial cpuset. A NULL parent stands for a > remote partition, whose CPUs come from top_cpuset. This makes the > interface flexible enough to handle both local and remote partition > validation. > > This also fixes a false PERR_NOCPUS error when expanding a remote > partition. The no CPU left for tasks check in partition_cpus_change() > is done against the cgroup parent, but the CPUs of a remote partition > come from top_cpuset, not from its parent. For example, on a 4-CPU > system: > > echo 2-3 > P/cpuset.cpus > echo 2-3 > P/cpuset.cpus.exclusive > echo 2-3 > P/C/cpuset.cpus > echo 2 > P/C/cpuset.cpus.exclusive > echo root > P/C/cpuset.cpus.partition > (a task in P) > echo 2-3 > P/C/cpuset.cpus.exclusive > cat P/C/cpuset.cpus.partition > root invalid (Parent is not a partition root) > > The effective_cpus of P is 3, a subset of the new exclusive CPUs 2-3 of > C, and P has a task, so the expansion is rejected. However, P is not a > partition root and does not give any CPU to C, and top_cpuset still has > CPUs 0-1 for the task. Enabling C directly with exclusive CPUs 2-3 > succeeds, as remote_partition_enable() checks top_cpuset instead. > > The no CPU left for tasks check now uses the given parent instead of > parent_cs(cs), and top_cpuset for a remote partition, as that is where > its CPUs are taken from. > > The housekeeping conflict check is folded into validate_partition(). It > is skipped when the parent is not a valid partition root, as @cs can't > be a valid local partition in that case. > > Use the new interface for local partition enablement in > update_parent_effective_cpumask() and in partition_cpus_change(), > replacing the open-coded checks there. The order of the checks is now > the same for both paths: empty CPUs, housekeeping conflict, then no CPU > left for tasks. > > Add a test case to test_cpuset_prs.sh for the remote partition > expansion above. > > Fixes: 181c8e091aae ("cgroup/cpuset: Introduce remote partition") > Signed-off-by: Ridong Chen <chenridong@xiaomi.com> > --- > kernel/cgroup/cpuset.c | 119 +++++++++--------- > .../selftests/cgroup/test_cpuset_prs.sh | 5 + > 2 files changed, 64 insertions(+), 60 deletions(-) > > diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c > index c7d4cfb615b7..7e374210c123 100644 > --- a/kernel/cgroup/cpuset.c > +++ b/kernel/cgroup/cpuset.c > @@ -1414,6 +1414,48 @@ static bool prstate_housekeeping_conflict(int new_prs, int parent_prs, > return res; > } > > +/** > + * validate_partition - Validate a cpuset partition configuration > + * @cs: The cpuset to validate > + * @parent: Parent partition of @cs, NULL for remote partition > + * @new_prs: The proposed new partition root state > + * @new_excpus: The new effective exclusive CPUs mask to validate > + * @del: exclusive CPUs to be deleted from @cs, can be NULL > + * > + * A remote partition gets its CPUs from top_cpuset, so top_cpuset is used > + * as the parent when @parent is NULL. > + * > + * The housekeeping check is skipped if @parent is not a valid partition > + * root as @cs can't be a valid local partition in that case. > + * > + * Return: PRS error code (0 if valid, non-zero error code if invalid) > + */ > +static enum prs_errcode validate_partition(struct cpuset *cs, > + struct cpuset *parent, int new_prs, > + struct cpumask *new_excpus, > + struct cpumask *del) > +{ > + int parent_prs; > + > + if (new_prs == PRS_MEMBER) > + return PERR_NONE; > + > + if (cpumask_empty(new_excpus)) > + return PERR_INVCPUS; > + > + if (!parent) > + parent = &top_cpuset; > + parent_prs = parent->partition_root_state; > + if (parent_prs > 0 && > + prstate_housekeeping_conflict(new_prs, parent_prs, new_excpus, del)) > + return PERR_HKEEPING; > + > + if (tasks_nocpu_error(parent, cs, new_excpus)) > + return PERR_NOCPUS; > + > + return PERR_NONE; > +} > + > /* > * cpuset_update_sd_hk_unlock - Rebuild sched domains, update HK & unlock > * > @@ -1934,18 +1976,10 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd, > if ((cmd == partcmd_enable) || (cmd == partcmd_enablei)) { > new_prs = (cmd == partcmd_enable) ? PRS_ROOT : PRS_ISOLATED; > > - /* > - * Enabling partition root is not allowed if its > - * effective_xcpus is empty. > - */ > - if (cpumask_empty(tmp->delmask)) > - return PERR_INVCPUS; > - > - if (prstate_housekeeping_conflict(new_prs, parent_prs, tmp->delmask, NULL)) > - return PERR_HKEEPING; > - > - if (nocpu) > - return PERR_NOCPUS; > + part_error = validate_partition(cs, parent, new_prs, > + tmp->delmask, NULL); > + if (part_error) > + return part_error; > > /* > * This function will only be called when all the preliminary > @@ -2428,32 +2462,6 @@ static int parse_cpuset_cpulist(const char *buf, struct cpumask *out_mask) > return 0; > } > > -/** > - * validate_partition - Validate a cpuset partition configuration > - * @cs: The cpuset to validate > - * @trialcs: The trial cpuset containing proposed configuration changes > - * > - * If any validation check fails, the appropriate error code is set in the > - * cpuset's prs_err field. > - * > - * Return: PRS error code (0 if valid, non-zero error code if invalid) > - */ > -static enum prs_errcode validate_partition(struct cpuset *cs, struct cpuset *trialcs) > -{ > - struct cpuset *parent = parent_cs(cs); > - > - if (cs_is_member(trialcs)) > - return PERR_NONE; > - > - if (cpumask_empty(trialcs->effective_xcpus)) > - return PERR_INVCPUS; > - > - if (tasks_nocpu_error(parent, cs, trialcs->effective_xcpus)) > - return PERR_NOCPUS; > - > - return PERR_NONE; > -} > - > /** > * partition_cpus_change - Handle partition state changes due to CPU mask updates > * @cs: The target cpuset being modified > @@ -2467,33 +2475,24 @@ static void partition_cpus_change(struct cpuset *cs, struct cpuset *trialcs, > struct tmpmasks *tmp) > { > enum prs_errcode prs_err; > - int parent_prs = is_remote_partition(cs) > - ? PRS_ROOT : parent_cs(cs)->partition_root_state; > + struct cpuset *parent = is_remote_partition(cs) ? NULL : parent_cs(cs); > > if (cs_is_member(cs)) > return; > > - prs_err = validate_partition(cs, trialcs); > - if (!prs_err && (parent_prs > 0) && > - !cpumask_empty(trialcs->effective_xcpus)) { > - int prs = cs->partition_root_state; > - > - /* > - * Check for housekeeping conflict by passing the whole > - * effective_xcpus as well as those CPUs that are to be > - * removed from cs as delmask. > - */ > - if (prs > 0) > - cpumask_andnot(tmp->delmask, cs->effective_xcpus, > - trialcs->effective_xcpus); > - else > - cpumask_clear(tmp->delmask); > - > - if (prstate_housekeeping_conflict(prs, parent_prs, > - trialcs->effective_xcpus, tmp->delmask)) > - prs_err = PERR_HKEEPING; > - } > + /* > + * Check for housekeeping conflict by passing the whole > + * effective_xcpus as well as those CPUs that are to be > + * removed from cs as delmask. > + */ > + if (is_partition_valid(cs)) > + cpumask_andnot(tmp->delmask, cs->effective_xcpus, > + trialcs->effective_xcpus); > + else > + cpumask_clear(tmp->delmask); > > + prs_err = validate_partition(cs, parent, trialcs->partition_root_state, > + trialcs->effective_xcpus, tmp->delmask); > if (prs_err) { > WRITE_ONCE(cs->prs_err, prs_err); > trialcs->prs_err = prs_err; > diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh > index 1e9e6b4c6f7b..1397ec428c46 100755 > --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh > +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh > @@ -707,6 +707,11 @@ REMOTE_TEST_MATRIX=( > " C1-4:P1 C5-6 C1-2 . C5 . \ > . P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \ > p1:P1|p2:P1|c11:P1" > + # Expanding a remote partition should check top cpuset, not its member > + # parent, for the no CPU left for tasks error. > + " CX2-3:T . C2-3:X2:P1 . . . \ > + . . X2-3 . . . p1:1,4-7|c11:2-3 \ > + p1:P0|c11:P1" > ) > > # LGTM Reviewed-by: Waiman Long <longman@redhat.com> ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-11-12 19:15 UTC | newest] Thread overview: 7+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-12 3:19 [PATCH for-7.4-fixes v4 0/2] cpuset: preparatory fixes and cleanups for partition rework Ridong Chen 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 1/2] cpuset: settle isolation state when CPUs are given back to parent Ridong Chen 2026-11-12 8:33 ` Guopeng Zhang 2026-11-12 19:14 ` Waiman Long 2026-11-12 3:19 ` [PATCH for-7.4-fixes v4 2/2] cpuset: generalize the validate_partition() interface Ridong Chen 2026-11-12 8:51 ` Guopeng Zhang 2026-11-12 19:15 ` Waiman Long
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox