From: Guopeng Zhang <guopeng.zhang@linux.dev>
To: Ridong Chen <ridong.chen@linux.dev>,
Waiman Long <longman@redhat.com>, Tejun Heo <tj@kernel.org>
Cc: "Johannes Weiner" <hannes@cmpxchg.org>,
"Michal Koutný" <mkoutny@suse.com>,
"Shuah Khan" <shuah@kernel.org>,
"Mathieu Desnoyers" <mathieu.desnoyers@efficios.com>,
cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org,
"Guopeng Zhang" <zhangguopeng@kylinos.cn>
Subject: Re: [PATCH v3 3/3] cgroup/cpuset: Keep the top sched domain non-empty on CPU hotplug
Date: Wed, 11 Nov 2026 18:56:06 +0800 [thread overview]
Message-ID: <a74dd545-76dd-450a-98a1-9541ce9189d3@linux.dev> (raw)
In-Reply-To: <19708635-67de-45ab-9539-8d686caa7132@linux.dev>
在 2026/11/10 20:55, Ridong Chen 写道:
>
>
> On 11/10/2026 1:03 PM, Guopeng Zhang wrote:
>> From: Guopeng Zhang <zhangguopeng@kylinos.cn>
>>
>> In cpuset_hotplug_update_tasks(), the local partition check passes only
>> the partition's own effective CPUs to tasks_nocpu_error(). For a
>> top-level partition with children, top_cpuset_sd_nocpu() treats the
>> children's CPUs as being returned, although hotplug does not return
>> them. This can leave the top sched domain empty when its last boot
>> domain housekeeping CPU goes offline.
>>
>> For example, on a 32-CPU system with isolcpus=domain,10-31:
>>
>> echo 0,2-9 > A/cpuset.cpus
>> echo root > A/cpuset.cpus.partition
>> echo 5-9 > A/B/cpuset.cpus
>> echo root > A/B/cpuset.cpus.partition
>> echo 0 > /sys/devices/system/cpu/cpu1/online
>> Oops: general protection fault
>> RIP: 0010:build_perf_domains+...
>>
>> The remote hotplug path does not check whether the top sched domain
>> is empty.
>>
>> Check the full effective_xcpus of top-level local and remote partitions
>> without assuming any CPUs have been returned. Only invalidate a
>> partition to restore the top domain if it can return an active boot
>> domain housekeeping CPU. Partitions holding only boot-isolated or
>> offline housekeeping CPUs cannot do that.
>>
>> Add hotplug regression tests.
>>
>> Fixes: 9b496a8bbed9 ("cgroup/cpuset: Prevent leakage of isolated CPUs into sched domains")
>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>> ---
>> kernel/cgroup/cpuset.c | 23 ++++++++++++++++---
>> .../selftests/cgroup/test_cpuset_prs.sh | 19 +++++++++++++++
>> 2 files changed, 39 insertions(+), 3 deletions(-)
>>
>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>> index 8b6831983803..0b392d50df27 100644
>> --- a/kernel/cgroup/cpuset.c
>> +++ b/kernel/cgroup/cpuset.c
>> @@ -4043,6 +4043,7 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>> static nodemask_t new_mems;
>> bool cpus_updated;
>> bool mems_updated;
>> + bool nocpu;
>> int partcmd = -1;
>> struct cpuset *parent;
>> @@ -4054,9 +4055,24 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>> if (!tmp || !cs->partition_root_state)
>> goto update_tasks;
>> + if (parent == &top_cpuset || is_remote_partition(cs)) {
>> + /*
>> + * Hotplug does not return CPUs held by child partitions. Only
>> + * invalidate a partition for an empty top sched domain if it
>> + * can return an active boot domain housekeeping CPU.
>> + */
>> + cpumask_and(tmp->addmask, cs->effective_xcpus,
>> + housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT));
>> + nocpu = cpumask_intersects(tmp->addmask, cpu_active_mask) &&
>> + top_cpuset_sd_nocpu(cs->effective_xcpus, NULL);
>> + nocpu |= cpumask_empty(&new_cpus) &&
>> + partition_is_populated(cs, NULL);
>> + } else {
>> + nocpu = tasks_nocpu_error(parent, cs, &new_cpus);
>> + }
>> +
>> if (is_remote_partition(cs) && (cpumask_empty(subpartitions_cpus) ||
>> - (cpumask_empty(&new_cpus) &&
>> - partition_is_populated(cs, NULL)))) {
>> + nocpu)) {
>> WRITE_ONCE(cs->prs_err, PERR_HOTPLUG);
>> remote_partition_disable(cs, tmp);
>> compute_effective_cpumask(&new_cpus, cs, parent);
>> @@ -4069,10 +4085,11 @@ static void cpuset_hotplug_update_tasks(struct cpuset *cs, struct tmpmasks *tmp)
>> * 2) parent is invalid or doesn't grant any cpus to child
>> * partitions.
>> * 3) subpartitions_cpus is empty.
>> + * 4) top_cpuset needs an active boot domain housekeeping CPU from cs.
>> */
>> if (is_local_partition(cs) &&
>> (!is_partition_valid(parent) ||
>> - tasks_nocpu_error(parent, cs, &new_cpus) ||
>> + nocpu ||
>> cpumask_empty(subpartitions_cpus)))
>> partcmd = partcmd_invalidate;
>> /*
>> diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
>> index ee4d57b7e35b..f345696e5b53 100755
>> --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
>> +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
>> @@ -648,8 +648,27 @@ ISOLCPUS_DOMAIN_TEST_MATRIX=(
>> # An invalid partition can recover once it leaves a top-level HK CPU.
>> " C0-9:P1 . . . C0-6,8-9 . . . 0 A1:0-6,8-9 A1:P1 7|"
>> +
>> + # A local root must not count its child's CPUs as already returned.
>> + " C0,2-9:P1 C5-9:P1 . . O1=0 . . . 0 A1:0,2-9|A2:5-9 A1:P-1|A2:P-1 1|"
>> +
>> + # A remote root must return CPUs when the top domain loses its last HK CPU.
>> + " X0,2-9 CX0,2-9:P1 . . O1=0 . . . 0 A2:0,2-9 A2:P-1 1|"
>> )
>> +# Keep an online boot-isolated CPU at the top so hotplug does not
>> +# clear subpartitions_cpus before checking the individual partitions.
>> +if [[ "$ONLINE_LAST" -gt 10 ]]; then
>> + ISOLCPUS_DOMAIN_TEST_MATRIX+=(
>> + # An offline HK CPU cannot restore the top domain. Keep this local
>> + # partition valid while its sibling returns active HK CPUs.
>> + " O2=0:C2,10:P2 . . C0,3-9:P1 O1=0 . . . 0 A1:10|B1:0,3-9 A1:P2|B1:P-1 1-2|2"
>> +
>> + # Check the same active-CPU rule for a remote partition.
>> + " O2=0:X2,10 CX2,10:P2 . C0,3-9:P1 O1=0 . . . 0 A2:10|B1:0,3-9 A2:P2|B1:P-1 1-2|2"
>> + )
>> +fi
>> +
>> # Test matrix to be run when boot time nohz_full CPUs cover all CPUs except CPUs 0-7
>> # This also means that CPU 8 is available and is a nohz_full CPU.
>> NOHZ_FULL_TEST_MATRIX=(
>
> Hi Guopeng,
>
> Looking at patches 1-3, the same check is changed back and forth in
> several places, and the places don't do it in the same way. I'd prefer
> a single helper that is unified and easy to understand, so that every
> place can just call it.
>
> As tasks_nocpu_error() is shared by the CPU mask update paths and the
> hotplug path, patches 1-3 end up checking the same thing in several
> places with different masks. How about making tasks_nocpu_error() take
> the CPUs to be returned as an explicit argument, the same way as
> top_cpuset_sd_nocpu() does, and use it for all parents? Something like
> the following (not tested):
>
> /*
> * tasks_nocpu_error - Return true if tasks will have no effective_cpus
> * @parent: parent partition, NULL for a remote partition
> * @cs: the partition
> * @xcpus: exclusive CPUs that @cs will hold after the update
> * @del_cpus: exclusive CPUs to be returned to @parent, can be NULL
> *
> * @del_cpus may include CPUs that are also in @xcpus and will stay in
> * @cs. They are skipped by the @xcpus check, so callers can pass the
> * current effective_xcpus of @cs without masking it first.
> *
> * top_cpuset always has tasks, and its sched domain needs an active boot
> * domain housekeeping CPU.
> */
> static bool tasks_nocpu_error(struct cpuset *parent, struct cpuset *cs,
> const struct cpumask *xcpus,
> const struct cpumask *del_cpus)
> {
> const struct cpumask *mask = cpu_possible_mask;
> int cpu;
>
> /* A populated @cs must have an active CPU in @xcpus */
> if (!cpumask_intersects(xcpus, cpu_active_mask) &&
> partition_is_populated(cs, NULL))
> return true;
>
> /*
> * A populated @parent must keep an active CPU in @mask, either from
> * its effective_cpus or from @del_cpus, that isn't taken by @cs.
> */
> if (!parent)
> parent = &top_cpuset;
>
> if (parent == &top_cpuset)
> mask = housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT);
> else if (!partition_is_populated(parent, cs))
> return false;
>
> for_each_cpu_and(cpu, mask, cpu_active_mask) {
> if (cpumask_test_cpu(cpu, xcpus))
> continue;
> if (cpumask_test_cpu(cpu, parent->effective_cpus) ||
> (del_cpus && cpumask_test_cpu(cpu, del_cpus)))
> return false;
> }
> return true;
> }
>
> The callers then become:
>
> /* update_parent_effective_cpumask() */
> nocpu = tasks_nocpu_error(parent, cs, tmp->delmask,
> is_partition_valid(cs) ? cs->effective_xcpus : NULL);
>
> /* validate_partition() */
> if (tasks_nocpu_error(is_remote_partition(cs) ? NULL : parent, cs,
> trialcs->effective_xcpus,
> is_partition_valid(cs) ? cs->effective_xcpus : NULL))
>
> /* remote_partition_enable(), instead of top_cpuset_sd_nocpu() */
> tasks_nocpu_error(NULL, cs, tmp->new_cpus, NULL)
>
> /* remote_cpus_update(), instead of top_cpuset_sd_nocpu() */
> tasks_nocpu_error(NULL, cs, excpus, cs->effective_xcpus)
>
> /* cpuset_hotplug_update_tasks(), hotplug returns nothing */
> nocpu = tasks_nocpu_error(is_remote_partition(cs) ? NULL : parent, cs,
> &new_cpus, NULL);
>
> In the hotplug path, @del_cpus is NULL as nothing is returned to the
> parent. new_cpus can be used as @xcpus for both checks, as the CPUs of
> the child partitions of cs are not in the parent's effective_cpus
> anyway. The check that cs can return an active boot domain housekeeping
> CPU before invalidating it can stay there.
>
> This also covers the local parent case I mentioned in patch 2, so
> patch 2 won't be needed. Could you give it a try with your test cases
> and that reproducer?
>
Hi Ridong,
Thanks for the detailed review and the alternative approach! I'll follow this approach in v4.
Thanks,
Guopeng
prev parent reply other threads:[~2026-11-11 10:56 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 5:03 [PATCH v3 0/3] cgroup/cpuset: Keep the top scheduling domain populated Guopeng Zhang
2026-11-10 5:03 ` [PATCH v3 1/3] cgroup/cpuset: Reject updates that empty the top sched domain Guopeng Zhang
2026-11-10 12:13 ` Ridong Chen
2026-11-11 10:51 ` Guopeng Zhang
2026-11-10 5:03 ` [PATCH v3 2/3] cgroup/cpuset: Allow top-level partition CPU replacement Guopeng Zhang
2026-11-10 12:11 ` Ridong Chen
2026-11-11 10:53 ` Guopeng Zhang
2026-11-10 5:03 ` [PATCH v3 3/3] cgroup/cpuset: Keep the top sched domain non-empty on CPU hotplug Guopeng Zhang
2026-11-10 12:55 ` Ridong Chen
2026-11-11 10:56 ` Guopeng Zhang [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=a74dd545-76dd-450a-98a1-9541ce9189d3@linux.dev \
--to=guopeng.zhang@linux.dev \
--cc=cgroups@vger.kernel.org \
--cc=hannes@cmpxchg.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=longman@redhat.com \
--cc=mathieu.desnoyers@efficios.com \
--cc=mkoutny@suse.com \
--cc=ridong.chen@linux.dev \
--cc=shuah@kernel.org \
--cc=tj@kernel.org \
--cc=zhangguopeng@kylinos.cn \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®