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 | scx_arena_pool_destroy(sch); | 4920 | scx_arena_pool_destroy(sch); |
| 4921 | if (sch->arena_map) | 4921 | if (sch->arena_map) |
| 4922 | bpf_map_put(sch->arena_map); | 4922 | bpf_map_put(sch->arena_map); |
| 4923 | |||
| 4924 | /* @sch is completely inactive by now */ | ||
| 4925 | scx_dec_has_subs(sch); | ||
| 4926 | |||
| 4923 | kfree(sch); | 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 | lockdep_assert_rq_held(rq); | 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 | pos = scx_next_descendant_pre(NULL, scx_root); | 759 | pos = scx_next_descendant_pre(NULL, scx_root); |
| 750 | while (pos) { | 760 | while (pos) { |
| 751 | bool forced = false; | 761 | bool forced = false; |
| @@ -2110,7 +2110,7 @@ do { \ · 2109 unmodified lines | |||
| 2110 | */ | 2110 | */ |
| 2111 | #define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \ | 2111 | #define SCX_CALL_OP_TASK(sch, op, locked_rq, task, args...) \ |
| 2112 | do { \ | 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 | __SCX_CALL_OP_TASK((sch), ops, op, locked_rq, task, ##args); \ | 2114 | __SCX_CALL_OP_TASK((sch), ops, op, locked_rq, task, ##args); \ |
| 2115 | } while (0) | 2115 | } while (0) |
| 2116 | 2116 | ||
| @@ -2125,7 +2125,7 @@ do { \ · 8 unmodified lines | |||
| 2125 | #define SCX_CALL_OP_TASK_RET(sch, op, locked_rq, task, args...) \ | 2125 | #define SCX_CALL_OP_TASK_RET(sch, op, locked_rq, task, args...) \ |
| 2126 | ({ \ | 2126 | ({ \ |
| 2127 | __typeof__((sch)->ops.op(task, ##args)) __ret; \ | 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 | WARN_ON_ONCE(current->scx.kf_tasks[0]); \ | 2129 | WARN_ON_ONCE(current->scx.kf_tasks[0]); \ |
| 2130 | current->scx.kf_tasks[0] = task; \ | 2130 | current->scx.kf_tasks[0] = task; \ |
| 2131 | __ret = SCX_CALL_OP_RET((sch), op, locked_rq, task, ##args); \ | 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 | #ifdef CONFIG_EXT_SUB_SCHED | 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 | * scx_task_sched - Find scx_sched scheduling a task | 2182 | * scx_task_sched - Find scx_sched scheduling a task |
| 2170 | * @p: task of interest | 2183 | * @p: task of interest |
| @@ -2259,6 +2272,8 @@ static inline struct scx_sched *scx_parent(struct scx_sched *sch) · 88 unmodified lines | |||
| 2259 | return NULL; | 2272 | return NULL; |
| 2260 | } | 2273 | } |
| 2261 | #else /* CONFIG_EXT_SUB_SCHED */ | 2274 | #else /* CONFIG_EXT_SUB_SCHED */ |
| 2275 | static inline bool scx_has_subs(void) { return false; } | ||
| 2276 | |||
| 2262 | static inline struct scx_sched *scx_task_sched(const struct task_struct *p) | 2277 | static inline struct scx_sched *scx_task_sched(const struct task_struct *p) |
| 2263 | { | 2278 | { |
| 2264 | return rcu_dereference_protected(scx_root, | 2279 | return rcu_dereference_protected(scx_root, |
| @@ -22,6 +22,12 @@ · 21 unmodified lines | |||
| 22 | 22 | ||
| 23 | #ifdef CONFIG_EXT_SUB_SCHED | 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 | * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk | 32 | * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk |
| 27 | * @pos: current position | 33 | * @pos: current position |
| @@ -236,6 +242,9 @@ void scx_init_root_caps(struct scx_sched *sch) · 208 unmodified lines | |||
| 236 | struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq, | 242 | struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq, |
| 237 | struct task_struct *p, u64 *enq_flags) | 243 | struct task_struct *p, u64 *enq_flags) |
| 238 | { | 244 | { |
| 245 | if (!scx_has_subs()) | ||
| 246 | return &rq->scx.local_dsq; | ||
| 247 | |||
| 239 | s32 cid = __scx_cpu_to_cid(cpu_of(rq)); | 248 | s32 cid = __scx_cpu_to_cid(cpu_of(rq)); |
| 240 | struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch; | 249 | struct scx_sched *asch = rq->scx.remote_activate_sch ?: sch; |
| 241 | u64 needed = scx_caps_for_enq(*enq_flags); | 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 | lockdep_assert_rq_held(rq); | 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 | return; | 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 | lockdep_assert_rq_held(rq); | 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 | return; | 510 | return; |
| 502 | 511 | ||
| 503 | /* | 512 | /* |
| @@ -1016,10 +1025,18 @@ void scx_sub_enable_workfn(struct kthread_work *work) · 512 unmodified lines | |||
| 1016 | kobject_get(&parent->kobj); | 1025 | kobject_get(&parent->kobj); |
| 1017 | raw_spin_unlock_irq(&scx_sched_lock); | 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 | /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ | 1035 | /* scx_alloc_and_add_sched() consumes @cgrp whether it succeeds or not */ |
| 1020 | sch = scx_alloc_and_add_sched(cmd, cgrp, parent); | 1036 | sch = scx_alloc_and_add_sched(cmd, cgrp, parent); |
| 1021 | kobject_put(&parent->kobj); | 1037 | kobject_put(&parent->kobj); |
| 1022 | if (IS_ERR(sch)) { | 1038 | if (IS_ERR(sch)) { |
| 1039 | static_branch_dec(&__scx_has_subs); | ||
| 1023 | ret = PTR_ERR(sch); | 1040 | ret = PTR_ERR(sch); |
| 1024 | goto out_unlock; | 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 | return sch->cgrp_path; | 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 | #else /* CONFIG_EXT_SUB_SCHED */ | 54 | #else /* CONFIG_EXT_SUB_SCHED */ |
| 48 | 55 | ||
| 49 | static inline struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; } | 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 | 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; } | 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 | static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; } | 74 | static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; } |
| 68 | static inline void scx_reenq_reject(struct rq *rq) {} | 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 | #endif /* CONFIG_EXT_SUB_SCHED */ | 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 | u64 ecaps; | 105 | u64 ecaps; |
| 98 | 106 | ||
| 107 | /* no sub-scheds, no missing caps */ | ||
| 108 | if (!scx_has_subs()) | ||
| 109 | return 0; | ||
| 110 | |||
| 99 | /* root holds every cap on every cpu */ | 111 | /* root holds every cap on every cpu */ |
| 100 | if (!sch->level) | 112 | if (!sch->level) |
| 101 | return 0; | 113 | return 0; |
| @@ -156,6 +168,9 @@ static inline u64 scx_caps_implied(u64 cap) · 54 unmodified lines | |||
| 156 | /* may @p keep running on @rq's cpu? requires baseline cpu access */ | 168 | /* may @p keep running on @rq's cpu? requires baseline cpu access */ |
| 157 | static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p) | 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 | /* a migration-disabled task is let in without caps, keep it likewise */ | 174 | /* a migration-disabled task is let in without caps, keep it likewise */ |
| 160 | if (unlikely(is_migration_disabled(p))) | 175 | if (unlikely(is_migration_disabled(p))) |
| 161 | return true; | 176 | return true; |