Skip to content

Commit f7574d3

Browse files
committed
Merge tag 'sched_ext-for-7.2-rc3-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext
Pull sched_ext fixes from Tejun Heo: - Lifecycle fixes for the new sub-scheduler support: two use-after-frees and an enable-failure path that left a half-initialized sub-scheduler linked. - Two dispatch-path locking bugs: a spurious scheduler abort from a migration race, and a lockdep splat from stale runqueue-lock tracking. - Callback and task-state fixes: stale scheduler-owned state on a task leaving SCX, a weight callback running after disable, and a bogus warning on core-scheduling forced idle. - On nohz_full, finite-slice tasks could miss the tick that expires their slice. Enable it when such a task is picked, with a selftest. - Smaller fixes: userspace CPU-mask helpers, ratelimited deprecation warnings, docs and a sparse annotation. * tag 'sched_ext-for-7.2-rc3-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext: sched_ext: Skip ops.set_weight() for disabled tasks tools/sched_ext: scx - Fix cmask_subset(), cmask_equal() and cmask_weight() sched_ext: Fix premature ops->priv publication in scx_alloc_and_add_sched() sched_ext: Record an error on errno-only sub-enable failure selftests/sched_ext: Verify nohz_full tick behavior sched_ext: Enable tick for finite slices on nohz_full sched_ext: Preserve rq tracking across local DSQ dispatch sched_ext: Documentation: Fix ops table header reference sched_ext: Don't warn on core-sched forced idle in put_prev_task_scx() sched_ext: Pin parent scx_sched across a child sub-scheduler's lifetime sched_ext: Annotate ksyncs with __rcu in alloc/free_kick_syncs() sched_ext: Check remote rq eligibility under task's rq lock sched_ext: Reset dsq_vtime and slice when a task leaves SCX sched_ext: Avoid flooding the log with deprecation warnings
2 parents f94f853 + 0e2f4ab commit f7574d3

7 files changed

Lines changed: 614 additions & 95 deletions

File tree

Documentation/scheduler/sched-ext.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -493,8 +493,9 @@ a freshly woken up task gets on a CPU.
493493
Where to Look
494494
=============
495495

496-
* ``include/linux/sched/ext.h`` defines the core data structures, ops table
497-
and constants.
496+
* ``include/linux/sched/ext.h`` defines the core data structures and
497+
constants, while the ops table (``struct sched_ext_ops``) is defined in
498+
``kernel/sched/ext/internal.h``.
498499

499500
* ``kernel/sched/ext/ext.c`` contains sched_ext core implementation and helpers.
500501
The functions prefixed with ``scx_bpf_`` can be called from the BPF
@@ -555,7 +556,8 @@ ABI Instability
555556
===============
556557

557558
The APIs provided by sched_ext to BPF schedulers programs have no stability
558-
guarantees. This includes the ops table callbacks and constants defined in
559+
guarantees. This includes the ops table callbacks defined in
560+
``kernel/sched/ext/internal.h`` and the constants defined in
559561
``include/linux/sched/ext.h``, as well as the ``scx_bpf_`` kfuncs defined in
560562
``kernel/sched/ext/ext.c`` and ``kernel/sched/ext/idle.c``.
561563

kernel/sched/ext/ext.c

Lines changed: 128 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,18 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags)
479479
*/
480480
DEFINE_PER_CPU(struct rq *, scx_locked_rq_state);
481481

482+
static void switch_rq_lock(struct rq *from, struct rq *to)
483+
{
484+
bool tracked = scx_locked_rq() == from;
485+
486+
if (tracked)
487+
update_locked_rq(NULL);
488+
raw_spin_rq_unlock(from);
489+
raw_spin_rq_lock(to);
490+
if (tracked)
491+
update_locked_rq(to);
492+
}
493+
482494
/*
483495
* Flipped on enable per sch->is_cid_type. Declared in internal.h so
484496
* subsystem inlines can read it.
@@ -2274,8 +2286,7 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
22742286
deactivate_task(src_rq, p, 0);
22752287
set_task_cpu(p, cpu_of(dst_rq));
22762288

2277-
raw_spin_rq_unlock(src_rq);
2278-
raw_spin_rq_lock(dst_rq);
2289+
switch_rq_lock(src_rq, dst_rq);
22792290

22802291
/*
22812292
* We want to pass scx-specific enq_flags but activate_task() will
@@ -2307,13 +2318,22 @@ static void move_remote_task_to_local_dsq(struct task_struct *p, u64 enq_flags,
23072318
* no to the BPF scheduler initiated migrations while offline.
23082319
*
23092320
* The caller must ensure that @p and @rq are on different CPUs.
2321+
* If enforce == true, caller must hold @p's rq lock.
23102322
*/
23112323
static bool task_can_run_on_remote_rq(struct scx_sched *sch,
23122324
struct task_struct *p, struct rq *rq,
23132325
bool enforce)
23142326
{
23152327
s32 cpu = cpu_of(rq);
23162328

2329+
/*
2330+
* To prevent races with @p still running on its old CPU while switching
2331+
* out, make sure we're holding @p's rq lock so as not to risk
2332+
* erroneously killing the BPF scheduler.
2333+
*/
2334+
if (enforce)
2335+
lockdep_assert_rq_held(task_rq(p));
2336+
23172337
WARN_ON_ONCE(task_cpu(p) == cpu);
23182338

23192339
/*
@@ -2581,13 +2601,6 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
25812601
return;
25822602
}
25832603

2584-
if (src_rq != dst_rq &&
2585-
unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) {
2586-
dispatch_enqueue(sch, rq, find_global_dsq(sch, task_cpu(p)), p,
2587-
enq_flags | SCX_ENQ_CLEAR_OPSS | SCX_ENQ_GDSQ_FALLBACK);
2588-
return;
2589-
}
2590-
25912604
/*
25922605
* @p is on a possibly remote @src_rq which we need to lock to move the
25932606
* task. If dequeue is in progress, it'd be locking @src_rq and waiting
@@ -2606,14 +2619,14 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
26062619

26072620
/* switch to @src_rq lock */
26082621
if (locked_rq != src_rq) {
2609-
raw_spin_rq_unlock(locked_rq);
2622+
switch_rq_lock(locked_rq, src_rq);
26102623
locked_rq = src_rq;
2611-
raw_spin_rq_lock(src_rq);
26122624
}
26132625

26142626
/* task_rq couldn't have changed if we're still the holding cpu */
26152627
if (likely(p->scx.holding_cpu == raw_smp_processor_id()) &&
26162628
!WARN_ON_ONCE(src_rq != task_rq(p))) {
2629+
bool fallback = false;
26172630
/*
26182631
* If @p is staying on the same rq, there's no need to go
26192632
* through the full deactivate/activate cycle. Optimize by
@@ -2623,6 +2636,11 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
26232636
p->scx.holding_cpu = -1;
26242637
dispatch_enqueue(sch, dst_rq, &dst_rq->scx.local_dsq, p,
26252638
enq_flags);
2639+
} else if (unlikely(!task_can_run_on_remote_rq(sch, p, dst_rq, true))) {
2640+
p->scx.holding_cpu = -1;
2641+
fallback = true;
2642+
dispatch_enqueue(sch, src_rq, find_global_dsq(sch, task_cpu(p)),
2643+
p, enq_flags | SCX_ENQ_GDSQ_FALLBACK);
26262644
} else {
26272645
move_remote_task_to_local_dsq(p, enq_flags,
26282646
src_rq, dst_rq);
@@ -2631,15 +2649,13 @@ static void dispatch_to_local_dsq(struct scx_sched *sch, struct rq *rq,
26312649
}
26322650

26332651
/* if the destination CPU is idle, wake it up */
2634-
if (sched_class_above(p->sched_class, dst_rq->curr->sched_class))
2652+
if (!fallback && sched_class_above(p->sched_class, dst_rq->curr->sched_class))
26352653
resched_curr(dst_rq);
26362654
}
26372655

26382656
/* switch back to @rq lock */
2639-
if (locked_rq != rq) {
2640-
raw_spin_rq_unlock(locked_rq);
2641-
raw_spin_rq_lock(rq);
2642-
}
2657+
if (locked_rq != rq)
2658+
switch_rq_lock(locked_rq, rq);
26432659
}
26442660

26452661
/**
@@ -2970,24 +2986,38 @@ static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
29702986

29712987
/*
29722988
* @p is getting newly scheduled or got kicked after someone updated its
2973-
* slice. Refresh whether tick can be stopped. See scx_can_stop_tick().
2989+
* slice. Update SCX_RQ_CAN_STOP_TICK to reflect whether the tick can be
2990+
* stopped. See scx_can_stop_tick().
2991+
*
2992+
* Moreover, refresh the load_avgs just when transitioning in and out of
2993+
* nohz. In the future, we might want to add a mechanism to update
2994+
* load_avgs periodically on tick-stopped CPUs.
29742995
*/
2975-
if ((p->scx.slice == SCX_SLICE_INF) !=
2976-
(bool)(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
2977-
if (p->scx.slice == SCX_SLICE_INF)
2996+
if (p->scx.slice == SCX_SLICE_INF) {
2997+
if (!(rq->scx.flags & SCX_RQ_CAN_STOP_TICK)) {
2998+
/*
2999+
* Bypass mode always assigns finite slices, so @p
3000+
* can't have an infinite slice while bypassing.
3001+
* Therefore, sched_update_tick_dependency() can safely
3002+
* evaluate the outgoing task.
3003+
*/
29783004
rq->scx.flags |= SCX_RQ_CAN_STOP_TICK;
2979-
else
2980-
rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
3005+
sched_update_tick_dependency(rq);
29813006

2982-
sched_update_tick_dependency(rq);
3007+
update_other_load_avgs(rq);
3008+
}
3009+
} else {
3010+
if (rq->scx.flags & SCX_RQ_CAN_STOP_TICK) {
3011+
rq->scx.flags &= ~SCX_RQ_CAN_STOP_TICK;
3012+
update_other_load_avgs(rq);
3013+
}
29833014

29843015
/*
2985-
* For now, let's refresh the load_avgs just when transitioning
2986-
* in and out of nohz. In the future, we might want to add a
2987-
* mechanism which calls the following periodically on
2988-
* tick-stopped CPUs.
3016+
* @rq still references the outgoing scheduling context. A finite
3017+
* slice is sufficient by itself to require the tick.
29893018
*/
2990-
update_other_load_avgs(rq);
3019+
if (tick_nohz_full_cpu(cpu_of(rq)))
3020+
tick_nohz_dep_set_cpu(cpu_of(rq), TICK_DEP_BIT_SCHED);
29913021
}
29923022
}
29933023

@@ -3082,9 +3112,14 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,
30823112
* sched_class, %SCX_OPS_ENQ_LAST must be set. Tell
30833113
* ops.enqueue() that @p is the only one available for this cpu,
30843114
* which should trigger an explicit follow-up scheduling event.
3115+
*
3116+
* Core scheduling can force this CPU idle while @p stays
3117+
* runnable. @p's cookie then won't match the core's, so skip
3118+
* the warning in that case.
30853119
*/
30863120
if (next && sched_class_above(&ext_sched_class, next->sched_class)) {
3087-
WARN_ON_ONCE(!(sch->ops.flags & SCX_OPS_ENQ_LAST));
3121+
WARN_ON_ONCE(sched_cpu_cookie_match(rq, p) &&
3122+
!(sch->ops.flags & SCX_OPS_ENQ_LAST));
30883123
do_enqueue_task(rq, p, SCX_ENQ_LAST, -1);
30893124
} else {
30903125
do_enqueue_task(rq, p, 0, -1);
@@ -3647,6 +3682,13 @@ static void scx_disable_task(struct scx_sched *sch, struct task_struct *p)
36473682
SCX_CALL_OP_TASK(sch, disable, rq, p);
36483683
scx_set_task_state(p, SCX_TASK_READY);
36493684

3685+
/*
3686+
* Reset the SCX-managed fields when @p leaves the BPF scheduler's
3687+
* control, after ops.disable() has observed their final values.
3688+
*/
3689+
p->scx.dsq_vtime = 0;
3690+
p->scx.slice = 0;
3691+
36503692
/*
36513693
* Verify the task is not in BPF scheduler's custody. If flag
36523694
* transitions are consistent, the flag should always be clear
@@ -3925,6 +3967,17 @@ static void reweight_task_scx(struct rq *rq, struct task_struct *p,
39253967
if (task_dead_and_done(p))
39263968
return;
39273969

3970+
/*
3971+
* When switching sched_class away from SCX, reweight_task_scx()
3972+
* is called _after_ scx_disable_task(). Skip calling ops.set_weight()
3973+
* since the BPF scheduler may have already forgotten the task in
3974+
* ops.disable().
3975+
* p->scx.weight will be recalculated in scx_enable_task() if the task
3976+
* ever returns to SCX class.
3977+
*/
3978+
if (scx_get_task_state(p) != SCX_TASK_ENABLED)
3979+
return;
3980+
39283981
p->scx.weight = sched_weight_to_cgroup(scale_load_down(lw->weight));
39293982
if (SCX_HAS_OP(sch, set_weight))
39303983
SCX_CALL_OP_TASK(sch, set_weight, rq, p, p->scx.weight);
@@ -4301,6 +4354,15 @@ bool scx_can_stop_tick(struct rq *rq)
43014354
if (p->sched_class != &ext_sched_class)
43024355
return true;
43034356

4357+
/*
4358+
* @rq->curr may still reference an outgoing EXT task after it has been
4359+
* dequeued. If no EXT tasks are accounted on @rq, ignore its stale
4360+
* slice state. If another task is dispatched from a DSQ,
4361+
* set_next_task_scx() will update the dependency for the incoming task.
4362+
*/
4363+
if (!rq->scx.nr_running)
4364+
return true;
4365+
43044366
if (scx_bypassing(sch, cpu_of(rq)))
43054367
return false;
43064368

@@ -4901,6 +4963,8 @@ static void scx_sched_free_rcu_work(struct work_struct *work)
49014963
cgroup_put(sch_cgroup(sch));
49024964
if (sch->sub_kset)
49034965
kobject_put(&sch->sub_kset->kobj);
4966+
if (scx_parent(sch))
4967+
kobject_put(&scx_parent(sch)->kobj);
49044968
#endif /* CONFIG_EXT_SUB_SCHED */
49054969

49064970
for_each_possible_cpu(cpu) {
@@ -5672,7 +5736,7 @@ static void free_kick_syncs(void)
56725736
int cpu;
56735737

56745738
for_each_possible_cpu(cpu) {
5675-
struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
5739+
struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
56765740
struct scx_kick_syncs *to_free;
56775741

56785742
to_free = rcu_replace_pointer(*ksyncs, NULL, true);
@@ -6653,7 +6717,7 @@ static int alloc_kick_syncs(void)
66536717
* can exceed percpu allocator limits on large machines.
66546718
*/
66556719
for_each_possible_cpu(cpu) {
6656-
struct scx_kick_syncs **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
6720+
struct scx_kick_syncs __rcu **ksyncs = per_cpu_ptr(&scx_kick_syncs, cpu);
66576721
struct scx_kick_syncs *new_ksyncs;
66586722

66596723
WARN_ON_ONCE(rcu_access_pointer(*ksyncs));
@@ -6825,11 +6889,6 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
68256889
sch->ops = *cmd->ops;
68266890
}
68276891

6828-
rcu_assign_pointer(ops->priv, sch);
6829-
6830-
sch->kobj.kset = scx_kset;
6831-
INIT_LIST_HEAD(&sch->all);
6832-
68336892
#ifdef CONFIG_EXT_SUB_SCHED
68346893
char *buf = kzalloc(PATH_MAX, GFP_KERNEL);
68356894
if (!buf) {
@@ -6847,13 +6906,32 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
68476906
sch->cgrp = cgrp;
68486907
INIT_LIST_HEAD(&sch->children);
68496908
INIT_LIST_HEAD(&sch->sibling);
6909+
#endif /* CONFIG_EXT_SUB_SCHED */
68506910

6851-
if (parent)
6911+
/*
6912+
* Publishing makes @sch visible to scx_prog_sched() readers. Failure
6913+
* paths after this point must free @sch through kobject_put() whose
6914+
* release path defers the actual freeing by an RCU grace period.
6915+
*/
6916+
rcu_assign_pointer(ops->priv, sch);
6917+
6918+
sch->kobj.kset = scx_kset;
6919+
INIT_LIST_HEAD(&sch->all);
6920+
6921+
#ifdef CONFIG_EXT_SUB_SCHED
6922+
if (parent) {
6923+
/*
6924+
* Pin @parent for @sch's lifetime. The kobject hierarchy pins
6925+
* it only via @parent->sub_kset, which is dropped during
6926+
* disable. Released in scx_sched_free_rcu_work().
6927+
*/
6928+
kobject_get(&parent->kobj);
68526929
ret = kobject_init_and_add(&sch->kobj, &scx_ktype,
68536930
&parent->sub_kset->kobj,
68546931
"sub-%llu", cgroup_id(cgrp));
6855-
else
6932+
} else {
68566933
ret = kobject_init_and_add(&sch->kobj, &scx_ktype, NULL, "root");
6934+
}
68576935

68586936
if (ret < 0) {
68596937
RCU_INIT_POINTER(ops->priv, NULL);
@@ -6895,7 +6973,6 @@ static struct scx_sched *scx_alloc_and_add_sched(struct scx_enable_cmd *cmd,
68956973

68966974
#ifdef CONFIG_EXT_SUB_SCHED
68976975
err_free_lb_resched:
6898-
RCU_INIT_POINTER(ops->priv, NULL);
68996976
free_cpumask_var(sch->bypass_lb_resched_cpumask);
69006977
#endif
69016978
err_free_lb_cpumask:
@@ -6988,7 +7065,7 @@ static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)
69887065
* run past the BPF allocation. Skip for cid-form.
69897066
*/
69907067
if (!sch->is_cid_type && (ops->cpu_acquire || ops->cpu_release))
6991-
pr_warn("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n");
7068+
pr_warn_ratelimited("ops->cpu_acquire/release() are deprecated, use sched_switch TP instead\n");
69927069

69937070
/*
69947071
* Sub-scheduler support is tied to the cid-form struct_ops. A sub-sched
@@ -7686,6 +7763,12 @@ static void scx_sub_enable_workfn(struct kthread_work *work)
76867763
percpu_up_write(&scx_fork_rwsem);
76877764
err_disable:
76887765
mutex_unlock(&scx_enable_mutex);
7766+
/*
7767+
* Some enable failures only return an errno (e.g. -ENOMEM from an
7768+
* allocation) without calling scx_error(). Record it so
7769+
* scx_flush_disable_work() runs the disable and ops.exit() fires.
7770+
*/
7771+
scx_error(sch, "scx_sub_enable() failed (%d)", ret);
76897772
scx_flush_disable_work(sch);
76907773
cmd->ret = 0;
76917774
}
@@ -7806,7 +7889,7 @@ static int bpf_scx_btf_struct_access(struct bpf_verifier_log *log,
78067889
off + size <= offsetofend(struct task_struct, scx.slice)) ||
78077890
(off >= offsetof(struct task_struct, scx.dsq_vtime) &&
78087891
off + size <= offsetofend(struct task_struct, scx.dsq_vtime))) {
7809-
pr_warn("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()");
7892+
pr_warn_ratelimited("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()\n");
78107893
return SCALAR_VALUE;
78117894
}
78127895

@@ -8796,10 +8879,8 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
87968879
in_balance = this_rq->scx.flags & SCX_RQ_IN_BALANCE;
87978880

87988881
if (in_balance) {
8799-
if (this_rq != src_rq) {
8800-
raw_spin_rq_unlock(this_rq);
8801-
raw_spin_rq_lock(src_rq);
8802-
}
8882+
if (this_rq != src_rq)
8883+
switch_rq_lock(this_rq, src_rq);
88038884
} else {
88048885
raw_spin_rq_lock(src_rq);
88058886
}
@@ -8831,10 +8912,8 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,
88318912
dispatched = true;
88328913
out:
88338914
if (in_balance) {
8834-
if (this_rq != locked_rq) {
8835-
raw_spin_rq_unlock(locked_rq);
8836-
raw_spin_rq_lock(this_rq);
8837-
}
8915+
if (this_rq != locked_rq)
8916+
switch_rq_lock(locked_rq, this_rq);
88388917
} else {
88398918
raw_spin_rq_unlock_irqrestore(locked_rq, flags);
88408919
}

0 commit comments

Comments
 (0)