mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Ridong Chen <ridong.chen@linux.dev>
To: Guopeng Zhang <guopeng.zhang@linux.dev>,
	Waiman Long <longman@redhat.com>, Tejun Heo <tj@kernel.org>
Cc: "Johannes Weiner" <hannes@cmpxchg.org>,
	"Michal Koutný" <mkoutny@suse.com>,
	cgroups@vger.kernel.org, linux-kernel@vger.kernel.org,
	"Guopeng Zhang" <zhangguopeng@kylinos.cn>
Subject: Re: [PATCH v3] cgroup/cpuset: Invalidate remote partition on housekeeping conflict
Date: Mon, 12 Oct 2026 09:35:09 +0800	[thread overview]
Message-ID: <47f5db16-b1ca-44a0-8714-38b0cf4db9e8@linux.dev> (raw)
In-Reply-To: <cf408084-9b6a-436d-a63a-8a247a49006f@linux.dev>



On 10/11/2026 7:23 PM, Guopeng Zhang wrote:
> 
> Hi Ridong,
> 在 2026/10/11 09:42, Ridong Chen 写道:
>>
>>
>> On 9/29/2026 10:38 AM, Guopeng Zhang wrote:
>>> From: Guopeng Zhang <zhangguopeng@kylinos.cn>
>>>
>>> Widening an ancestor's exclusive CPU mask can add a boot-isolated CPU
>>> to a valid remote partition root without touching the partition's own
>>> control files. The partition then load balances that CPU, silently
>>> defeating isolcpus=domain for it.
>>>
>>> This can be reproduced on a 32-CPU system booted with
>>> isolcpus=domain,4:
>>>
>>>       cd /sys/fs/cgroup
>>>       echo +cpuset > cgroup.subtree_control
>>>       mkdir -p A/B
>>>       echo +cpuset > A/cgroup.subtree_control
>>>       echo 2-4 > A/cpuset.cpus
>>>       echo 2-3 > A/cpuset.cpus.exclusive
>>>       echo 2-4 > A/B/cpuset.cpus
>>>       echo 2-4 > A/B/cpuset.cpus.exclusive
>>>       echo root > A/B/cpuset.cpus.partition
>>>       cat A/B/cpuset.cpus.effective               # 2-3
>>>       echo 2-4 > A/cpuset.cpus.exclusive
>>>       cat A/B/cpuset.cpus.partition               # root
>>>       cat A/B/cpuset.cpus.effective               # 2-4
>>>
>>> The last write returns 0 and leaves the hierarchy in this state:
>>>
>>>       root (cpuset.cpus.effective=0-1,5-31)
>>>       |
>>>       \-- A (member):           cpuset.cpus=2-4
>>>           |                     cpuset.cpus.exclusive=2-4
>>>           \-- B (root, remote): cpuset.cpus=2-4
>>>                                 cpuset.cpus.effective=2-4
>>>
>>> B is a remote partition: it takes its CPUs directly from the root
>>> cpuset, and A only passes its exclusive list down. Before the last
>>> write, that list is 2-3, so B holds 2-3 and CPU 4 stays in the
>>> root cpuset as a boot-isolated CPU. The write widens A's exclusive
>>> list to 2-4, which additionally grants CPU 4 to B. Nothing rejects
>>> the grant: B remains a valid root partition, and CPU 4 is still
>>> listed in cpuset.cpus.isolated while sitting in a load-balanced
>>> partition.
>>>
>>> remote_partition_enable() already rejects such grants through
>>> prstate_housekeeping_conflict(). remote_cpus_update(), which applies
>>> ancestor changes to a remote partition, does not.
>>>
>>> Both paths also open-code remote partition validation. Move those
>>> checks into validate_remote_partition(), and check the resulting
>>> effective exclusive CPU mask for a housekeeping conflict there. The
>>> existing prs_err path then invalidates the remote partition instead of
>>> assigning it a boot-isolated CPU.
>>>
>>> Fixes: f62a5d39368e ("cgroup/cpuset: Remove remote_partition_check() & make update_cpumasks_hier() handle remote partition")
>>> Suggested-by: Ridong Chen <ridong.chen@linux.dev>
>>> Suggested-by: Tejun Heo <tj@kernel.org>
>>> Reviewed-by: Ridong Chen <ridong.chen@linux.dev>
>>> Reviewed-by: Waiman Long <longman@redhat.com>
>>> Signed-off-by: Guopeng Zhang <zhangguopeng@kylinos.cn>
>>> ---
>>>
>>> Changes since v2:
>>> - Pass the enable/update mode explicitly to validate_remote_partition()
>>>     instead of deriving it from cs->remote_partition, which can remain set
>>>     after a failed partition state transition, as suggested by Tejun.
>>>
>>> Changes since v1:
>>> - Consolidate remote partition validation in a common helper, as
>>>     suggested by Ridong.
>>> - Check housekeeping conflicts against the resulting effective exclusive
>>>     CPU mask.
>>> - Rebase onto cgroup/for-7.3-fixes.
>>>
>>>    kernel/cgroup/cpuset.c | 69 ++++++++++++++++++++++++++++--------------
>>>    1 file changed, 47 insertions(+), 22 deletions(-)
>>>
>>> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
>>> index 1fcec89a28b9..d0a45241c070 100644
>>> --- a/kernel/cgroup/cpuset.c
>>> +++ b/kernel/cgroup/cpuset.c
>>> @@ -1561,6 +1561,43 @@ static inline bool is_local_partition(struct cpuset *cs)
>>>        return is_partition_valid(cs) && !is_remote_partition(cs);
>>>    }
>>>    +/**
>>> + * validate_remote_partition - Validate a remote partition CPU change
>>> + * @prs: partition root state to validate
>>> + * @updating: true for an update, false for an enable
>>> + * @excpus: resulting effective exclusive CPU mask
>>> + * @addcpus: exclusive CPUs to be added
>>> + * @delcpus: exclusive CPUs to be deleted, can be NULL
>>> + *
>>> + * Return: PERR_NONE if valid, otherwise an appropriate error code
>>> + */
>>> +static enum prs_errcode
>>> +validate_remote_partition(int prs, bool updating,
>>> +              struct cpumask *excpus,
>>> +              struct cpumask *addcpus,
>>> +              struct cpumask *delcpus)
>>> +{
>>> +    if (!capable(CAP_SYS_ADMIN))
>>> +        return PERR_ACCESS;
>>> +
>>> +    if (!updating &&
>>> +        (!cpumask_intersects(excpus, cpu_active_mask) ||
>>> +         cpumask_subset(top_cpuset.effective_cpus, addcpus)))
>>> +        return PERR_INVCPUS;
>>> +
>>
>> Do we really need the updating flag? I mean, whether or not it's an updating partition, it should still be an invalid remote partition, right? So why do we have to distinguish between updating and not updating?
>>
> 
> Both paths do invalidate the remote partition when validation fails, but
> the flag was added when factoring out this helper to preserve the
> different semantics of the two paths.
> 
> For example, suppose the top cpuset has only CPU 4 left:
> 
>      top_cpuset.effective_cpus = 4
> 
> When enabling a new remote partition:
> 
>      addcpus = 4
> 
> cpumask_subset(top_cpuset.effective_cpus, addcpus) is true, and the
> existing enable path returns PERR_INVCPUS.
> 
> Now suppose an existing remote partition owns CPUs 2-3 and is updated
> to add CPU 4:
> 
>      old excpus = 2-3
>      addcpus    = 4
>      new excpus = 2-4
> 
> The same cpumask_subset() condition is true, but the existing update
> path returns PERR_NOCPUS. The active CPU check also differs: the enable
> path requires the resulting mask to contain at least one active CPU,
> while the update path only checks whether the resulting mask is empty.
> 

What we want to do is to unify the remote partition check. I think enabling and 
updating should return the same error when the same condition check fails.

So I think we may change it 'a little' if it is reasonable. In my opinion, 
returning PERR_NOCPUS when the top_cpuset.effective_cpus becomes empty is 
reasonable. And I do not want to see some special case, which makes the code 
complicated.

I would like to see what Longman's opinion is.

> If we want to remove the flag, I think the cleaner approach is to keep
> the checks specific to each path in their respective callers and leave
> only the shared checks in validate_remote_partition().
> 
> Thanks,
> Guopeng
> 
>>> +    if (cpumask_intersects(addcpus, subpartitions_cpus) ||
>>> +        (updating &&
>>> +         cpumask_subset(top_cpuset.effective_cpus, addcpus)))
>>> +        return PERR_NOCPUS;
>>> +
>>> +    if ((prs == PRS_ISOLATED &&
>>> +         !isolated_cpus_can_update(addcpus, delcpus)) ||
>>> +        prstate_housekeeping_conflict(prs, excpus))
>>> +        return PERR_HKEEPING;
>>> +
>>> +    return PERR_NONE;
>>> +}
>>> +
>>>    /*
>>>     * remote_partition_enable - Enable current cpuset as a remote partition root
>>>     * @cs: the cpuset to update
>>> @@ -1574,11 +1611,7 @@ static inline bool is_local_partition(struct cpuset *cs)
>>>    static int remote_partition_enable(struct cpuset *cs, int new_prs,
>>>                       struct tmpmasks *tmp)
>>>    {
>>> -    /*
>>> -     * The user must have sysadmin privilege.
>>> -     */
>>> -    if (!capable(CAP_SYS_ADMIN))
>>> -        return PERR_ACCESS;
>>> +    enum prs_errcode err;
>>>          /*
>>>         * The requested exclusive_cpus must not be allocated to other
>>> @@ -1591,15 +1624,10 @@ static int remote_partition_enable(struct cpuset *cs, int new_prs,
>>>         * above it or remote partition root underneath it is not allowed.
>>>         */
>>>        compute_excpus(cs, tmp->new_cpus);
>>> -    if (!cpumask_intersects(tmp->new_cpus, cpu_active_mask) ||
>>> -        cpumask_subset(top_cpuset.effective_cpus, tmp->new_cpus))
>>> -        return PERR_INVCPUS;
>>> -    if (cpumask_intersects(tmp->new_cpus, subpartitions_cpus))
>>> -        return PERR_NOCPUS;
>>> -    if (((new_prs == PRS_ISOLATED) &&
>>> -         !isolated_cpus_can_update(tmp->new_cpus, NULL)) ||
>>> -        prstate_housekeeping_conflict(new_prs, tmp->new_cpus))
>>> -        return PERR_HKEEPING;
>>> +    err = validate_remote_partition(new_prs, false, tmp->new_cpus,
>>> +                    tmp->new_cpus, NULL);
>>> +    if (err)
>>> +        return err;
>>>          spin_lock_irq(&callback_lock);
>>>        partition_xcpus_add(new_prs, NULL, tmp->new_cpus);
>>> @@ -1672,6 +1700,7 @@ static void remote_partition_disable(struct cpuset *cs, struct tmpmasks *tmp)
>>>    static void remote_cpus_update(struct cpuset *cs, struct cpumask *xcpus,
>>>                       struct cpumask *excpus, struct tmpmasks *tmp)
>>>    {
>>> +    enum prs_errcode err;
>>>        bool adding, deleting;
>>>        int prs = cs->partition_root_state;
>>>    @@ -1695,14 +1724,10 @@ static void remote_cpus_update(struct cpuset *cs, struct cpumask *xcpus,
>>>         */
>>>        if (adding) {
>>>            WARN_ON_ONCE(cpumask_intersects(tmp->addmask, subpartitions_cpus));
>>> -        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))
>>> -            WRITE_ONCE(cs->prs_err, PERR_NOCPUS);
>>> -        else if ((prs == PRS_ISOLATED) &&
>>> -             !isolated_cpus_can_update(tmp->addmask, tmp->delmask))
>>> -            WRITE_ONCE(cs->prs_err, PERR_HKEEPING);
>>> +        err = validate_remote_partition(prs, true, excpus, tmp->addmask,
>>> +                        tmp->delmask);
>>> +        if (err)
>>> +            WRITE_ONCE(cs->prs_err, err);
>>>            if (cs->prs_err)
>>>                goto invalidate;
>>>        }
>>>
>>> base-commit: 31c88350b7dd1522792f726f79607f31bb55c50f
>>
> 

-- 
Best regards
Ridong


  reply	other threads:[~2026-10-12  1:35 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-29  2:38 Guopeng Zhang
2026-10-11  1:42 ` Ridong Chen
2026-10-11 11:23   ` Guopeng Zhang
2026-10-12  1:35     ` Ridong Chen [this message]
2026-10-12 17:09       ` Waiman Long

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=47f5db16-b1ca-44a0-8714-38b0cf4db9e8@linux.dev \
    --to=ridong.chen@linux.dev \
    --cc=cgroups@vger.kernel.org \
    --cc=guopeng.zhang@linux.dev \
    --cc=hannes@cmpxchg.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=mkoutny@suse.com \
    --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®