@@ -479,6 +479,18 @@ static bool rq_is_open(struct rq *rq, u64 enq_flags)
479479 */
480480DEFINE_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 */
23112323static 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
68976975err_free_lb_resched :
6898- RCU_INIT_POINTER (ops -> priv , NULL );
68996976 free_cpumask_var (sch -> bypass_lb_resched_cpumask );
69006977#endif
69016978err_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 );
76877764err_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;
88328913out :
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