* Re: [PATCH v2 0/2] cgroup/cpuset: Fix sibling CPU exclusion in partcmd_update
From: Tejun Heo @ 2026-05-27 19:05 UTC (permalink / raw)
To: Sun Shaojie
Cc: Waiman Long, Chen Ridong, Johannes Weiner, Michal Koutný,
cgroups, linux-kernel, zhangguopeng
In-Reply-To: <20260527064329.640060-1-sunshaojie@kylinos.cn>
Hello,
On Wed, May 27, 2026 at 02:43:27PM +0800, Sun Shaojie wrote:
> Sun Shaojie (2):
> cgroup/cpuset: Use effective_xcpus in partcmd_update add/del mask
> calculation
> cgroup/cpuset: Add test cases for sibling CPU exclusion on partition
> update
Applied to cgroup/for-7.1-fixes with the following changes:
- Added Cc: stable@vger.kernel.org # v7.0+ to the fix since 2a3602030d80
shipped in v7.0.
- Added Reviewed-by: Waiman Long <longman@redhat.com> to both patches.
Thanks.
--
tejun
^ permalink raw reply
* Re: [PATCH v2 2/2] cgroup/cpuset: Add test cases for sibling CPU exclusion on partition update
From: Waiman Long @ 2026-05-27 18:08 UTC (permalink / raw)
To: Sun Shaojie, Chen Ridong, Tejun Heo, Johannes Weiner,
Michal Koutný
Cc: cgroups, linux-kernel, zhangguopeng
In-Reply-To: <20260527070509.648304-1-sunshaojie@kylinos.cn>
On 5/27/26 3:05 AM, Sun Shaojie wrote:
> When sibling CPU exclusion occurs, a partition's effective_xcpus may be
> a subset of its user_xcpus. The partcmd_update path must use
> effective_xcpus instead of user_xcpus when calculating CPUs to return
> to or request from the parent.
>
> Add two test cases to verify this behavior:
>
> 1) Narrowing cpuset.cpus to only the sibling-excluded CPUs should not
> return CPUs to parent that the partition never actually owned.
>
> 2) Expanding cpuset.cpus after a sibling becomes a member should
> correctly request the additional CPUs from parent.
>
> Co-developed-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
> Signed-off-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
> Signed-off-by: Sun Shaojie <sunshaojie@kylinos.cn>
> ---
> tools/testing/selftests/cgroup/test_cpuset_prs.sh | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> index a56f4153c64d..683b05062810 100755
> --- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> +++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
> @@ -492,6 +492,16 @@ REMOTE_TEST_MATRIX=(
> " C1-5:P1 . C1-4:P1 C2-3 . . \
> . . . P1 . . p1:5|c11:1-4|c12:5 \
> p1:P1|c11:P1|c12:P-1"
> + # Narrowing cpuset.cpus to previously sibling-excluded CPUs should
> + # not return CPUs that were never actually owned.
> + " C1-4:P1 . C1-2:P1 C1-3:P2 . . \
> + . . . C3 . . p1:4|c11:1-2|c12:3 \
> + p1:P1|c11:P1|c12:P2 3"
> + # Expanding cpuset.cpus to include a previously sibling-excluded CPU
> + # after the sibling has become a member should correctly request it.
> + " C1-4:P1 . C1-2:P1 C1-3:P2 . . \
> + . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \
> + p1:P1|c11:P0|c12:P2 2-3"
> )
>
> #
Reviewed-by: Waiman Long <longman@redhat.com>
^ permalink raw reply
* Re: [PATCH v2 1/2] cgroup/cpuset: Use effective_xcpus in partcmd_update add/del mask calculation
From: Waiman Long @ 2026-05-27 18:01 UTC (permalink / raw)
To: Sun Shaojie, Chen Ridong, Tejun Heo, Johannes Weiner,
Michal Koutný
Cc: cgroups, linux-kernel, zhangguopeng
In-Reply-To: <20260527064329.640060-2-sunshaojie@kylinos.cn>
On 5/27/26 2:43 AM, Sun Shaojie wrote:
> When sibling CPU exclusion occurs, a partition's user_xcpus may contain
> CPUs that were never actually granted to it. These CPUs are present in
> user_xcpus(cs) but not in cs->effective_xcpus.
>
> The partcmd_update path in update_parent_effective_cpumask() uses
> user_xcpus(cs) (via the local variable xcpus) to compute the addmask
> (CPUs to return to parent) and delmask (CPUs to request from parent).
> This is incorrect:
>
> 1) When newmask removes a CPU that was previously excluded by a
> sibling, addmask incorrectly includes that CPU and tries to return
> it to the parent even though the partition never actually owned it,
> causing CPU overlap with sibling partitions and triggering warnings
> in generate_sched_domains().
>
> 2) When newmask adds a previously excluded CPU that is now available,
> delmask fails to request it from the parent because user_xcpus(cs)
> already includes it.
>
> Fix this by using cs->effective_xcpus instead of user_xcpus(cs) in all
> partcmd_update paths that calculate addmask or delmask, including the
> PERR_NOCPUS error handling paths.
>
> Reproducers:
>
> Example 1 - Removing a sibling-excluded CPU incorrectly returns it:
>
> # cd /sys/fs/cgroup
> # echo "0-1" > a1/cpuset.cpus
> # echo "root" > a1/cpuset.cpus.partition
> # echo "0-2" > b1/cpuset.cpus
> # echo "root" > b1/cpuset.cpus.partition
> # echo "2" > b1/cpuset.cpus
> # cat cpuset.cpus.effective
> # Actual: 0-1,3 Expected: 3
>
> Example 2 - Expanding to a previously excluded CPU fails to request it:
>
> # cd /sys/fs/cgroup
> # echo "0-1" > a1/cpuset.cpus
> # echo "root" > a1/cpuset.cpus.partition
> # echo "0-2" > b1/cpuset.cpus
> # echo "root" > b1/cpuset.cpus.partition
> # echo "member" > a1/cpuset.cpus.partition
> # echo "1-2" > b1/cpuset.cpus
> # cat cpuset.cpus.effective
> # Actual: 0-1,3 Expected: 0,3
>
> Fixes: 2a3602030d80 ("cgroup/cpuset: Don't invalidate sibling partitions on cpuset.cpus conflict")
> Suggested-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
> Signed-off-by: Sun Shaojie <sunshaojie@kylinos.cn>
> ---
> kernel/cgroup/cpuset.c | 13 +++++++------
> 1 file changed, 7 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
> index 1335e437098e..2395c5aec871 100644
> --- a/kernel/cgroup/cpuset.c
> +++ b/kernel/cgroup/cpuset.c
> @@ -1807,9 +1807,9 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
> * Compute add/delete mask to/from effective_cpus
> *
> * For valid partition:
> - * addmask = exclusive_cpus & ~newmask
> + * addmask = effective_xcpus & ~newmask
> * & parent->effective_xcpus
> - * delmask = newmask & ~exclusive_cpus
> + * delmask = newmask & ~effective_xcpus
> * & parent->effective_xcpus
> *
> * For invalid partition:
> @@ -1821,11 +1821,11 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
> deleting = cpumask_and(tmp->delmask,
> newmask, parent->effective_xcpus);
> } else {
> - cpumask_andnot(tmp->addmask, xcpus, newmask);
> + cpumask_andnot(tmp->addmask, cs->effective_xcpus, newmask);
> adding = cpumask_and(tmp->addmask, tmp->addmask,
> parent->effective_xcpus);
>
> - cpumask_andnot(tmp->delmask, newmask, xcpus);
> + cpumask_andnot(tmp->delmask, newmask, cs->effective_xcpus);
> deleting = cpumask_and(tmp->delmask, tmp->delmask,
> parent->effective_xcpus);
> }
> @@ -1864,7 +1864,7 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
> part_error = PERR_NOCPUS;
> deleting = false;
> adding = cpumask_and(tmp->addmask,
> - xcpus, parent->effective_xcpus);
> + cs->effective_xcpus, parent->effective_xcpus);
> }
> } else {
> /*
> @@ -1886,7 +1886,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
> part_error = PERR_NOCPUS;
> if (is_partition_valid(cs))
> adding = cpumask_and(tmp->addmask,
> - xcpus, parent->effective_xcpus);
> + cs->effective_xcpus,
> + parent->effective_xcpus);
> } else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&
> cpumask_subset(xcpus, parent->effective_xcpus)) {
> struct cgroup_subsys_state *css;
Reviewed-by: Waiman Long <longman@redhat.com>
^ permalink raw reply
* Re: [PATCH v7 4/4] mm: swap: filter swap allocation by memcg tier mask
From: Kairui Song @ 2026-05-27 17:50 UTC (permalink / raw)
To: Youngjun Park
Cc: akpm, chrisl, linux-mm, cgroups, linux-kernel, kasong, hannes,
mhocko, roman.gushchin, shakeel.butt, muchun.song, shikemeng,
nphamcs, baoquan.he, baohua, gunho.lee, taejoon.song,
hyungjun.cho, mkoutny, baver.bae, matia.kim
In-Reply-To: <20260527062247.3440692-5-youngjun.park@lge.com>
On Wed, May 27, 2026 at 03:22:47PM +0800, Youngjun Park wrote:
> Apply memcg tier effective mask during swap slot allocation to
> enforce per-cgroup swap tier restrictions.
>
> In the fast path, check the percpu cached swap_info's tier_mask
> against the folio's effective mask. If it does not match, fall
> through to the slow path. In the slow path, skip swap devices
> whose tier_mask is not covered by the folio's effective mask.
>
> This works correctly when there is only one non-rotational
> device in the system and no devices share the same priority.
> However, there are known limitations:
>
> - When non-rotational devices are distributed across multiple
> tiers, and different memcgs are configured to use those
> distinct tiers, they may constantly overwrite the shared
> percpu swap cache. This cache thrashing leads to frequent
> fast path misses.
>
> - Combined with the above issue, if same-priority devices exist
> among them, a percpu cache miss (overwritten by another memcg)
> forces the allocator to round-robin to the next device
> prematurely, even if the current cluster is not fully
> exhausted.
>
> These edge cases do not affect the primary use case of
> directing swap traffic per cgroup. Further optimization is
> planned for future work.
>
> Signed-off-by: Youngjun Park <youngjun.park@lge.com>
> ---
> mm/swapfile.c | 10 +++++++++-
> 1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 9a86ebe992f4..1a2d29735b71 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -1365,14 +1365,18 @@ static bool swap_alloc_fast(struct folio *folio)
> struct swap_cluster_info *ci;
> struct swap_info_struct *si;
> unsigned int offset;
> + int mask = folio_tier_effective_mask(folio);
>
> /*
> * Once allocated, swap_info_struct will never be completely freed,
> * so checking it's liveness by get_swap_device_info is enough.
> */
> si = this_cpu_read(percpu_swap_cluster.si[order]);
> + if (!si || !swap_tiers_mask_test(si->tier_mask, mask))
> + return false;
> +
> offset = this_cpu_read(percpu_swap_cluster.offset[order]);
> - if (!si || !offset || !get_swap_device_info(si))
> + if (!offset || !get_swap_device_info(si))
> return false;
>
> ci = swap_cluster_lock(si, offset);
> @@ -1392,10 +1396,14 @@ static bool swap_alloc_fast(struct folio *folio)
> static void swap_alloc_slow(struct folio *folio)
> {
> struct swap_info_struct *si, *next;
> + int mask = folio_tier_effective_mask(folio);
>
> spin_lock(&swap_avail_lock);
> start_over:
> plist_for_each_entry_safe(si, next, &swap_avail_head, avail_list) {
> + if (!swap_tiers_mask_test(si->tier_mask, mask))
> + continue;
> +
> /* Rotate the device and switch to a new cluster */
> plist_requeue(&si->avail_list, &swap_avail_head);
> spin_unlock(&swap_avail_lock);
> --
> 2.34.1
This part looks good to me, the known limitations are not regression
and only for tiering, so can be improved later, and we do have plan
to refine the priority / rotation / pcp cluster so they aligns well.
Reviewed-by: Kairui Song <kasong@tencent.com>
^ permalink raw reply
* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Aaron Tomlin @ 2026-05-27 17:41 UTC (permalink / raw)
To: Peter Zijlstra
Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
linux-security-module, selinux, linux-kernel
In-Reply-To: <20260527155404.GV3126523@noisy.programming.kicks-ass.net>
[-- Attachment #1: Type: text/plain, Size: 2480 bytes --]
On Wed, May 27, 2026 at 05:54:04PM +0200, Peter Zijlstra wrote:
> > The LSM hook is currently the only infrastructure positioned to do
> > this safely for eBPF-driven security policies.
>
> But is that correct use of LSM? Or is that working around short comings
> elsewhere?
Hi Peter,
I am in complete agreement that we should avoid indiscriminately grafting
hooks onto the kernel simply to accommodate BPF. Nevertheless, I would
argue that this represents a textbook application of LSM.
> I realize that bpf people rarely care about things like this, they just
> want to hack their thing and will take any hook they can get. But I feel
> people *should* care.
>
> > The actual use case here is multi-tenant workload isolation and visibility.
> > Passing the evaluated cpumask to the BPF LSM allows operators to write a
> > simple eBPF program to detect spatial boundary overlaps (e.g., logging an
> > event if a requested mask intersects with platform-reserved cores).
> >
> > If this justification makes more sense, I will focus strictly on the
> > seccomp pointer limitations and multi-tenant workload isolation.
>
> I suppose it does, my only remaining question is if that is indeed
> proper use of LSM -- I really don't know much about that.
>
We are not creating a bespoke BPF hook here; rather, we are rectifying a
historical blind spot within the API. The existing LSM hook is invoked
during sched_setaffinity(), yet it presently receives only the task_struct
pointer. Consequently, the security module is essentially asked, "Should
Process A be permitted to alter Process B's affinity?" without being
informed of the proposed affinity itself. Providing in_mask simply
furnishes the existing hook with the requisite payload to make an informed
decision.
Were the objective solely one of observability, a tracepoint would indeed
be the most suitable mechanism. However, if the aim within multi-tenant
environments is active enforcement (namely, safely returning -EPERM to deny
the pinning request before the scheduler applies it), the LSM layer remains
the standard, architecturally supported gateway for returning syscall
errors in accordance with administrative policy.
I shall defer to Paul Moore and the LSM maintainers for their final
blessing on the LSM API semantics.
Thank you once again for the thorough review and for keeping the
architectural boundaries honest.
Kind regards,
--
Aaron Tomlin
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Peter Zijlstra @ 2026-05-27 15:54 UTC (permalink / raw)
To: Aaron Tomlin
Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
linux-security-module, selinux, linux-kernel
In-Reply-To: <bgjagepcfb7gz6jawatu6kpfmecw46gwg5cvb6r7dl3dn7bt4l@rtymdaslx7ef>
[-- Attachment #1: Type: text/plain, Size: 2815 bytes --]
On Wed, May 27, 2026 at 11:05:17AM -0400, Aaron Tomlin wrote:
> On Wed, May 27, 2026 at 10:52:21AM +0200, Peter Zijlstra wrote:
> > I'm not sure I really buy the Real-Time argument here; that really feels
> > like a straw man. Real-Time will need to account for the shared resource
> > usage inherent in using a single kernel image across the CPUs, affinity
> > alone does not Real-Time make in any way shape or form.
> >
> > And the compromised task vs crypto thing feels like it wants sandboxing,
> > but wasn't that what seccomp is for, rather than lsm?
> >
> > So while I don't think I object very much to the patch, I do find the
> > whole Changelog to be utterly questionable. Which makes me very
> > suspicious as to wtf this is actually for.
>
> Hi Peter,
>
> Thank you for the blunt and honest feedback.
>
> You are completely right to call out the changelog. It obscured the actual
> practical use case. I will rewrite the commit message to drop those
> statements.
>
> To answer your question regarding seccomp: seccomp-bpf is strictly limited
> to inspecting syscall arguments by value at the syscall entry boundary. For
> sched_setaffinity(), the mask is passed as a "__user" pointer. Seccomp
> cannot safely dereference this pointer to inspect the requested CPU bits.
There has been work to allow tracepoints, specifically syscall
tracepoints, to access the syscall arguments and to do exactly this
(deref user pointers). I *think* most of that work landed, but I might
be mistaken.
Would this then not also allow seccomp-bpf to access these?
(while writing this, I wonder if that would then not be subject to
TOCTOU)
> To actually evaluate which CPUs a task is trying to pin to, we must
> evaluate the mask after copy_from_user() has safely brought it into kernel
> memory.
Right this.
> The LSM hook is currently the only infrastructure positioned to do
> this safely for eBPF-driven security policies.
But is that correct use of LSM? Or is that working around short comings
elsewhere?
I realize that bpf people rarely care about things like this, they just
want to hack their thing and will take any hook they can get. But I feel
people *should* care.
> The actual use case here is multi-tenant workload isolation and visibility.
> Passing the evaluated cpumask to the BPF LSM allows operators to write a
> simple eBPF program to detect spatial boundary overlaps (e.g., logging an
> event if a requested mask intersects with platform-reserved cores).
>
> If this justification makes more sense, I will focus strictly on the
> seccomp pointer limitations and multi-tenant workload isolation.
I suppose it does, my only remaining question is if that is indeed
proper use of LSM -- I really don't know much about that.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [RFC PATCH bpf-next v7 04/11] libbpf: introduce bpf_map__attach_struct_ops_opts()
From: Yonghong Song @ 2026-05-27 15:43 UTC (permalink / raw)
To: Hui Zhu, Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Jiri Olsa, Johannes Weiner,
Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
JP Kobryn, Andrew Morton, Shuah Khan, davem, Jakub Kicinski,
Jesper Dangaard Brouer, Stanislav Fomichev, KP Singh, Tao Chen,
Mykyta Yatsenko, Leon Hwang, Anton Protopopov, Amery Hung,
Tobias Klauser, Eyal Birger, Rong Tao, Hao Luo, Peter Zijlstra,
Miguel Ojeda, Nathan Chancellor, Kees Cook, Tejun Heo, Jeff Xu,
mkoutny, Jan Hendrik Farr, Christian Brauner, Randy Dunlap,
Brian Gerst, Masahiro Yamada, Willem de Bruijn, Jason Xing,
Paul Chaignon, Chen Ridong, Lance Yang, Jiayuan Chen,
linux-kernel, bpf, cgroups, linux-mm, netdev, linux-kselftest
Cc: geliang, baohua
In-Reply-To: <20bdaa33cc19364f5f10208c79ef94fe43bd5ac1.1779760876.git.zhuhui@kylinos.cn>
On 5/25/26 7:20 PM, Hui Zhu wrote:
> From: Roman Gushchin <roman.gushchin@linux.dev>
>
> Introduce bpf_map__attach_struct_ops_opts(), an extended version of
> bpf_map__attach_struct_ops(), which takes additional struct
> bpf_struct_ops_opts argument.
>
> This allows to pass a target_fd argument and the BPF_F_CGROUP_FD flag
> and attach the struct ops to a cgroup as a result.
>
> Signed-off-by: Roman Gushchin <roman.gushchin@linux.dev>
> ---
> tools/lib/bpf/libbpf.c | 20 +++++++++++++++++---
> tools/lib/bpf/libbpf.h | 14 ++++++++++++++
> tools/lib/bpf/libbpf.map | 1 +
> 3 files changed, 32 insertions(+), 3 deletions(-)
>
> diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c
> index 1e8688975d16..a1b54da1ded2 100644
> --- a/tools/lib/bpf/libbpf.c
> +++ b/tools/lib/bpf/libbpf.c
> @@ -13683,11 +13683,18 @@ static int bpf_link__detach_struct_ops(struct bpf_link *link)
> return close(link->fd);
> }
>
> -struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
> +struct bpf_link *bpf_map__attach_struct_ops_opts(const struct bpf_map *map,
> + const struct bpf_struct_ops_opts *opts)
> {
> + DECLARE_LIBBPF_OPTS(bpf_link_create_opts, link_opts);
> struct bpf_link_struct_ops *link;
> + int err, fd, target_fd;
> __u32 zero = 0;
> - int err, fd;
> +
> + if (!OPTS_VALID(opts, bpf_struct_ops_opts)) {
> + pr_warn("map '%s': invalid opts\n", map->name);
> + return libbpf_err_ptr(-EINVAL);
> + }
>
> if (!bpf_map__is_struct_ops(map)) {
> pr_warn("map '%s': can't attach non-struct_ops map\n", map->name);
> @@ -13724,7 +13731,9 @@ struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
> return &link->link;
> }
>
> - fd = bpf_link_create(map->fd, 0, BPF_STRUCT_OPS, NULL);
> + link_opts.flags = OPTS_GET(opts, flags, 0);
> + target_fd = OPTS_GET(opts, target_fd, 0);
> + fd = bpf_link_create(map->fd, target_fd, BPF_STRUCT_OPS, &link_opts);
> if (fd < 0) {
> free(link);
> return libbpf_err_ptr(fd);
> @@ -13736,6 +13745,11 @@ struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
> return &link->link;
> }
>
> +struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map)
> +{
> + return bpf_map__attach_struct_ops_opts(map, NULL);
> +}
> +
> /*
> * Swap the back struct_ops of a link with a new struct_ops map.
> */
> diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h
> index bba4e8464396..18af178547ad 100644
> --- a/tools/lib/bpf/libbpf.h
> +++ b/tools/lib/bpf/libbpf.h
> @@ -945,6 +945,20 @@ bpf_program__attach_cgroup_opts(const struct bpf_program *prog, int cgroup_fd,
> struct bpf_map;
>
> LIBBPF_API struct bpf_link *bpf_map__attach_struct_ops(const struct bpf_map *map);
> +
> +struct bpf_struct_ops_opts {
> + /* size of this struct, for forward/backward compatibility */
> + size_t sz;
> + __u32 flags;
> + __u32 target_fd;
> + __u64 expected_revision;
> + size_t :0;
> +};
> +#define bpf_struct_ops_opts__last_field expected_revision
> +
> +LIBBPF_API struct bpf_link *
> +bpf_map__attach_struct_ops_opts(const struct bpf_map *map,
> + const struct bpf_struct_ops_opts *opts);
> LIBBPF_API int bpf_link__update_map(struct bpf_link *link, const struct bpf_map *map);
>
> struct bpf_iter_attach_opts {
> diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map
> index dfed8d60af05..6105619b5ecf 100644
> --- a/tools/lib/bpf/libbpf.map
> +++ b/tools/lib/bpf/libbpf.map
> @@ -454,6 +454,7 @@ LIBBPF_1.7.0 {
> bpf_prog_assoc_struct_ops;
> bpf_program__assoc_struct_ops;
> btf__permute;
> + bpf_map__attach_struct_ops_opts;
Function bpf_map__attach_struct_ops_opts should be in
LIBBPF_1.8.0.
> } LIBBPF_1.6.0;
>
> LIBBPF_1.8.0 {
^ permalink raw reply
* [PATCH-next v3 5/5] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach()
From: Waiman Long @ 2026-05-27 15:38 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long
In-Reply-To: <20260527153800.1557449-1-longman@redhat.com>
With cgroup v2, the cgroup_taskset structure passed into the cgroup
can_attach() and attach() methods can contain task migration data with
multiple destination or source cpusets when the cpuset controller is
enabled or disabled respectively.
Since cpuset is threaded in both v1 and v2, another possible way to
cause many-to-one migration is to move the whole process with multiple
threads in different cpuset enabled threaded cgroups into another cpuset
enabled cgroup.
The current cpuset_can_attach() and cpuset_attach() functions still
expect task migration is from one source cpuset to one destination
cpuset. This has been the case since cpuset was enabled for cgroup v2
in commit 4ec22e9c5a90 ("cpuset: Enable cpuset controller in default
hierarchy").
This problem is less an issue when enabling the cpuset controller as all
the newly created child cpusets will have exactly the same set of CPUs
and memory nodes except when deadline tasks are involved in migration
as the deadline task accounting data can be off.
It can be more problematic when the cpuset controller is disabled as
their set of CPUs and memory nodes may differ from their parent or with
the moving of multi-threaded process from different threaded cgroups.
Fix that by tracking the set of source (old) and destination cpusets
in singly linked lists and iterating them all to properly update the
internal data. Also keep the current cs and oldcs variables up-to-date
with the css and task iterators.
To ensure proper DL tasks accounting, the nr_migrate_dl_tasks in both
the source and destination cpusets are decremented/incremented with
their values added to nr_deadline_tasks when the migration is successful.
Fixes: 4ec22e9c5a90 ("cpuset: Enable cpuset controller in default hierarchy")
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/cgroup/cpuset-internal.h | 6 +
kernel/cgroup/cpuset.c | 206 +++++++++++++++++++++++---------
2 files changed, 157 insertions(+), 55 deletions(-)
diff --git a/kernel/cgroup/cpuset-internal.h b/kernel/cgroup/cpuset-internal.h
index f7aaf01f7cd5..4c2772a7fd5e 100644
--- a/kernel/cgroup/cpuset-internal.h
+++ b/kernel/cgroup/cpuset-internal.h
@@ -161,6 +161,12 @@ struct cpuset {
*/
bool remote_partition;
+ /*
+ * cpuset_can_attach() and cpuset_attach() specific data
+ */
+ bool attach_node_in_llist;
+ struct llist_node attach_node;
+
/*
* number of SCHED_DEADLINE tasks attached to this cpuset, so that we
* know when to rebuild associated root domain bandwidth information.
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 7100575927f6..98ee001ef950 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -37,6 +37,7 @@
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/task_work.h>
+#include <linux/llist.h>
DEFINE_STATIC_KEY_FALSE(cpusets_pre_enable_key);
DEFINE_STATIC_KEY_FALSE(cpusets_enabled_key);
@@ -2983,6 +2984,8 @@ static int update_prstate(struct cpuset *cs, int new_prs)
* matter which child cpuset is selected as cpuset_attach_old_cs.
*/
static struct cpuset *cpuset_attach_old_cs;
+static LLIST_HEAD(src_cs_head);
+static LLIST_HEAD(dst_cs_head);
static bool attach_cpus_updated;
static bool attach_mems_updated;
@@ -2995,9 +2998,10 @@ static bool attach_mems_updated;
* Also set the boolean flag passed in by @psetsched depending on if
* security_task_setscheduler() call is needed and @oldcs is not NULL.
*/
-static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
- bool *psetsched)
+static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs, bool *psetsched)
{
+ bool cpu_match, mem_match;
+
if (cpumask_empty(cs->effective_cpus) ||
(!is_in_v2_mode() && nodes_empty(cs->mems_allowed)))
return -ENOSPC;
@@ -3008,15 +3012,34 @@ static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
/*
* Update attach specific data
*/
- attach_cpus_updated = !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus);
- attach_mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
+ if (!cs->attach_node_in_llist) {
+ llist_add(&cs->attach_node, &dst_cs_head);
+ cs->attach_node_in_llist = true;
+ }
+ if (!oldcs->attach_node_in_llist) {
+ llist_add(&oldcs->attach_node, &src_cs_head);
+ oldcs->attach_node_in_llist = true;
+ }
+
+ cpu_match = cpumask_equal(cs->effective_cpus, oldcs->effective_cpus);
+ mem_match = nodes_equal(cs->effective_mems, oldcs->effective_mems);
+
+ /*
+ * Set the updated flags whenever there is a mismatch in any of the
+ * src/dst pairs.
+ */
+ if (!attach_cpus_updated)
+ attach_cpus_updated = !cpu_match;
+
+ if (!attach_mems_updated)
+ attach_mems_updated = !mem_match;
/*
* Skip rights over task setsched check in v2 when nothing changes,
* migration permission derives from hierarchy ownership in
* cgroup_procs_write_permission()).
*/
- *psetsched = !cpuset_v2() || attach_cpus_updated || attach_mems_updated;
+ *psetsched = !cpuset_v2() || !cpu_match || !mem_match;
/*
* A v1 cpuset with tasks will have no CPU left only when CPU hotplug
@@ -3031,33 +3054,103 @@ static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
return 0;
}
-static int cpuset_reserve_dl_bw(struct cpuset *cs)
+/*
+ * If reset_dl_bw is set, reset the previous dl_bw_alloc() call. Otherwise,
+ * update nr_deadline_tasks according to nr_migrate_dl_tasks in both source
+ * and destination cpusets.
+ */
+static void clear_attach_data(bool reset_dl_bw)
+{
+ struct cpuset *cs, *next;
+
+ llist_for_each_entry_safe(cs, next, src_cs_head.first, attach_node) {
+ cs->attach_node.next = NULL;
+ cs->attach_node_in_llist = false;
+ if (cs->nr_migrate_dl_tasks && !reset_dl_bw)
+ cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
+ cs->nr_migrate_dl_tasks = 0;
+ }
+
+ llist_for_each_entry_safe(cs, next, dst_cs_head.first, attach_node) {
+ cs->attach_node.next = NULL;
+ cs->attach_node_in_llist = false;
+ if (reset_dl_bw && cs->dl_bw_cpu >= 0)
+ dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw);
+ if (cs->nr_migrate_dl_tasks && !reset_dl_bw)
+ cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
+ cs->nr_migrate_dl_tasks = 0;
+ cs->sum_migrate_dl_bw = 0;
+ cs->dl_bw_cpu = -1;
+ }
+
+ src_cs_head.first = NULL;
+ dst_cs_head.first = NULL;
+ attach_cpus_updated = false;
+ attach_mems_updated = false;
+}
+
+static int cpuset_reserve_dl_bw(void)
{
+ struct cpuset *cs;
int cpu, ret;
- if (!cs->sum_migrate_dl_bw)
- return 0;
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node) {
+ if (!cs->sum_migrate_dl_bw)
+ continue;
- cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
- if (unlikely(cpu >= nr_cpu_ids))
- return -EINVAL;
+ cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
+ if (unlikely(cpu >= nr_cpu_ids))
+ return -EINVAL;
- ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
- if (ret)
- return ret;
+ ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
+ if (ret)
+ return ret;
- cs->dl_bw_cpu = cpu;
+ cs->dl_bw_cpu = cpu;
+ }
return 0;
}
-static void reset_migrate_dl_data(struct cpuset *cs)
+static void set_attach_in_progress(void)
{
- cs->nr_migrate_dl_tasks = 0;
- cs->sum_migrate_dl_bw = 0;
- cs->dl_bw_cpu = -1;
+ struct cpuset *cs;
+
+ /*
+ * Mark attach is in progress. This makes validate_change() fail
+ * changes which zero cpus/mems_allowed.
+ */
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node)
+ cs->attach_in_progress++;
+}
+
+static void reset_attach_in_progress(void)
+{
+ struct cpuset *cs;
+
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node)
+ dec_attach_in_progress_locked(cs);
}
-/* Called by cgroups to determine if a cpuset is usable; cpuset_mutex held */
+/*
+ * Called by cgroups to determine if a cpuset is usable; cpuset_mutex held.
+ *
+ * With cgroup v2, enabling of cpuset controller in a cgroup subtree can
+ * cause @tset to contain task migration data from one parent cpuset to multiple
+ * child cpusets. Not much is needed to be done here other than tracking the
+ * number of DL tasks in each cpuset as the CPUs and memory nodes of the child
+ * cpusets are exactly the same as the parent.
+ *
+ * Conversely, disabling of cpuset controller can cause @tset to contain task
+ * migration data from multiple child cpusets to one parent cpuset. Here, the
+ * CPUs and memory nodes of the child cpusets may be different from the parent,
+ * but must be a subset of its parent.
+ *
+ * Another possible many-to-one migration is the moving of the whole
+ * multithreaded process with threads in different cpusets to another cpuset.
+ *
+ * For all other use cases, @tset task migration data should be from one source
+ * cpuset to one destination cpuset.
+ */
static int cpuset_can_attach(struct cgroup_taskset *tset)
{
struct cgroup_subsys_state *css;
@@ -3079,6 +3172,16 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
goto out_unlock;
cgroup_taskset_for_each(task, css, tset) {
+ struct cpuset *newcs = css_cs(css);
+ struct cpuset *new_oldcs = task_cs(task);
+
+ if ((newcs != cs) || (new_oldcs != oldcs)) {
+ cs = newcs;
+ oldcs = new_oldcs;
+ ret = cpuset_can_attach_check(cs, oldcs, &setsched_check);
+ if (ret)
+ goto out_unlock;
+ }
ret = task_can_attach(task);
if (ret)
goto out_unlock;
@@ -3100,23 +3203,19 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
* contribute to sum_migrate_dl_bw.
*/
cs->nr_migrate_dl_tasks++;
+ oldcs->nr_migrate_dl_tasks--;
if (dl_task_needs_bw_move(task, cs->effective_cpus))
cs->sum_migrate_dl_bw += task->dl.dl_bw;
}
}
- ret = cpuset_reserve_dl_bw(cs);
+ ret = cpuset_reserve_dl_bw();
out_unlock:
- if (ret) {
- reset_migrate_dl_data(cs);
- } else {
- /*
- * Mark attach is in progress. This makes validate_change() fail
- * changes which zero cpus/mems_allowed.
- */
- cs->attach_in_progress++;
- }
+ if (ret)
+ clear_attach_data(true);
+ else
+ set_attach_in_progress();
mutex_unlock(&cpuset_mutex);
return ret;
@@ -3131,14 +3230,8 @@ static void cpuset_cancel_attach(struct cgroup_taskset *tset)
cs = css_cs(css);
mutex_lock(&cpuset_mutex);
- dec_attach_in_progress_locked(cs);
-
- if (cs->dl_bw_cpu >= 0)
- dl_bw_free(cs->dl_bw_cpu, cs->sum_migrate_dl_bw);
-
- if (cs->nr_migrate_dl_tasks)
- reset_migrate_dl_data(cs);
-
+ reset_attach_in_progress();
+ clear_attach_data(true);
mutex_unlock(&cpuset_mutex);
}
@@ -3210,42 +3303,45 @@ static void cpuset_attach(struct cgroup_taskset *tset)
struct task_struct *task;
struct cgroup_subsys_state *css;
struct cpuset *cs;
- struct cpuset *oldcs = cpuset_attach_old_cs;
cgroup_taskset_first(tset, &css);
cs = css_cs(css);
-
lockdep_assert_cpus_held(); /* see cgroup_attach_lock() */
mutex_lock(&cpuset_mutex);
queue_task_work = false;
/*
* In the default hierarchy, enabling cpuset in the child cgroups
- * will trigger a number of cpuset_attach() calls with no change
- * in effective cpus and mems. In that case, we can optimize out
- * by skipping the task iteration and update.
+ * will trigger a cpuset_attach() call with no change in effective cpus
+ * and mems. In that case, we can optimize out by skipping the task
+ * iteration and update, but the destination cpuset list is iterated to
+ * set old_mems_sllowed.
*/
- if (cpuset_v2() && !attach_cpus_updated && !attach_mems_updated)
+ if (cpuset_v2() && !attach_cpus_updated && !attach_mems_updated) {
+ llist_for_each_entry(cs, dst_cs_head.first, attach_node)
+ cs->old_mems_allowed = cs->effective_mems;
goto out;
+ }
guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
- cgroup_taskset_for_each(task, css, tset)
+ cgroup_taskset_for_each(task, css, tset) {
+ struct cpuset *newcs = css_cs(css);
+
+ if (newcs != cs) {
+ cs->old_mems_allowed = cs->effective_mems;
+ cs = newcs;
+ guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
+ }
cpuset_attach_task(cs, task);
+ }
-out:
if (queue_task_work)
schedule_flush_migrate_mm();
cs->old_mems_allowed = cs->effective_mems;
-
- if (cs->nr_migrate_dl_tasks) {
- cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
- oldcs->nr_deadline_tasks -= cs->nr_migrate_dl_tasks;
- reset_migrate_dl_data(cs);
- }
-
- dec_attach_in_progress_locked(cs);
-
+out:
+ reset_attach_in_progress();
+ clear_attach_data(false);
mutex_unlock(&cpuset_mutex);
}
--
2.54.0
^ permalink raw reply related
* [PATCH-next v3 4/5] cgroup/cpuset: Move mpol_rebind_mm/cpuset_migrate_mm() calls inside cpuset_attach_task()
From: Waiman Long @ 2026-05-27 15:37 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long
In-Reply-To: <20260527153800.1557449-1-longman@redhat.com>
The cpuset_attach_task() was introduced in commit 42a11bf5c543
("cgroup/cpuset: Make cpuset_fork() handle CLONE_INTO_CGROUP properly")
to enable the CLONE_INTO_CGROUP flag of clone(2) to behave more like
moving a task from one cpuset into another one. That commits didn't
move the mpol_rebind_mm() and cpuset_migrate_mm() calls for group leader
into cpuset_attach_task().
When the CLONE_INTO_CGROUP flag is used without CLONE_THREAD, the new
task is its own group leader. So it is still not equivalent to moving
task between cpusets in this case. Make CLONE_INTO_CGROUP behaves
more close to cpuset_attach() by moving the mpol_rebind_mm() and
cpuset_migrate_mm() calls inside cpuset_attach_task(). As a result,
cpuset_attach_old_cs, attach_cpus_updated and attach_mems_updated will
also need to be updated in cpuset_fork().
Besides, the original code use cpuset_attach_nodemask_to for
both nodemask returned by guarantee_online_mems() used only by
cpuset_change_task_nodemask() and cs->effective_mems in all other cases.
Such dual use is now impractical by merging the two task iteration loops
into one. So keep cpuset_attach_nodemask_to for the nodemask returned
by guarantee_online_mems() and reference cs->effective_mems directly
in all the other cases.
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/cgroup/cpuset.c | 90 ++++++++++++++++++++++--------------------
1 file changed, 47 insertions(+), 43 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index b233a71f9b7c..7100575927f6 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -3149,9 +3149,12 @@ static void cpuset_cancel_attach(struct cgroup_taskset *tset)
*/
static cpumask_var_t cpus_attach;
static nodemask_t cpuset_attach_nodemask_to;
+static bool queue_task_work;
static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
{
+ struct mm_struct *mm;
+
lockdep_assert_cpuset_lock_held();
if (cs != &top_cpuset)
@@ -3165,24 +3168,56 @@ static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
*/
WARN_ON_ONCE(set_cpus_allowed_ptr(task, cpus_attach));
+ if (cpuset_v2() && !attach_mems_updated)
+ return;
+
cpuset_change_task_nodemask(task, &cpuset_attach_nodemask_to);
cpuset1_update_task_spread_flags(cs, task);
+
+ if (task != task->group_leader)
+ return;
+
+ /*
+ * Change mm for threadgroup leader. This is expensive and may
+ * sleep and should be moved outside migration path proper.
+ */
+ mm = get_task_mm(task);
+ if (mm) {
+ struct cpuset *oldcs = cpuset_attach_old_cs;
+
+ mpol_rebind_mm(mm, &cs->effective_mems);
+
+ /*
+ * old_mems_allowed is the same with mems_allowed
+ * here, except if this task is being moved
+ * automatically due to hotplug. In that case
+ * @mems_allowed has been updated and is empty, so
+ * @old_mems_allowed is the right nodesets that we
+ * migrate mm from.
+ */
+ if (is_memory_migrate(cs)) {
+ cpuset_migrate_mm(mm, &oldcs->old_mems_allowed,
+ &cs->effective_mems);
+ queue_task_work = true;
+ } else {
+ mmput(mm);
+ }
+ }
}
static void cpuset_attach(struct cgroup_taskset *tset)
{
struct task_struct *task;
- struct task_struct *leader;
struct cgroup_subsys_state *css;
struct cpuset *cs;
struct cpuset *oldcs = cpuset_attach_old_cs;
- bool queue_task_work = false;
cgroup_taskset_first(tset, &css);
cs = css_cs(css);
lockdep_assert_cpus_held(); /* see cgroup_attach_lock() */
mutex_lock(&cpuset_mutex);
+ queue_task_work = false;
/*
* In the default hierarchy, enabling cpuset in the child cgroups
@@ -3190,53 +3225,18 @@ static void cpuset_attach(struct cgroup_taskset *tset)
* in effective cpus and mems. In that case, we can optimize out
* by skipping the task iteration and update.
*/
- if (cpuset_v2() && !attach_cpus_updated && !attach_mems_updated) {
- cpuset_attach_nodemask_to = cs->effective_mems;
+ if (cpuset_v2() && !attach_cpus_updated && !attach_mems_updated)
goto out;
- }
guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
cgroup_taskset_for_each(task, css, tset)
cpuset_attach_task(cs, task);
- /*
- * Change mm for all threadgroup leaders. This is expensive and may
- * sleep and should be moved outside migration path proper. Skip it
- * if there is no change in effective_mems and CS_MEMORY_MIGRATE is
- * not set.
- */
- cpuset_attach_nodemask_to = cs->effective_mems;
- if (!is_memory_migrate(cs) && !attach_mems_updated)
- goto out;
-
- cgroup_taskset_for_each_leader(leader, css, tset) {
- struct mm_struct *mm = get_task_mm(leader);
-
- if (mm) {
- mpol_rebind_mm(mm, &cpuset_attach_nodemask_to);
-
- /*
- * old_mems_allowed is the same with mems_allowed
- * here, except if this task is being moved
- * automatically due to hotplug. In that case
- * @mems_allowed has been updated and is empty, so
- * @old_mems_allowed is the right nodesets that we
- * migrate mm from.
- */
- if (is_memory_migrate(cs)) {
- cpuset_migrate_mm(mm, &oldcs->old_mems_allowed,
- &cpuset_attach_nodemask_to);
- queue_task_work = true;
- } else
- mmput(mm);
- }
- }
-
out:
if (queue_task_work)
schedule_flush_migrate_mm();
- cs->old_mems_allowed = cpuset_attach_nodemask_to;
+ cs->old_mems_allowed = cs->effective_mems;
if (cs->nr_migrate_dl_tasks) {
cs->nr_deadline_tasks += cs->nr_migrate_dl_tasks;
@@ -3666,15 +3666,14 @@ static void cpuset_cancel_fork(struct task_struct *task, struct css_set *cset)
*/
static void cpuset_fork(struct task_struct *task)
{
- struct cpuset *cs;
- bool same_cs;
+ struct cpuset *cs, *oldcs;
rcu_read_lock();
cs = task_cs(task);
- same_cs = (cs == task_cs(current));
+ oldcs = task_cs(current);
rcu_read_unlock();
- if (same_cs) {
+ if (cs == oldcs) {
if (cs == &top_cpuset)
return;
@@ -3686,7 +3685,12 @@ static void cpuset_fork(struct task_struct *task)
/* CLONE_INTO_CGROUP */
mutex_lock(&cpuset_mutex);
guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
+ /* Assume CPUs and memory nodes are updated */
+ attach_cpus_updated = attach_mems_updated = true;
+ cpuset_attach_old_cs = oldcs;
+ oldcs->old_mems_allowed = oldcs->effective_mems;
cpuset_attach_task(cs, task);
+ attach_cpus_updated = attach_mems_updated = false;
dec_attach_in_progress_locked(cs);
mutex_unlock(&cpuset_mutex);
--
2.54.0
^ permalink raw reply related
* [PATCH-next v3 3/5] cgroup/cpuset: Made cpuset_attach_old_cs track task group leaders
From: Waiman Long @ 2026-05-27 15:37 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long, Ridong Chen
In-Reply-To: <20260527153800.1557449-1-longman@redhat.com>
There are two possible ways that migration of tasks from multiple source
cpusets to a target cpuset can happen. Either a multithread application
with threads in different cpusets is wholely moved to a new cpuset
or disabling of v2 cpuset controller will move all the tasks in child
cpusets to the parent cpuset.
In the former case, t is the mm setting of the group leader that really
matters. So cpuset_attach_old_cs should track the oldcs of the thread
leader. In the latter case, effective_mems of child cpusets must always
be a subset of the parent. So no real page migration will be necessary
no matter which child cpuset is selected as cpuset_attach_old_cs.
IOW, cpuset_attach_old_cs should be updated to match the latest task
group leader in cpuset_can_attach().
Suggested-by: Ridong Chen <ridong.chen@linux.dev>
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/cgroup/cpuset.c | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 4457c4f11fce..b233a71f9b7c 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2967,6 +2967,20 @@ static int update_prstate(struct cpuset *cs, int new_prs)
/*
* cpuset_can_attach() and cpuset_attach() specific internal data
* Protected by cpuset_mutex
+ *
+ * The cpuset_attach_old_cs is used mainly by cpuset_migrate_mm() tp get the
+ * old_mems_allowed value. There are two ways that many-to-one cpuset migration
+ * can happen:
+ * 1) A multithread application with threads in different cpusets is wholely
+ * moved to a new cpuset.
+ * 2) Disabling v2 cpuset controller will move all the tasks in child cpusets
+ * to the parent cpuset.
+ *
+ * In the former case, it is the mm setting of the group leader that really
+ * matters. So cpuset_attach_old_cs should track the oldcs of the thread
+ * leader. In the latter case, effective_mems of child cpusets must always
+ * be a subset of the parent. So no real page migration will be necessary no
+ * matter which child cpuset is selected as cpuset_attach_old_cs.
*/
static struct cpuset *cpuset_attach_old_cs;
static bool attach_cpus_updated;
@@ -3069,6 +3083,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
if (ret)
goto out_unlock;
+ /* Update cpuset_attach_old_cs to the latest group leader */
+ if (task == task->group_leader)
+ cpuset_attach_old_cs = task_cs(task);
+
if (setsched_check) {
ret = security_task_setscheduler(task);
if (ret)
--
2.54.0
^ permalink raw reply related
* [PATCH-next v3 2/5] cgroup/cpuset: Expand the scope of cpuset_can_attach_check()
From: Waiman Long @ 2026-05-27 15:37 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long
In-Reply-To: <20260527153800.1557449-1-longman@redhat.com>
Expand the scope of cpuset_can_attach_check() by including the setting
of setsched flag inside cpuset_can_attach_check() with the new @oldcs
and @psetsched argument. As cpuset_can_attach_check() is also called
from cpuset_can_fork(), set the new arguments to NULL from that caller.
While at it, expose the source and destination cpuset cpu/memory check
results in the new attach_cpus_updated and attach_mems_updated static
flags so that these flags can be used directly from cpuset_attach()
without the need to do the same computations again.
No functional change is expected.
Reviewed-by: Chen Ridong <chenridong@huaweicloud.com>
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/cgroup/cpuset.c | 70 +++++++++++++++++++++++++-----------------
1 file changed, 42 insertions(+), 28 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index d720bcc7ef83..4457c4f11fce 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2964,19 +2964,56 @@ static int update_prstate(struct cpuset *cs, int new_prs)
return 0;
}
+/*
+ * cpuset_can_attach() and cpuset_attach() specific internal data
+ * Protected by cpuset_mutex
+ */
static struct cpuset *cpuset_attach_old_cs;
+static bool attach_cpus_updated;
+static bool attach_mems_updated;
/*
* Check to see if a cpuset can accept a new task
* For v1, cpus_allowed and mems_allowed can't be empty.
* For v2, effective_cpus can't be empty.
* Note that in v1, effective_cpus = cpus_allowed.
+ *
+ * Also set the boolean flag passed in by @psetsched depending on if
+ * security_task_setscheduler() call is needed and @oldcs is not NULL.
*/
-static int cpuset_can_attach_check(struct cpuset *cs)
+static int cpuset_can_attach_check(struct cpuset *cs, struct cpuset *oldcs,
+ bool *psetsched)
{
if (cpumask_empty(cs->effective_cpus) ||
(!is_in_v2_mode() && nodes_empty(cs->mems_allowed)))
return -ENOSPC;
+
+ if (!oldcs)
+ return 0;
+
+ /*
+ * Update attach specific data
+ */
+ attach_cpus_updated = !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus);
+ attach_mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
+
+ /*
+ * Skip rights over task setsched check in v2 when nothing changes,
+ * migration permission derives from hierarchy ownership in
+ * cgroup_procs_write_permission()).
+ */
+ *psetsched = !cpuset_v2() || attach_cpus_updated || attach_mems_updated;
+
+ /*
+ * A v1 cpuset with tasks will have no CPU left only when CPU hotplug
+ * brings the last online CPU offline as users are not allowed to empty
+ * cpuset.cpus when there are active tasks inside. When that happens,
+ * we should allow tasks to migrate out without security check to make
+ * sure they will be able to run after migration.
+ */
+ if (!is_in_v2_mode() && cpumask_empty(oldcs->effective_cpus))
+ *psetsched = false;
+
return 0;
}
@@ -3023,29 +3060,10 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
mutex_lock(&cpuset_mutex);
/* Check to see if task is allowed in the cpuset */
- ret = cpuset_can_attach_check(cs);
+ ret = cpuset_can_attach_check(cs, oldcs, &setsched_check);
if (ret)
goto out_unlock;
- /*
- * Skip rights over task setsched check in v2 when nothing changes,
- * migration permission derives from hierarchy ownership in
- * cgroup_procs_write_permission()).
- */
- setsched_check = !cpuset_v2() ||
- !cpumask_equal(cs->effective_cpus, oldcs->effective_cpus) ||
- !nodes_equal(cs->effective_mems, oldcs->effective_mems);
-
- /*
- * A v1 cpuset with tasks will have no CPU left only when CPU hotplug
- * brings the last online CPU offline as users are not allowed to empty
- * cpuset.cpus when there are active tasks inside. When that happens,
- * we should allow tasks to migrate out without security check to make
- * sure they will be able to run after migration.
- */
- if (!is_in_v2_mode() && cpumask_empty(oldcs->effective_cpus))
- setsched_check = false;
-
cgroup_taskset_for_each(task, css, tset) {
ret = task_can_attach(task);
if (ret)
@@ -3140,7 +3158,6 @@ static void cpuset_attach(struct cgroup_taskset *tset)
struct cgroup_subsys_state *css;
struct cpuset *cs;
struct cpuset *oldcs = cpuset_attach_old_cs;
- bool cpus_updated, mems_updated;
bool queue_task_work = false;
cgroup_taskset_first(tset, &css);
@@ -3148,9 +3165,6 @@ static void cpuset_attach(struct cgroup_taskset *tset)
lockdep_assert_cpus_held(); /* see cgroup_attach_lock() */
mutex_lock(&cpuset_mutex);
- cpus_updated = !cpumask_equal(cs->effective_cpus,
- oldcs->effective_cpus);
- mems_updated = !nodes_equal(cs->effective_mems, oldcs->effective_mems);
/*
* In the default hierarchy, enabling cpuset in the child cgroups
@@ -3158,7 +3172,7 @@ static void cpuset_attach(struct cgroup_taskset *tset)
* in effective cpus and mems. In that case, we can optimize out
* by skipping the task iteration and update.
*/
- if (cpuset_v2() && !cpus_updated && !mems_updated) {
+ if (cpuset_v2() && !attach_cpus_updated && !attach_mems_updated) {
cpuset_attach_nodemask_to = cs->effective_mems;
goto out;
}
@@ -3175,7 +3189,7 @@ static void cpuset_attach(struct cgroup_taskset *tset)
* not set.
*/
cpuset_attach_nodemask_to = cs->effective_mems;
- if (!is_memory_migrate(cs) && !mems_updated)
+ if (!is_memory_migrate(cs) && !attach_mems_updated)
goto out;
cgroup_taskset_for_each_leader(leader, css, tset) {
@@ -3590,7 +3604,7 @@ static int cpuset_can_fork(struct task_struct *task, struct css_set *cset)
mutex_lock(&cpuset_mutex);
/* Check to see if task is allowed in the cpuset */
- ret = cpuset_can_attach_check(cs);
+ ret = cpuset_can_attach_check(cs, NULL, NULL);
if (ret)
goto out_unlock;
--
2.54.0
^ permalink raw reply related
* [PATCH-next v3 1/5] cgroup/cpuset: Add a cpuset_reserve_dl_bw() helper
From: Waiman Long @ 2026-05-27 15:37 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long
In-Reply-To: <20260527153800.1557449-1-longman@redhat.com>
Extract the DL bandwidth allocation code in cpuset_attach() to a new
cpuset_reserve_dl_bw() helper to simplify code.
No functional change is expected.
Signed-off-by: Waiman Long <longman@redhat.com>
---
kernel/cgroup/cpuset.c | 53 ++++++++++++++++++++++++------------------
1 file changed, 30 insertions(+), 23 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 51327333980a..d720bcc7ef83 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -2980,6 +2980,25 @@ static int cpuset_can_attach_check(struct cpuset *cs)
return 0;
}
+static int cpuset_reserve_dl_bw(struct cpuset *cs)
+{
+ int cpu, ret;
+
+ if (!cs->sum_migrate_dl_bw)
+ return 0;
+
+ cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
+ if (unlikely(cpu >= nr_cpu_ids))
+ return -EINVAL;
+
+ ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
+ if (ret)
+ return ret;
+
+ cs->dl_bw_cpu = cpu;
+ return 0;
+}
+
static void reset_migrate_dl_data(struct cpuset *cs)
{
cs->nr_migrate_dl_tasks = 0;
@@ -2994,7 +3013,7 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
struct cpuset *cs, *oldcs;
struct task_struct *task;
bool setsched_check;
- int cpu, ret;
+ int ret;
/* used later by cpuset_attach() */
cpuset_attach_old_cs = task_cs(cgroup_taskset_first(tset, &css));
@@ -3050,31 +3069,19 @@ static int cpuset_can_attach(struct cgroup_taskset *tset)
}
}
- if (!cs->sum_migrate_dl_bw)
- goto out_success;
-
- cpu = cpumask_any_and(cpu_active_mask, cs->effective_cpus);
- if (unlikely(cpu >= nr_cpu_ids)) {
- ret = -EINVAL;
- goto out_unlock;
- }
-
- ret = dl_bw_alloc(cpu, cs->sum_migrate_dl_bw);
- if (ret)
- goto out_unlock;
-
- cs->dl_bw_cpu = cpu;
-
-out_success:
- /*
- * Mark attach is in progress. This makes validate_change() fail
- * changes which zero cpus/mems_allowed.
- */
- cs->attach_in_progress++;
+ ret = cpuset_reserve_dl_bw(cs);
out_unlock:
- if (ret)
+ if (ret) {
reset_migrate_dl_data(cs);
+ } else {
+ /*
+ * Mark attach is in progress. This makes validate_change() fail
+ * changes which zero cpus/mems_allowed.
+ */
+ cs->attach_in_progress++;
+ }
+
mutex_unlock(&cpuset_mutex);
return ret;
}
--
2.54.0
^ permalink raw reply related
* [PATCH-next v3 0/5] cgroup/cpuset: Support multiple source/destination cpusets for cpuset_*attach()
From: Waiman Long @ 2026-05-27 15:37 UTC (permalink / raw)
To: Chen Ridong, Tejun Heo, Johannes Weiner, Michal Koutný,
Ingo Molnar, Peter Zijlstra
Cc: cgroups, linux-kernel, Aaron Tomlin, Waiman Long
v3:
- Rebased to the lastest linux-next tree.
- Keep cpuset_attach_old_cs as suggested by Chen Ridong and replace
patch 3 by a new one to make it track task group leader.
Sashiko AI review of another cpuset patch had found that cpuset_attach()
and cpuset_can_attach() can be passed a cgroup_taskset with tasks
migrating from one source cpuset to multiple destination cpusets and
vice versa. Further testing of the cpuset code indicates that this is
indeed the case when the v2 cpuset controller is enabled or disabled.
Unfortunately, cpuset_attach() and cpuset_can_attach() still assume that
there will be one source and one destinaton cpuset which may result in
inocrrect behavior.
This patch series is created to fix this issue. The first 2 patches are
just preparatory patches to make the remaining patches easier to review.
Patch 3 makes cpuset_attach_old_cs to track group leader for use by
cpuset_migrate_mm().
Patch 4 moves mpol_rebind_mm() and cpuset_migrate_mm() inside
cpuset_attach_task() to make CLONE_INTO_CGROUP flag of clone(2) works
more like moving task from one cpuset to another one, while also make
supporting multiple source and destination cpusets easier.
Patch 5 makes the necessary changes to enable the support of multiple
source and destination cpusets by keeping all the source and destination
cpusets found during task iterations in two singly linked lists for
source and destination cpusets respectively.
Waiman Long (5):
cgroup/cpuset: Add a cpuset_reserve_dl_bw() helper
cgroup/cpuset: Expand the scope of cpuset_can_attach_check()
cgroup/cpuset: Made cpuset_attach_old_cs track task group leaders
cgroup/cpuset: Move mpol_rebind_mm/cpuset_migrate_mm() calls inside
cpuset_attach_task()
cgroup/cpuset: Support multiple source/destination cpusets for
cpuset_*attach()
kernel/cgroup/cpuset-internal.h | 6 +
kernel/cgroup/cpuset.c | 375 ++++++++++++++++++++++----------
2 files changed, 263 insertions(+), 118 deletions(-)
--
2.54.0
^ permalink raw reply
* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Aaron Tomlin @ 2026-05-27 15:05 UTC (permalink / raw)
To: Peter Zijlstra
Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
linux-security-module, selinux, linux-kernel
In-Reply-To: <20260527085221.GQ3126523@noisy.programming.kicks-ass.net>
[-- Attachment #1: Type: text/plain, Size: 1941 bytes --]
On Wed, May 27, 2026 at 10:52:21AM +0200, Peter Zijlstra wrote:
> I'm not sure I really buy the Real-Time argument here; that really feels
> like a straw man. Real-Time will need to account for the shared resource
> usage inherent in using a single kernel image across the CPUs, affinity
> alone does not Real-Time make in any way shape or form.
>
> And the compromised task vs crypto thing feels like it wants sandboxing,
> but wasn't that what seccomp is for, rather than lsm?
>
> So while I don't think I object very much to the patch, I do find the
> whole Changelog to be utterly questionable. Which makes me very
> suspicious as to wtf this is actually for.
Hi Peter,
Thank you for the blunt and honest feedback.
You are completely right to call out the changelog. It obscured the actual
practical use case. I will rewrite the commit message to drop those
statements.
To answer your question regarding seccomp: seccomp-bpf is strictly limited
to inspecting syscall arguments by value at the syscall entry boundary. For
sched_setaffinity(), the mask is passed as a "__user" pointer. Seccomp
cannot safely dereference this pointer to inspect the requested CPU bits.
To actually evaluate which CPUs a task is trying to pin to, we must
evaluate the mask after copy_from_user() has safely brought it into kernel
memory. The LSM hook is currently the only infrastructure positioned to do
this safely for eBPF-driven security policies.
The actual use case here is multi-tenant workload isolation and visibility.
Passing the evaluated cpumask to the BPF LSM allows operators to write a
simple eBPF program to detect spatial boundary overlaps (e.g., logging an
event if a requested mask intersects with platform-reserved cores).
If this justification makes more sense, I will focus strictly on the
seccomp pointer limitations and multi-tenant workload isolation.
Kind regards,
--
Aaron Tomlin
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v2] blk-throttle: schedule parent dispatch in tg_flush_bios()
From: Jens Axboe @ 2026-05-27 14:38 UTC (permalink / raw)
To: tj, josef, cgroups, Tao Cui; +Cc: linux-block, Shin'ichiro Kawasaki
In-Reply-To: <20260522091530.1901437-1-cuitao@kylinos.cn>
On Fri, 22 May 2026 17:15:30 +0800, Tao Cui wrote:
> tg_flush_bios() schedules pending_timer on the child tg's own
> service_queue, which causes throtl_pending_timer_fn() to dispatch from
> the child's pending_tree. For leaf cgroups this tree is empty, so the
> timer fires and exits without dispatching the throttled bio.
>
> The throttled bio sits in the parent's pending_tree with disptime set
> to jiffies (THROTL_TG_CANCELING zeroes all dispatch times), but the
> parent's timer is never explicitly rescheduled. The bio only gets
> dispatched when the parent timer eventually fires at its previously
> scheduled expiry.
>
> [...]
Applied, thanks!
[1/1] blk-throttle: schedule parent dispatch in tg_flush_bios()
commit: 6235ea3f8b8ffca0333ade0863992f3cd69592ea
Best regards,
--
Jens Axboe
^ permalink raw reply
* Re: [RFC PATCH rdma-next 0/5] cgroup/rdma: add per-type resource accounting for QP, MR and MR memory
From: Jason Gunthorpe @ 2026-05-27 13:34 UTC (permalink / raw)
To: Tao Cui; +Cc: tj, hannes, mkoutny, leon, linux-rdma, cgroups
In-Reply-To: <b8269d9b-bd14-4ba1-be60-a210a9a1d093@kylinos.cn>
On Wed, May 27, 2026 at 07:28:59PM +0800, Tao Cui wrote:
> Hi,Jason
>
> Thanks for the review.
>
> 在 2026/5/25 21:43, Jason Gunthorpe 写道:
> >
> > I would agree to mr_mem as a reasonable extension, but not splitting
> > out objects to finer grains. There are endless objects we don't want a
> > 100 different cgroup knobs, it is not usable.
> >
>
> Understood. Our initial motivation was
> multi-tenant isolation: a tenant could consume disproportionate
> resources by creating many objects of a single type. In hindsight,
> though, the real bottleneck is pinned memory, not object counts —
> modern hardware has large object pools, and the scarce resource is
> how much physical memory gets registered through MRs. mr_mem
> addresses that directly, while hca_object remains sufficient for
> coarse object accounting.
This was the same motivation that lead us to a single object
limit. Inside a modern NIC the objects tend to pool from the same memory
pool so it doesn't matter if you have 100 QPs or 100 SRQs or whatever
they sort of cost the same.
memory pin accounting should ideally be limited by the cgroup directly
but we argued about that for a while and could never get an agreement
of an acceptable implementation. There are many nasty corner cases
around cgroups and fork and other cases IIRC
So I'm not sure if making it rdma specific can easially solve these
problems
Jason
^ permalink raw reply
* Re: [RFC PATCH rdma-next 0/5] cgroup/rdma: add per-type resource accounting for QP, MR and MR memory
From: Tao Cui @ 2026-05-27 11:28 UTC (permalink / raw)
To: Jason Gunthorpe; +Cc: tj, hannes, mkoutny, leon, linux-rdma, cgroups
In-Reply-To: <20260525134314.GI7702@ziepe.ca>
Hi,Jason
Thanks for the review.
在 2026/5/25 21:43, Jason Gunthorpe 写道:
>
> I would agree to mr_mem as a reasonable extension, but not splitting
> out objects to finer grains. There are endless objects we don't want a
> 100 different cgroup knobs, it is not usable.
>
Understood. Our initial motivation was
multi-tenant isolation: a tenant could consume disproportionate
resources by creating many objects of a single type. In hindsight,
though, the real bottleneck is pinned memory, not object counts —
modern hardware has large object pools, and the scarce resource is
how much physical memory gets registered through MRs. mr_mem
addresses that directly, while hca_object remains sufficient for
coarse object accounting.
I'll send a v2 shortly that removes the qp and mr counters entirely
and retains only mr_mem.
---
Tao
^ permalink raw reply
* Re: [PATCH 5/5] cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()
From: Mark Brown @ 2026-05-27 10:45 UTC (permalink / raw)
To: Tejun Heo
Cc: Johannes Weiner, Michal Koutný, Sebastian Andrzej Siewior,
Petr Malat, Bert Karwatzki, kernel test robot, Martin Pitt,
cgroups, linux-kernel, Aishwarya.TCV
In-Reply-To: <20260505005121.1230198-6-tj@kernel.org>
[-- Attachment #1: Type: text/plain, Size: 16233 bytes --]
On Mon, May 04, 2026 at 02:51:21PM -1000, Tejun Heo wrote:
> Same race shape as the rmdir path that 93618edf7538 ("cgroup: Defer css
> percpu_ref kill on rmdir until cgroup is depopulated") fixed: a task past
> exit_signals() whose cset subsys[ssid] still pins the disabled controller's
> css can be touching subsys state while ->css_offline() runs. The earlier
> patches in this series built up the per-subsys-css deferral machinery and
> routed cgroup_destroy_locked() through it. Apply the same shape to
> cgroup_apply_control_disable():
We've been seeing hangs during testing in our testing of -next on
multiple arm64 platforms when running LTP test jobs which bisect to this
patch, which is 1dffd95575eb05bc7e in -next. It looks like we hit a
deadlock running stress tests, the end of a typical log looks like this:
<12>[ 181.849144] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_3_3_none: end (returncode: 0)
<12>[ 181.860375] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_3_3_one: start (command: cgroup_fj_stress.sh blkio 3 3 one)
cgroup_fj_stress_blkio_3_3_one: pass (1.166s)
<12>[ 183.053379] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_3_3_one: end (returncode: 0)
<12>[ 183.064884] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_4_4_each: start (command: cgroup_fj_stress.sh blkio 4 4 each)
cgroup_fj_stress_blkio_4_4_each: pass (8.183s)
<12>[ 191.275815] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_4_4_each: end (returncode: 0)
<12>[ 191.287614] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_4_4_none: start (command: cgroup_fj_stress.sh blkio 4 4 none)
cgroup_fj_stress_blkio_4_4_none: pass (3.570s)
<12>[ 194.884173] /opt/ltp/kirk[558]: cgroup_fj_stress_blkio_4_4_none: end (returncode: 0)
<12>[ 194.895255] /opt/ltp/kirk[558]: cgroup_fj_stress_cpu_1_200_each: start (command: cgroup_fj_stress.sh cpu 1 200 each)
with no further output and given that this is a cgroup locking change
this does seem like a plausible commmit, though I didn't look into it in
detail. Bisect log and the list of LTP tests we're running in our test
job below. We are running multuple tests in parallel.
bisect log:
git bisect start
# status: waiting for both good and bad commits
# bad: [d387b06f7c15b4639244ad66b4b0900c6a02b430] Add linux-next specific files for 20260525
git bisect bad d387b06f7c15b4639244ad66b4b0900c6a02b430
# status: waiting for good commit(s), bad commit known
# good: [c745c46074da99cdcef8c1fc6093030c6f9d7143] Merge branch 'for-linux-next-fixes' of https://gitlab.freedesktop.org/drm/misc/kernel.git
git bisect good c745c46074da99cdcef8c1fc6093030c6f9d7143
# good: [4c0ea14d8ec6f6fcb94b7ad9248679ffcf747e9b] Merge branch 'libcrypto-next' of https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git
git bisect good 4c0ea14d8ec6f6fcb94b7ad9248679ffcf747e9b
# good: [af3c2d822a4b67034aa47554c178b5ffcf973456] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/robh/linux.git
git bisect good af3c2d822a4b67034aa47554c178b5ffcf973456
# good: [2f8df83b202ec7f8080a25b2e9da79c3361775fd] Merge branch 'char-misc-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/char-misc.git
git bisect good 2f8df83b202ec7f8080a25b2e9da79c3361775fd
# good: [1c70737e40e31fb0ba2d49ba06f8cac7a5e809a3] Merge branch 'staging-next' of https://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git
git bisect good 1c70737e40e31fb0ba2d49ba06f8cac7a5e809a3
# bad: [4be0d6b749c63b04d4daebf43925149271943af4] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/linusw/linux-pinctrl.git
git bisect bad 4be0d6b749c63b04d4daebf43925149271943af4
# bad: [1feee04d568fb4c3f56beaea3735da71d159b6f6] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/remoteproc/linux.git
git bisect bad 1feee04d568fb4c3f56beaea3735da71d159b6f6
# bad: [adfcff24160cca9a20fd0bf8a1b8b5cacba6061d] Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git
git bisect bad adfcff24160cca9a20fd0bf8a1b8b5cacba6061d
# good: [a234764e334b01b4b4a631b1c94df3458f3f57cb] Merge branch 'for-7.1-fixes' into for-next
git bisect good a234764e334b01b4b4a631b1c94df3458f3f57cb
# bad: [81807796db07bb1a4c066c75ccb5fcf04cbea3ed] Merge branch 'for-7.1-fixes' into for-next
git bisect bad 81807796db07bb1a4c066c75ccb5fcf04cbea3ed
# good: [c4799253a3ee74ebb27be72fb991c597a5902c01] cgroup: Move populated counters to cgroup_subsys_state
git bisect good c4799253a3ee74ebb27be72fb991c597a5902c01
# bad: [6c79fb30f5cd939f22959bd9b54d7f30c713a759] Merge branch 'for-7.2' into for-next
git bisect bad 6c79fb30f5cd939f22959bd9b54d7f30c713a759
# bad: [1dffd95575eb05bc7ec20ec096ce73be4c5d1ed5] cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()
git bisect bad 1dffd95575eb05bc7ec20ec096ce73be4c5d1ed5
# good: [cfc1da7e1127b4c8787f4dc25d59987c10c9107f] cgroup: Add per-subsys-css kill_css_finish deferral
git bisect good cfc1da7e1127b4c8787f4dc25d59987c10c9107f
# first bad commit: [1dffd95575eb05bc7ec20ec096ce73be4c5d1ed5] cgroup: Defer kill_css_finish() in cgroup_apply_control_disable()
test list:
abort01 abort01
abs01 abs01
accept01 accept01
accept4_01 accept4_01
access01 access01
access02 access02
access03 access03
access04 access04
acct01 acct01
acct02 acct02
add_key01 add_key01
add_key02 add_key02
add_key03 add_key03
add_key04 add_key04
add_key05 add_key05
adjtimex01 adjtimex01
adjtimex02 adjtimex02
adjtimex03 adjtimex03
alarm02 alarm02
alarm03 alarm03
alarm05 alarm05
alarm06 alarm06
alarm07 alarm07
ar_sh export TCdat=$LTPROOT/testcases/bin; ar01.sh
asapi_01 asapi_01
asapi_02 asapi_02
asapi_03 asapi_03
atof01 atof01
autogroup01 autogroup01
bind01 bind01
bind02 bind02
bind03 bind03
bind04 bind04
bind05 bind05
binfmt_misc01 binfmt_misc01.sh
binfmt_misc02 binfmt_misc02.sh
brk01 brk01
capget01 capget01
capget02 capget02
capset01 capset01
capset02 capset02
capset03 capset03
capset04 capset04
cgroup_fj_function_blkio cgroup_fj_function.sh blkio
cgroup_fj_function_cpu cgroup_fj_function.sh cpu
cgroup_fj_function_cpuacct cgroup_fj_function.sh cpuacct
cgroup_fj_function_cpuset cgroup_fj_function.sh cpuset
cgroup_fj_function_devices cgroup_fj_function.sh devices
cgroup_fj_function_hugetlb cgroup_fj_function.sh hugetlb
cgroup_fj_function_memory cgroup_fj_function.sh memory
cgroup_fj_function_perf_event cgroup_fj_function.sh perf_event
cgroup_fj_stress_blkio_1_200_each cgroup_fj_stress.sh blkio 1 200 each
cgroup_fj_stress_blkio_1_200_none cgroup_fj_stress.sh blkio 1 200 none
cgroup_fj_stress_blkio_1_200_one cgroup_fj_stress.sh blkio 1 200 one
cgroup_fj_stress_blkio_200_1_each cgroup_fj_stress.sh blkio 200 1 each
cgroup_fj_stress_blkio_200_1_none cgroup_fj_stress.sh blkio 200 1 none
cgroup_fj_stress_blkio_2_2_each cgroup_fj_stress.sh blkio 2 2 each
cgroup_fj_stress_blkio_2_2_none cgroup_fj_stress.sh blkio 2 2 none
cgroup_fj_stress_blkio_2_2_one cgroup_fj_stress.sh blkio 2 2 one
cgroup_fj_stress_blkio_2_9_none cgroup_fj_stress.sh blkio 2 9 none
cgroup_fj_stress_blkio_3_3_each cgroup_fj_stress.sh blkio 3 3 each
cgroup_fj_stress_blkio_3_3_none cgroup_fj_stress.sh blkio 3 3 none
cgroup_fj_stress_blkio_3_3_one cgroup_fj_stress.sh blkio 3 3 one
cgroup_fj_stress_blkio_4_4_each cgroup_fj_stress.sh blkio 4 4 each
cgroup_fj_stress_blkio_4_4_none cgroup_fj_stress.sh blkio 4 4 none
cgroup_fj_stress_cpu_1_200_each cgroup_fj_stress.sh cpu 1 200 each
cgroup_fj_stress_cpu_1_200_none cgroup_fj_stress.sh cpu 1 200 none
cgroup_fj_stress_cpu_1_200_one cgroup_fj_stress.sh cpu 1 200 one
cgroup_fj_stress_cpu_200_1_each cgroup_fj_stress.sh cpu 200 1 each
cgroup_fj_stress_cpu_200_1_none cgroup_fj_stress.sh cpu 200 1 none
cgroup_fj_stress_cpu_2_2_each cgroup_fj_stress.sh cpu 2 2 each
cgroup_fj_stress_cpu_2_2_none cgroup_fj_stress.sh cpu 2 2 none
cgroup_fj_stress_cpu_2_2_one cgroup_fj_stress.sh cpu 2 2 one
cgroup_fj_stress_cpu_2_9_none cgroup_fj_stress.sh cpu 2 9 none
cgroup_fj_stress_cpu_3_3_each cgroup_fj_stress.sh cpu 3 3 each
cgroup_fj_stress_cpu_3_3_none cgroup_fj_stress.sh cpu 3 3 none
cgroup_fj_stress_cpu_3_3_one cgroup_fj_stress.sh cpu 3 3 one
cgroup_fj_stress_cpu_4_4_each cgroup_fj_stress.sh cpu 4 4 each
cgroup_fj_stress_cpu_4_4_none cgroup_fj_stress.sh cpu 4 4 none
cgroup_fj_stress_cpuacct_10_3_none cgroup_fj_stress.sh cpuacct 10 3 none
cgroup_fj_stress_cpuacct_1_200_each cgroup_fj_stress.sh cpuacct 1 200 each
cgroup_fj_stress_cpuacct_1_200_none cgroup_fj_stress.sh cpuacct 1 200 none
cgroup_fj_stress_cpuacct_1_200_one cgroup_fj_stress.sh cpuacct 1 200 one
cgroup_fj_stress_cpuacct_200_1_none cgroup_fj_stress.sh cpuacct 200 1 none
cgroup_fj_stress_cpuacct_200_1_one cgroup_fj_stress.sh cpuacct 200 1 one
cgroup_fj_stress_cpuacct_2_2_each cgroup_fj_stress.sh cpuacct 2 2 each
cgroup_fj_stress_cpuacct_2_2_none cgroup_fj_stress.sh cpuacct 2 2 none
cgroup_fj_stress_cpuacct_2_2_one cgroup_fj_stress.sh cpuacct 2 2 one
cgroup_fj_stress_cpuacct_2_9_none cgroup_fj_stress.sh cpuacct 2 9 none
cgroup_fj_stress_cpuacct_3_3_each cgroup_fj_stress.sh cpuacct 3 3 each
cgroup_fj_stress_cpuacct_3_3_none cgroup_fj_stress.sh cpuacct 3 3 none
cgroup_fj_stress_cpuacct_3_3_one cgroup_fj_stress.sh cpuacct 3 3 one
cgroup_fj_stress_cpuacct_4_4_each cgroup_fj_stress.sh cpuacct 4 4 each
cgroup_fj_stress_cpuacct_4_4_none cgroup_fj_stress.sh cpuacct 4 4 none
cgroup_fj_stress_cpuset_1_200_each cgroup_fj_stress.sh cpuset 1 200 each
cgroup_fj_stress_cpuset_1_200_none cgroup_fj_stress.sh cpuset 1 200 none
cgroup_fj_stress_cpuset_1_200_one cgroup_fj_stress.sh cpuset 1 200 one
cgroup_fj_stress_cpuset_200_1_none cgroup_fj_stress.sh cpuset 200 1 none
cgroup_fj_stress_cpuset_2_2_each cgroup_fj_stress.sh cpuset 2 2 each
cgroup_fj_stress_cpuset_2_2_none cgroup_fj_stress.sh cpuset 2 2 none
cgroup_fj_stress_cpuset_2_2_one cgroup_fj_stress.sh cpuset 2 2 one
cgroup_fj_stress_cpuset_3_3_each cgroup_fj_stress.sh cpuset 3 3 each
cgroup_fj_stress_cpuset_3_3_none cgroup_fj_stress.sh cpuset 3 3 none
cgroup_fj_stress_cpuset_3_3_one cgroup_fj_stress.sh cpuset 3 3 one
cgroup_fj_stress_cpuset_4_4_none cgroup_fj_stress.sh cpuset 4 4 none
cgroup_fj_stress_devices_10_3_none cgroup_fj_stress.sh devices 10 3 none
cgroup_fj_stress_devices_1_200_each cgroup_fj_stress.sh devices 1 200 each
cgroup_fj_stress_devices_1_200_none cgroup_fj_stress.sh devices 1 200 none
cgroup_fj_stress_devices_1_200_one cgroup_fj_stress.sh devices 1 200 one
cgroup_fj_stress_devices_200_1_each cgroup_fj_stress.sh devices 200 1 each
cgroup_fj_stress_devices_200_1_none cgroup_fj_stress.sh devices 200 1 none
cgroup_fj_stress_devices_2_2_each cgroup_fj_stress.sh devices 2 2 each
cgroup_fj_stress_devices_2_2_none cgroup_fj_stress.sh devices 2 2 none
cgroup_fj_stress_devices_2_2_one cgroup_fj_stress.sh devices 2 2 one
cgroup_fj_stress_devices_2_9_none cgroup_fj_stress.sh devices 2 9 none
cgroup_fj_stress_devices_3_3_each cgroup_fj_stress.sh devices 3 3 each
cgroup_fj_stress_devices_3_3_none cgroup_fj_stress.sh devices 3 3 none
cgroup_fj_stress_devices_3_3_one cgroup_fj_stress.sh devices 3 3 one
cgroup_fj_stress_devices_4_4_each cgroup_fj_stress.sh devices 4 4 each
cgroup_fj_stress_devices_4_4_none cgroup_fj_stress.sh devices 4 4 none
cgroup_fj_stress_hugetlb_1_200_each cgroup_fj_stress.sh hugetlb 1 200 each
cgroup_fj_stress_hugetlb_1_200_none cgroup_fj_stress.sh hugetlb 1 200 none
cgroup_fj_stress_hugetlb_1_200_one cgroup_fj_stress.sh hugetlb 1 200 one
cgroup_fj_stress_hugetlb_200_1_each cgroup_fj_stress.sh hugetlb 200 1 each
cgroup_fj_stress_hugetlb_200_1_none cgroup_fj_stress.sh hugetlb 200 1 none
cgroup_fj_stress_hugetlb_2_2_each cgroup_fj_stress.sh hugetlb 2 2 each
cgroup_fj_stress_hugetlb_2_2_none cgroup_fj_stress.sh hugetlb 2 2 none
cgroup_fj_stress_hugetlb_2_2_one cgroup_fj_stress.sh hugetlb 2 2 one
cgroup_fj_stress_hugetlb_2_9_none cgroup_fj_stress.sh hugetlb 2 9 none
cgroup_fj_stress_hugetlb_3_3_each cgroup_fj_stress.sh hugetlb 3 3 each
cgroup_fj_stress_hugetlb_3_3_none cgroup_fj_stress.sh hugetlb 3 3 none
cgroup_fj_stress_hugetlb_3_3_one cgroup_fj_stress.sh hugetlb 3 3 one
cgroup_fj_stress_hugetlb_4_4_each cgroup_fj_stress.sh hugetlb 4 4 each
cgroup_fj_stress_hugetlb_4_4_none cgroup_fj_stress.sh hugetlb 4 4 none
cgroup_fj_stress_memory_10_3_none cgroup_fj_stress.sh memory 10 3 none
cgroup_fj_stress_memory_1_200_each cgroup_fj_stress.sh memory 1 200 each
cgroup_fj_stress_memory_1_200_none cgroup_fj_stress.sh memory 1 200 none
cgroup_fj_stress_memory_1_200_one cgroup_fj_stress.sh memory 1 200 one
cgroup_fj_stress_memory_200_1_each cgroup_fj_stress.sh memory 200 1 each
cgroup_fj_stress_memory_200_1_none cgroup_fj_stress.sh memory 200 1 none
cgroup_fj_stress_memory_2_2_each cgroup_fj_stress.sh memory 2 2 each
cgroup_fj_stress_memory_2_2_none cgroup_fj_stress.sh memory 2 2 none
cgroup_fj_stress_memory_2_2_one cgroup_fj_stress.sh memory 2 2 one
cgroup_fj_stress_memory_2_9_none cgroup_fj_stress.sh memory 2 9 none
cgroup_fj_stress_memory_3_3_each cgroup_fj_stress.sh memory 3 3 each
cgroup_fj_stress_memory_3_3_none cgroup_fj_stress.sh memory 3 3 none
cgroup_fj_stress_memory_3_3_one cgroup_fj_stress.sh memory 3 3 one
cgroup_fj_stress_memory_4_4_each cgroup_fj_stress.sh memory 4 4 each
cgroup_fj_stress_memory_4_4_none cgroup_fj_stress.sh memory 4 4 none
cgroup_fj_stress_perf_event_1_200_each cgroup_fj_stress.sh perf_event 1 200 each
cgroup_fj_stress_perf_event_1_200_none cgroup_fj_stress.sh perf_event 1 200 none
cgroup_fj_stress_perf_event_1_200_one cgroup_fj_stress.sh perf_event 1 200 one
cgroup_fj_stress_perf_event_200_1_none cgroup_fj_stress.sh perf_event 200 1 none
cgroup_fj_stress_perf_event_200_1_one cgroup_fj_stress.sh perf_event 200 1 one
cgroup_fj_stress_perf_event_2_2_each cgroup_fj_stress.sh perf_event 2 2 each
cgroup_fj_stress_perf_event_2_2_none cgroup_fj_stress.sh perf_event 2 2 none
cgroup_fj_stress_perf_event_2_2_one cgroup_fj_stress.sh perf_event 2 2 one
cgroup_fj_stress_perf_event_2_9_none cgroup_fj_stress.sh perf_event 2 9 none
cgroup_fj_stress_perf_event_3_3_each cgroup_fj_stress.sh perf_event 3 3 each
cgroup_fj_stress_perf_event_3_3_none cgroup_fj_stress.sh perf_event 3 3 none
cgroup_fj_stress_perf_event_3_3_one cgroup_fj_stress.sh perf_event 3 3 one
cgroup_fj_stress_perf_event_4_4_each cgroup_fj_stress.sh perf_event 4 4 each
cgroup_fj_stress_perf_event_4_4_none cgroup_fj_stress.sh perf_event 4 4 none
cgroup_xattr cgroup_xattr
chdir01 chdir01
chdir04 chdir04
chmod01 chmod01
chmod03 chmod03
chmod05 chmod05
chmod06 chmod06
chmod07 chmod07
chown01 chown01
chown02 chown02
chown03 chown03
chown04 chown04
chown05 chown05
chroot01 chroot01
chroot02 chroot02
chroot03 chroot03
chroot04 chroot04
clock_adjtime01 clock_adjtime01
clock_adjtime02 clock_adjtime02
clock_getres01 clock_getres01
clock_gettime01 clock_gettime01
clock_gettime02 clock_gettime02
clock_nanosleep01 clock_nanosleep01
clock_nanosleep02 clock_nanosleep02
clock_nanosleep03 clock_nanosleep03
clock_nanosleep03 clock_nanosleep03
clock_nanosleep04 clock_nanosleep04
clock_settime01 clock_settime01
clock_settime02 clock_settime02
clock_settime03 clock_settime03
clone01 clone01
clone02 clone02
clone03 clone03
clone04 clone04
clone05 clone05
clone06 clone06
clone07 clone07
clone08 clone08
clone09 clone09
clone301 clone301
clone302 clone302
close01 close01
close02 close02
confstr01 confstr01
connect01 connect01
connect02 connect02
copy_file_range01 copy_file_range01
copy_file_range02 copy_file_range02
copy_file_range03 copy_file_range03
cp01_sh cp_tests.sh
cpio01_sh cpio_tests.sh
cpuacct_100_1 cpuacct.sh 100 1
cpuacct_10_10 cpuacct.sh 10 10
cpuacct_1_1 cpuacct.sh 1 1
cpuacct_1_10 cpuacct.sh 1 10
cpuacct_1_100 cpuacct.sh 1 100
cpuhotplug02 cpuhotplug02.sh -c 1 -l 1
cpuhotplug03 cpuhotplug03.sh -c 1 -l 1
cpuhotplug04 cpuhotplug04.sh -l 1
cpuhotplug06 cpuhotplug06.sh -c 1 -l 1
cpuset_hotplug cpuset_hotplug_test.sh
cpuset_inherit cpuset_inherit_testset.sh
cpuset_regression_test cpuset_regression_test.sh
creat01 creat01
creat03 creat03
creat04 creat04
creat05 creat05
creat06 creat06
creat08 creat08
cve-2011-2183 ksm05 -I 10
cve-2012-0957 uname04
cve-2014-0196 cve-2014-0196
cve-2015-0235 gethostbyname_r01
cve-2015-7550 keyctl02
cve-2016-10044 cve-2016-10044
cve-2016-4997 setsockopt03
cve-2016-5195 dirtyc0w
cve-2016-7042 cve-2016-7042
cve-2016-7117 cve-2016-7117
cve-2016-8655 setsockopt06
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply
* Re: [PATCH v2 00/10] sched: Flatten the pick
From: Peter Zijlstra @ 2026-05-27 9:41 UTC (permalink / raw)
To: Tejun Heo
Cc: mingo, longman, chenridong, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid, hannes,
mkoutny, cgroups, linux-kernel, jstultz, kprateek.nayak, qyousef
In-Reply-To: <agtkR_kTkMW4Gc5d@slm.duckdns.org>
On Mon, May 18, 2026 at 09:11:03AM -1000, Tejun Heo wrote:
> Hello, Peter.
>
> On Mon, May 18, 2026 at 09:14:56AM +0200, Peter Zijlstra wrote:
> ...
> > So the current scheme will inflate the part of A to be double the weight
> > (of B), giving them 2 out of 3 parts on the contended CPUs, but then B
> > will still get complete / uncontested access to those extra 128 CPUs,
> > resulting in a 2:4 weight distribution.
> >
> > Which also isn't as straight forward as one might think.
>
> Right, the current behavior isn't quite what people would expect intuitively
> either.
>
> ...
> > So for the one contended CPU A gets 256 out of 257 parts, while B gets
> > the full CPU for the remaining 255 CPUs, for a:
> >
> > 256 1 257
> > --- : --- + 255*--- = 256:65535 ~ 1:256
> > 257 257 257
> >
> > distribution. While with the new scheme it would be:
> >
> > 1 1 2
> > - : - + 255*- = 1:511
> > 2 2 2
> >
> > Which, realistically isn't all that different, except the old scheme has
> > this really large weight to deal with.
> >
> > So from where I'm sitting, yes different, but it behaves better.
FWIW if the workload was single threads per CPU; the above is also the
exact behaviour we'd have without cgroups.
> I see. Thread cardinality and affinity problems make weight based
> distribution such a pain. I wonder whether this can be better solved by
> turning it into a two-layer allocation problem - groups to CPUs and then
> timeshare on CPUs as necessary. That comes with a lot of its own problems
> but it can, aspirationally at least, approximate global weight distribution
> and would have better locality properties.
If people want, they can already do this today. I don't see a reason to
mandate something like that. That is, combine cpuset and cpu in a v2
hierarchy and you get this.
The main problem with doing something like that is of course that it
isn't always clear how many CPUs will be needed for a particular 'job'.
So assigning groups to CPUs isn't a straight forward thing.
If I remember, Meta was actually doing some of this. It was dynamically
resizing cpusets based on load predictions and the like in order to
separate various worloads on the same large machine, right?
Anyway, while it is somewhat tedious to change behaviour, I do think it
is worth doing in this case.
^ permalink raw reply
* Re: [RFC PATCH bpf-next v7 00/11] mm: BPF struct_ops for dynamic memory protection and async reclaim
From: teawater @ 2026-05-27 9:31 UTC (permalink / raw)
To: Usama Arif
Cc: Usama Arif, Daniel Borkmann, John Fastabend, Andrii Nakryiko,
Martin KaFai Lau, Eduard Zingerman, Kumar Kartikeya Dwivedi,
Song Liu, Yonghong Song, Jiri Olsa, Johannes Weiner, Michal Hocko,
Roman Gushchin, Shakeel Butt, Muchun Song, JP Kobryn,
Andrew Morton, Shuah Khan, davem, Jakub Kicinski,
Jesper Dangaard Brouer, Stanislav Fomichev, KP Singh, Tao Chen,
Mykyta Yatsenko, Leon Hwang, Anton Protopopov, Amery Hung,
Tobias Klauser, Eyal Birger, Rong Tao, Hao Luo, Peter Zijlstra,
Miguel Ojeda, Nathan Chancellor, Kees Cook, Tejun Heo, Jeff Xu,
mkoutny, Jan Hendrik Farr, Christian Brauner, Randy Dunlap,
Brian Gerst, Masahiro Yamada, Willem de Bruijn, Jason Xing,
Paul Chaignon, Chen Ridong, Lance Yang, Jiayuan Chen,
linux-kernel, bpf, cgroups, linux-mm, netdev, linux-kselftest,
geliang, baohua, Hui Zhu
In-Reply-To: <20260526134115.816081-1-usama.arif@linux.dev>
>
> On Tue, 26 May 2026 10:20:00 +0800 Hui Zhu <hui.zhu@linux.dev> wrote:
>
> >
> > From: Hui Zhu <zhuhui@kylinos.cn>
> >
> > Overview:
> > This series introduces BPF struct_ops support for the memory controller,
> > enabling userspace BPF programs to implement custom, dynamic memory
> > management policies per cgroup. The feature allows BPF programs to hook
> > into the core reclaim and charge paths without requiring kernel
> > modifications, providing a flexible alternative to static knobs such as
> > memory.low and memory.min.
> >
> > The series enables two complementary use cases.
> >
...
...
...
> >
> > Asynchronous proactive reclaim: the memcg_charged and memcg_uncharged
> > hooks, combined with the BPF workqueue mechanism and the new
> > bpf_try_to_free_mem_cgroup_pages() kfunc, enable BPF programs to perform
> > proactive background reclaim without blocking the charge path. The
> > pattern works as follows: the memcg_charged callback tracks accumulated
> > memory usage; when usage crosses a configurable threshold, it enqueues an
> > asynchronous work item via bpf_wq_start() and returns immediately without
> > throttling the charging task. The workqueue callback then invokes
> > bpf_try_to_free_mem_cgroup_pages() to reclaim pages from the target
> > cgroup; if usage remains elevated after reclaim, the callback re-enqueues
> > itself to continue. This allows a BPF program to keep a cgroup's
> > footprint below its hard limit (memory.max) entirely in the background,
> > avoiding the OOM killer or direct-reclaim stalls that would otherwise
> > occur. The selftest for this feature (patch 10/11) validates the
> > mechanism concretely: a workload that writes and mmaps a 64 MB file inside
> > a 32 MB cgroup reliably triggers memory.events "max" events without BPF;
> > with the async reclaim program attached, the "max" counter does not
> > increase at all across the same workload.
> >
> Hi Hui,
>
> Thanks for the series.
> Would it not be simpler to just have another memcg knob, something like
> memory.high_async.
> When memory usage > memory.high_async, queue a per-memcg work item that calls
> try_to_free_mem_cgroup_pages() until usage drops back below some threshold.
> I am not sure I see what programability aspect from bpf you need here.
>
> Thanks
Hi Usama,
That's a good question.
By introducing a new BPF kfunc bpf_try_to_free_mem_cgroup_pages,
a BPF program can flexibly control when to start and stop async
reclaim, rather than being constrained to trigger and stop based
solely on memcg usage or one or two fixed events, as with
traditional proactive reclaim interfaces.
For example, async reclaim could be triggered based on PSI, or
on the number of page faults, or even on a combination of
multiple events working together to decide both when to start
and when to stop async reclaim.
That is the motivation behind adding the BPF kfunc
bpf_try_to_free_mem_cgroup_pages in this patch set.
I admit the cover letter did not explain this well enough, and
the example code does not demonstrate this use case either. I
will address both in the next version.
Best,
Hui
>
> >
> > 08/11 selftests/bpf: Add tests for memcg_bpf_ops
> > Adds prog_tests/memcg_ops.c covering three scenarios:
> > memcg_charged-only throttling, below_low + memcg_charged
> > interaction, and below_min + memcg_charged interaction. A
> > tracepoint on memcg:count_memcg_events (PGFAULT) is used to
> > detect memory pressure and trigger hooks accordingly.
...
...
...
> > --
> > 2.43.0
> >
>
^ permalink raw reply
* Re: [PATCH v3] security: Expand task_setscheduler LSM hook to include CPU affinity mask
From: Peter Zijlstra @ 2026-05-27 8:52 UTC (permalink / raw)
To: Aaron Tomlin
Cc: tsbogend, paul, jmorris, serge, mingo, juri.lelli,
vincent.guittot, stephen.smalley.work, casey, longman, tj, hannes,
mkoutny, chenridong, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, omosnace, kees, neelx, sean, chjohnst,
steve, mproche, nick.lange, cgroups, linux-mips, linux-fsdevel,
linux-security-module, selinux, linux-kernel
In-Reply-To: <20260526142838.774711-1-atomlin@atomlin.com>
On Tue, May 26, 2026 at 10:28:38AM -0400, Aaron Tomlin wrote:
> At present, the task_setscheduler LSM hook provides security modules
> with the opportunity to mediate changes to a task's scheduling policy.
> However, when invoked via sched_setaffinity(), the hook lacks
> visibility into the actual CPU affinity mask being requested.
> Consequently, BPF-based security modules are entirely blind to the
> target CPUs and cannot make granular access control decisions based on
> spatial isolation.
>
> In modern multi-tenant and real-time environments, CPU isolation is a
> critical boundary. The inability to audit or restrict specific CPU
> pinning requests limits the effectiveness of eBPF-driven security
> policies, particularly when attempting to shield isolated or
> cryptographic cores from unprivileged or compromised tasks.
>
> This patch expands the security_task_setscheduler() hook signature to
> include a pointer to the requested cpumask. Because this is a shared
> hook used for multiple scheduling attribute changes, call sites that do
> not modify CPU affinity are updated to safely pass NULL.
> To protect against unverified dereferences, the parameter is annotated
> with __nullable in the LSM hook definition, ensuring the BPF verifier
> mandates explicit NULL checks for attached eBPF programs.
>
> This change updates all in-tree security modules (SELinux and Smack) to
> accommodate the new parameter mechanically, whilst providing BPF LSMs
> with the necessary context to enforce strict affinity policies.
I'm not sure I really buy the Real-Time argument here; that really feels
like a straw man. Real-Time will need to account for the shared resource
usage inherent in using a single kernel image across the CPUs, affinity
alone does not Real-Time make in any way shape or form.
And the compromised task vs crypto thing feels like it wants sandboxing,
but wasn't that what seccomp is for, rather than lsm?
So while I don't think I object very much to the patch, I do find the
whole Changelog to be utterly questionable. Which makes me very
suspicious as to wtf this is actually for.
^ permalink raw reply
* Re: [RFC PATCH bpf-next v7 00/11] mm: BPF struct_ops for dynamic memory protection and async reclaim
From: Michal Hocko @ 2026-05-27 8:47 UTC (permalink / raw)
To: Hui Zhu
Cc: Alexei Starovoitov, Daniel Borkmann, John Fastabend,
Andrii Nakryiko, Martin KaFai Lau, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Song Liu, Yonghong Song, Jiri Olsa,
Johannes Weiner, Roman Gushchin, Shakeel Butt, Muchun Song,
JP Kobryn, Andrew Morton, Shuah Khan, davem, Jakub Kicinski,
Jesper Dangaard Brouer, Stanislav Fomichev, KP Singh, Tao Chen,
Mykyta Yatsenko, Leon Hwang, Anton Protopopov, Amery Hung,
Tobias Klauser, Eyal Birger, Rong Tao, Hao Luo, Peter Zijlstra,
Miguel Ojeda, Nathan Chancellor, Kees Cook, Tejun Heo, Jeff Xu,
mkoutny, Jan Hendrik Farr, Christian Brauner, Randy Dunlap,
Brian Gerst, Masahiro Yamada, Willem de Bruijn, Jason Xing,
Paul Chaignon, Chen Ridong, Lance Yang, Jiayuan Chen,
linux-kernel, bpf, cgroups, linux-mm, netdev, linux-kselftest,
geliang, baohua, Hui Zhu
In-Reply-To: <cover.1779760876.git.zhuhui@kylinos.cn>
On Tue 26-05-26 10:20:00, Hui Zhu wrote:
> From: Hui Zhu <zhuhui@kylinos.cn>
>
> Overview:
> This series introduces BPF struct_ops support for the memory controller,
> enabling userspace BPF programs to implement custom, dynamic memory
> management policies per cgroup. The feature allows BPF programs to hook
> into the core reclaim and charge paths without requiring kernel
> modifications, providing a flexible alternative to static knobs such as
> memory.low and memory.min.
>
> The series enables two complementary use cases.
>
> Dynamic memory protection: static memory protection thresholds
> (memory.low, memory.min) are poor fits for workloads whose actual memory
> activity varies over time. A high-priority cgroup holding a large working
> set but temporarily idle will still suppress reclaim on its siblings,
> wasting available memory. A BPF-driven approach can observe real workload
> activity -- page faults, charge/uncharge events -- and activate or
> withdraw protection dynamically.
Why the same cannot be achieved by dynamically changing protection?
> The test results at the end of this
> letter quantify the difference: in a scenario where the high-priority
> cgroup is idle, the BPF-controlled low-priority cgroup achieves roughly
> 37x higher throughput than with static memory.low.
>
> Asynchronous proactive reclaim: the memcg_charged and memcg_uncharged
> hooks, combined with the BPF workqueue mechanism and the new
> bpf_try_to_free_mem_cgroup_pages() kfunc, enable BPF programs to perform
> proactive background reclaim without blocking the charge path. The
> pattern works as follows: the memcg_charged callback tracks accumulated
> memory usage; when usage crosses a configurable threshold, it enqueues an
> asynchronous work item via bpf_wq_start() and returns immediately without
> throttling the charging task. The workqueue callback then invokes
> bpf_try_to_free_mem_cgroup_pages() to reclaim pages from the target
> cgroup; if usage remains elevated after reclaim, the callback re-enqueues
> itself to continue. This allows a BPF program to keep a cgroup's
> footprint below its hard limit (memory.max) entirely in the background,
> avoiding the OOM killer or direct-reclaim stalls that would otherwise
> occur.
How do you account the overall work done to the specific memcg as the
large part of the reclaim is done from WQ context?
Also when introducing a BPF hook please focus on describing why existing
interfaces fail to achieve what you need. For the async reclaim why it
is not practical or feasible to use userspace driven memory reclaim.
--
Michal Hocko
SUSE Labs
^ permalink raw reply
* Re: [PATCH v7 4/4] mm: swap: filter swap allocation by memcg tier mask
From: Baoquan He @ 2026-05-27 7:32 UTC (permalink / raw)
To: Youngjun Park
Cc: akpm, chrisl, linux-mm, cgroups, linux-kernel, kasong, hannes,
mhocko, roman.gushchin, shakeel.butt, muchun.song, shikemeng,
nphamcs, baohua, gunho.lee, taejoon.song, hyungjun.cho, mkoutny,
baver.bae, matia.kim
In-Reply-To: <20260527062247.3440692-5-youngjun.park@lge.com>
On 05/27/26 at 03:22pm, Youngjun Park wrote:
> Apply memcg tier effective mask during swap slot allocation to
> enforce per-cgroup swap tier restrictions.
>
> In the fast path, check the percpu cached swap_info's tier_mask
> against the folio's effective mask. If it does not match, fall
> through to the slow path. In the slow path, skip swap devices
> whose tier_mask is not covered by the folio's effective mask.
>
> This works correctly when there is only one non-rotational
> device in the system and no devices share the same priority.
> However, there are known limitations:
>
> - When non-rotational devices are distributed across multiple
> tiers, and different memcgs are configured to use those
> distinct tiers, they may constantly overwrite the shared
> percpu swap cache. This cache thrashing leads to frequent
> fast path misses.
>
> - Combined with the above issue, if same-priority devices exist
> among them, a percpu cache miss (overwritten by another memcg)
> forces the allocator to round-robin to the next device
> prematurely, even if the current cluster is not fully
> exhausted.
>
> These edge cases do not affect the primary use case of
> directing swap traffic per cgroup. Further optimization is
> planned for future work.
>
> Signed-off-by: Youngjun Park <youngjun.park@lge.com>
> ---
> mm/swapfile.c | 10 +++++++++-
> 1 file changed, 9 insertions(+), 1 deletion(-)
LGTM,
Reviewed-by: Baoquan He <baoquan.he@linux.dev>
>
> diff --git a/mm/swapfile.c b/mm/swapfile.c
> index 9a86ebe992f4..1a2d29735b71 100644
> --- a/mm/swapfile.c
> +++ b/mm/swapfile.c
> @@ -1365,14 +1365,18 @@ static bool swap_alloc_fast(struct folio *folio)
> struct swap_cluster_info *ci;
> struct swap_info_struct *si;
> unsigned int offset;
> + int mask = folio_tier_effective_mask(folio);
>
> /*
> * Once allocated, swap_info_struct will never be completely freed,
> * so checking it's liveness by get_swap_device_info is enough.
> */
> si = this_cpu_read(percpu_swap_cluster.si[order]);
> + if (!si || !swap_tiers_mask_test(si->tier_mask, mask))
> + return false;
> +
> offset = this_cpu_read(percpu_swap_cluster.offset[order]);
> - if (!si || !offset || !get_swap_device_info(si))
> + if (!offset || !get_swap_device_info(si))
> return false;
>
> ci = swap_cluster_lock(si, offset);
> @@ -1392,10 +1396,14 @@ static bool swap_alloc_fast(struct folio *folio)
> static void swap_alloc_slow(struct folio *folio)
> {
> struct swap_info_struct *si, *next;
> + int mask = folio_tier_effective_mask(folio);
>
> spin_lock(&swap_avail_lock);
> start_over:
> plist_for_each_entry_safe(si, next, &swap_avail_head, avail_list) {
> + if (!swap_tiers_mask_test(si->tier_mask, mask))
> + continue;
> +
> /* Rotate the device and switch to a new cluster */
> plist_requeue(&si->avail_list, &swap_avail_head);
> spin_unlock(&swap_avail_lock);
> --
> 2.34.1
>
>
^ permalink raw reply
* [PATCH v2 2/2] cgroup/cpuset: Add test cases for sibling CPU exclusion on partition update
From: Sun Shaojie @ 2026-05-27 7:05 UTC (permalink / raw)
To: Waiman Long, Chen Ridong, Tejun Heo, Johannes Weiner,
Michal Koutný
Cc: cgroups, linux-kernel, zhangguopeng, Sun Shaojie
In-Reply-To: <20260527064329.640060-2-sunshaojie@kylinos.cn>
When sibling CPU exclusion occurs, a partition's effective_xcpus may be
a subset of its user_xcpus. The partcmd_update path must use
effective_xcpus instead of user_xcpus when calculating CPUs to return
to or request from the parent.
Add two test cases to verify this behavior:
1) Narrowing cpuset.cpus to only the sibling-excluded CPUs should not
return CPUs to parent that the partition never actually owned.
2) Expanding cpuset.cpus after a sibling becomes a member should
correctly request the additional CPUs from parent.
Co-developed-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
Signed-off-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
Signed-off-by: Sun Shaojie <sunshaojie@kylinos.cn>
---
tools/testing/selftests/cgroup/test_cpuset_prs.sh | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/tools/testing/selftests/cgroup/test_cpuset_prs.sh b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
index a56f4153c64d..683b05062810 100755
--- a/tools/testing/selftests/cgroup/test_cpuset_prs.sh
+++ b/tools/testing/selftests/cgroup/test_cpuset_prs.sh
@@ -492,6 +492,16 @@ REMOTE_TEST_MATRIX=(
" C1-5:P1 . C1-4:P1 C2-3 . . \
. . . P1 . . p1:5|c11:1-4|c12:5 \
p1:P1|c11:P1|c12:P-1"
+ # Narrowing cpuset.cpus to previously sibling-excluded CPUs should
+ # not return CPUs that were never actually owned.
+ " C1-4:P1 . C1-2:P1 C1-3:P2 . . \
+ . . . C3 . . p1:4|c11:1-2|c12:3 \
+ p1:P1|c11:P1|c12:P2 3"
+ # Expanding cpuset.cpus to include a previously sibling-excluded CPU
+ # after the sibling has become a member should correctly request it.
+ " C1-4:P1 . C1-2:P1 C1-3:P2 . . \
+ . . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \
+ p1:P1|c11:P0|c12:P2 2-3"
)
#
--
2.43.0
^ permalink raw reply related
* [PATCH v2 1/2] cgroup/cpuset: Use effective_xcpus in partcmd_update add/del mask calculation
From: Sun Shaojie @ 2026-05-27 6:43 UTC (permalink / raw)
To: Waiman Long, Chen Ridong, Tejun Heo, Johannes Weiner,
Michal Koutný
Cc: cgroups, linux-kernel, zhangguopeng, Sun Shaojie
In-Reply-To: <20260527064329.640060-1-sunshaojie@kylinos.cn>
When sibling CPU exclusion occurs, a partition's user_xcpus may contain
CPUs that were never actually granted to it. These CPUs are present in
user_xcpus(cs) but not in cs->effective_xcpus.
The partcmd_update path in update_parent_effective_cpumask() uses
user_xcpus(cs) (via the local variable xcpus) to compute the addmask
(CPUs to return to parent) and delmask (CPUs to request from parent).
This is incorrect:
1) When newmask removes a CPU that was previously excluded by a
sibling, addmask incorrectly includes that CPU and tries to return
it to the parent even though the partition never actually owned it,
causing CPU overlap with sibling partitions and triggering warnings
in generate_sched_domains().
2) When newmask adds a previously excluded CPU that is now available,
delmask fails to request it from the parent because user_xcpus(cs)
already includes it.
Fix this by using cs->effective_xcpus instead of user_xcpus(cs) in all
partcmd_update paths that calculate addmask or delmask, including the
PERR_NOCPUS error handling paths.
Reproducers:
Example 1 - Removing a sibling-excluded CPU incorrectly returns it:
# cd /sys/fs/cgroup
# echo "0-1" > a1/cpuset.cpus
# echo "root" > a1/cpuset.cpus.partition
# echo "0-2" > b1/cpuset.cpus
# echo "root" > b1/cpuset.cpus.partition
# echo "2" > b1/cpuset.cpus
# cat cpuset.cpus.effective
# Actual: 0-1,3 Expected: 3
Example 2 - Expanding to a previously excluded CPU fails to request it:
# cd /sys/fs/cgroup
# echo "0-1" > a1/cpuset.cpus
# echo "root" > a1/cpuset.cpus.partition
# echo "0-2" > b1/cpuset.cpus
# echo "root" > b1/cpuset.cpus.partition
# echo "member" > a1/cpuset.cpus.partition
# echo "1-2" > b1/cpuset.cpus
# cat cpuset.cpus.effective
# Actual: 0-1,3 Expected: 0,3
Fixes: 2a3602030d80 ("cgroup/cpuset: Don't invalidate sibling partitions on cpuset.cpus conflict")
Suggested-by: Zhang Guopeng <zhangguopeng@kylinos.cn>
Signed-off-by: Sun Shaojie <sunshaojie@kylinos.cn>
---
kernel/cgroup/cpuset.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 1335e437098e..2395c5aec871 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -1807,9 +1807,9 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
* Compute add/delete mask to/from effective_cpus
*
* For valid partition:
- * addmask = exclusive_cpus & ~newmask
+ * addmask = effective_xcpus & ~newmask
* & parent->effective_xcpus
- * delmask = newmask & ~exclusive_cpus
+ * delmask = newmask & ~effective_xcpus
* & parent->effective_xcpus
*
* For invalid partition:
@@ -1821,11 +1821,11 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
deleting = cpumask_and(tmp->delmask,
newmask, parent->effective_xcpus);
} else {
- cpumask_andnot(tmp->addmask, xcpus, newmask);
+ cpumask_andnot(tmp->addmask, cs->effective_xcpus, newmask);
adding = cpumask_and(tmp->addmask, tmp->addmask,
parent->effective_xcpus);
- cpumask_andnot(tmp->delmask, newmask, xcpus);
+ cpumask_andnot(tmp->delmask, newmask, cs->effective_xcpus);
deleting = cpumask_and(tmp->delmask, tmp->delmask,
parent->effective_xcpus);
}
@@ -1864,7 +1864,7 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
part_error = PERR_NOCPUS;
deleting = false;
adding = cpumask_and(tmp->addmask,
- xcpus, parent->effective_xcpus);
+ cs->effective_xcpus, parent->effective_xcpus);
}
} else {
/*
@@ -1886,7 +1886,8 @@ static int update_parent_effective_cpumask(struct cpuset *cs, int cmd,
part_error = PERR_NOCPUS;
if (is_partition_valid(cs))
adding = cpumask_and(tmp->addmask,
- xcpus, parent->effective_xcpus);
+ cs->effective_xcpus,
+ parent->effective_xcpus);
} else if (is_partition_invalid(cs) && !cpumask_empty(xcpus) &&
cpumask_subset(xcpus, parent->effective_xcpus)) {
struct cgroup_subsys_state *css;
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox