History

sched_ext: Add the scx_has_subs static key and gate sub-sched hot paths

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.

Tejun Heo committed 4 months ago

Commit 8946dbd1 parent 6ba3bd65 files · +65 −4Browse files

Showing the changes to kernel (5 of 5 files). Show all

Unified · Split

5 files
  1. kernel/sched/ext/ext.c +4 −0
  2. kernel/sched/ext/idle.c +10 −0
  3. kernel/sched/ext/internal.h +17 −2
  4. kernel/sched/ext/sub.c +19 −2
  5. kernel/sched/ext/sub.h +15 −0
kernel/sched/ext/ext.c+4−0
@@ -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
kernel/sched/ext/idle.c+10−0
@@ -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;
kernel/sched/ext/internal.h+17−2
@@ -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...) \
2112do { \2112do { \
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_SCHED2167#ifdef CONFIG_EXT_SUB_SCHED
2168DECLARE_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 */
2176static 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 task2182 * scx_task_sched - Find scx_sched scheduling a task
2170 * @p: task of interest2183 * @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 */
2275static inline bool scx_has_subs(void) { return false; }
2276
2262static inline struct scx_sched *scx_task_sched(const struct task_struct *p)2277static 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,
kernel/sched/ext/sub.c+19−2
@@ -22,6 +22,12 @@ · 21 unmodified lines
22 22
23#ifdef CONFIG_EXT_SUB_SCHED23#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 */
29DEFINE_STATIC_KEY_FALSE(__scx_has_subs);
30
25/**31/**
26 * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk32 * scx_skip_subtree_pre - Skip @pos's subtree in a pre-order walk
27 * @pos: current position33 * @pos: current position
@@ -236,6 +242,9 @@ void scx_init_root_caps(struct scx_sched *sch) · 208 unmodified lines
236struct scx_dispatch_q *scx_local_or_reject_dsq(struct scx_sched *sch, struct rq *rq,242struct 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 }
kernel/sched/ext/sub.h+15−0
@@ -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() */
48static 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
49static inline struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos, struct scx_sched *root) { return pos ? NULL : root; }56static 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
66static 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; }73static 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; }
67static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; }74static inline bool scx_task_reenq_on_cap_revoke(struct rq *rq, struct task_struct *p) { return false; }
68static inline void scx_reenq_reject(struct rq *rq) {}75static inline void scx_reenq_reject(struct rq *rq) {}
76static 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 */
157static inline bool scx_task_can_stay_on_cpu(struct rq *rq, struct task_struct *p)169static 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;