* [PATCH] sched_ext: idle: Respect task affinity in restricted CPU selection
@ 2026-10-11 2:52 Hui Su
2026-10-11 7:02 ` Andrea Righi
0 siblings, 1 reply; 6+ messages in thread
From: Hui Su @ 2026-10-11 2:52 UTC (permalink / raw)
To: tj
Cc: void, arighi, changwoo, mingo, peterz, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, sched-ext, linux-kernel, bpf, Hui Su,
stable
The restricted select_cpu kfunc path handles single-CPU and
migration-disabled tasks without going through scx_select_cpu_dfl().
When a caller supplies @allowed, the path currently checks prev_cpu only
against that mask. It can therefore return an idle prev_cpu which isn't
in p->cpus_ptr.
Require prev_cpu to belong to both p->cpus_ptr and the caller-provided
mask before consuming its idle state.
Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()")
Cc: stable@vger.kernel.org
Signed-off-by: Hui Su <sh_def@163.com>
---
Testing:
- x86_64 KVM/QEMU.
- A private regression harness pinned the task to CPU 0 and called
scx_bpf_select_cpu_and() with an idle CPU 1 outside the task affinity
but inside the caller-provided mask.
- On the baseline kernel, the helper returned CPU 1.
- With this fix, the helper rejected the out-of-affinity CPU.
- The existing allowed_cpus selftest passed with the fix.
kernel/sched/ext/idle.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
index aa9fb6de0ad6..20efa463cafa 100644
--- a/kernel/sched/ext/idle.c
+++ b/kernel/sched/ext/idle.c
@@ -999,7 +999,8 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
* used CPU is idle and within the allowed cpumask.
*/
if (p->nr_cpus_allowed == 1 || is_bpf_migration_disabled(p)) {
- if (cpumask_test_cpu(prev_cpu, allowed ?: p->cpus_ptr) &&
+ if (cpumask_test_cpu(prev_cpu, p->cpus_ptr) &&
+ (!allowed || cpumask_test_cpu(prev_cpu, allowed)) &&
scx_idle_test_and_clear_cpu(prev_cpu))
cpu = prev_cpu;
else
base-commit: 7b63ef2d55f24519e7e9e5f4d15dbea03f126e40
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] sched_ext: idle: Respect task affinity in restricted CPU selection
2026-10-11 2:52 [PATCH] sched_ext: idle: Respect task affinity in restricted CPU selection Hui Su
@ 2026-10-11 7:02 ` Andrea Righi
2026-10-11 9:10 ` Hui Su
2026-10-11 13:17 ` [PATCH v2] " Hui Su
0 siblings, 2 replies; 6+ messages in thread
From: Andrea Righi @ 2026-10-11 7:02 UTC (permalink / raw)
To: Hui Su
Cc: tj, void, changwoo, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, sched-ext, linux-kernel, bpf, stable
Hi Hui,
On Sun, Oct 11, 2026 at 11:52:56AM +0900, Hui Su wrote:
> The restricted select_cpu kfunc path handles single-CPU and
> migration-disabled tasks without going through scx_select_cpu_dfl().
>
> When a caller supplies @allowed, the path currently checks prev_cpu only
> against that mask. It can therefore return an idle prev_cpu which isn't
> in p->cpus_ptr.
>
> Require prev_cpu to belong to both p->cpus_ptr and the caller-provided
> mask before consuming its idle state.
>
> Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Hui Su <sh_def@163.com>
The fix makes sense, but I'm wondering if we should derive the candidate CPU
from the task's state instead of relying on prev_cpu. In practice prev_cpu
should be always considered a hint rather than a guarantee that the task can
still run there.
Per-CPU tasks could use cpumask_first(p->cpus_ptr) and migration-disabled tasks
could use task_cpu(p) directly.
Something like this maybe:
migration_disabled = is_bpf_migration_disabled(p);
if (p->nr_cpus_allowed == 1 || migration_disabled) {
if (migration_disabled)
cpu = task_cpu(p);
else
cpu = cpumask_first(p->cpus_ptr);
if ((migration_disabled &&
!cpumask_test_cpu(cpu, p->cpus_ptr)) ||
(allowed && !cpumask_test_cpu(cpu, allowed)) ||
!scx_idle_test_and_clear_cpu(cpu))
cpu = -EBUSY;
} else {
cpu = scx_select_cpu_dfl(p, prev_cpu, wake_flags,
allowed ?: p->cpus_ptr, flags);
}
This would let us try the task's only allowed CPU after an affinity change,
instead of rejecting a stale/invalid prev_cpu.
And could you also clarify in the patch description that selecting the CPU
consumes its idle bit? Without the affinity check, we can claim an idle CPU that
the task cannot use and later dispatch validation doesn't undo that side effect.
It would also help to mention that per-CPU and migration-disabled tasks skip
ops.select_cpu(), so this affects calls from other contexts, such as
ops.enqueue(), leaving the normal ops.select_cpu() path unaffected.
What do you think?
Thanks,
-Andrea
> ---
> Testing:
> - x86_64 KVM/QEMU.
> - A private regression harness pinned the task to CPU 0 and called
> scx_bpf_select_cpu_and() with an idle CPU 1 outside the task affinity
> but inside the caller-provided mask.
> - On the baseline kernel, the helper returned CPU 1.
> - With this fix, the helper rejected the out-of-affinity CPU.
> - The existing allowed_cpus selftest passed with the fix.
>
> kernel/sched/ext/idle.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
> index aa9fb6de0ad6..20efa463cafa 100644
> --- a/kernel/sched/ext/idle.c
> +++ b/kernel/sched/ext/idle.c
> @@ -999,7 +999,8 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
> * used CPU is idle and within the allowed cpumask.
> */
> if (p->nr_cpus_allowed == 1 || is_bpf_migration_disabled(p)) {
> - if (cpumask_test_cpu(prev_cpu, allowed ?: p->cpus_ptr) &&
> + if (cpumask_test_cpu(prev_cpu, p->cpus_ptr) &&
> + (!allowed || cpumask_test_cpu(prev_cpu, allowed)) &&
> scx_idle_test_and_clear_cpu(prev_cpu))
> cpu = prev_cpu;
> else
>
> base-commit: 7b63ef2d55f24519e7e9e5f4d15dbea03f126e40
>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH] sched_ext: idle: Respect task affinity in restricted CPU selection
2026-10-11 7:02 ` Andrea Righi
@ 2026-10-11 9:10 ` Hui Su
2026-10-11 13:17 ` [PATCH v2] " Hui Su
1 sibling, 0 replies; 6+ messages in thread
From: Hui Su @ 2026-10-11 9:10 UTC (permalink / raw)
To: Andrea Righi
Cc: tj, void, changwoo, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, sched-ext, linux-kernel, bpf, stable
Hi Andrea,
> The fix makes sense, but I'm wondering if we should derive the candidate CPU
> from the task's state instead of relying on prev_cpu. In practice prev_cpu
> should be always considered a hint rather than a guarantee that the task can
> still run there.
>
> Per-CPU tasks could use cpumask_first(p->cpus_ptr) and migration-disabled tasks
> could use task_cpu(p) directly.
Agreed. prev_cpu is only a hint, so deriving the candidate from the task's
state is the better behavior after an affinity change. The restricted path
should use cpumask_first(p->cpus_ptr) for a single-CPU task and task_cpu(p)
for a migration-disabled task, while validating the candidate against the
task's effective affinity and the caller-provided allowed mask as appropriate.
> And could you also clarify in the patch description that selecting the CPU
> consumes its idle bit? Without the affinity check, we can claim an idle CPU that
> the task cannot use and later dispatch validation doesn't undo that side effect.
Yes. scx_idle_test_and_clear_cpu() consumes the idle bit, so the affinity
checks need to happen before the claim. I will make that side effect explicit
in the patch description.
> It would also help to mention that per-CPU and migration-disabled tasks skip
> ops.select_cpu(), so this affects calls from other contexts, such as
> ops.enqueue(), leaving the normal ops.select_cpu() path unaffected.
That distinction is useful. These tasks skip ops.select_cpu(), so the
restricted path can be reached from other contexts such as ops.enqueue(),
while the normal ops.select_cpu() path continues through
scx_select_cpu_dfl() and is unaffected.
Thanks for the review. I will incorporate these points in the follow-up
revision.
--
Hui
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2] sched_ext: idle: Respect task affinity in restricted CPU selection
@ 2026-10-11 13:17 ` Hui Su
2026-10-11 14:39 ` Andrea Righi
0 siblings, 1 reply; 6+ messages in thread
From: Hui Su @ 2026-10-11 13:17 UTC (permalink / raw)
To: tj
Cc: void, arighi, changwoo, mingo, peterz, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, kprateek.nayak, sched-ext, linux-kernel, bpf, sh_def,
stable
The restricted select_cpu kfunc path handles single-CPU and
migration-disabled tasks without going through scx_select_cpu_dfl().
It currently uses prev_cpu as the candidate. However, prev_cpu is only a
hint and may no longer be usable after an affinity change. When the caller
supplies @allowed, the restricted path can also claim and return an idle
CPU which isn't in p->cpus_ptr.
Derive the candidate from task state instead. Use the sole CPU in
p->cpus_ptr for single-CPU tasks and task_cpu(p) for migration-disabled
tasks. Validate the candidate against the task's effective affinity and
the caller-provided mask before claiming its idle state.
The validation must happen before scx_idle_test_and_clear_cpu(), as a
successful call consumes the CPU's idle bit. A later placement validation
therefore cannot undo the idle-state side effect.
Single-CPU and migration-disabled tasks skip ops.select_cpu(), so this
path can instead be reached when the CPU-selection kfunc is called from
other contexts such as ops.enqueue(). The normal ops.select_cpu() path is
unchanged.
Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()")
Cc: stable@vger.kernel.org
Signed-off-by: Hui Su <sh_def@163.com>
---
Testing:
- x86_64 KVM/QEMU private BPF test-run harness:
- The baseline returned CPU1 outside the task affinity; v2 returned
-EBUSY.
- With a stale prev_cpu, the baseline returned -EBUSY; v2 selected the
task's idle CPU0.
- A caller mask excluding the task's candidate CPU returned -EBUSY with v2.
- The stock sched_ext allowed_cpus selftest passed.
- Built vmlinux and bzImage with CONFIG_PREEMPT_RCU=y.
- Built kernel/sched/build_policy.o with CONFIG_PREEMPT_RCU=n and CONFIG_SMP=n.
Changes in v2:
- Derive the restricted-path candidate from task state instead of prev_cpu.
- Document the idle-bit side effect and the ops.enqueue() reachability.
v1: https://lore.kernel.org/lkml/20261011025256.4180374-1-sh_def@163.com/
---
kernel/sched/ext/idle.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
index aa9fb6de0ad6..1ccea60e9012 100644
--- a/kernel/sched/ext/idle.c
+++ b/kernel/sched/ext/idle.c
@@ -958,6 +958,7 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
{
unsigned long irq_flags;
bool we_locked = false;
+ bool migration_disabled;
s32 cpu;
if (!scx_cpu_valid(sch, prev_cpu, NULL))
@@ -995,14 +996,21 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
/*
* This may also be called from ops.enqueue(), so we need to handle
* per-CPU tasks as well. For these tasks, we can skip all idle CPU
- * selection optimizations and simply check whether the previously
- * used CPU is idle and within the allowed cpumask.
+ * selection optimizations and simply check whether the task's only
+ * usable CPU is idle and within the allowed cpumask.
*/
- if (p->nr_cpus_allowed == 1 || is_bpf_migration_disabled(p)) {
- if (cpumask_test_cpu(prev_cpu, allowed ?: p->cpus_ptr) &&
- scx_idle_test_and_clear_cpu(prev_cpu))
- cpu = prev_cpu;
+ migration_disabled = is_bpf_migration_disabled(p);
+
+ if (p->nr_cpus_allowed == 1 || migration_disabled) {
+ if (migration_disabled)
+ cpu = task_cpu(p);
else
+ cpu = cpumask_first(p->cpus_ptr);
+
+ if ((migration_disabled &&
+ !cpumask_test_cpu(cpu, p->cpus_ptr)) ||
+ (allowed && !cpumask_test_cpu(cpu, allowed)) ||
+ !scx_idle_test_and_clear_cpu(cpu))
cpu = -EBUSY;
} else {
cpu = scx_select_cpu_dfl(p, prev_cpu, wake_flags,
base-commit: 7b63ef2d55f24519e7e9e5f4d15dbea03f126e40
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] sched_ext: idle: Respect task affinity in restricted CPU selection
2026-10-11 13:17 ` [PATCH v2] " Hui Su
@ 2026-10-11 14:39 ` Andrea Righi
2026-10-11 17:09 ` Hui Su
0 siblings, 1 reply; 6+ messages in thread
From: Andrea Righi @ 2026-10-11 14:39 UTC (permalink / raw)
To: Hui Su
Cc: tj, void, changwoo, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, sched-ext, linux-kernel, bpf, stable
Hi Hui,
On Sun, Oct 11, 2026 at 10:17:00PM +0900, Hui Su wrote:
> The restricted select_cpu kfunc path handles single-CPU and
> migration-disabled tasks without going through scx_select_cpu_dfl().
>
> It currently uses prev_cpu as the candidate. However, prev_cpu is only a
> hint and may no longer be usable after an affinity change. When the caller
> supplies @allowed, the restricted path can also claim and return an idle
> CPU which isn't in p->cpus_ptr.
>
> Derive the candidate from task state instead. Use the sole CPU in
> p->cpus_ptr for single-CPU tasks and task_cpu(p) for migration-disabled
> tasks. Validate the candidate against the task's effective affinity and
> the caller-provided mask before claiming its idle state.
>
> The validation must happen before scx_idle_test_and_clear_cpu(), as a
> successful call consumes the CPU's idle bit. A later placement validation
> therefore cannot undo the idle-state side effect.
>
> Single-CPU and migration-disabled tasks skip ops.select_cpu(), so this
> path can instead be reached when the CPU-selection kfunc is called from
> other contexts such as ops.enqueue(). The normal ops.select_cpu() path is
> unchanged.
>
> Fixes: 683d2d0faba1 ("sched_ext: idle: Introduce scx_bpf_select_cpu_and()")
> Cc: stable@vger.kernel.org
> Signed-off-by: Hui Su <sh_def@163.com>
> ---
> Testing:
> - x86_64 KVM/QEMU private BPF test-run harness:
> - The baseline returned CPU1 outside the task affinity; v2 returned
> -EBUSY.
> - With a stale prev_cpu, the baseline returned -EBUSY; v2 selected the
> task's idle CPU0.
> - A caller mask excluding the task's candidate CPU returned -EBUSY with v2.
> - The stock sched_ext allowed_cpus selftest passed.
> - Built vmlinux and bzImage with CONFIG_PREEMPT_RCU=y.
> - Built kernel/sched/build_policy.o with CONFIG_PREEMPT_RCU=n and CONFIG_SMP=n.
>
> Changes in v2:
> - Derive the restricted-path candidate from task state instead of prev_cpu.
> - Document the idle-bit side effect and the ops.enqueue() reachability.
>
> v1: https://lore.kernel.org/lkml/20261011025256.4180374-1-sh_def@163.com/
> ---
> kernel/sched/ext/idle.c | 20 ++++++++++++++------
> 1 file changed, 14 insertions(+), 6 deletions(-)
>
> diff --git a/kernel/sched/ext/idle.c b/kernel/sched/ext/idle.c
> index aa9fb6de0ad6..1ccea60e9012 100644
> --- a/kernel/sched/ext/idle.c
> +++ b/kernel/sched/ext/idle.c
> @@ -958,6 +958,7 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
> {
> unsigned long irq_flags;
> bool we_locked = false;
> + bool migration_disabled;
> s32 cpu;
>
> if (!scx_cpu_valid(sch, prev_cpu, NULL))
> @@ -995,14 +996,21 @@ static s32 select_cpu_from_kfunc(struct scx_sched *sch, struct task_struct *p,
> /*
> * This may also be called from ops.enqueue(), so we need to handle
> * per-CPU tasks as well. For these tasks, we can skip all idle CPU
> - * selection optimizations and simply check whether the previously
> - * used CPU is idle and within the allowed cpumask.
> + * selection optimizations and simply check whether the task's only
> + * usable CPU is idle and within the allowed cpumask.
> */
> - if (p->nr_cpus_allowed == 1 || is_bpf_migration_disabled(p)) {
> - if (cpumask_test_cpu(prev_cpu, allowed ?: p->cpus_ptr) &&
> - scx_idle_test_and_clear_cpu(prev_cpu))
> - cpu = prev_cpu;
> + migration_disabled = is_bpf_migration_disabled(p);
> +
> + if (p->nr_cpus_allowed == 1 || migration_disabled) {
> + if (migration_disabled)
> + cpu = task_cpu(p);
> else
> + cpu = cpumask_first(p->cpus_ptr);
> +
> + if ((migration_disabled &&
> + !cpumask_test_cpu(cpu, p->cpus_ptr)) ||
> + (allowed && !cpumask_test_cpu(cpu, allowed)) ||
> + !scx_idle_test_and_clear_cpu(cpu))
> cpu = -EBUSY;
Sorry, I suggested this, but I just realized that we break SCX_PICK_IDLE_IN_NODE
if we do this. What if we factor the check with an helper? Something like this:
static s32 select_cpu_restricted(struct task_struct *p, s32 prev_cpu,
const struct cpumask *allowed, u64 flags,
bool migration_disabled)
{
s32 cpu;
if (migration_disabled)
cpu = task_cpu(p);
else
cpu = cpumask_first(p->cpus_ptr);
if (!cpumask_test_cpu(cpu, p->cpus_ptr))
return -EBUSY;
if (allowed && !cpumask_test_cpu(cpu, allowed))
return -EBUSY;
if (flags & SCX_PICK_IDLE_IN_NODE) {
int node = scx_cpu_node_if_enabled(prev_cpu);
if (scx_cpu_node_if_enabled(cpu) != node)
return -EBUSY;
}
if (!scx_idle_test_and_clear_cpu(cpu))
return -EBUSY;
return cpu;
}
Then the caller becomes:
migration_disabled = is_bpf_migration_disabled(p);
if (p->nr_cpus_allowed == 1 || migration_disabled)
cpu = select_cpu_restricted(p, prev_cpu, allowed, flags, migration_disabled);
else
cpu = scx_select_cpu_dfl(p, prev_cpu, wake_flags, allowed ?: p->cpus_ptr, flags);
Thanks,
-Andrea
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2] sched_ext: idle: Respect task affinity in restricted CPU selection
2026-10-11 14:39 ` Andrea Righi
@ 2026-10-11 17:09 ` Hui Su
0 siblings, 0 replies; 6+ messages in thread
From: Hui Su @ 2026-10-11 17:09 UTC (permalink / raw)
To: Andrea Righi
Cc: tj, void, changwoo, mingo, peterz, juri.lelli, vincent.guittot,
dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, sched-ext, linux-kernel, bpf, stable
Hi Andrea,
> Sorry, I suggested this, but I just realized that we break
> SCX_PICK_IDLE_IN_NODE if we do this. What if we factor the check with
> an helper?
Good catch. You're right that deriving the candidate from task state also
needs to preserve the SCX_PICK_IDLE_IN_NODE constraint.
I'll factor the restricted path into a helper along the lines you
suggested, with the candidate derived from task state first, followed by
the task-affinity, caller-mask, and node checks before consuming the idle
bit.
I'll also add focused validation for the cross-node case and send a v3.
I'll keep SCX_PICK_IDLE_CORE handling as a separate follow-up rather than
mixing it into this fix.
Thanks,
Hui
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-11 17:11 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-11 2:52 [PATCH] sched_ext: idle: Respect task affinity in restricted CPU selection Hui Su
2026-10-11 7:02 ` Andrea Righi
2026-10-11 9:10 ` Hui Su
2026-10-11 13:17 ` [PATCH v2] " Hui Su
2026-10-11 14:39 ` Andrea Righi
2026-10-11 17:09 ` Hui Su
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®