With CONFIG_EXT_SUB_SCHED=y but no sub-scheduler attached - the common case - hot paths still pay for sub-sched bookkeeping. Gate it behind __scx_has_subs, a static key counting live sub-schedulers, so that a root-only system stops paying. Most conversions are simple skip-if-no-sub tests. scx_idle_notify() is special - it's a hierarchy walk, so give it a fast path which notifies the root directly using the same tests as the walk. A pending SCX_RQ_SUB_IDLE_RENOTIFY can be ignored as no sub can be owed one and the caller clears the flag either way.
Unified · Split
| @@ -4920,6 +4920,10 @@ static void scx_sched_free_rcu_work(struct work_struct *work) · 4919 unmodified lines | ||
| 4920 | 4920 | scx_arena_pool_destroy(sch); |
| 4921 | 4921 | if (sch->arena_map) |
| 4922 | 4922 | bpf_map_put(sch->arena_map); |
| 4923 | + | |
| 4924 | + /* @sch is completely inactive by now */ | |
| 4925 | + scx_dec_has_subs(sch); | |
| 4926 | + | |
| 4923 | 4927 | kfree(sch); |
| 4924 | 4928 | } |
| 4925 | 4929 | |
| @@ -746,6 +746,16 @@ static void scx_idle_notify(struct rq *rq, bool idle, bool do_notify, bool root_ · 745 unmodified lines | ||
| 746 | 746 | |
| 747 | 747 | lockdep_assert_rq_held(rq); |
| 748 | 748 | |
| 749 | + /* with no sub-sched, only the root can be owed a notification */ | |
| 750 | + if (!scx_has_subs()) { | |
| 751 | + struct scx_sched *sch = scx_root; | |
| 752 | + | |
| 753 | + if ((do_notify || root_renotify) && | |
| 754 | + SCX_HAS_OP(sch, update_idle) && !scx_bypassing(sch, cpu)) | |
| 755 | + SCX_CALL_OP(sch, update_idle, rq, cid, idle); | |
| 756 | + return; | |
| 757 | + } | |
| 758 | + | |
| 749 | 759 | pos = scx_next_descendant_pre(NULL, scx_root); |
| 750 | 760 | while (pos) { |
| 751 | 761 | bool forced = false; |
| @@ -2110,7 +2110,7 @@ do { \ · 2109 unmodified lines | ||
| 2110 | 2110 | */ |
| 2111 | 2111 | #define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \ |
| 2112 | 2112 | do { \ |
| 2113 | − WARN_ON_ONCE((sch) != scx_task_sched_rcu(task)); \ | |
| 2113 | + WARN_ON_ONCE(scx_has_subs() && (sch) != scx_task_sched_rcu(task)); \ | |
| 2114 | 2114 | __SCX_CALL_OP_TASK((sch), ops, op, locked_rq, task, ##args); \ |
| 2115 | 2115 | } while (0) |
| 2116 | 2116 | |
| @@ -2125,7 +2125,7 @@ do { \ · 8 unmodified lines | ||
| 2125 | 2125 | #define SCX_CALL_OP_TASK_RET(sch, op, locked_rq, task, args...) \ |
| 2126 | 2126 | ({ \ |
| 2127 | 2127 | __typeof__((sch)->ops.op(task, ##args)) __ret; \ |
| 2128 | − WARN_ON_ONCE((sch) != scx_task_sched_rcu(task)); \ | |
| 2128 | + WARN_ON_ONCE(scx_has_subs() && (sch) != scx_task_sched_rcu(task)); \ | |
| 2129 | 2129 | WARN_ON_ONCE(current->scx.kf_tasks[0]); \ |
| 2130 | 2130 | current->scx.kf_tasks[0] = task; \ |
| 2131 | 2131 | __ret = SCX_CALL_OP_RET((sch), op, locked_rq, task, ##args); \ |
| @@ -2165,6 +2165,19 @@ static inline bool scx_bypassing(struct scx_sched *sch, s32 cpu) · 33 unmodified lines | ||
| 2165 | 2165 | } |
| 2166 | 2166 | |
| 2167 | 2167 | #ifdef CONFIG_EXT_SUB_SCHED |
| 2168 | +DECLARE_STATIC_KEY_FALSE(__scx_has_subs); | |
| 2169 | + | |
| 2170 | +/** | |
| 2171 | + * scx_has_subs - Whether any sub-scheduler exists | |
| 2172 | + * | |
| 2173 | + * Gates the sub-sched portions of hot paths so that a root-only system doesn't | |
| 2174 | + * pay for them. See scx_sub_enable_workfn() and scx_sched_free_rcu_work(). | |
| 2175 | + */ | |
| 2176 | +static inline bool scx_has_subs(void) | |
| 2177 | +{ | |
| 2178 | + return static_branch_unlikely(&__scx_has_subs); | |
| 2179 | +} | |
| 2180 | + | |
| 2168 | 2181 | /** |
| 2169 | 2182 | * scx_task_sched - Find scx_sched scheduling a task |
| 2170 | 2183 | * @p: task of interest |
| @@ -2259,6 +2272,8 @@ static inline struct scx_sched *scx_parent(struct scx_sched *sch) · 88 unmodified lines | ||
| 2259 | 2272 | return NULL; |
| 2260 | 2273 | } |
| 2261 | 2274 | #else /* CONFIG_EXT_SUB_SCHED */ |
| 2275 | +static inline bool scx_has_subs(void) { return false; } | |
| 2276 | + | |
| 2262 | 2277 | static inline struct scx_sched *scx_task_sched(const struct task_struct *p) |
| 2263 | 2278 | { |
| 2264 | 2279 | return rcu_dereference_protected(scx_root, |
| @@ -22,6 +22,12 @@ · 21 unmodified lines | ||
| 22 | 22 | |
| 23 | 23 | #ifdef CONFIG_EXT_SUB_SCHED |
| 24 | 24 | |
| 25 | +/* | |
| 26 | + * On while any sub-scheduler exists so that a root-only system doesn't pay for | |
| 27 | + * the sub-sched portions of hot paths. See scx_has_subs(). | |
| 28 | + */ | |
| 29 | +DEFINE_STATIC_KEY_FALSE(__scx_has_subs); | |
| 30 | + | |
| 25 | 31 | /** |
| 26 | 32 | * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk |
| 27 | 33 | * @pos: current position |
| @@ -236,6 +242,9 @@ void scx_init_root_caps(struct scx_sched *sch) · 208 unmodified lines | ||
| 236 | 242 | struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq, |
| 237 | 243 | struct task_struct *p, u64 *enq_flags) |
| 238 | 244 | { |
| 245 | + if (!scx_has_subs()) | |
| 246 | + return &rq->scx.local_dsq; | |
| 247 | + | |
| 239 | 248 | s32 cid = __scx_cpu_to_cid(cpu_of(rq)); |
| 240 | 249 | struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch; |
| 241 | 250 | u64 needed = scx_caps_for_enq(*enq_flags); |
| @@ -314,7 +323,7 @@ void scx_reenq_reject(struct rq *rq) · 72 unmodified lines | ||
| 314 | 323 | |
| 315 | 324 | lockdep_assert_rq_held(rq); |
| 316 | 325 | |
| 317 | − if (list_empty(&rq->scx.reject_dsq.list)) | |
| 326 | + if (!scx_has_subs() || list_empty(&rq->scx.reject_dsq.list)) | |
| 318 | 327 | return; |
| 319 | 328 | |
| 320 | 329 | /* |
| @@ -497,7 +506,7 @@ void scx_process_sync_ecaps(struct rq *rq, struct task_struct *prev) · 176 unmodified lines | ||
| 497 | 506 | |
| 498 | 507 | lockdep_assert_rq_held(rq); |
| 499 | 508 | |
| 500 | − if (likely(llist_empty(&rq->scx.ecaps_to_sync))) | |
| 509 | + if (!scx_has_subs() || likely(llist_empty(&rq->scx.ecaps_to_sync))) | |
| 501 | 510 | return; |
| 502 | 511 | |
| 503 | 512 | /* |
| @@ -1016,10 +1025,18 @@ void scx_sub_enable_workfn(struct kthread_work *work) · 512 unmodified lines | ||
| 1016 | 1025 | kobject_get(&parent->kobj); |
| 1017 | 1026 | raw_spin_unlock_irq(&scx_sched_lock); |
| 1018 | 1027 | |
| 1028 | + /* | |
| 1029 | + * Flip the hot-path gates before ops->priv is published - the sub's | |
| 1030 | + * programs can e.g. kick cpus from that point on. The matching dec is | |
| 1031 | + * at the end of scx_sched_free_rcu_work(). | |
| 1032 | + */ | |
| 1033 | + static_branch_inc(&__scx_has_subs); | |
| 1034 | + | |
| 1019 | 1035 | /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ |
| 1020 | 1036 | sch = scx_alloc_and_add_sched(cmd, cgrp, parent); |
| 1021 | 1037 | kobject_put(&parent->kobj); |
| 1022 | 1038 | if (IS_ERR(sch)) { |
| 1039 | + static_branch_dec(&__scx_has_subs); | |
| 1023 | 1040 | ret = PTR_ERR(sch); |
| 1024 | 1041 | goto out_unlock; |
| 1025 | 1042 | } |
| @@ -44,6 +44,13 @@ static inline const char *sch_cgrp_path(struct scx_sched *sch) · 43 unmodified lines | ||
| 44 | 44 | return sch->cgrp_path; |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | +/* a dying sub's hot-path influence ends in scx_sched_free_rcu_work() */ | |
| 48 | +static inline void scx_dec_has_subs(struct scx_sched *sch) | |
| 49 | +{ | |
| 50 | + if (sch->level) | |
| 51 | + static_branch_dec(&__scx_has_subs); | |
| 52 | +} | |
| 53 | + | |
| 47 | 54 | #else /* CONFIG_EXT_SUB_SCHED */ |
| 48 | 55 | |
| 49 | 56 | static inline struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; } |
| @@ -66,6 +73,7 @@ static inline void scx_discard_stale_ecaps_syncs(void) {} · 16 unmodified lines | ||
| 66 | 73 | static inline struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq, struct task_struct *p, u64 *enq_flags) { return &rq->scx.local_dsq; } |
| 67 | 74 | static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; } |
| 68 | 75 | static inline void scx_reenq_reject(struct rq *rq) {} |
| 76 | +static inline void scx_dec_has_subs(struct scx_sched *sch) {} | |
| 69 | 77 | |
| 70 | 78 | #endif /* CONFIG_EXT_SUB_SCHED */ |
| 71 | 79 | |
| @@ -96,6 +104,10 @@ static inline u64 scx_missing_caps(struct scx_sched *sch, s32 cpu, u64 needed) · 24 unmodified lines | ||
| 96 | 104 | { |
| 97 | 105 | u64 ecaps; |
| 98 | 106 | |
| 107 | + /* no sub-scheds, no missing caps */ | |
| 108 | + if (!scx_has_subs()) | |
| 109 | + return 0; | |
| 110 | + | |
| 99 | 111 | /* root holds every cap on every cpu */ |
| 100 | 112 | if (!sch->level) |
| 101 | 113 | return 0; |
| @@ -156,6 +168,9 @@ static inline u64 scx_caps_implied(u64 cap) · 54 unmodified lines | ||
| 156 | 168 | /* may @p keep running on @rq's cpu? requires baseline cpu access */ |
| 157 | 169 | static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p) |
| 158 | 170 | { |
| 171 | + if (!scx_has_subs()) | |
| 172 | + return true; | |
| 173 | + | |
| 159 | 174 | /* a migration-disabled task is let in without caps, keep it likewise */ |
| 160 | 175 | if (unlikely(is_migration_disabled(p))) |
| 161 | 176 | return true; |