mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Waiman Long <longman@redhat.com>
To: Guopeng Zhang <guopeng.zhang@linux.dev>,
	tj@kernel.org, ridong.chen@linux.dev
Cc: hannes@cmpxchg.org, mkoutny@suse.com, shuah@kernel.org,
	cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
	linux-kselftest@vger.kernel.org,
	Guopeng Zhang <zhangguopeng@kylinos.cn>
Subject: Re: [PATCH v2 1/3] cgroup/cpuset: Reject updates that empty the top sched domain
Date: Fri, 6 Nov 2026 12:22:47 -0500	[thread overview]
Message-ID: <ccdab485-e1d1-4055-bb0b-f8f712fc0a9b@redhat.com> (raw)
In-Reply-To: <20261105130344.664336-2-guopeng.zhang@linux.dev>

On 11/5/26 8:03 AM, Guopeng Zhang wrote:
> From: Guopeng Zhang <zhangguopeng@kylinos.cn>
>
> Boot-isolated CPUs remain in top_cpuset.effective_cpus, but are excluded
> from its scheduling domain by generate_sched_domains(). Checking only
> effective_cpus when distributing CPUs to partitions can therefore allow
> all boot domain housekeeping CPUs to be taken from the top cpuset.
>
> The resulting empty domain triggers a warning in build_sched_domains()
> and can cause an out-of-bounds CPU access in build_perf_domains(). Both
> root and isolated partitions can trigger this, whether local or remote.
> For example, on a 32-CPU system booted with isolcpus=domain,10-31:
>
>    echo 0-9 > A/cpuset.cpus
>    echo root > A/cpuset.cpus.partition
>    Oops: general protection fault
>    RIP: 0010:build_perf_domains+...
>
> Require local and remote partition creation and CPU mask updates to
> leave an active boot domain housekeeping CPU in the top cpuset,
> accounting for CPUs returned by the same update.
>
> Add test cases to test_cpuset_prs.sh to verify that every partition
> update leaves an active boot domain housekeeping CPU in the top cpuset.
>
> Fixes: 9b496a8bbed9 ("cgroup/cpuset: Prevent leakage of isolated CPUs into sched domains")
> Co-developed-by: Ridong Chen <ridong.chen@linux.dev>
> Signed-off-by: Ridong Chen <ridong.chen@linux.dev>
> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
> ---
>   kernel/cgroup/cpuset.c                        | 48 ++++++++++++++++++-
>   .../selftests/cgroup/test_cpuset_prs.sh       | 47 ++++++++++++++++++
>   2 files changed, 93 insertions(+), 2 deletions(-)
>
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index 3052413e41e7..c405719ccd12 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -1180,12 +1180,56 @@ static void update_partition_sd_lb(struct cpuset *cs, int old_prs)
>   		cpuset_force_rebuild();
>   }
>   
> +/*
> + * top_cpuset_sd_nocpu - Check if top_cpuset will have no sched domain CPU
> + * @add_cpus: CPUs to be taken away from top_cpuset
> + * @del_cpus: CPUs to be given back to top_cpuset, can be NULL
> + * Return: true if no active CPU will be left in the top_cpuset sched domain
> + *
> + * Note that @del_cpus may include CPUs that are also in @add_cpus and
> + * will stay in the partition. They are skipped by the @add_cpus check,
> + * so callers can pass the current effective_xcpus of a partition
> + * without masking it first.
> + *
> + * Boot time isolated CPUs stay in top_cpuset.effective_cpus, but they are
> + * excluded from the sched domain of top_cpuset in generate_sched_domains().
> + * So checking top_cpuset.effective_cpus alone is not enough to ensure that
> + * the top_cpuset sched domain won't become empty after @add_cpus are taken
> + * away from it. The sched domain of top_cpuset must keep at least one
> + * active boot domain housekeeping CPU.
> + */
> +static bool top_cpuset_sd_nocpu(const struct cpumask *add_cpus,
> +				const struct cpumask *del_cpus)
> +{
> +	const struct cpumask *hk_mask = housekeeping_cpumask(HK_TYPE_DOMAIN_BOOT);
> +	int cpu;
> +
> +	for_each_cpu_and(cpu, hk_mask, cpu_active_mask) {
> +		if (cpumask_test_cpu(cpu, add_cpus))
> +			continue;
> +		if (cpumask_test_cpu(cpu, top_cpuset.effective_cpus) ||
> +		    (del_cpus && cpumask_test_cpu(cpu, del_cpus)))
> +			return false;
> +	}
> +	return true;
> +}
> +
>   /*
>    * tasks_nocpu_error - Return true if tasks will have no effective_cpus
>    */
>   static bool tasks_nocpu_error(struct cpuset *parent, struct cpuset *cs,
>   			      struct cpumask *xcpus)
>   {
> +	/*
> +	 * The top_cpuset always has tasks (kthreads at least), so its sched
> +	 * domain can't be empty. The current effective_xcpus of a valid
> +	 * partition will be given back to top_cpuset if not in @xcpus.
> +	 */
> +	if (parent == &top_cpuset &&
> +	    top_cpuset_sd_nocpu(xcpus, is_partition_valid(cs)
> +				     ? cs->effective_xcpus : NULL))
> +		return true;
> +
>   	/*
>   	 * A populated partition (cs or parent) can't have empty effective_cpus
>   	 */
> @@ -1724,7 +1768,7 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs,
>   	 */
>   	compute_excpus(cs, tmp->new_cpus);
>   	if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) ||
> -	    cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus))
> +	    top_cpuset_sd_nocpu(tmp->new_cpus, NULL))
>   		return PERR_INVCPUS;
>   	if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus))
>   		return PERR_NOCPUS;
> @@ -1828,7 +1872,7 @@ static void remote_cpus_update(struct cpuset *cs, struct cpumask *xcpus,
>   		if (!capable(CAP_SYS_ADMIN))
>   			WRITE_ONCE(cs->prs_err, PERR_ACCESS);
>   		else if (cpumask_intersects(tmp->addmask, subpartitions_cpus) ||
> -			 cpumask_subset(top_cpuset.effective_cpus, tmp->addmask))
> +			 top_cpuset_sd_nocpu(tmp->addmask, tmp->delmask))
>   			WRITE_ONCE(cs->prs_err, PERR_NOCPUS);
>   		else if (prstate_housekeeping_conflict(prs, PRS_ROOT,
>   						       excpus, tmp->delmask))
The code change above looks good to me.
> diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> index bdca6c471f87..e813ea9959f4 100755
> --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> @@ -133,6 +133,15 @@ LAST_PRESENT_CPU=$(cat /sys/devices/system/cpu/present | sed -e "s/.*[,-]//")
>   [[ -n "$FIRST_NOHZ_FULL_CPU" && "$FIRST_NOHZ_FULL_CPU" = 8 &&
>      "$LAST_NOHZ_FULL_CPU" = $LAST_PRESENT_CPU ]] && RUN_NOHZ_FULL_TESTS=1
>   
> +# These tests require boot domain housekeeping CPUs to be exactly 0-9.
> +# Check the actual masks to cover isolcpus= with or without explicit flags.
> +DOMAIN_ISOLCPUS="10-$LAST_PRESENT_CPU"
> +[[ "$LAST_PRESENT_CPU" = 10 ]] && DOMAIN_ISOLCPUS=10
I think you should use "==", not "=".
> +RUN_ISOLCPUS_DOMAIN_TESTS=
> +[[ -z "$NOHZ_FULL_CPUS" &&
> +	"$(cat /sys/devices/system/cpu/isolated)" = "$DOMAIN_ISOLCPUS" ]] &&
> +	RUN_ISOLCPUS_DOMAIN_TESTS=1

Why there shouldn't be any nohz_full CPUs? Domain isolated CPUs and 
nohz_full CPUs don't need to be exclusive.

Maybe you can move the new testing code below the ISOLCPUS section as 
the value of /sys/devices/system/cpu/isolated would have been read into 
the BOOT_ISOLCPUS variable which you can use instead of reading the 
sysfs file again.

> +
>   #
>   # If isolated CPUs have been reserved at boot time (as shown in
>   # /sys/devices/system/cpu/isolated), these isolated CPUs should be outside of
> @@ -602,6 +611,43 @@ ISOLCPUS_TEST_MATRIX=(
>   	" CX1-2,I:X1,I:P1 . .      .     X1,2,I  .      .      .     0 A1:1-2,I|XA1:1-2,I A1:P-1 ."
>   )
>   
> +# Test matrix for isolcpus=domain,10-<last> or isolcpus=10-<last> boots.
> +# The top cpuset must retain an active boot domain housekeeping CPU.
> +ISOLCPUS_DOMAIN_TEST_MATRIX=(
> +	#  old-A1 old-A2 old-A3 old-B1 new-A1 new-A2 new-A3 new-B1 fail ECPUs Pstate ISOLCPUS
> +	#  ------ ------ ------ ------ ------ ------ ------ ------ ---- ----- ------ --------
> +	# Neither a root nor an isolated partition may take all boot HK CPUs.
> +	" C0-9      .      .      .     P1      .      .      .     0 A1:0-9 A1:P-1 ."
> +	" C0-9      .      .      .     P2      .      .      .     0 A1:0-9 A1:P-2 ."
> +
> +	# Growing cpuset.cpus must not take the last top-level HK CPU.
> +	" C0-8:P1   .      .      .    C0-9     .      .      .     0 A1:0-9 A1:P-1 ."
> +
> +	# CPUs returned by the same update can keep the top sched domain alive.
> +	" C2-9:P2   .      .      .    C0-4     .      .      .     0 A1:0-4 A1:P2 0-4"
> +
> +	# Root and isolated partitions together must leave a top-level HK CPU.
> +	" C0-4:P1   .      .     C5-9    .      .      .     P2    0 A1:0-4|B1:5-9 A1:P1|B1:P-2 ."
> +
> +	# Remote partition creation must enforce the same constraint.
> +	" X0-9    CX0-9    .      .      .     P1      .      .     0 A2:0-9 A2:P-1 ."
> +
> +	# Growing a remote partition through cpuset.cpus.exclusive is rejected.
> +	" X0-9 C0-9:X0-8:P1 .     .      .    X0-9     .      .     0 A2:0-9 A2:P-1 ."
> +
> +	# Remote CPU replacements must account for the CPUs being returned.
> +	" X0-9 C0-9:X2-9:P2 .     .      .    X0-4     .      .     0 A2:0-4 A2:P2 0-4"
> +
> +	# Local exclusive CPU updates must not consume the last top-level HK CPU.
> +	" C0-9:X0-8:P2 .   .      .    X0-9     .      .      .     0 A1:0-9 A1:P-2 ."
> +
> +	# Ancestor updates must not grow a remote partition over the last HK CPU.
> +	" X0-8 CX0-9:P1    .      .    X0-9     .      .      .     0 A2:0-9 A2:P-1 ."
> +
> +	# An invalid partition can recover once it leaves a top-level HK CPU.
> +	" C0-9:P1   .      .      .    C0-8     .      .      .     0 A1:0-8 A1:P1 ."
> +)
> +

Could you also put this new test matrix together with the other test 
matrices?

Thanks,
Longman

>   # 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=(
> @@ -1472,6 +1518,7 @@ test_inotify()
>   run_state_test TEST_MATRIX
>   run_remote_state_test REMOTE_TEST_MATRIX
>   TEST_ISOLCPUS=1 run_state_test ISOLCPUS_TEST_MATRIX
> +[[ -n "$RUN_ISOLCPUS_DOMAIN_TESTS" ]] && run_state_test ISOLCPUS_DOMAIN_TEST_MATRIX
>   [[ -n "$RUN_NOHZ_FULL_TESTS" ]] && run_state_test NOHZ_FULL_TEST_MATRIX
>   test_isolated
>   test_boot_isolated


  reply	other threads:[~2026-11-06 17:22 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-05 13:03 [PATCH v2 0/3] cgroup/cpuset: Keep the top scheduling domain populated Guopeng Zhang
2026-11-05 13:03 ` [PATCH v2 1/3] cgroup/cpuset: Reject updates that empty the top sched domain Guopeng Zhang
2026-11-06 17:22   ` Waiman Long [this message]
2026-11-05 13:03 ` [PATCH v2 2/3] cgroup/cpuset: Keep the top sched domain non-empty on CPU hotplug Guopeng Zhang
2026-11-05 13:03 ` [PATCH v2 3/3] cgroup/cpuset: Release isolated CPUs when the parent is invalid Guopeng Zhang
2026-11-06  4:35   ` Ridong Chen
2026-11-06  7:35     ` Guopeng Zhang

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=ccdab485-e1d1-4055-bb0b-f8f712fc0a9b@redhat.com \
    --to=longman@redhat.com \
    --cc=cgroups@vger.kernel.org \
    --cc=guopeng.zhang@linux.dev \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --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®