sched_ext: Implement cgroup sub-scheduler support
Subsystems (5)
Tejun Heo
maintainerPATCH v2 1/1 sched_ext: Implement cgroup sub-scheduler support
This patchset implements cgroup sub-scheduler support for sched_ext, enabling multiple scheduler instances to be attached to the cgroup hierarchy. This is a partial implementation focusing on the dispatch path - select_cpu and enqueue paths will be updated in subsequent patchsets. While incomplete, the dispatch path changes are sufficient to demonstrate and exercise the core sub-scheduler structures.
Motivation
==========
Applications often have domain-specific knowledge that generic schedulers cannot possess. Database systems understand query priorities and lock holder criticality. Virtual machine monitors can coordinate with guest schedulers and handle vCPU placement intelligently. Game engines know rendering deadlines and which threads are latency-critical.
On multi-tenant systems where multiple such workloads coexist, implementing application-customized scheduling is difficult. Hard partitioning with cpuset lacks the dynamism needed - users often don't care about specific CPU assignments and want optimizations enabled by sharing a larger machine:
opportunistic over-commit, improving latency-critical workload characteristics while maintaining bandwidth fairness, and packing similar workloads on the same L3 caches for efficiency.
Sub-scheduler support addresses this by allowing schedulers to be attached to the cgroup hierarchy. Each application domain runs its own BPF scheduler tailored to its needs, while a parent scheduler dynamically controls CPU allocation to children without static partitioning.
Structure
=========
Schedulers attach to cgroup nodes forming a hierarchy up to SCX_SUB_MAX_DEPTH
(4) levels deep. Each scheduler instance maintains its own state including default time slice, watchdog, and bypass mode. Tasks belong to exactly one scheduler - the one attached to their cgroup or the nearest ancestor with a scheduler attached.
A parent scheduler is responsible for allocating CPU time to its children. When a parent's ops.dispatch() is invoked, it can call scx_bpf_sub_dispatch() to trigger dispatch on a child scheduler, allowing the parent to control when and how much CPU time each child receives. Currently only the dispatch path supports this - ops.select_cpu() and ops.enqueue() always operate on the task's own scheduler. Full support for these paths will follow in subsequent patchsets.
Kfuncs use the new KF_IMPLICIT_ARGS BPF feature to identify their calling scheduler - the kernel passes bpf_prog_aux implicitly, from which scx_prog_sched() finds the associated scx_sched. This enables authority enforcement ensuring schedulers can only manipulate their own tasks, preventing cross-scheduler interference.
Bypass mode, used for error recovery and orderly shutdown, propagates hierarchically - when a scheduler enters bypass, its descendants follow. This ensures forward progress even when nested schedulers malfunction. The dump infrastructure supports multiple schedulers, identifying which scheduler each task and DSQ belongs to for debugging.
Patches
=======
0001-0004: Preparatory changes exposing cgroup helpers, adding cgroup subtree iteration for sched_ext, passing kernel_clone_args to scx_fork(), and reordering sched_post_fork() after cgroup_post_fork().
0005-0006: Reorganize enable/disable paths in preparation for multiple scheduler instances.
0007-0009: Core sub-scheduler infrastructure introducing scx_sched structure, cgroup attachment, scx_task_sched() for task-to-scheduler mapping, and scx_prog_sched() for BPF program-to-scheduler association.
0010-0012: Authority enforcement ensuring schedulers can only manipulate their own tasks in dispatch, DSQ operations, and task state updates.
0013-0014: Refactor task init/exit helpers and update scx_prio_less() to handle tasks from different schedulers.
0015-0018: Migrate global state to per-scheduler fields: default slice, aborting flag, bypass DSQ, and bypass state.
0019-0023: Implement hierarchical bypass mode where bypass state propagates from parent to descendants, with proper separation of bypass dispatch enabling.
0024-0028: Multi-scheduler dispatch and diagnostics - dispatching from all scheduler instances, per-scheduler dispatch context, watchdog awareness, and multi-scheduler dump support.
0029: Implement sub-scheduler enabling and disabling with proper task migration between parent and child schedulers.
0030-0034: Building blocks for nested dispatching including scx_sched back pointers, reenqueue awareness, scheduler linking helpers, rhashtable lookup, and scx_bpf_sub_dispatch() kfunc.
v2: Rebased to sched_ext/for-7.1. This patchset depends on the following fix
which is being sent separately targeting sched_ext/for-7.0-fixes:
sched_ext: Disable preemption between scx_claim_exit() and kicking helper work
v1: http://lkml.kernel.org/r/[email protected]
Based on sched_ext/for-7.1 (477174ac35c5) + scx-fix-claim-preempt (097f6d24de4a).
Git tree:
git://git.kernel.org/pub/scm/linux/kernel/git/tj/sched_ext.git scx-sub-sched-v2
include/linux/cgroup-defs.h | 4 + include/linux/cgroup.h | 65 +- include/linux/sched/ext.h | 11 + init/Kconfig | 4 + kernel/cgroup/cgroup-internal.h | 6 - kernel/cgroup/cgroup.c | 55 - kernel/fork.c | 6 +- kernel/sched/core.c | 2 +- kernel/sched/ext.c | 2353 +++++++++++++++++++++++------- kernel/sched/ext.h | 4 +- kernel/sched/ext_idle.c | 104 +- kernel/sched/ext_internal.h | 248 +++- kernel/sched/sched.h | 7 +- tools/sched_ext/include/scx/common.bpf.h | 1 + tools/sched_ext/include/scx/compat.h | 10 + tools/sched_ext/scx_qmap.bpf.c | 44 +- tools/sched_ext/scx_qmap.c | 13 +- 17 files changed, 2294 insertions(+), 643 deletions(-)
--
tejun
Tejun Heo
maintainerPATCH 1/34 sched_ext: Implement cgroup subtree iteration for scx_task_iter
For the planned cgroup sub-scheduler support, enable/disable operations are going to be subtree specific and iterating all tasks in the system for those operations can be unnecessarily expensive and disruptive.
cgroup already has mechanisms to perform subtree task iterations. Implement cgroup subtree iteration for scx_task_iter:
- Add optional @cgrp to scx_task_iter_start() which enables cgroup subtree iteration.
- Make scx_task_iter use css_next_descendant_pre() and css_task_iter to iterate all tasks in the cgroup subtree.
- Update all existing callers to pass NULL to maintain current behavior.
The two iteration mechanisms are independent and duplicate. It's likely that scx_tasks can be removed in favor of always using cgroup iteration if CONFIG_SCHED_CLASS_EXT depends on CONFIG_CGROUPS.
--- kernel/sched/ext.c | 64 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 6 deletions(-)
kernel/sched/ext.c +58 -6
@@ -514,14 +514,31 @@ struct scx_task_iter {struct rq_flags rf;u32 cnt;bool list_locked; +#ifdef CONFIG_CGROUPS +struct cgroup *cgrp; +struct cgroup_subsys_state *css_pos; +struct css_task_iter css_iter; +#endif };/** * scx_task_iter_start - Lock scx_tasks_lock and start a task iteration * @iter: iterator to init +* @cgrp: Optional root of cgroup subhierarchy to iterate +* +* Initialize @iter. Once initialized, @iter must eventually be stopped with +* scx_task_iter_stop(). +* +* If @cgrp is %NULL, scx_tasks is used for iteration and this function returns +* with scx_tasks_lock held and @iter->cursor inserted into scx_tasks. +* +* If @cgrp is not %NULL, @cgrp and its descendants' tasks are walked using +* @iter->css_iter. The caller must be holding cgroup_lock() to prevent cgroup +* task migrations. * -* Initialize @iter and return with scx_tasks_lock held. Once initialized, @iter -* must eventually be stopped with scx_task_iter_stop(). +* The two modes of iterations are largely independent and it's likely that +* scx_tasks can be removed in favor of always using cgroup iteration if +* CONFIG_SCHED_CLASS_EXT depends on CONFIG_CGROUPS. * * scx_tasks_lock and the rq lock may be released using scx_task_iter_unlock() * between this and the first next() call or between any two next() calls. If
@@ -532,10 +549,19 @@ struct scx_task_iter {* All tasks which existed when the iteration started are guaranteed to be * visited as long as they are not dead. */ -static void scx_task_iter_start (struct scx_task_iter *iter) +static void scx_task_iter_start (struct scx_task_iter *iter,struct cgroup *cgrp) {memset (iter,0 ,sizeof (*iter)); +#ifdef CONFIG_CGROUPS +if (cgrp) { +lockdep_assert_held (&cgroup_mutex); + iter->cgrp = cgrp; + iter->css_pos =css_next_descendant_pre (NULL , &iter->cgrp->self); +css_task_iter_start (iter->css_pos,0 , &iter->css_iter); +return ; + } +#endif raw_spin_lock_irq (&scx_tasks_lock); iter->cursor = (struct sched_ext_entity ){ .flags =SCX_TASK_CURSOR };
@@ -588,6 +614,14 @@ static void __scx_task_iter_maybe_relock(struct scx_task_iter *iter)*/ static void scx_task_iter_stop (struct scx_task_iter *iter) { +#ifdef CONFIG_CGROUPS +if (iter->cgrp) { +if (iter->css_pos) +css_task_iter_end (&iter->css_iter); +__scx_task_iter_rq_unlock (iter); +return ; + } +#endif __scx_task_iter_maybe_relock (iter);list_del_init (&iter->cursor.tasks_node);scx_task_iter_unlock (iter);
@@ -611,6 +645,24 @@ static struct task_struct *scx_task_iter_next(struct scx_task_iter *iter)cond_resched (); } +#ifdef CONFIG_CGROUPS +if (iter->cgrp) { +while (iter->css_pos) { +struct task_struct *p; + + p =css_task_iter_next (&iter->css_iter); +if (p) +return p; + +css_task_iter_end (&iter->css_iter); + iter->css_pos =css_next_descendant_pre (iter->css_pos, + &iter->cgrp->self); +if (iter->css_pos) +css_task_iter_start (iter->css_pos,0 , &iter->css_iter); + } +return NULL ; + } +#endif __scx_task_iter_maybe_relock (iter);list_for_each_entry (pos, cursor, tasks_node) {
@@ -4433,7 +4485,7 @@ static void scx_disable_workfn(struct kthread_work *work) scx_init_task_enabled = false; -scx_task_iter_start (&sti); +scx_task_iter_start (&sti,NULL );while ((p =scx_task_iter_next_locked (&sti))) {unsigned int queue_flags =DEQUEUE_SAVE |DEQUEUE_MOVE |DEQUEUE_NOCLOCK ;const struct sched_class *old_class = p->sched_class;
@@ -5213,7 +5265,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)if (ret) goto err_disable_unlock_all; -scx_task_iter_start (&sti); +scx_task_iter_start (&sti,NULL );while ((p =scx_task_iter_next_locked (&sti))) {/* * @p may already be dead, have lost all its usages counts and
@@ -5255,7 +5307,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)* scx_tasks_lock. */ percpu_down_write (&scx_fork_rwsem); -scx_task_iter_start (&sti); +scx_task_iter_start (&sti,NULL );while ((p =scx_task_iter_next_locked (&sti))) {unsigned int queue_flags =DEQUEUE_SAVE |DEQUEUE_MOVE ;const struct sched_class *old_class = p->sched_class;
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 2/34 sched_ext: Add @kargs to scx_fork()
In reply to Tejun HeoMake sched_cgroup_fork() pass @kargs to scx_fork(). This will be used to determine @p's cgroup for cgroup sub-sched support.
--- kernel/sched/core.c | 2 +- kernel/sched/ext.c | 2 +- kernel/sched/ext.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-)
kernel/sched/core.c +1 -1
@@ -4721,7 +4721,7 @@ int sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) p->sched_class->task_fork (p);raw_spin_unlock_irqrestore (&p->pi_lock, flags); -return scx_fork (p); +return scx_fork (p, kargs); }void sched_cancel_fork (struct task_struct *p)
kernel/sched/ext.c +1 -1
@@ -3167,7 +3167,7 @@ void scx_pre_fork(struct task_struct *p)percpu_down_read (&scx_fork_rwsem); } -int scx_fork (struct task_struct *p) +int scx_fork (struct task_struct *p,struct kernel_clone_args *kargs) {percpu_rwsem_assert_held (&scx_fork_rwsem);
kernel/sched/ext.h +2 -2
@@ -11,7 +11,7 @@void scx_tick (struct rq *rq);void init_scx_entity (struct sched_ext_entity *scx);void scx_pre_fork (struct task_struct *p); -int scx_fork (struct task_struct *p); +int scx_fork (struct task_struct *p,struct kernel_clone_args *kargs);void scx_post_fork (struct task_struct *p);void scx_cancel_fork (struct task_struct *p);bool scx_can_stop_tick (struct rq *rq);
@@ -44,7 +44,7 @@ bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,static inline void scx_tick (struct rq *rq) {}static inline void scx_pre_fork (struct task_struct *p) {} -static inline int scx_fork (struct task_struct *p) {return 0 ; } +static inline int scx_fork (struct task_struct *p,struct kernel_clone_args *kargs) {return 0 ; }static inline void scx_post_fork (struct task_struct *p) {}static inline void scx_cancel_fork (struct task_struct *p) {}static inline u32 scx_cpuperf_target (s32 cpu) {return 0 ; }
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 3/34 sched/core: Swap the order between sched_post_fork() and cgroup_post_fork()
In reply to Tejun HeoThe planned sched_ext cgroup sub-scheduler support needs the newly forked task to be associated with its cgroup in its post_fork() hook. There is no existing ordering requirement between the two now. Swap them and note the new ordering requirement.
--- kernel/fork.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
kernel/fork.c +5 -1
@@ -2463,8 +2463,12 @@ __latent_entropy struct task_struct *copy_process(fd_install (pidfd, pidfile);proc_fork_connector (p); -sched_post_fork (p); +/* +* sched_ext needs @p to be associated with its cgroup in its post_fork +* hook. cgroup_post_fork() should come before sched_post_fork(). +*/ cgroup_post_fork (p, args); +sched_post_fork (p);perf_event_fork (p);trace_task_newtask (p, clone_flags);
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 4/34 cgroup: Expose some cgroup helpers
In reply to Tejun HeoExpose the following through cgroup.h:
- cgroup_on_dfl()
- cgroup_is_dead()
- cgroup_for_each_live_child()
- cgroup_for_each_live_descendant_pre()
- cgroup_for_each_live_descendant_post()
Until now, these didn't need to be exposed because controllers only cared about the css hierarchy. The planned sched_ext hierarchical scheduler support will be based on the default cgroup hierarchy, which is in line with the existing BPF cgroup support, and thus needs these exposed.
--- include/linux/cgroup.h | 65 ++++++++++++++++++++++++++++++++- kernel/cgroup/cgroup-internal.h | 6 --- kernel/cgroup/cgroup.c | 55 ---------------------------- 3 files changed, 63 insertions(+), 63 deletions(-)
include/linux/cgroup.h +63 -2
@@ -42,6 +42,14 @@ struct kernel_clone_args;#ifdef CONFIG_CGROUPS +/* +* To avoid confusing the compiler (and generating warnings) with code +* that attempts to access what would be a 0-element array (i.e. sized +* to a potentially empty array when CGROUP_SUBSYS_COUNT == 0), this +* constant expression can be added. +*/ +#define CGROUP_HAS_SUBSYS_CONFIG (CGROUP_SUBSYS_COUNT > 0) +enum css_task_iter_flags {CSS_TASK_ITER_PROCS = (1U <<0 ),/* walk only threadgroup leaders */ CSS_TASK_ITER_THREADED = (1U << 1), /* walk all threaded css_sets in the domain */
@@ -76,6 +84,7 @@ enum cgroup_lifetime_events {extern struct file_system_type cgroup_fs_type;extern struct cgroup_root cgrp_dfl_root;extern struct css_set init_css_set; +extern struct mutex cgroup_mutex;extern spinlock_t css_set_lock;extern struct blocking_notifier_head cgroup_lifetime_notifier;
@@ -103,6 +112,8 @@ extern struct blocking_notifier_head cgroup_lifetime_notifier;#define cgroup_subsys_on_dfl (ss) \ static_branch_likely(&ss ## _on_dfl_key) +bool cgroup_on_dfl (const struct cgroup *cgrp); +bool css_has_online_children (struct cgroup_subsys_state *css);struct cgroup_subsys_state *css_from_id (int id,struct cgroup_subsys *ss); struct cgroup_subsys_state *cgroup_e_css(struct cgroup *cgroup,
@@ -274,6 +285,32 @@ void css_task_iter_end(struct css_task_iter *it);for ((pos) =css_next_descendant_post (NULL , (css)); (pos); \ (pos) =css_next_descendant_post ((pos), (css))) +/* iterate over child cgrps, lock should be held throughout iteration */ +#define cgroup_for_each_live_child (child, cgrp) \ + list_for_each_entry((child), &(cgrp)->self.children, self.sibling) \ + if (({ lockdep_assert_held(&cgroup_mutex); \ + cgroup_is_dead(child); })) \ + ; \ + else + +/* walk live descendants in pre order */ +#define cgroup_for_each_live_descendant_pre (dsct, d_css, cgrp) \ + css_for_each_descendant_pre((d_css), cgroup_css((cgrp), NULL)) \ + if (({ lockdep_assert_held(&cgroup_mutex); \ + (dsct) = (d_css)->cgroup; \ + cgroup_is_dead(dsct); })) \ + ; \ + else + +/* walk live descendants in postorder */ +#define cgroup_for_each_live_descendant_post (dsct, d_css, cgrp) \ + css_for_each_descendant_post((d_css), cgroup_css((cgrp), NULL)) \ + if (({ lockdep_assert_held(&cgroup_mutex); \ + (dsct) = (d_css)->cgroup; \ + cgroup_is_dead(dsct); })) \ + ; \ + else +/** * cgroup_taskset_for_each - iterate cgroup_taskset * @task: the loop cursor
@@ -336,6 +373,27 @@ static inline u64 cgroup_id(const struct cgroup *cgrp)return cgrp->kn->id; } +/** +* cgroup_css - obtain a cgroup's css for the specified subsystem +* @cgrp: the cgroup of interest +* @ss: the subsystem of interest (%NULL returns @cgrp->self) +* +* Return @cgrp's css (cgroup_subsys_state) associated with @ss. This +* function must be called either under cgroup_mutex or rcu_read_lock() and +* the caller is responsible for pinning the returned css if it wants to +* keep accessing it outside the said locks. This function may return +* %NULL if @cgrp doesn't have @subsys_id enabled. +*/ +static inline struct cgroup_subsys_state *cgroup_css (struct cgroup *cgrp, +struct cgroup_subsys *ss) +{ +if (CGROUP_HAS_SUBSYS_CONFIG && ss) +return rcu_dereference_check (cgrp->subsys[ss->id], +lockdep_is_held (&cgroup_mutex)); +else +return &cgrp->self; +} +/** * css_is_dying - test whether the specified css is dying * @css: target css
@@ -372,6 +430,11 @@ static inline bool css_is_self(struct cgroup_subsys_state *css)return false; } +static inline bool cgroup_is_dead (const struct cgroup *cgrp) +{ +return !(cgrp->self.flags &CSS_ONLINE ); +} +static inline void cgroup_get (struct cgroup *cgrp) {css_get (&cgrp->self);
@@ -387,8 +450,6 @@ static inline void cgroup_put(struct cgroup *cgrp)css_put (&cgrp->self); } -extern struct mutex cgroup_mutex; -static inline void cgroup_lock (void ) {mutex_lock (&cgroup_mutex);
kernel/cgroup/cgroup-internal.h +0 -6
@@ -184,11 +184,6 @@ extern bool cgrp_dfl_visible;for ((ssid) =0 ; (ssid) <CGROUP_SUBSYS_COUNT && \ (((ss) = cgroup_subsys[ssid]) || true); (ssid)++) -static inline bool cgroup_is_dead(const struct cgroup *cgrp) -{ -return !(cgrp->self.flags &CSS_ONLINE ); -} - static inline bool notify_on_release(const struct cgroup *cgrp) {return test_bit (CGRP_NOTIFY_ON_RELEASE , &cgrp->flags);
@@ -222,7 +217,6 @@ static inline void get_css_set(struct css_set *cset) }bool cgroup_ssid_enabled (int ssid); -bool cgroup_on_dfl (const struct cgroup *cgrp);struct cgroup_root *cgroup_root_from_kf (struct kernfs_root *kf_root); struct cgroup *task_cgroup_from_root(struct task_struct *task,
kernel/cgroup/cgroup.c +0 -55
73 lines not on this page · raw message
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 5/34 sched_ext: Update p->scx.disallow warning in scx_init_task()
In reply to Tejun Heo- Always trigger the warning if p->scx.disallow is set for fork inits. There is no reason to set it during forks.
- Flip the positions of if/else arms to ease adding error conditions.
--- kernel/sched/ext.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
kernel/sched/ext.c +4 -4
@@ -3028,7 +3028,10 @@ static int scx_init_task(struct task_struct *p, struct task_group *tg, bool forkscx_set_task_state (p,SCX_TASK_INIT );if (p->scx.disallow) { -if (!fork) { +if (unlikely (fork)) { +scx_error (sch,"ops.init_task() set task->scx.disallow for %s[%d] during fork" , + p->comm, p->pid); + }else {struct rq *rq;struct rq_flags rf;
@@ -3047,9 +3050,6 @@ static int scx_init_task(struct task_struct *p, struct task_group *tg, bool fork }task_rq_unlock (rq, p, &rf); - }else if (p->policy ==SCHED_EXT ) { -scx_error (sch,"ops.init_task() set task->scx.disallow for %s[%d] during fork" , - p->comm, p->pid); } }
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 6/34 sched_ext: Reorganize enable/disable path for multi-scheduler support
In reply to Tejun HeoIn preparation for multiple scheduler support, reorganize the enable and disable paths to make scheduler instances explicit. Extract scx_root_disable() from scx_disable_workfn(). Rename scx_enable() to scx_root_enable(). Change scx_disable() to take @sch parameter and only queue disable_work if scx_claim_exit() succeeds for consistency. Move exit_kind validation into scx_claim_exit(). The sysrq handler now prints a message when no scheduler is loaded.
These changes don't materially affect user-visible behavior.
--- kernel/sched/ext.c | 75 ++++++++++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 33 deletions(-)
kernel/sched/ext.c +42 -33
@@ -3263,8 +3263,8 @@ void sched_ext_dead(struct task_struct *p)raw_spin_unlock_irqrestore (&scx_tasks_lock, flags);/* -* @p is off scx_tasks and wholly ours. scx_enable()'s READY -> ENABLED -* transitions can't race us. Disable ops for @p. +* @p is off scx_tasks and wholly ours. scx_root_enable()'s READY -> +* ENABLED transitions can't race us. Disable ops for @p. */ if (scx_get_task_state (p) !=SCX_TASK_NONE ) {struct rq_flags rf;
@@ -4423,24 +4423,12 @@ static void free_kick_syncs(void) } } -static void scx_disable_workfn (struct kthread_work *work) +static void scx_root_disable (struct scx_sched *sch) { - struct scx_sched *sch = container_of(work, struct scx_sched, disable_work);struct scx_exit_info *ei = sch->exit_info;struct scx_task_iter sti;struct task_struct *p; -int kind, cpu; - - kind =atomic_read (&sch->exit_kind); -while (true) { -if (kind ==SCX_EXIT_DONE )/* already disabled? */ -return ; -WARN_ON_ONCE (kind ==SCX_EXIT_NONE ); -if (atomic_try_cmpxchg (&sch->exit_kind, &kind,SCX_EXIT_DONE )) -break ; - } - ei->kind = kind; - ei->reason =scx_exit_reason (ei->kind); +int cpu;/* guarantee forward progress by bypassing scx_ops */ scx_bypass (true);
@@ -4584,6 +4572,9 @@ static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind)lockdep_assert_preemption_disabled (); +if (WARN_ON_ONCE (kind ==SCX_EXIT_NONE || kind ==SCX_EXIT_DONE )) + kind =SCX_EXIT_ERROR ; +if (!atomic_try_cmpxchg (&sch->exit_kind, &none, kind))return false;
@@ -4596,21 +4587,31 @@ static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind)return true; } -static void scx_disable (enum scx_exit_kind kind) +static void scx_disable_workfn (struct kthread_work *work) { -struct scx_sched *sch; + struct scx_sched *sch = container_of(work, struct scx_sched, disable_work); +struct scx_exit_info *ei = sch->exit_info; +int kind; -if (WARN_ON_ONCE (kind ==SCX_EXIT_NONE || kind ==SCX_EXIT_DONE )) - kind =SCX_EXIT_ERROR ; + kind =atomic_read (&sch->exit_kind); +while (true) { +if (kind ==SCX_EXIT_DONE )/* already disabled? */ +return ; +WARN_ON_ONCE (kind ==SCX_EXIT_NONE ); +if (atomic_try_cmpxchg (&sch->exit_kind, &kind,SCX_EXIT_DONE )) +break ; + } + ei->kind = kind; + ei->reason =scx_exit_reason (ei->kind); -rcu_read_lock (); - sch =rcu_dereference (scx_root); -if (sch) { -guard (preempt)(); -scx_claim_exit (sch, kind); +scx_root_disable (sch); +} + +static void scx_disable (struct scx_sched *sch,enum scx_exit_kind kind) +{ +guard (preempt)(); +if (scx_claim_exit (sch, kind))kthread_queue_work (sch->helper, &sch->disable_work); - } -rcu_read_unlock (); }static void dump_newline (struct seq_buf *s)
@@ -5115,13 +5116,13 @@ static int validate_ops(struct scx_sched *sch, const struct sched_ext_ops *ops)return 0 ; } -static int scx_enable (struct sched_ext_ops *ops,struct bpf_link *link) +static s32 scx_root_enable (struct sched_ext_ops *ops,struct bpf_link *link) {struct scx_sched *sch;struct scx_task_iter sti;struct task_struct *p;unsigned long timeout; -int i, cpu, ret; +s32 i, cpu, ret;if (!cpumask_equal (housekeeping_cpumask (HK_TYPE_DOMAIN ), cpu_possible_mask)) {
@@ -5368,7 +5369,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)* Flush scx_disable_work to ensure that error is reported before init * completion. sch's base reference will be put by bpf_scx_unreg(). */ -scx_error (sch,"scx_enable() failed (%d)" , ret); +scx_error (sch,"scx_root_enable() failed (%d)" , ret);kthread_flush_work (&sch->disable_work);return 0 ; }
@@ -5500,7 +5501,7 @@ static int bpf_scx_check_member(const struct btf_type *t,static int bpf_scx_reg (void *kdata,struct bpf_link *link) { -return scx_enable (kdata, link); +return scx_root_enable (kdata, link); }static void bpf_scx_unreg (void *kdata,struct bpf_link *link)
@@ -5508,7 +5509,7 @@ static void bpf_scx_unreg(void *kdata, struct bpf_link *link)struct sched_ext_ops *ops = kdata;struct scx_sched *sch = ops->priv; -scx_disable (SCX_EXIT_UNREG ); +scx_disable (sch,SCX_EXIT_UNREG );kthread_flush_work (&sch->disable_work);kobject_put (&sch->kobj); }
@@ -5636,7 +5637,15 @@ static struct bpf_struct_ops bpf_sched_ext_ops = {static void sysrq_handle_sched_ext_reset (u8 key) { -scx_disable (SCX_EXIT_SYSRQ ); +struct scx_sched *sch; + +rcu_read_lock (); + sch =rcu_dereference (scx_root); +if (likely (sch)) +scx_disable (sch,SCX_EXIT_SYSRQ ); +else +pr_info ("sched_ext: BPF schedulers not loaded\n" ); +rcu_read_unlock (); }static const struct sysrq_key_op sysrq_sched_ext_reset_op = {
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 7/34 sched_ext: Introduce cgroup sub-sched support
In reply to Tejun HeoA system often runs multiple workloads especially in multi-tenant server environments where a system is split into partitions servicing separate more-or-less independent workloads each requiring an application-specific scheduler. To support such and other use cases, sched_ext is in the process of growing multiple scheduler support.
When partitioning a system in terms of CPUs for such use cases, an oft-taken approach is hard partitioning the system using cpuset. While it would be possible to tie sched_ext multiple scheduler support to cpuset partitions, such an approach would have fundamental limitations stemming from the lack of dynamism and flexibility.
Users often don't care which specific CPUs are assigned to which workload and want to take advantage of optimizations which are enabled by running workloads on a larger machine - e.g. opportunistic over-commit, improving latency critical workload characteristics while maintaining bandwidth fairness, employing control mechanisms based on different criteria than on-CPU time for e.g. flexible memory bandwidth isolation, packing similar parts from different workloads on same L3s to improve cache efficiency, and so on.
As this sort of dynamic behaviors are impossible or difficult to implement with hard partitioning, sched_ext is implementing cgroup sub-sched support where schedulers can be attached to the cgroup hierarchy and a parent scheduler is responsible for controlling the CPUs that each child can use at any given moment. This makes CPU distribution dynamically controlled by BPF allowing high flexibility.
This patch adds the skeletal sched_ext cgroup sub-sched support:
- sched_ext_ops.sub_cgroup_id and .sub_attach/detach() are added. Non-zero sub_cgroup_id indicates that the scheduler is to be attached to the identified cgroup. A sub-sched is attached to the cgroup iff the nearest ancestor scheduler implements .sub_attach() and grants the attachment. Max nesting depth is limited by SCX_SUB_MAX_DEPTH.
- When a scheduler exits, all its descendant schedulers are exited together. Also, cgroup.scx_sched added which points to the effective scheduler instance for the cgroup. This is updated on scheduler init/exit and inherited on cgroup online. When a cgroup is offlined, the attached scheduler is automatically exited.
- Sub-sched support is gated on CONFIG_EXT_SUB_SCHED which is automatically enabled if both SCX and cgroups are enabled. Sub-sched support is not tied to the CPU controller but rather the cgroup hierarchy itself. This is intentional as the support for cpu.weight and cpu.max based resource control is orthogonal to sub-sched support. Note that CONFIG_CGROUPS around cgroup subtree iteration support for scx_task_iter is replaced with CONFIG_EXT_SUB_SCHED for consistency.
- This allows loading sub-scheds and most framework operations such as propagating disable down the hierarchy work. However, sub-scheds are not operational yet and all tasks stay with the root sched. This will serve as the basis for building up full sub-sched support.
- DSQs point to the scx_sched they belong to.
- scx_qmap is updated to allow attachment of sub-scheds and also serving as sub-scheds.
--- include/linux/cgroup-defs.h | 4 + include/linux/sched/ext.h | 3 + init/Kconfig | 4 + kernel/sched/ext.c | 513 +++++++++++++++++++++++++++++++-- kernel/sched/ext_internal.h | 67 ++++- tools/sched_ext/scx_qmap.bpf.c | 9 +- tools/sched_ext/scx_qmap.c | 13 +- 7 files changed, 579 insertions(+), 34 deletions(-)
include/linux/cgroup-defs.h +4 -0
@@ -17,6 +17,7 @@#include <linux/refcount.h> #include <linux/percpu-refcount.h> #include <linux/percpu-rwsem.h> +#include <linux/sched.h> #include <linux/u64_stats_sync.h> #include <linux/workqueue.h> #include <linux/bpf-cgroup-defs.h>
@@ -624,6 +625,9 @@ struct cgroup {#ifdef CONFIG_BPF_SYSCALL struct bpf_local_storage __rcu *bpf_cgrp_storage;#endif +#ifdef CONFIG_EXT_SUB_SCHED +struct scx_sched __rcu *scx_sched; +#endif /* All ancestors including self */ union {
include/linux/sched/ext.h +3 -0
@@ -78,6 +78,7 @@ struct scx_dispatch_q {u64 id;struct rhash_head hash_node;struct llist_node free_node; +struct scx_sched *sched;struct rcu_head rcu; };
@@ -157,6 +158,8 @@ struct scx_dsq_list_node { .priv = (__priv), \ } +struct scx_sched ; +/* * The following is embedded in task_struct and contains all fields necessary * for a task to be scheduled by SCX.
init/Kconfig +4 -0
@@ -1176,6 +1176,10 @@ config EXT_GROUP_SCHED endif #CGROUP_SCHED +config EXT_SUB_SCHED + def_bool y + depends on SCHED_CLASS_EXT + config SCHED_MM_CID def_bool y depends on SMP && RSEQ
kernel/sched/ext.c +487 -26
760 lines not on this page · raw message
kernel/sched/ext_internal.h +61 -6
149 lines not on this page · raw message
tools/sched_ext/scx_qmap.bpf.c +8 -1
@@ -41,6 +41,7 @@ const volatile u32 dsp_batch;const volatile bool highpri_boosting;const volatile bool print_dsqs_and_events;const volatile bool print_msgs; +const volatile u64 sub_cgroup_id;const volatile s32 disallow_tgid;const volatile bool suppress_dump;
@@ -862,7 +863,7 @@ s32 BPF_STRUCT_OPS_SLEEPABLE(qmap_init)struct bpf_timer *timer;s32 ret; -if (print_msgs) +if (print_msgs && !sub_cgroup_id)print_cpus (); ret =scx_bpf_create_dsq (SHARED_DSQ ,-1 );
@@ -892,6 +893,11 @@ void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)UEI_RECORD (uei, ei); } +s32 BPF_STRUCT_OPS (qmap_sub_attach ,struct scx_sub_attach_args *args) +{ +return 0 ; +} + SCX_OPS_DEFINE(qmap_ops, .select_cpu = (void *)qmap_select_cpu, .enqueue = (void *)qmap_enqueue,
@@ -907,6 +913,7 @@ SCX_OPS_DEFINE(qmap_ops, .cgroup_init = (void *)qmap_cgroup_init, .cgroup_set_weight = (void *)qmap_cgroup_set_weight, .cgroup_set_bandwidth = (void *)qmap_cgroup_set_bandwidth, + .sub_attach = (void *)qmap_sub_attach, .cpu_online = (void *)qmap_cpu_online, .cpu_offline = (void *)qmap_cpu_offline, .init = (void *)qmap_init,
tools/sched_ext/scx_qmap.c +12 -1
@@ -10,6 +10,7 @@#include <inttypes.h> #include <signal.h> #include <libgen.h> +#include <sys/stat.h> #include <bpf/bpf.h> #include <scx/common.h> #include "scx_qmap.bpf.skel.h"
@@ -67,7 +68,7 @@ int main(int argc, char **argv) skel->rodata->slice_ns =__COMPAT_ENUM_OR_ZERO ("scx_public_consts" ,"SCX_SLICE_DFL" ); -while ((opt =getopt (argc, argv,"s:e:t:T:l:b:PMHd:D:Spvh" )) !=-1 ) { +while ((opt =getopt (argc, argv,"s:e:t:T:l:b:PMHc:d:D:Spvh" )) !=-1 ) {switch (opt) {case 's' : skel->rodata->slice_ns =strtoull (optarg,NULL ,0 ) *1000 ;
@@ -96,6 +97,16 @@ int main(int argc, char **argv)case 'H' : skel->rodata->highpri_boosting = true;break ; +case 'c' : { +struct stat st; +if (stat (optarg, &st) <0 ) { +perror ("stat" ); +return 1 ; + } + skel->struct_ops.qmap_ops->sub_cgroup_id = st.st_ino; + skel->rodata->sub_cgroup_id = st.st_ino; +break ; + }case 'd' : skel->rodata->disallow_tgid =strtol (optarg,NULL ,0 );if (skel->rodata->disallow_tgid <0 )
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 8/34 sched_ext: Introduce scx_task_sched[_rcu]()
In reply to Tejun HeoIn preparation of multiple scheduler support, add p->scx.sched which points to the scx_sched instance that the task is scheduled by, which is currently always scx_root. Add scx_task_sched[_rcu]() accessors which return the associated scx_sched of the specified task and replace the raw scx_root dereferences with it where applicable. scx_task_on_sched() is also added to test whether a given task is on the specified sched.
As scx_root is still the only scheduler, this shouldn't introduce user-visible behavior changes.
--- include/linux/sched/ext.h | 7 +++++ kernel/sched/ext.c | 63 +++++++++++++++++++++++-------------- kernel/sched/ext_internal.h | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 24 deletions(-)
include/linux/sched/ext.h +7 -0
@@ -165,6 +165,13 @@ struct scx_sched;* for a task to be scheduled by SCX. */ struct sched_ext_entity { +#ifdef CONFIG_CGROUPS +/* +* Associated scx_sched. Updated either during fork or while holding +* both p->pi_lock and rq lock. +*/ +struct scx_sched __rcu *sched; +#endif struct scx_dispatch_q *dsq;atomic_long_t ops_state;u64 ddsp_dsq_id;
kernel/sched/ext.c +39 -24
213 lines not on this page · raw message
kernel/sched/ext_internal.h +59 -0
68 lines not on this page · raw message
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 9/34 sched_ext: Introduce scx_prog_sched()
In reply to Tejun HeoIn preparation for multiple scheduler support, introduce scx_prog_sched() accessor which returns the scx_sched instance associated with a BPF program. The association is determined via the special KF_IMPLICIT_ARGS kfunc parameter, which provides access to bpf_prog_aux. This aux can be used to retrieve the struct_ops (sched_ext_ops) that the program is associated with, and from there, the corresponding scx_sched instance.
For compatibility, when ops.sub_attach is not implemented (older schedulers without sub-scheduler support), unassociated programs fall back to scx_root. A warning is logged once per scheduler for such programs.
As scx_root is still the only scheduler, this shouldn't introduce user-visible behavior changes.
--- kernel/sched/ext.c | 157 ++++++++++++++++----------- kernel/sched/ext_idle.c | 90 +++++++++------ kernel/sched/ext_internal.h | 44 +++++++- tools/sched_ext/include/scx/compat.h | 10 ++ 4 files changed, 199 insertions(+), 102 deletions(-)
kernel/sched/ext.c +92 -65
@@ -5271,7 +5271,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops,init_irq_work (&sch->error_irq_work, scx_error_irq_workfn);kthread_init_work (&sch->disable_work, scx_disable_workfn); sch->ops = *ops; - ops->priv = sch; +rcu_assign_pointer (ops->priv, sch); sch->kobj.kset = scx_kset;
@@ -5977,10 +5977,11 @@ static int bpf_scx_reg(void *kdata, struct bpf_link *link)static void bpf_scx_unreg (void *kdata,struct bpf_link *link) {struct sched_ext_ops *ops = kdata; -struct scx_sched *sch = ops->priv; +struct scx_sched *sch =rcu_dereference_protected (ops->priv, true);scx_disable (sch,SCX_EXIT_UNREG );kthread_flush_work (&sch->disable_work); +RCU_INIT_POINTER (ops->priv,NULL );kobject_put (&sch->kobj); }
@@ -6444,6 +6445,7 @@ __bpf_kfunc_start_defs();* @dsq_id: DSQ to insert into * @slice: duration @p can run for in nsecs, 0 to keep the current value * @enq_flags: SCX_ENQ_* +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Insert @p into the FIFO queue of the DSQ identified by @dsq_id. It is safe to * call this function spuriously. Can be called from ops.enqueue(),
@@ -6478,12 +6480,13 @@ __bpf_kfunc_start_defs();* to check the return value. */ __bpf_kfunc bool scx_bpf_dsq_insert___v2(struct task_struct *p, u64 dsq_id, -u64 slice,u64 enq_flags) +u64 slice,u64 enq_flags, +const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return false;
@@ -6504,9 +6507,10 @@ __bpf_kfunc bool scx_bpf_dsq_insert___v2(struct task_struct *p, u64 dsq_id,* COMPAT: Will be removed in v6.23 along with the ___v2 suffix. */ __bpf_kfunc void scx_bpf_dsq_insert(struct task_struct *p, u64 dsq_id, -u64 slice,u64 enq_flags) +u64 slice,u64 enq_flags, +const struct bpf_prog_aux *aux) { -scx_bpf_dsq_insert___v2 (p, dsq_id, slice, enq_flags); +scx_bpf_dsq_insert___v2 (p, dsq_id, slice, enq_flags, aux); } static bool scx_dsq_insert_vtime(struct scx_sched *sch, struct task_struct *p,
@@ -6543,6 +6547,7 @@ struct scx_bpf_dsq_insert_vtime_args {* @args->slice: duration @p can run for in nsecs, 0 to keep the current value * @args->vtime: @p's ordering inside the vtime-sorted queue of the target DSQ * @args->enq_flags: SCX_ENQ_* +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Wrapper kfunc that takes arguments via struct to work around BPF's 5 argument * limit. BPF programs should use scx_bpf_dsq_insert_vtime() which is provided
@@ -6567,13 +6572,14 @@ struct scx_bpf_dsq_insert_vtime_args {*/ __bpf_kfunc bool__scx_bpf_dsq_insert_vtime (struct task_struct *p, -struct scx_bpf_dsq_insert_vtime_args *args) +struct scx_bpf_dsq_insert_vtime_args *args, +const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return false;
@@ -6601,9 +6607,9 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id,__bpf_kfunc_end_defs (); BTF_KFUNCS_START(scx_kfunc_ids_enqueue_dispatch) -BTF_ID_FLAGS(func, scx_bpf_dsq_insert, KF_RCU) -BTF_ID_FLAGS(func, scx_bpf_dsq_insert___v2, KF_RCU) -BTF_ID_FLAGS(func, __scx_bpf_dsq_insert_vtime, KF_RCU) +BTF_ID_FLAGS(func, scx_bpf_dsq_insert, KF_IMPLICIT_ARGS | KF_RCU) +BTF_ID_FLAGS(func, scx_bpf_dsq_insert___v2, KF_IMPLICIT_ARGS | KF_RCU) +BTF_ID_FLAGS(func, __scx_bpf_dsq_insert_vtime, KF_IMPLICIT_ARGS | KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_insert_vtime, KF_RCU) BTF_KFUNCS_END(scx_kfunc_ids_enqueue_dispatch)
@@ -6703,16 +6709,17 @@ __bpf_kfunc_start_defs();/** * scx_bpf_dispatch_nr_slots - Return the number of remaining dispatch slots +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Can only be called from ops.dispatch(). */ -__bpf_kfunc u32 scx_bpf_dispatch_nr_slots(void) +__bpf_kfunc u32 scx_bpf_dispatch_nr_slots(const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return 0 ;
@@ -6724,18 +6731,19 @@ __bpf_kfunc u32 scx_bpf_dispatch_nr_slots(void)/** * scx_bpf_dispatch_cancel - Cancel the latest dispatch +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Cancel the latest dispatch. Can be called multiple times to cancel further * dispatches. Can only be called from ops.dispatch(). */ -__bpf_kfunc void scx_bpf_dispatch_cancel(void) +__bpf_kfunc void scx_bpf_dispatch_cancel(const struct bpf_prog_aux *aux) {struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx);struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return ;
@@ -6751,6 +6759,7 @@ __bpf_kfunc void scx_bpf_dispatch_cancel(void)/** * scx_bpf_dsq_move_to_local - move a task from a DSQ to the current CPU's local DSQ * @dsq_id: DSQ to move task from +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Move a task from the non-local DSQ identified by @dsq_id to the current CPU's * local DSQ for execution. Can only be called from ops.dispatch().
@@ -6762,7 +6771,7 @@ __bpf_kfunc void scx_bpf_dispatch_cancel(void)* Returns %true if a task has been moved, %false if there isn't any task to * move. */ -__bpf_kfunc bool scx_bpf_dsq_move_to_local(u64 dsq_id) +__bpf_kfunc bool scx_bpf_dsq_move_to_local(u64 dsq_id, const struct bpf_prog_aux *aux) {struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx);struct scx_dispatch_q *dsq;
@@ -6770,7 +6779,7 @@ __bpf_kfunc bool scx_bpf_dsq_move_to_local(u64 dsq_id)guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return false;
@@ -6897,9 +6906,9 @@ __bpf_kfunc bool scx_bpf_dsq_move_vtime(struct bpf_iter_scx_dsq *it__iter,__bpf_kfunc_end_defs (); BTF_KFUNCS_START(scx_kfunc_ids_dispatch) -BTF_ID_FLAGS(func, scx_bpf_dispatch_nr_slots) -BTF_ID_FLAGS(func, scx_bpf_dispatch_cancel) -BTF_ID_FLAGS(func, scx_bpf_dsq_move_to_local) +BTF_ID_FLAGS(func, scx_bpf_dispatch_nr_slots, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_dispatch_cancel, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_dsq_move_to_local, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_slice, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_vtime, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move, KF_RCU)
@@ -6957,6 +6966,7 @@ __bpf_kfunc_start_defs();/** * scx_bpf_reenqueue_local - Re-enqueue tasks on a local DSQ +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Iterate over all of the tasks currently enqueued on the local DSQ of the * caller's CPU, and re-enqueue them in the BPF scheduler. Returns the number of
@@ -6965,13 +6975,13 @@ __bpf_kfunc_start_defs();* COMPAT: Will be removed in v6.23 along with the ___v2 suffix on the void * returning variant that can be called from anywhere. */ -__bpf_kfunc u32 scx_bpf_reenqueue_local(void) +__bpf_kfunc u32 scx_bpf_reenqueue_local(const struct bpf_prog_aux *aux) {struct scx_sched *sch;struct rq *rq;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return 0 ;
@@ -6987,7 +6997,7 @@ __bpf_kfunc u32 scx_bpf_reenqueue_local(void)__bpf_kfunc_end_defs ();BTF_KFUNCS_START (scx_kfunc_ids_cpu_release) -BTF_ID_FLAGS (func ,scx_bpf_reenqueue_local ) +BTF_ID_FLAGS (func ,scx_bpf_reenqueue_local ,KF_IMPLICIT_ARGS )BTF_KFUNCS_END (scx_kfunc_ids_cpu_release) static const struct btf_kfunc_id_set scx_kfunc_set_cpu_release = {
@@ -7001,11 +7011,12 @@ __bpf_kfunc_start_defs();* scx_bpf_create_dsq - Create a custom DSQ * @dsq_id: DSQ to create * @node: NUMA node to allocate from +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Create a custom DSQ identified by @dsq_id. Can be called from any sleepable * scx callback, and any BPF_PROG_TYPE_SYSCALL prog. */ -__bpf_kfunc s32 scx_bpf_create_dsq(u64 dsq_id, s32 node) +__bpf_kfunc s32 scx_bpf_create_dsq(u64 dsq_id, s32 node, const struct bpf_prog_aux *aux) {struct scx_dispatch_q *dsq;struct scx_sched *sch;
@@ -7024,7 +7035,7 @@ __bpf_kfunc s32 scx_bpf_create_dsq(u64 dsq_id, s32 node)rcu_read_lock (); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (sch) {init_dsq (dsq, dsq_id, sch); ret = rhashtable_lookup_insert_fast(&sch->dsq_hash, &dsq->hash_node,
@@ -7042,7 +7053,7 @@ __bpf_kfunc s32 scx_bpf_create_dsq(u64 dsq_id, s32 node)__bpf_kfunc_end_defs (); BTF_KFUNCS_START(scx_kfunc_ids_unlocked) -BTF_ID_FLAGS(func, scx_bpf_create_dsq, KF_SLEEPABLE) +BTF_ID_FLAGS(func, scx_bpf_create_dsq, KF_IMPLICIT_ARGS | KF_SLEEPABLE) BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_slice, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_vtime, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move, KF_RCU)
@@ -7141,18 +7152,19 @@ static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)* scx_bpf_kick_cpu - Trigger reschedule on a CPU * @cpu: cpu to kick * @flags: %SCX_KICK_* flags +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Kick @cpu into rescheduling. This can be used to wake up an idle CPU or * trigger rescheduling on a busy CPU. This can be called from any online * scx_ops operation and the actual kicking is performed asynchronously through * an irq work. */ -__bpf_kfunc void scx_bpf_kick_cpu(s32 cpu, u64 flags) +__bpf_kfunc void scx_bpf_kick_cpu(s32 cpu, u64 flags, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (likely (sch))scx_kick_cpu (sch, cpu, flags); }
@@ -7226,13 +7238,14 @@ __bpf_kfunc void scx_bpf_destroy_dsq(u64 dsq_id)* @it: iterator to initialize * @dsq_id: DSQ to iterate * @flags: %SCX_DSQ_ITER_* +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Initialize BPF iterator @it which can be used with bpf_for_each() to walk * tasks in the DSQ specified by @dsq_id. Iteration using @it only includes * tasks which are already queued when this function is invoked. */ __bpf_kfunc int bpf_iter_scx_dsq_new(struct bpf_iter_scx_dsq *it, u64 dsq_id, -u64 flags) +u64 flags,const struct bpf_prog_aux *aux) {struct bpf_iter_scx_dsq_kern *kit = (void *)it;struct scx_sched *sch;
@@ -7250,7 +7263,7 @@ __bpf_kfunc int bpf_iter_scx_dsq_new(struct bpf_iter_scx_dsq *it, u64 dsq_id,*/ kit->dsq =NULL ; - sch =rcu_dereference_check (scx_root,rcu_read_lock_bh_held ()); + sch =scx_prog_sched (aux);if (unlikely (!sch))return -ENODEV ;
@@ -7339,6 +7352,7 @@ __bpf_kfunc void bpf_iter_scx_dsq_destroy(struct bpf_iter_scx_dsq *it)/** * scx_bpf_dsq_peek - Lockless peek at the first element. * @dsq_id: DSQ to examine. +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Read the first element in the DSQ. This is semantically equivalent to using * the DSQ iterator, but is lockfree. Of course, like any lockless operation,
@@ -7347,12 +7361,13 @@ __bpf_kfunc void bpf_iter_scx_dsq_destroy(struct bpf_iter_scx_dsq *it)* * Returns the pointer, or NULL indicates an empty queue OR internal error. */ -__bpf_kfunc struct task_struct *scx_bpf_dsq_peek(u64 dsq_id) +__bpf_kfunc struct task_struct *scx_bpf_dsq_peek(u64 dsq_id, +const struct bpf_prog_aux *aux) {struct scx_sched *sch;struct scx_dispatch_q *dsq; - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return NULL ;
@@ -7424,18 +7439,20 @@ __bpf_kfunc_start_defs();* @fmt: error message format string * @data: format string parameters packaged using ___bpf_fill() macro * @data__sz: @data len, must end in '__sz' for the verifier +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Indicate that the BPF scheduler wants to exit gracefully, and initiate ops * disabling. */ __bpf_kfunc void scx_bpf_exit_bstr(s64 exit_code, char *fmt, -unsigned long long *data,u32 data__sz) +unsigned long long *data,u32 data__sz, +const struct bpf_prog_aux *aux) {struct scx_sched *sch;unsigned long flags;raw_spin_lock_irqsave (&scx_exit_bstr_buf_lock, flags); - sch =rcu_dereference_bh (scx_root); + sch =scx_prog_sched (aux);if (likely (sch) &&bstr_format (sch, &scx_exit_bstr_buf, fmt, data, data__sz) >=0 )scx_exit (sch,SCX_EXIT_UNREG_BPF , exit_code,"%s" , scx_exit_bstr_buf.line);
@@ -7447,18 +7464,19 @@ __bpf_kfunc void scx_bpf_exit_bstr(s64 exit_code, char *fmt,* @fmt: error message format string * @data: format string parameters packaged using ___bpf_fill() macro * @data__sz: @data len, must end in '__sz' for the verifier +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Indicate that the BPF scheduler encountered a fatal error and initiate ops * disabling. */ __bpf_kfunc void scx_bpf_error_bstr(char *fmt, unsigned long long *data, -u32 data__sz) +u32 data__sz,const struct bpf_prog_aux *aux) {struct scx_sched *sch;unsigned long flags;raw_spin_lock_irqsave (&scx_exit_bstr_buf_lock, flags); - sch =rcu_dereference_bh (scx_root); + sch =scx_prog_sched (aux);if (likely (sch) &&bstr_format (sch, &scx_exit_bstr_buf, fmt, data, data__sz) >=0 )scx_exit (sch,SCX_EXIT_ERROR_BPF ,0 ,"%s" , scx_exit_bstr_buf.line);
@@ -7470,6 +7488,7 @@ __bpf_kfunc void scx_bpf_error_bstr(char *fmt, unsigned long long *data,* @fmt: format string * @data: format string parameters packaged using ___bpf_fill() macro * @data__sz: @data len, must end in '__sz' for the verifier +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * To be called through scx_bpf_dump() helper from ops.dump(), dump_cpu() and * dump_task() to generate extra debug dump specific to the BPF scheduler.
@@ -7478,7 +7497,7 @@ __bpf_kfunc void scx_bpf_error_bstr(char *fmt, unsigned long long *data,* multiple calls. The last line is automatically terminated. */ __bpf_kfunc void scx_bpf_dump_bstr(char *fmt, unsigned long long *data, -u32 data__sz) +u32 data__sz,const struct bpf_prog_aux *aux) {struct scx_sched *sch;struct scx_dump_data *dd = &scx_dump_data;
@@ -7487,7 +7506,7 @@ __bpf_kfunc void scx_bpf_dump_bstr(char *fmt, unsigned long long *data,guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return ;
@@ -7544,18 +7563,19 @@ __bpf_kfunc void scx_bpf_reenqueue_local___v2(void)/** * scx_bpf_cpuperf_cap - Query the maximum relative capacity of a CPU * @cpu: CPU of interest +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Return the maximum relative capacity of @cpu in relation to the most * performant CPU in the system. The return value is in the range [1, * %SCX_CPUPERF_ONE]. See scx_bpf_cpuperf_cur(). */ -__bpf_kfunc u32 scx_bpf_cpuperf_cap(s32 cpu) +__bpf_kfunc u32 scx_bpf_cpuperf_cap(s32 cpu, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (likely (sch) &&ops_cpu_valid (sch, cpu,NULL ))return arch_scale_cpu_capacity (cpu);else
@@ -7565,6 +7585,7 @@ __bpf_kfunc u32 scx_bpf_cpuperf_cap(s32 cpu)/** * scx_bpf_cpuperf_cur - Query the current relative performance of a CPU * @cpu: CPU of interest +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Return the current relative performance of @cpu in relation to its maximum. * The return value is in the range [1, %SCX_CPUPERF_ONE].
@@ -7576,13 +7597,13 @@ __bpf_kfunc u32 scx_bpf_cpuperf_cap(s32 cpu)* * The result is in the range [1, %SCX_CPUPERF_ONE]. */ -__bpf_kfunc u32 scx_bpf_cpuperf_cur(s32 cpu) +__bpf_kfunc u32 scx_bpf_cpuperf_cur(s32 cpu, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (likely (sch) &&ops_cpu_valid (sch, cpu,NULL ))return arch_scale_freq_capacity (cpu);else
@@ -7593,6 +7614,7 @@ __bpf_kfunc u32 scx_bpf_cpuperf_cur(s32 cpu)* scx_bpf_cpuperf_set - Set the relative performance target of a CPU * @cpu: CPU of interest * @perf: target performance level [0, %SCX_CPUPERF_ONE] +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Set the target performance level of @cpu to @perf. @perf is in linear * relative scale between 0 and %SCX_CPUPERF_ONE. This determines how the
@@ -7603,13 +7625,13 @@ __bpf_kfunc u32 scx_bpf_cpuperf_cur(s32 cpu)* use. Consult hardware and cpufreq documentation for more information. The * current performance level can be monitored using scx_bpf_cpuperf_cur(). */ -__bpf_kfunc void scx_bpf_cpuperf_set(s32 cpu, u32 perf) +__bpf_kfunc void scx_bpf_cpuperf_set(s32 cpu, u32 perf, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return ;
@@ -7719,14 +7741,15 @@ __bpf_kfunc s32 scx_bpf_task_cpu(const struct task_struct *p)/** * scx_bpf_cpu_rq - Fetch the rq of a CPU * @cpu: CPU of the rq +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs */ -__bpf_kfunc struct rq *scx_bpf_cpu_rq(s32 cpu) +__bpf_kfunc struct rq *scx_bpf_cpu_rq(s32 cpu, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return NULL ;
@@ -7745,18 +7768,19 @@ __bpf_kfunc struct rq *scx_bpf_cpu_rq(s32 cpu)/** * scx_bpf_locked_rq - Return the rq currently locked by SCX +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Returns the rq if a rq lock is currently held by SCX. * Otherwise emits an error and returns NULL. */ -__bpf_kfunc struct rq *scx_bpf_locked_rq(void) +__bpf_kfunc struct rq *scx_bpf_locked_rq(const struct bpf_prog_aux *aux) {struct scx_sched *sch;struct rq *rq;guard (preempt)(); - sch =rcu_dereference_sched (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return NULL ;
@@ -7772,16 +7796,17 @@ __bpf_kfunc struct rq *scx_bpf_locked_rq(void)/** * scx_bpf_cpu_curr - Return remote CPU's curr task * @cpu: CPU of interest +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Callers must hold RCU read lock (KF_RCU). */ -__bpf_kfunc struct task_struct *scx_bpf_cpu_curr(s32 cpu) +__bpf_kfunc struct task_struct *scx_bpf_cpu_curr(s32 cpu, const struct bpf_prog_aux *aux) {struct scx_sched *sch;guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch))return NULL ;
@@ -7794,6 +7819,7 @@ __bpf_kfunc struct task_struct *scx_bpf_cpu_curr(s32 cpu)/** * scx_bpf_task_cgroup - Return the sched cgroup of a task * @p: task of interest +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * @p->sched_task_group->css.cgroup represents the cgroup @p is associated with * from the scheduler's POV. SCX operations should use this function to
@@ -7803,7 +7829,8 @@ __bpf_kfunc struct task_struct *scx_bpf_cpu_curr(s32 cpu)* operations. The restriction guarantees that @p's rq is locked by the caller. */ #ifdef CONFIG_CGROUP_SCHED -__bpf_kfunc struct cgroup *scx_bpf_task_cgroup(struct task_struct *p) +__bpf_kfunc struct cgroup *scx_bpf_task_cgroup(struct task_struct *p, +const struct bpf_prog_aux *aux) {struct task_group *tg = p->sched_task_group;struct cgroup *cgrp = &cgrp_dfl_root.cgrp;
@@ -7811,7 +7838,7 @@ __bpf_kfunc struct cgroup *scx_bpf_task_cgroup(struct task_struct *p)guard (rcu)(); - sch =rcu_dereference (scx_root); + sch =scx_prog_sched (aux);if (unlikely (!sch)) goto out;
@@ -7944,20 +7971,20 @@ __bpf_kfunc_end_defs();BTF_KFUNCS_START (scx_kfunc_ids_any)BTF_ID_FLAGS (func, scx_bpf_task_set_slice, KF_RCU);BTF_ID_FLAGS (func, scx_bpf_task_set_dsq_vtime,KF_RCU ); -BTF_ID_FLAGS(func, scx_bpf_kick_cpu) +BTF_ID_FLAGS(func, scx_bpf_kick_cpu, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_dsq_nr_queued) BTF_ID_FLAGS(func, scx_bpf_destroy_dsq) -BTF_ID_FLAGS(func, scx_bpf_dsq_peek, KF_RCU_PROTECTED | KF_RET_NULL) -BTF_ID_FLAGS(func, bpf_iter_scx_dsq_new, KF_ITER_NEW | KF_RCU_PROTECTED) +BTF_ID_FLAGS(func, scx_bpf_dsq_peek, KF_IMPLICIT_ARGS | KF_RCU_PROTECTED | KF_RET_NULL) +BTF_ID_FLAGS(func, bpf_iter_scx_dsq_new, KF_IMPLICIT_ARGS | KF_ITER_NEW | KF_RCU_PROTECTED) BTF_ID_FLAGS(func, bpf_iter_scx_dsq_next, KF_ITER_NEXT | KF_RET_NULL) BTF_ID_FLAGS(func, bpf_iter_scx_dsq_destroy, KF_ITER_DESTROY) -BTF_ID_FLAGS(func, scx_bpf_exit_bstr) -BTF_ID_FLAGS(func, scx_bpf_error_bstr) -BTF_ID_FLAGS(func, scx_bpf_dump_bstr) +BTF_ID_FLAGS(func, scx_bpf_exit_bstr, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_error_bstr, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_dump_bstr, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_reenqueue_local___v2) -BTF_ID_FLAGS(func, scx_bpf_cpuperf_cap) -BTF_ID_FLAGS(func, scx_bpf_cpuperf_cur) -BTF_ID_FLAGS(func, scx_bpf_cpuperf_set) +BTF_ID_FLAGS(func, scx_bpf_cpuperf_cap, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_cpuperf_cur, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_cpuperf_set, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_nr_node_ids) BTF_ID_FLAGS(func, scx_bpf_nr_cpu_ids)BTF_ID_FLAGS (func, scx_bpf_get_possible_cpumask,KF_ACQUIRE )
@@ -7965,11 +7992,11 @@ BTF_ID_FLAGS(func, scx_bpf_get_online_cpumask, KF_ACQUIRE) BTF_ID_FLAGS(func, scx_bpf_put_cpumask, KF_RELEASE) BTF_ID_FLAGS(func, scx_bpf_task_running, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_task_cpu, KF_RCU) -BTF_ID_FLAGS(func, scx_bpf_cpu_rq) -BTF_ID_FLAGS(func, scx_bpf_locked_rq, KF_RET_NULL) -BTF_ID_FLAGS(func, scx_bpf_cpu_curr, KF_RET_NULL | KF_RCU_PROTECTED) +BTF_ID_FLAGS(func, scx_bpf_cpu_rq, KF_IMPLICIT_ARGS) +BTF_ID_FLAGS(func, scx_bpf_locked_rq, KF_IMPLICIT_ARGS | KF_RET_NULL) +BTF_ID_FLAGS(func, scx_bpf_cpu_curr, KF_IMPLICIT_ARGS | KF_RET_NULL | KF_RCU_PROTECTED)#ifdef CONFIG_CGROUP_SCHED -BTF_ID_FLAGS(func, scx_bpf_task_cgroup, KF_RCU | KF_ACQUIRE) +BTF_ID_FLAGS(func, scx_bpf_task_cgroup, KF_IMPLICIT_ARGS | KF_RCU | KF_ACQUIRE)#endif BTF_ID_FLAGS(func, scx_bpf_now)BTF_ID_FLAGS (func, scx_bpf_events)
kernel/sched/ext_idle.c +54 -36
294 lines not on this page · raw message
kernel/sched/ext_internal.h +43 -1
66 lines not on this page · raw message
tools/sched_ext/include/scx/compat.h +10 -0
@@ -180,8 +180,18 @@ static inline long scx_hotplug_seq(void) })#define SCX_OPS_LOAD (__skel, __ops_name, __scx_name, __uei_name) ({ \ + struct bpf_program *__prog; \ UEI_SET_SIZE(__skel, __ops_name, __uei_name); \ SCX_BUG_ON(__scx_name##__load((__skel)), "Failed to load skel"); \ + bpf_object__for_each_program(__prog, (__skel)->obj) { \ + if (bpf_program__type(__prog) == BPF_PROG_TYPE_STRUCT_OPS) \ + continue; \ + s32 err = bpf_program__assoc_struct_ops(__prog, \ + (__skel)->maps.__ops_name, NULL); \ + if (err) \ + fprintf(stderr, "ERROR: Failed to associate %s with %s: %d\n", \ + bpf_program__name(__prog), #__ops_name, err); \ + } \ })/*
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 10/34 sched_ext: Enforce scheduling authority in dispatch and select_cpu operations
In reply to Tejun HeoAdd checks to enforce scheduling authority boundaries when multiple schedulers are present:
1. In scx_dsq_insert_preamble() and the dispatch retry path, ignore attempts to insert tasks that the scheduler doesn't own, counting them via SCX_EV_INSERT_NOT_OWNED. As BPF schedulers are allowed to ignore dequeues, such attempts can occur legitimately during sub-scheduler enabling when tasks move between schedulers. The counter helps distinguish normal cases from scheduler bugs.
2. For scx_bpf_dsq_insert_vtime() and scx_bpf_select_cpu_and(), error out when sub-schedulers are attached. These functions lack the aux__prog parameter needed to identify the calling scheduler, so they cannot be used safely with multiple schedulers. BPF programs should use the arg-wrapped versions (__scx_bpf_dsq_insert_vtime() and __scx_bpf_select_cpu_and()) instead.
These checks ensure that with multiple concurrent schedulers, scheduler identity can be properly determined and unauthorized task operations are prevented or tracked.
--- kernel/sched/ext.c | 26 ++++++++++++++++++++++++++ kernel/sched/ext_idle.c | 11 +++++++++++ kernel/sched/ext_internal.h | 12 ++++++++++++ 3 files changed, 49 insertions(+)
kernel/sched/ext.c +26 -0
@@ -2321,6 +2321,12 @@ static void finish_dispatch(struct scx_sched *sch, struct rq *rq,if ((opss &SCX_OPSS_QSEQ_MASK ) != qseq_at_dispatch)return ; +/* see SCX_EV_INSERT_NOT_OWNED definition */ +if (unlikely (!scx_task_on_sched (sch, p))) { +__scx_add_event (sch,SCX_EV_INSERT_NOT_OWNED ,1 ); +return ; + } +/* * While we know @p is accessible, we don't yet have a claim on * it - the BPF scheduler is allowed to dispatch tasks
@@ -4011,6 +4017,7 @@ static ssize_t scx_attr_events_show(struct kobject *kobj, at +=scx_attr_event_show (buf, at, &events,SCX_EV_BYPASS_DURATION ); at +=scx_attr_event_show (buf, at, &events,SCX_EV_BYPASS_DISPATCH ); at +=scx_attr_event_show (buf, at, &events,SCX_EV_BYPASS_ACTIVATE ); + at +=scx_attr_event_show (buf, at, &events,SCX_EV_INSERT_NOT_OWNED );return at; }SCX_ATTR (events);
@@ -5131,6 +5138,7 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len)scx_dump_event (s, &events,SCX_EV_BYPASS_DURATION );scx_dump_event (s, &events,SCX_EV_BYPASS_DISPATCH );scx_dump_event (s, &events,SCX_EV_BYPASS_ACTIVATE ); +scx_dump_event (s, &events,SCX_EV_INSERT_NOT_OWNED );if (seq_buf_has_overflowed (&s) && dump_len >=sizeof (trunc_marker)) memcpy(ei->dump + dump_len - sizeof(trunc_marker),
@@ -6409,6 +6417,12 @@ static bool scx_dsq_insert_preamble(struct scx_sched *sch, struct task_struct *preturn false; } +/* see SCX_EV_INSERT_NOT_OWNED definition */ +if (unlikely (!scx_task_on_sched (sch, p))) { +__scx_add_event (sch,SCX_EV_INSERT_NOT_OWNED ,1 ); +return false; + } +return true; }
@@ -6601,6 +6615,17 @@ __bpf_kfunc void scx_bpf_dsq_insert_vtime(struct task_struct *p, u64 dsq_id,if (unlikely (!sch))return ; +#ifdef CONFIG_EXT_SUB_SCHED +/* +* Disallow if any sub-scheds are attached. There is no way to tell +* which scheduler called us, just error out @p's scheduler. +*/ +if (unlikely (!list_empty (&sch->children))) { +scx_error (scx_task_sched (p),"__scx_bpf_dsq_insert_vtime() must be used" ); +return ; + } +#endif +scx_dsq_insert_vtime (sch, p, dsq_id, slice, vtime, enq_flags); }
@@ -7933,6 +7958,7 @@ static void scx_read_events(struct scx_sched *sch, struct scx_event_stats *eventscx_agg_event (events, e_cpu,SCX_EV_BYPASS_DURATION );scx_agg_event (events, e_cpu,SCX_EV_BYPASS_DISPATCH );scx_agg_event (events, e_cpu,SCX_EV_BYPASS_ACTIVATE ); +scx_agg_event (events, e_cpu,SCX_EV_INSERT_NOT_OWNED ); } }
kernel/sched/ext_idle.c +11 -0
@@ -1061,6 +1061,17 @@ __bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p, s32 prev_cpu, u64if (unlikely (!sch))return -ENODEV ; +#ifdef CONFIG_EXT_SUB_SCHED +/* +* Disallow if any sub-scheds are attached. There is no way to tell +* which scheduler called us, just error out @p's scheduler. +*/ +if (unlikely (!list_empty (&sch->children))) { +scx_error (scx_task_sched (p),"__scx_bpf_select_cpu_and() must be used" ); +return -EINVAL ; + } +#endif +return select_cpu_from_kfunc (sch, p, prev_cpu, wake_flags, cpus_allowed, flags); }
kernel/sched/ext_internal.h +12 -0
@@ -911,6 +911,18 @@ struct scx_event_stats {* The number of times the bypassing mode has been activated. */ s64 SCX_EV_BYPASS_ACTIVATE ; + +/* +* The number of times the scheduler attempted to insert a task that it +* doesn't own into a DSQ. Such attempts are ignored. +* +* As BPF schedulers are allowed to ignore dequeues, it's difficult to +* tell whether such an attempt is from a scheduler malfunction or an +* ignored dequeue around sub-sched enabling. If this count keeps going +* up regardless of sub-sched enabling, it likely indicates a bug in the +* scheduler. +*/ +s64 SCX_EV_INSERT_NOT_OWNED ; };struct scx_sched_pcpu {
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 11/34 sched_ext: Enforce scheduler ownership when updating slice and dsq_vtime
In reply to Tejun Heoscx_bpf_task_set_slice() and scx_bpf_task_set_dsq_vtime() now verify that the calling scheduler has authority over the task before allowing updates. This prevents schedulers from modifying tasks that don't belong to them in hierarchical scheduling configurations.
Direct writes to p->scx.slice and p->scx.dsq_vtime are deprecated and now trigger warnings. They will be disallowed in a future release.
--- kernel/sched/ext.c | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-)
kernel/sched/ext.c +32 -9
@@ -5872,12 +5872,17 @@ static int bpf_scx_btf_struct_access(struct bpf_verifier_log *log, t =btf_type_by_id (reg->btf, reg->btf_id);if (t == task_struct_type) { - if (off >= offsetof(struct task_struct, scx.slice) && - off + size <= offsetofend(struct task_struct, scx.slice)) -return SCALAR_VALUE ; - if (off >= offsetof(struct task_struct, scx.dsq_vtime) && - off + size <= offsetofend(struct task_struct, scx.dsq_vtime)) +/* +* COMPAT: Will be removed in v6.23. +*/ + if ((off >= offsetof(struct task_struct, scx.slice) && + off + size <= offsetofend(struct task_struct, scx.slice)) || + (off >= offsetof(struct task_struct, scx.dsq_vtime) && + off + size <= offsetofend(struct task_struct, scx.dsq_vtime))) { +pr_warn ("sched_ext: Writing directly to p->scx.slice/dsq_vtime is deprecated, use scx_bpf_task_set_slice/dsq_vtime()" );return SCALAR_VALUE ; + } + if (off >= offsetof(struct task_struct, scx.disallow) && off + size <= offsetofend(struct task_struct, scx.disallow))return SCALAR_VALUE ;
@@ -7096,12 +7101,21 @@ __bpf_kfunc_start_defs();* scx_bpf_task_set_slice - Set task's time slice * @p: task of interest * @slice: time slice to set in nsecs +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Set @p's time slice to @slice. Returns %true on success, %false if the * calling scheduler doesn't have authority over @p. */ -__bpf_kfunc bool scx_bpf_task_set_slice(struct task_struct *p, u64 slice) +__bpf_kfunc bool scx_bpf_task_set_slice(struct task_struct *p, u64 slice, +const struct bpf_prog_aux *aux) { +struct scx_sched *sch; + +guard (rcu)(); + sch =scx_prog_sched (aux); +if (unlikely (!scx_task_on_sched (sch, p))) +return false; + p->scx.slice = slice;return true; }
@@ -7110,12 +7124,21 @@ __bpf_kfunc bool scx_bpf_task_set_slice(struct task_struct *p, u64 slice)* scx_bpf_task_set_dsq_vtime - Set task's virtual time for DSQ ordering * @p: task of interest * @vtime: virtual time to set +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Set @p's virtual time to @vtime. Returns %true on success, %false if the * calling scheduler doesn't have authority over @p. */ -__bpf_kfunc bool scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime) +__bpf_kfunc bool scx_bpf_task_set_dsq_vtime(struct task_struct *p, u64 vtime, +const struct bpf_prog_aux *aux) { +struct scx_sched *sch; + +guard (rcu)(); + sch =scx_prog_sched (aux); +if (unlikely (!scx_task_on_sched (sch, p))) +return false; + p->scx.dsq_vtime = vtime;return true; }
@@ -7995,8 +8018,8 @@ __bpf_kfunc void scx_bpf_events(struct scx_event_stats *events,__bpf_kfunc_end_defs ();BTF_KFUNCS_START (scx_kfunc_ids_any) -BTF_ID_FLAGS (func, scx_bpf_task_set_slice, KF_RCU); -BTF_ID_FLAGS (func, scx_bpf_task_set_dsq_vtime,KF_RCU ); +BTF_ID_FLAGS(func, scx_bpf_task_set_slice, KF_IMPLICIT_ARGS | KF_RCU); +BTF_ID_FLAGS (func, scx_bpf_task_set_dsq_vtime,KF_IMPLICIT_ARGS |KF_RCU ); BTF_ID_FLAGS(func, scx_bpf_kick_cpu, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_dsq_nr_queued)BTF_ID_FLAGS (func, scx_bpf_destroy_dsq)
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 12/34 sched_ext: scx_dsq_move() should validate the task belongs to the right scheduler
In reply to Tejun Heoscx_bpf_dsq_move[_vtime]() calls scx_dsq_move() to move task from a DSQ to another. However, @p doesn't necessarily have to come form the containing iteration and can thus be a task which belongs to another scx_sched. Verify that @p is on the same scx_sched as the DSQ being iterated.
--- kernel/sched/ext.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-)
kernel/sched/ext.c +6 -1
@@ -6651,8 +6651,8 @@ static const struct btf_kfunc_id_set scx_kfunc_set_enqueue_dispatch = {static bool scx_dsq_move (struct bpf_iter_scx_dsq_kern *kit,struct task_struct *p,u64 dsq_id,u64 enq_flags) { -struct scx_sched *sch = scx_root;struct scx_dispatch_q *src_dsq = kit->dsq, *dst_dsq; +struct scx_sched *sch = src_dsq->sched;struct rq *this_rq, *src_rq, *locked_rq;bool dispatched = false;bool in_balance;
@@ -6669,6 +6669,11 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,if (unlikely (READ_ONCE (scx_aborting)))return false; +if (unlikely (!scx_task_on_sched (sch, p))) { +scx_error (sch,"scx_bpf_dsq_move[_vtime]() on %s[%d] but the task belongs to a different scheduler" , + p->comm, p->pid); + } +/* * Can be called from either ops.dispatch() locking this_rq() or any * context where no rq lock is held. If latter, lock @p's task_rq which
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 13/34 sched_ext: Refactor task init/exit helpers
In reply to Tejun Heo- Add the @sch parameter to scx_init_task() and drop @tg as it can be obtained from @p. Separate out __scx_init_task() which does everything except for the task state transition.
- Add the @sch parameter to scx_enable_task(). Separate out __scx_enable_task() which does everything except for the task state transition.
- Add the @sch parameter to scx_disable_task().
- Rename scx_exit_task() to scx_disable_and_exit_task() and separate out __scx_disable_and_exit_task() which does everything except for the task state transition.
While some task state transitions are relocated, no meaningful behavior changes are expected.
--- kernel/sched/ext.c | 67 +++++++++++++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 22 deletions(-)
kernel/sched/ext.c +45 -22
@@ -3107,9 +3107,9 @@ static void scx_set_task_state(struct task_struct *p, enum scx_task_state state) p->scx.flags |= state <<SCX_TASK_STATE_SHIFT ; } -static int scx_init_task (struct task_struct *p,struct task_group *tg,bool fork) +static int __scx_init_task (struct scx_sched *sch,struct task_struct *p,bool fork) { -struct scx_sched *sch = scx_root; +struct task_group *tg =task_group (p);int ret; p->scx.disallow = false;
@@ -3128,8 +3128,6 @@ static int scx_init_task(struct task_struct *p, struct task_group *tg, bool fork } } -scx_set_task_state (p,SCX_TASK_INIT ); -if (p->scx.disallow) {if (unlikely (scx_parent (sch))) { scx_error(sch, "non-root ops.init_task() set task->scx.disallow for %s[%d]",
@@ -3159,13 +3157,27 @@ static int scx_init_task(struct task_struct *p, struct task_group *tg, bool fork } } - p->scx.flags |=SCX_TASK_RESET_RUNNABLE_AT ;return 0 ; } -static void scx_enable_task (struct task_struct *p) +static int scx_init_task (struct scx_sched *sch,struct task_struct *p,bool fork) +{ +int ret; + + ret =__scx_init_task (sch, p, fork); +if (!ret) { +/* +* While @p's rq is not locked. @p is not visible to the rest of +* SCX yet and it's safe to update the flags and state. +*/ + p->scx.flags |=SCX_TASK_RESET_RUNNABLE_AT ; +scx_set_task_state (p,SCX_TASK_INIT ); + } +return ret; +} + +static void __scx_enable_task (struct scx_sched *sch,struct task_struct *p) { -struct scx_sched *sch = scx_root;struct rq *rq =task_rq (p);u32 weight;
@@ -3191,16 +3203,20 @@ static void scx_enable_task(struct task_struct *p)if (SCX_HAS_OP (sch, enable))SCX_CALL_OP_TASK (sch,SCX_KF_REST , enable, rq, p); -scx_set_task_state (p,SCX_TASK_ENABLED );if (SCX_HAS_OP (sch, set_weight))SCX_CALL_OP_TASK (sch,SCX_KF_REST , set_weight, rq, p, p->scx.weight); } -static void scx_disable_task (struct task_struct *p) +static void scx_enable_task (struct scx_sched *sch,struct task_struct *p) +{ +__scx_enable_task (sch, p); +scx_set_task_state (p,SCX_TASK_ENABLED ); +} + +static void scx_disable_task (struct scx_sched *sch,struct task_struct *p) { -struct scx_sched *sch = scx_root;struct rq *rq =task_rq (p);lockdep_assert_rq_held (rq);
@@ -3218,9 +3234,9 @@ static void scx_disable_task(struct task_struct *p)WARN_ON_ONCE (p->scx.flags &SCX_TASK_IN_CUSTODY ); } -static void scx_exit_task (struct task_struct *p) +static void __scx_disable_and_exit_task (struct scx_sched *sch, +struct task_struct *p) { -struct scx_sched *sch =scx_task_sched (p);struct scx_exit_task_args args = { .cancelled = false, };
@@ -3237,7 +3253,7 @@ static void scx_exit_task(struct task_struct *p)case SCX_TASK_READY :break ;case SCX_TASK_ENABLED : -scx_disable_task (p); +scx_disable_task (sch, p);break ;default :WARN_ON_ONCE (true);
@@ -3247,6 +3263,13 @@ static void scx_exit_task(struct task_struct *p)if (SCX_HAS_OP (sch, exit_task))SCX_CALL_OP_TASK (sch,SCX_KF_REST , exit_task,task_rq (p), p, &args); +} + +static void scx_disable_and_exit_task (struct scx_sched *sch, +struct task_struct *p) +{ +__scx_disable_and_exit_task (sch, p); +scx_set_task_sched (p,NULL );scx_set_task_state (p,SCX_TASK_NONE ); }
@@ -3282,7 +3305,7 @@ int scx_fork(struct task_struct *p, struct kernel_clone_args *kargs)percpu_rwsem_assert_held (&scx_fork_rwsem);if (scx_init_task_enabled) { - ret =scx_init_task (p,task_group (p), true); + ret =scx_init_task (scx_root, p, true);if (!ret)scx_set_task_sched (p, scx_root);return ret;
@@ -3306,7 +3329,7 @@ void scx_post_fork(struct task_struct *p)struct rq *rq; rq =task_rq_lock (p, &rf); -scx_enable_task (p); +scx_enable_task (scx_task_sched (p), p);task_rq_unlock (rq, p, &rf); } }
@@ -3326,7 +3349,7 @@ void scx_cancel_fork(struct task_struct *p) rq =task_rq_lock (p, &rf);WARN_ON_ONCE (scx_get_task_state (p) >=SCX_TASK_READY ); -scx_exit_task (p); +scx_disable_and_exit_task (scx_task_sched (p), p);task_rq_unlock (rq, p, &rf); }
@@ -3385,7 +3408,7 @@ void sched_ext_dead(struct task_struct *p)struct rq *rq; rq =task_rq_lock (p, &rf); -scx_exit_task (p); +scx_disable_and_exit_task (scx_task_sched (p), p);task_rq_unlock (rq, p, &rf); } }
@@ -3417,7 +3440,7 @@ static void switching_to_scx(struct rq *rq, struct task_struct *p)if (task_dead_and_done (p))return ; -scx_enable_task (p); +scx_enable_task (sch, p);/* * set_cpus_allowed_scx() is not called while @p is associated with a
@@ -3433,7 +3456,7 @@ static void switched_from_scx(struct rq *rq, struct task_struct *p)if (task_dead_and_done (p))return ; -scx_disable_task (p); +scx_disable_task (scx_task_sched (p), p); }static void wakeup_preempt_scx (struct rq *rq,struct task_struct *p,int wake_flags) {}
@@ -4662,7 +4685,7 @@ static void scx_root_disable(struct scx_sched *sch)/* * Shut down cgroup support before tasks so that the cgroup attach path -* doesn't race against scx_exit_task(). +* doesn't race against scx_disable_and_exit_task(). */ scx_cgroup_lock ();scx_cgroup_exit (sch);
@@ -4691,7 +4714,7 @@ static void scx_root_disable(struct scx_sched *sch) p->sched_class = new_class; } -scx_exit_task (p); +scx_disable_and_exit_task (scx_task_sched (p), p); }scx_task_iter_stop (&sti);
@@ -5567,7 +5590,7 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)scx_task_iter_unlock (&sti); - ret =scx_init_task (p,task_group (p), false); + ret =scx_init_task (sch, p, false);if (ret) {put_task_struct (p);scx_task_iter_stop (&sti);
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 14/34 sched_ext: Make scx_prio_less() handle multiple schedulers
In reply to Tejun HeoCall ops.core_sched_before() iff both tasks belong to the same scx_sched. Otherwise, use timestamp based ordering.
--- kernel/sched/ext.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
kernel/sched/ext.c +4 -3
@@ -2805,16 +2805,17 @@ void ext_server_init(struct rq *rq)bool scx_prio_less (const struct task_struct *a,const struct task_struct *b,bool in_fi) { -struct scx_sched *sch = scx_root; +struct scx_sched *sch_a =scx_task_sched (a); +struct scx_sched *sch_b =scx_task_sched (b);/* * The const qualifiers are dropped from task_struct pointers when * calling ops.core_sched_before(). Accesses are controlled by the * verifier. */ -if (SCX_HAS_OP (sch, core_sched_before) && +if (sch_a == sch_b &&SCX_HAS_OP (sch_a, core_sched_before) && !scx_rq_bypassing (task_rq (a))) -return SCX_CALL_OP_2TASKS_RET (sch,SCX_KF_REST , core_sched_before, +return SCX_CALL_OP_2TASKS_RET (sch_a,SCX_KF_REST , core_sched_before,NULL , (struct task_struct *)a, (struct task_struct *)b);
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 15/34 sched_ext: Move default slice to per-scheduler field
In reply to Tejun HeoThe default time slice was stored in the global scx_slice_dfl variable which was dynamically modified when entering and exiting bypass mode. With hierarchical scheduling, each scheduler instance needs its own default slice configuration so that bypass operations on one scheduler don't affect others.
Move slice_dfl into struct scx_sched and update all access sites. The bypass logic now modifies the root scheduler's slice_dfl. At task initialization in init_scx_entity(), use the SCX_SLICE_DFL constant directly since the task may not yet be associated with a specific scheduler.
--- kernel/sched/ext.c | 14 ++++++++------ kernel/sched/ext_internal.h | 1 + 2 files changed, 9 insertions(+), 6 deletions(-)
kernel/sched/ext.c +8 -6
@@ -164,7 +164,6 @@ static struct kset *scx_kset;* There usually is no reason to modify these as normal scheduler operation * shouldn't be affected by them. The knobs are primarily for debugging. */ -static u64 scx_slice_dfl =SCX_SLICE_DFL ;static unsigned int scx_slice_bypass_us =SCX_SLICE_BYPASS /NSEC_PER_USEC ;static unsigned int scx_bypass_lb_intv_us =SCX_BYPASS_LB_DFL_INTV_US ;
@@ -1131,7 +1130,7 @@ static void dsq_mod_nr(struct scx_dispatch_q *dsq, s32 delta)static void refill_task_slice_dfl (struct scx_sched *sch,struct task_struct *p) { - p->scx.slice =READ_ONCE (scx_slice_dfl); + p->scx.slice =READ_ONCE (sch->slice_dfl);__scx_add_event (sch,SCX_EV_REFILL_SLICE_DFL ,1 ); }
@@ -3285,7 +3284,7 @@ void init_scx_entity(struct sched_ext_entity *scx)INIT_LIST_HEAD (&scx->runnable_node); scx->runnable_at = jiffies; scx->ddsp_dsq_id =SCX_DSQ_INVALID ; - scx->slice =READ_ONCE (scx_slice_dfl); + scx->slice =SCX_SLICE_DFL ; }void scx_pre_fork (struct task_struct *p)
@@ -4431,6 +4430,8 @@ static void scx_bypass(bool bypass)raw_spin_lock_irqsave (&bypass_lock, flags); sch =rcu_dereference_bh (scx_root); +if (!sch) + goto unlock;if (bypass) {u32 intv_us;
@@ -4439,7 +4440,7 @@ static void scx_bypass(bool bypass)WARN_ON_ONCE (scx_bypass_depth <=0 );if (scx_bypass_depth !=1 ) goto unlock; -WRITE_ONCE (scx_slice_dfl, scx_slice_bypass_us *NSEC_PER_USEC ); +WRITE_ONCE (sch->slice_dfl, scx_slice_bypass_us *NSEC_PER_USEC ); bypass_timestamp =ktime_get_ns ();if (sch)scx_add_event (sch,SCX_EV_BYPASS_ACTIVATE ,1 );
@@ -4455,7 +4456,7 @@ static void scx_bypass(bool bypass)WARN_ON_ONCE (scx_bypass_depth <0 );if (scx_bypass_depth !=0 ) goto unlock; -WRITE_ONCE (scx_slice_dfl,SCX_SLICE_DFL ); +WRITE_ONCE (sch->slice_dfl,SCX_SLICE_DFL );if (sch)scx_add_event (sch,SCX_EV_BYPASS_DURATION ,ktime_get_ns () - bypass_timestamp);
@@ -5299,6 +5300,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops, sch->ancestors[level] = sch; sch->level = level; + sch->slice_dfl =SCX_SLICE_DFL ;atomic_set (&sch->exit_kind,SCX_EXIT_NONE );init_irq_work (&sch->error_irq_work, scx_error_irq_workfn);kthread_init_work (&sch->disable_work, scx_disable_workfn);
@@ -5635,7 +5637,7 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link) queue_flags |=DEQUEUE_CLASS ; scoped_guard (sched_change, p, queue_flags) { - p->scx.slice =READ_ONCE (scx_slice_dfl); + p->scx.slice =READ_ONCE (sch->slice_dfl); p->sched_class = new_class; } }
kernel/sched/ext_internal.h +1 -0
@@ -950,6 +950,7 @@ struct scx_sched {struct scx_dispatch_q **global_dsqs;struct scx_sched_pcpu __percpu *pcpu; +u64 slice_dfl;s32 level;/*
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 16/34 sched_ext: Move aborting flag to per-scheduler field
In reply to Tejun HeoThe abort state was tracked in the global scx_aborting flag which was used to break out of potential live-lock scenarios when an error occurs. With hierarchical scheduling, each scheduler instance must track its own abort state independently so that an aborting scheduler doesn't interfere with others.
Move the aborting flag into struct scx_sched and update all access sites. The early initialization check in scx_root_enable() that warned about residual aborting state is no longer needed as each scheduler instance now starts with a clean state.
--- kernel/sched/ext.c | 10 +++------- kernel/sched/ext_internal.h | 1 + 2 files changed, 4 insertions(+), 7 deletions(-)
kernel/sched/ext.c +3 -7
@@ -44,7 +44,6 @@ static atomic_t scx_enable_state_var = ATOMIC_INIT(SCX_DISABLED);static int scx_bypass_depth;static cpumask_var_t scx_bypass_lb_donee_cpumask;static cpumask_var_t scx_bypass_lb_resched_cpumask; -static bool scx_aborting;static bool scx_init_task_enabled;static bool scx_switching_all;DEFINE_STATIC_KEY_FALSE (__scx_switched_all);
@@ -2147,7 +2146,7 @@ static bool consume_dispatch_q(struct scx_sched *sch, struct rq *rq,* the system into the bypass mode. This can easily live-lock the * machine. If aborting, exit from all non-bypass DSQs. */ -if (unlikely (READ_ONCE (scx_aborting)) && dsq->id !=SCX_DSQ_BYPASS ) +if (unlikely (READ_ONCE (sch->aborting)) && dsq->id !=SCX_DSQ_BYPASS )break ;if (rq == task_rq) {
@@ -4659,7 +4658,6 @@ static void scx_root_disable(struct scx_sched *sch)/* guarantee forward progress and wait for descendants to be disabled */ scx_bypass (true); -WRITE_ONCE (scx_aborting, false);drain_descendants (sch);switch (scx_set_enable_state (SCX_DISABLING )) {
@@ -4820,7 +4818,7 @@ static bool scx_claim_exit(struct scx_sched *sch, enum scx_exit_kind kind)* flag to break potential live-lock scenarios, ensuring we can * successfully reach scx_bypass(). */ -WRITE_ONCE (scx_aborting, true); +WRITE_ONCE (sch->aborting, true);/* * Propagate exits to descendants immediately. Each has a dedicated
@@ -5458,8 +5456,6 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)*/ WARN_ON_ONCE (scx_set_enable_state (SCX_ENABLING ) !=SCX_DISABLED );WARN_ON_ONCE (scx_root); -if (WARN_ON_ONCE (READ_ONCE (scx_aborting))) -WRITE_ONCE (scx_aborting, false);atomic_long_set (&scx_nr_rejected,0 );
@@ -6692,7 +6688,7 @@ static bool scx_dsq_move(struct bpf_iter_scx_dsq_kern *kit,* If the BPF scheduler keeps calling this function repeatedly, it can * cause similar live-lock conditions as consume_dispatch_q(). */ -if (unlikely (READ_ONCE (scx_aborting))) +if (unlikely (READ_ONCE (sch->aborting)))return false;if (unlikely (!scx_task_on_sched (sch, p))) {
kernel/sched/ext_internal.h +1 -0
@@ -951,6 +951,7 @@ struct scx_sched {struct scx_sched_pcpu __percpu *pcpu;u64 slice_dfl; +bool aborting;s32 level;/*
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 17/34 sched_ext: Move bypass_dsq into scx_sched_pcpu
In reply to Tejun HeoTo support bypass mode for sub-schedulers, move bypass_dsq from struct scx_rq to struct scx_sched_pcpu. Add bypass_dsq() helper. Move bypass_dsq initialization from init_sched_ext_class() to scx_alloc_and_attach_sched(). bypass_lb_cpu() now takes a CPU number instead of rq pointer. All callers updated. No behavior change as all tasks use the root scheduler.
--- kernel/sched/ext.c | 52 +++++++++++++++++++------------------ kernel/sched/ext_internal.h | 2 ++ kernel/sched/sched.h | 1 - 3 files changed, 29 insertions(+), 26 deletions(-)
kernel/sched/ext.c +27 -25
@@ -359,6 +359,11 @@ static const struct sched_class *scx_setscheduler_class(struct task_struct *p)return __setscheduler_class (p->policy, p->prio); } +static struct scx_dispatch_q *bypass_dsq (struct scx_sched *sch,s32 cpu) +{ +return &per_cpu_ptr (sch->pcpu, cpu)->bypass_dsq; +} +/* * scx_kf_mask enforcement. Some kfuncs can only be called from specific SCX * ops. When invoking SCX ops, SCX_CALL_OP[_RET]() should be used to indicate
@@ -1628,7 +1633,7 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, dsq =find_global_dsq (sch, p); goto enqueue; bypass: - dsq = &task_rq (p)->scx.bypass_dsq; + dsq =bypass_dsq (sch,task_cpu (p)); goto enqueue; enqueue:
@@ -2439,7 +2444,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev) goto has_tasks;if (scx_rq_bypassing (rq)) { -if (consume_dispatch_q (sch, rq, &rq->scx.bypass_dsq)) +if (consume_dispatch_q (sch, rq,bypass_dsq (sch,cpu_of (rq)))) goto has_tasks;else goto no_tasks;
@@ -4192,11 +4197,12 @@ bool scx_hardlockup(int cpu)return true; } -static u32 bypass_lb_cpu (struct scx_sched *sch,struct rq *rq, +static u32 bypass_lb_cpu (struct scx_sched *sch,s32 donor,struct cpumask *donee_mask,struct cpumask *resched_mask,u32 nr_donor_target,u32 nr_donee_target) { -struct scx_dispatch_q *donor_dsq = &rq->scx.bypass_dsq; +struct rq *donor_rq =cpu_rq (donor); +struct scx_dispatch_q *donor_dsq =bypass_dsq (sch, donor);struct task_struct *p, *n;struct scx_dsq_list_node cursor =INIT_DSQ_LIST_CURSOR (cursor,0 ,0 );s32 delta =READ_ONCE (donor_dsq->nr) - nr_donor_target;
@@ -4212,7 +4218,7 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq,if (delta <DIV_ROUND_UP (min_delta_us, scx_slice_bypass_us))return 0 ; -raw_spin_rq_lock_irq (rq); +raw_spin_rq_lock_irq (donor_rq);raw_spin_lock (&donor_dsq->lock);list_add (&cursor.node, &donor_dsq->list); resume:
@@ -4220,7 +4226,6 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq, n =nldsq_next_task (donor_dsq, n, false);while ((p = n)) { -struct rq *donee_rq;struct scx_dispatch_q *donee_dsq;int donee;
@@ -4236,14 +4241,13 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq,if (donee >= nr_cpu_ids)continue ; - donee_rq =cpu_rq (donee); - donee_dsq = &donee_rq->scx.bypass_dsq; + donee_dsq =bypass_dsq (sch, donee);/* * $p's rq is not locked but $p's DSQ lock protects its * scheduling properties making this test safe. */ -if (!task_can_run_on_remote_rq (sch, p, donee_rq, false)) +if (!task_can_run_on_remote_rq (sch, p,cpu_rq (donee), false))continue ;/*
@@ -4258,7 +4262,7 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq,* between bypass DSQs. */ dispatch_dequeue_locked (p, donor_dsq); -dispatch_enqueue (sch, donee_rq, donee_dsq, p,SCX_ENQ_NESTED ); +dispatch_enqueue (sch,cpu_rq (donee), donee_dsq, p,SCX_ENQ_NESTED );/* * $donee might have been idle and need to be woken up. No need
@@ -4273,9 +4277,9 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq,if (!(nr_balanced %SCX_BYPASS_LB_BATCH ) && n) {list_move_tail (&cursor.node, &n->scx.dsq_list.node);raw_spin_unlock (&donor_dsq->lock); -raw_spin_rq_unlock_irq (rq); +raw_spin_rq_unlock_irq (donor_rq);cpu_relax (); -raw_spin_rq_lock_irq (rq); +raw_spin_rq_lock_irq (donor_rq);raw_spin_lock (&donor_dsq->lock); goto resume; }
@@ -4283,7 +4287,7 @@ static u32 bypass_lb_cpu(struct scx_sched *sch, struct rq *rq,list_del_init (&cursor.node);raw_spin_unlock (&donor_dsq->lock); -raw_spin_rq_unlock_irq (rq); +raw_spin_rq_unlock_irq (donor_rq);return nr_balanced; }
@@ -4301,7 +4305,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)/* count the target tasks and CPUs */ for_each_cpu_and (cpu, cpu_online_mask, node_mask) { -u32 nr =READ_ONCE (cpu_rq (cpu)->scx.bypass_dsq.nr); +u32 nr =READ_ONCE (bypass_dsq (sch, cpu)->nr); nr_tasks += nr; nr_cpus++;
@@ -4323,24 +4327,21 @@ static void bypass_lb_node(struct scx_sched *sch, int node)cpumask_clear (donee_mask);for_each_cpu_and (cpu, cpu_online_mask, node_mask) { -if (READ_ONCE (cpu_rq (cpu)->scx.bypass_dsq.nr) < nr_target) +if (READ_ONCE (bypass_dsq (sch, cpu)->nr) < nr_target)cpumask_set_cpu (cpu, donee_mask); }/* iterate !donee CPUs and see if they should be offloaded */ cpumask_clear (resched_mask);for_each_cpu_and (cpu, cpu_online_mask, node_mask) { -struct rq *rq =cpu_rq (cpu); -struct scx_dispatch_q *donor_dsq = &rq->scx.bypass_dsq; -if (cpumask_empty (donee_mask))break ;if (cpumask_test_cpu (cpu, donee_mask))continue ; -if (READ_ONCE (donor_dsq->nr) <= nr_donor_target) +if (READ_ONCE (bypass_dsq (sch, cpu)->nr) <= nr_donor_target)continue ; - nr_balanced +=bypass_lb_cpu (sch, rq, donee_mask, resched_mask, + nr_balanced +=bypass_lb_cpu (sch, cpu, donee_mask, resched_mask, nr_donor_target, nr_target); }
@@ -4348,7 +4349,7 @@ static void bypass_lb_node(struct scx_sched *sch, int node)resched_cpu (cpu);for_each_cpu_and (cpu, cpu_online_mask, node_mask) { -u32 nr =READ_ONCE (cpu_rq (cpu)->scx.bypass_dsq.nr); +u32 nr =READ_ONCE (bypass_dsq (sch, cpu)->nr); after_min =min (nr, after_min); after_max =max (nr, after_max);
@@ -5243,7 +5244,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops, {struct scx_sched *sch;s32 level = parent ? parent->level +1 :0 ; -int node, ret; +s32 node, cpu, ret; sch =kzalloc_flex (*sch, ancestors, level);if (!sch)
@@ -5284,6 +5285,9 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops, goto err_free_gdsqs; } +for_each_possible_cpu (cpu) +init_dsq (bypass_dsq (sch, cpu),SCX_DSQ_BYPASS , sch); + sch->helper =kthread_run_worker (0 ,"sched_ext_helper" );if (IS_ERR (sch->helper)) { ret =PTR_ERR (sch->helper);
@@ -5463,7 +5467,6 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)struct rq *rq =cpu_rq (cpu); rq->scx.local_dsq.sched = sch; - rq->scx.bypass_dsq.sched = sch; rq->scx.cpuperf_target =SCX_CPUPERF_ONE ; }
@@ -6399,9 +6402,8 @@ void __init init_sched_ext_class(void)struct rq *rq =cpu_rq (cpu);int n =cpu_to_node (cpu); -/* local/bypass dsq's sch will be set during scx_root_enable() */ +/* local_dsq's sch will be set during scx_root_enable() */ init_dsq (&rq->scx.local_dsq,SCX_DSQ_LOCAL ,NULL ); -init_dsq (&rq->scx.bypass_dsq,SCX_DSQ_BYPASS ,NULL );INIT_LIST_HEAD (&rq->scx.runnable_list);INIT_LIST_HEAD (&rq->scx.ddsp_deferred_locals);
kernel/sched/ext_internal.h +2 -0
@@ -932,6 +932,8 @@ struct scx_sched_pcpu {* constructed when requested by scx_bpf_events(). */ struct scx_event_stats event_stats; + +struct scx_dispatch_q bypass_dsq; };struct scx_sched {
kernel/sched/sched.h +0 -1
@@ -810,7 +810,6 @@ struct scx_rq {struct balance_callback deferred_bal_cb;struct irq_work deferred_irq_work; struct irq_work kick_cpus_irq_work; - struct scx_dispatch_q bypass_dsq; };#endif /* CONFIG_SCHED_CLASS_EXT */
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 18/34 sched_ext: Move bypass state into scx_sched
In reply to Tejun HeoIn preparation of multiple scheduler support, make bypass state per-scx_sched. Move scx_bypass_depth, bypass_timestamp and bypass_lb_timer from globals into scx_sched. Move SCX_RQ_BYPASSING from rq to scx_sched_pcpu as SCX_SCHED_PCPU_BYPASSING.
scx_bypass() now takes @sch and scx_rq_bypassing(rq) is replaced with scx_bypassing(sch, cpu). All callers updated.
scx_bypassed_for_enable existed to balance the global scx_bypass_depth when enable failed. Now that bypass_depth is per-scheduler, the counter is destroyed along with the scheduler on enable failure. Remove scx_bypassed_for_enable.
As all tasks currently use the root scheduler, there's no observable behavior change.
--- kernel/sched/ext.c | 143 +++++++++++++++++------------------- kernel/sched/ext_idle.c | 3 +- kernel/sched/ext_internal.h | 14 +++- kernel/sched/sched.h | 1 - 4 files changed, 80 insertions(+), 81 deletions(-)
kernel/sched/ext.c +66 -77
@@ -41,20 +41,12 @@ static DEFINE_MUTEX(scx_enable_mutex);DEFINE_STATIC_KEY_FALSE (__scx_enabled);DEFINE_STATIC_PERCPU_RWSEM (scx_fork_rwsem);static atomic_t scx_enable_state_var =ATOMIC_INIT (SCX_DISABLED ); -static int scx_bypass_depth;static cpumask_var_t scx_bypass_lb_donee_cpumask;static cpumask_var_t scx_bypass_lb_resched_cpumask;static bool scx_init_task_enabled;static bool scx_switching_all;DEFINE_STATIC_KEY_FALSE (__scx_switched_all); -/* -* Tracks whether scx_enable() called scx_bypass(true). Used to balance bypass -* depth on enable failure. Will be removed when bypass depth is moved into the -* sched instance. -*/ -static bool scx_bypassed_for_enable; -static atomic_long_t scx_nr_rejected =ATOMIC_LONG_INIT (0 );static atomic_long_t scx_hotplug_seq =ATOMIC_LONG_INIT (0 );
@@ -1566,7 +1558,7 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,if (!scx_rq_online (rq)) goto local; -if (scx_rq_bypassing (rq)) { +if (scx_bypassing (sch,cpu_of (rq))) {__scx_add_event (sch,SCX_EV_BYPASS_DISPATCH ,1 ); goto bypass; }
@@ -1947,7 +1939,7 @@ static bool task_can_run_on_remote_rq(struct scx_sched *sch, struct task_struct *p, struct rq *rq, bool enforce) { - int cpu = cpu_of(rq); + s32 cpu = cpu_of(rq);WARN_ON_ONCE (task_cpu (p) == cpu);
@@ -2398,6 +2390,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)bool prev_on_scx = prev->sched_class == &ext_sched_class;bool prev_on_rq = prev->scx.flags &SCX_TASK_QUEUED ;int nr_loops =SCX_DSP_MAX_LOOPS ; +s32 cpu =cpu_of (rq);lockdep_assert_rq_held (rq); rq->scx.flags |=SCX_RQ_IN_BALANCE ;
@@ -2412,8 +2405,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)* emitted in switch_class(). */ if (SCX_HAS_OP (sch, cpu_acquire)) -SCX_CALL_OP (sch,SCX_KF_REST , cpu_acquire, rq, -cpu_of (rq),NULL ); +SCX_CALL_OP (sch,SCX_KF_REST , cpu_acquire, rq, cpu,NULL ); rq->scx.cpu_released = false; }
@@ -2430,7 +2422,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)* See scx_disable_workfn() for the explanation on the bypassing * test. */ -if (prev_on_rq && prev->scx.slice && !scx_rq_bypassing (rq)) { +if (prev_on_rq && prev->scx.slice && !scx_bypassing (sch, cpu)) { rq->scx.flags |=SCX_RQ_BAL_KEEP ; goto has_tasks; }
@@ -2443,8 +2435,8 @@ static int balance_one(struct rq *rq, struct task_struct *prev)if (consume_global_dsq (sch, rq)) goto has_tasks; -if (scx_rq_bypassing (rq)) { -if (consume_dispatch_q (sch, rq,bypass_dsq (sch,cpu_of (rq)))) +if (scx_bypassing (sch, cpu)) { +if (consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu))) goto has_tasks;else goto no_tasks;
@@ -2465,8 +2457,8 @@ static int balance_one(struct rq *rq, struct task_struct *prev) do { dspc->nr_tasks = 0; - SCX_CALL_OP(sch, SCX_KF_DISPATCH, dispatch, rq, - cpu_of(rq), prev_on_scx ? prev : NULL); + SCX_CALL_OP(sch, SCX_KF_DISPATCH, dispatch, rq, cpu, + prev_on_scx ? prev : NULL); flush_dispatch_buf(sch, rq);
@@ -2489,7 +2481,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)* scx_kick_cpu() for deferred kicking. */ if (unlikely (!--nr_loops)) { -scx_kick_cpu (sch,cpu_of (rq),0 ); +scx_kick_cpu (sch, cpu,0 );break ; } }while (dspc->nr_tasks);
@@ -2500,7 +2492,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)* %SCX_OPS_ENQ_LAST is in effect. */ if (prev_on_rq && - (!(sch->ops.flags &SCX_OPS_ENQ_LAST ) ||scx_rq_bypassing (rq))) { + (!(sch->ops.flags &SCX_OPS_ENQ_LAST ) ||scx_bypassing (sch, cpu))) { rq->scx.flags |=SCX_RQ_BAL_KEEP ;__scx_add_event (sch,SCX_EV_DISPATCH_KEEP_LAST ,1 ); goto has_tasks;
@@ -2659,7 +2651,7 @@ static void put_prev_task_scx(struct rq *rq, struct task_struct *p,* forcing a different task. Leave it at the head of the local * DSQ. */ -if (p->scx.slice && !scx_rq_bypassing (rq)) { +if (p->scx.slice && !scx_bypassing (sch,cpu_of (rq))) {dispatch_enqueue (sch, rq, &rq->scx.local_dsq, p,SCX_ENQ_HEAD ); goto switch_class;
@@ -2742,7 +2734,8 @@ do_pick_task_scx(struct rq *rq, struct rq_flags *rf, bool force_scx)if (unlikely (!p->scx.slice)) {struct scx_sched *sch =scx_task_sched (p); -if (!scx_rq_bypassing (rq) && !sch->warned_zero_slice) { +if (!scx_bypassing (sch,cpu_of (rq)) && + !sch->warned_zero_slice) {printk_deferred (KERN_WARNING "sched_ext: %s[%d] has zero slice in %s()\n" , p->comm, p->pid, __func__); sch->warned_zero_slice = true;
@@ -2817,7 +2810,7 @@ bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,* verifier. */ if (sch_a == sch_b &&SCX_HAS_OP (sch_a, core_sched_before) && - !scx_rq_bypassing (task_rq (a))) + !scx_bypassing (sch_a,task_cpu (a))) return SCX_CALL_OP_2TASKS_RET(sch_a, SCX_KF_REST, core_sched_before, NULL, (struct task_struct *)a,
@@ -2830,7 +2823,7 @@ bool scx_prio_less(const struct task_struct *a, const struct task_struct *b,static int select_task_rq_scx (struct task_struct *p,int prev_cpu,int wake_flags) {struct scx_sched *sch =scx_task_sched (p); -bool rq_bypass; +bool bypassing;/* * sched_exec() calls with %WF_EXEC when @p is about to exec(2) as it
@@ -2845,8 +2838,8 @@ static int select_task_rq_scx(struct task_struct *p, int prev_cpu, int wake_flagif (unlikely (wake_flags &WF_EXEC ))return prev_cpu; - rq_bypass =scx_rq_bypassing (task_rq (p)); -if (likely (SCX_HAS_OP (sch, select_cpu)) && !rq_bypass) { + bypassing =scx_bypassing (sch,task_cpu (p)); +if (likely (SCX_HAS_OP (sch, select_cpu)) && !bypassing) {s32 cpu;struct task_struct **ddsp_taskp;
@@ -2876,7 +2869,7 @@ static int select_task_rq_scx(struct task_struct *p, int prev_cpu, int wake_flag } p->scx.selected_cpu = cpu; -if (rq_bypass) +if (bypassing)__scx_add_event (sch,SCX_EV_BYPASS_DISPATCH ,1 );return cpu; }
@@ -2913,7 +2906,7 @@ static void set_cpus_allowed_scx(struct task_struct *p,static void handle_hotplug (struct rq *rq,bool online) {struct scx_sched *sch = scx_root; -int cpu =cpu_of (rq); +s32 cpu =cpu_of (rq);atomic_long_inc (&scx_hotplug_seq);
@@ -3042,7 +3035,7 @@ static void task_tick_scx(struct rq *rq, struct task_struct *curr, int queued)* While disabling, always resched and refresh core-sched timestamp as * we can't trust the slice management or ops.core_sched_before(). */ -if (scx_rq_bypassing (rq)) { +if (scx_bypassing (sch,cpu_of (rq))) { curr->scx.slice =0 ;touch_core_sched (rq, curr); }else if (SCX_HAS_OP (sch, tick)) {
@@ -3483,13 +3476,14 @@ int scx_check_setscheduler(struct task_struct *p, int policy)bool scx_can_stop_tick (struct rq *rq) {struct task_struct *p = rq->curr; - -if (scx_rq_bypassing (rq)) -return false; +struct scx_sched *sch =scx_task_sched (p);if (p->sched_class != &ext_sched_class)return true; +if (scx_bypassing (sch,cpu_of (rq))) +return false; +/* * @rq can dispatch from different DSQs, so we can't tell whether it * needs the tick or not by looking at nr_running. Allow stopping ticks
@@ -3979,6 +3973,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work)irq_work_sync (&sch->error_irq_work);kthread_destroy_worker (sch->helper); +timer_shutdown_sync (&sch->bypass_lb_timer);#ifdef CONFIG_EXT_SUB_SCHED kfree (sch->cgrp_path);
@@ -4371,12 +4366,11 @@ static void bypass_lb_node(struct scx_sched *sch, int node)*/ static void scx_bypass_lb_timerfn (struct timer_list *timer) { -struct scx_sched *sch; + struct scx_sched *sch = container_of(timer, struct scx_sched, bypass_lb_timer);int node;u32 intv_us; - sch =rcu_dereference_all (scx_root); -if (unlikely (!sch) || !READ_ONCE (scx_bypass_depth)) +if (!READ_ONCE (sch->bypass_depth))return ;for_each_node_with_cpus (node)
@@ -4387,10 +4381,9 @@ static void scx_bypass_lb_timerfn(struct timer_list *timer)mod_timer (timer, jiffies +usecs_to_jiffies (intv_us)); } -static DEFINE_TIMER(scx_bypass_lb_timer, scx_bypass_lb_timerfn); -/** * scx_bypass - [Un]bypass scx_ops and guarantee forward progress +* @sch: sched to bypass * @bypass: true for bypass, false for unbypass * * Bypassing guarantees that all runnable tasks make forward progress without
@@ -4420,51 +4413,44 @@ static DEFINE_TIMER(scx_bypass_lb_timer, scx_bypass_lb_timerfn);* * - scx_prio_less() reverts to the default core_sched_at order. */ -static void scx_bypass (bool bypass) +static void scx_bypass (struct scx_sched *sch,bool bypass) {static DEFINE_RAW_SPINLOCK (bypass_lock); -static unsigned long bypass_timestamp; -struct scx_sched *sch;unsigned long flags;int cpu;raw_spin_lock_irqsave (&bypass_lock, flags); - sch =rcu_dereference_bh (scx_root); -if (!sch) - goto unlock;if (bypass) {u32 intv_us; -WRITE_ONCE (scx_bypass_depth, scx_bypass_depth +1 ); -WARN_ON_ONCE (scx_bypass_depth <=0 ); -if (scx_bypass_depth !=1 ) +WRITE_ONCE (sch->bypass_depth, sch->bypass_depth +1 ); +WARN_ON_ONCE (sch->bypass_depth <=0 ); +if (sch->bypass_depth !=1 ) goto unlock;WRITE_ONCE (sch->slice_dfl, scx_slice_bypass_us *NSEC_PER_USEC ); - bypass_timestamp =ktime_get_ns (); -if (sch) -scx_add_event (sch,SCX_EV_BYPASS_ACTIVATE ,1 ); + sch->bypass_timestamp =ktime_get_ns (); +scx_add_event (sch,SCX_EV_BYPASS_ACTIVATE ,1 ); intv_us =READ_ONCE (scx_bypass_lb_intv_us); -if (intv_us && !timer_pending (&scx_bypass_lb_timer)) { - scx_bypass_lb_timer.expires = +if (intv_us && !timer_pending (&sch->bypass_lb_timer)) { + sch->bypass_lb_timer.expires = jiffies +usecs_to_jiffies (intv_us); -add_timer_global (&scx_bypass_lb_timer); +add_timer_global (&sch->bypass_lb_timer); } }else { -WRITE_ONCE (scx_bypass_depth, scx_bypass_depth -1 ); -WARN_ON_ONCE (scx_bypass_depth <0 ); -if (scx_bypass_depth !=0 ) +WRITE_ONCE (sch->bypass_depth, sch->bypass_depth -1 ); +WARN_ON_ONCE (sch->bypass_depth <0 ); +if (sch->bypass_depth !=0 ) goto unlock;WRITE_ONCE (sch->slice_dfl,SCX_SLICE_DFL ); -if (sch) -scx_add_event (sch,SCX_EV_BYPASS_DURATION , -ktime_get_ns () - bypass_timestamp); +scx_add_event (sch,SCX_EV_BYPASS_DURATION , +ktime_get_ns () - sch->bypass_timestamp); }/* * No task property is changing. We just need to make sure all currently -* queued tasks are re-queued according to the new scx_rq_bypassing() +* queued tasks are re-queued according to the new scx_bypassing() * state. As an optimization, walk each rq's runnable_list instead of * the scx_tasks list. *
@@ -4473,22 +4459,23 @@ static void scx_bypass(bool bypass)*/ for_each_possible_cpu (cpu) {struct rq *rq =cpu_rq (cpu); +struct scx_sched_pcpu *pcpu =per_cpu_ptr (sch->pcpu, cpu);struct task_struct *p, *n;raw_spin_rq_lock (rq);if (bypass) { -WARN_ON_ONCE (rq->scx.flags &SCX_RQ_BYPASSING ); - rq->scx.flags |=SCX_RQ_BYPASSING ; +WARN_ON_ONCE (pcpu->flags &SCX_SCHED_PCPU_BYPASSING ); + pcpu->flags |=SCX_SCHED_PCPU_BYPASSING ; }else { -WARN_ON_ONCE (!(rq->scx.flags &SCX_RQ_BYPASSING )); - rq->scx.flags &= ~SCX_RQ_BYPASSING ; +WARN_ON_ONCE (!(pcpu->flags &SCX_SCHED_PCPU_BYPASSING )); + pcpu->flags &= ~SCX_SCHED_PCPU_BYPASSING ; }/* * We need to guarantee that no tasks are on the BPF scheduler * while bypassing. Either we see enabled or the enable path -* sees scx_rq_bypassing() before moving tasks to SCX. +* sees scx_bypassing() before moving tasks to SCX. */ if (!scx_enabled ()) {raw_spin_rq_unlock (rq);
@@ -4658,7 +4645,7 @@ static void scx_root_disable(struct scx_sched *sch)int cpu;/* guarantee forward progress and wait for descendants to be disabled */ -scx_bypass (true); +scx_bypass (sch, true);drain_descendants (sch);switch (scx_set_enable_state (SCX_DISABLING )) {
@@ -4783,16 +4770,11 @@ static void scx_root_disable(struct scx_sched *sch) scx_dsp_max_batch =0 ;free_kick_syncs (); -if (scx_bypassed_for_enable) { - scx_bypassed_for_enable = false; -scx_bypass (false); - } -mutex_unlock (&scx_enable_mutex);WARN_ON_ONCE (scx_set_enable_state (SCX_DISABLED ) !=SCX_DISABLING ); done: -scx_bypass (false); +scx_bypass (sch, false); }/*
@@ -5306,6 +5288,7 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops,atomic_set (&sch->exit_kind,SCX_EXIT_NONE );init_irq_work (&sch->error_irq_work, scx_error_irq_workfn);kthread_init_work (&sch->disable_work, scx_disable_workfn); +timer_setup (&sch->bypass_lb_timer, scx_bypass_lb_timerfn,0 ); sch->ops = *ops;rcu_assign_pointer (ops->priv, sch);
@@ -5542,8 +5525,7 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)* scheduling) may not function correctly before all tasks are switched. * Init in bypass mode to guarantee forward progress. */ -scx_bypass (true); - scx_bypassed_for_enable = true; +scx_bypass (sch, true);for (i =SCX_OPI_NORMAL_BEGIN ; i <SCX_OPI_NORMAL_END ; i++)if (((void (**)(void ))ops)[i])
@@ -5643,8 +5625,7 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)scx_task_iter_stop (&sti);percpu_up_write (&scx_fork_rwsem); - scx_bypassed_for_enable = false; -scx_bypass (false); +scx_bypass (sch, false);if (!scx_tryset_enable_state (SCX_ENABLED ,SCX_ENABLING )) {WARN_ON_ONCE (atomic_read (&sch->exit_kind) ==SCX_EXIT_NONE );
@@ -6358,6 +6339,14 @@ void print_scx_info(const char *log_lvl, struct task_struct *p)static int scx_pm_handler (struct notifier_block *nb,unsigned long event,void *ptr) { +struct scx_sched *sch; + +guard (rcu)(); + + sch =rcu_dereference (scx_root); +if (!sch) +return NOTIFY_OK ; +/* * SCX schedulers often have userspace components which are sometimes * involved in critial scheduling paths. PM operations involve freezing
@@ -6368,12 +6357,12 @@ static int scx_pm_handler(struct notifier_block *nb, unsigned long event, void *case PM_HIBERNATION_PREPARE :case PM_SUSPEND_PREPARE :case PM_RESTORE_PREPARE : -scx_bypass (true); +scx_bypass (sch, true);break ;case PM_POST_HIBERNATION :case PM_POST_SUSPEND :case PM_POST_RESTORE : -scx_bypass (false); +scx_bypass (sch, false);break ; }
@@ -7189,7 +7178,7 @@ static void scx_kick_cpu(struct scx_sched *sch, s32 cpu, u64 flags)* lead to irq_work_queue() malfunction such as infinite busy wait for * IRQ status update. Suppress kicking. */ -if (scx_rq_bypassing (this_rq)) +if (scx_bypassing (sch,cpu_of (this_rq))) goto out;/*
kernel/sched/ext_idle.c +2 -1
@@ -768,7 +768,8 @@ void __scx_update_idle(struct rq *rq, bool idle, bool do_notify)* either enqueue() sees the idle bit or update_idle() sees the task * that enqueue() queued. */ -if (SCX_HAS_OP (sch, update_idle) && do_notify && !scx_rq_bypassing (rq)) +if (SCX_HAS_OP (sch, update_idle) && do_notify && + !scx_bypassing (sch,cpu_of (rq)))SCX_CALL_OP (sch,SCX_KF_REST , update_idle, rq,cpu_of (rq), idle); }
kernel/sched/ext_internal.h +12 -2
@@ -925,7 +925,13 @@ struct scx_event_stats {s64 SCX_EV_INSERT_NOT_OWNED ; }; +enum scx_sched_pcpu_flags { +SCX_SCHED_PCPU_BYPASSING =1LLU <<0 , +}; +struct scx_sched_pcpu { +u64 flags;/* protected by rq lock */ +/* * The event counters are in a per-CPU variable to minimize the * accounting overhead. A system-wide view on the event counter is
@@ -953,6 +959,8 @@ struct scx_sched {struct scx_sched_pcpu __percpu *pcpu;u64 slice_dfl; +u64 bypass_timestamp; +s32 bypass_depth;bool aborting;s32 level;
@@ -984,6 +992,7 @@ struct scx_sched {struct kthread_worker *helper;struct irq_work error_irq_work;struct kthread_work disable_work; +struct timer_list bypass_lb_timer;struct rcu_work rcu_work;/* all ancestors including self */
@@ -1175,9 +1184,10 @@ static inline bool scx_kf_allowed_if_unlocked(void)return !current->scx.kf_mask; } -static inline bool scx_rq_bypassing (struct rq *rq) +static inline bool scx_bypassing (struct scx_sched *sch,s32 cpu) { -return unlikely (rq->scx.flags &SCX_RQ_BYPASSING ); +return unlikely (per_cpu_ptr (sch->pcpu, cpu)->flags & +SCX_SCHED_PCPU_BYPASSING ); }#ifdef CONFIG_EXT_SUB_SCHED
kernel/sched/sched.h +0 -1
@@ -782,7 +782,6 @@ enum scx_rq_flags {SCX_RQ_ONLINE =1 <<0 ,SCX_RQ_CAN_STOP_TICK =1 <<1 ,SCX_RQ_BAL_KEEP =1 <<3 ,/* balance decided to keep current */ -SCX_RQ_BYPASSING =1 <<4 ,SCX_RQ_CLK_VALID =1 <<5 ,/* RQ clock is fresh and valid */ SCX_RQ_BAL_CB_PENDING = 1 << 6, /* must queue a cb after dispatching */
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 19/34 sched_ext: Prepare bypass mode for hierarchical operation
In reply to Tejun HeoBypass mode is used to simplify enable and disable paths and guarantee forward progress when something goes wrong. When enabled, all tasks skip BPF scheduling and fall back to simple in-kernel FIFO scheduling. While this global behavior can be used as-is when dealing with sub-scheds, that would allow any sub-sched instance to affect the whole system in a significantly disruptive manner.
Make bypass state hierarchical by propagating it to descendants and updating per-cpu flags accordingly. This allows an scx_sched to bypass if itself or any of its ancestors are in bypass mode. However, this doesn't make the actual bypass enqueue and dispatch paths hierarchical yet. That will be done in later patches.
--- kernel/sched/ext.c | 85 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 22 deletions(-)
kernel/sched/ext.c +63 -22
@@ -41,6 +41,7 @@ static DEFINE_MUTEX(scx_enable_mutex);DEFINE_STATIC_KEY_FALSE (__scx_enabled);DEFINE_STATIC_PERCPU_RWSEM (scx_fork_rwsem);static atomic_t scx_enable_state_var =ATOMIC_INIT (SCX_DISABLED ); +static DEFINE_RAW_SPINLOCK (scx_bypass_lock);static cpumask_var_t scx_bypass_lb_donee_cpumask;static cpumask_var_t scx_bypass_lb_resched_cpumask;static bool scx_init_task_enabled;
@@ -4381,6 +4382,36 @@ static void scx_bypass_lb_timerfn(struct timer_list *timer)mod_timer (timer, jiffies +usecs_to_jiffies (intv_us)); } +static bool inc_bypass_depth (struct scx_sched *sch) +{ +lockdep_assert_held (&scx_bypass_lock); + +WARN_ON_ONCE (sch->bypass_depth <0 ); +WRITE_ONCE (sch->bypass_depth, sch->bypass_depth +1 ); +if (sch->bypass_depth !=1 ) +return false; + +WRITE_ONCE (sch->slice_dfl, scx_slice_bypass_us *NSEC_PER_USEC ); + sch->bypass_timestamp =ktime_get_ns (); +scx_add_event (sch,SCX_EV_BYPASS_ACTIVATE ,1 ); +return true; +} + +static bool dec_bypass_depth (struct scx_sched *sch) +{ +lockdep_assert_held (&scx_bypass_lock); + +WARN_ON_ONCE (sch->bypass_depth <1 ); +WRITE_ONCE (sch->bypass_depth, sch->bypass_depth -1 ); +if (sch->bypass_depth !=0 ) +return false; + +WRITE_ONCE (sch->slice_dfl,SCX_SLICE_DFL ); +scx_add_event (sch,SCX_EV_BYPASS_DURATION , +ktime_get_ns () - sch->bypass_timestamp); +return true; +} +/** * scx_bypass - [Un]bypass scx_ops and guarantee forward progress * @sch: sched to bypass
@@ -4415,22 +4446,17 @@ static void scx_bypass_lb_timerfn(struct timer_list *timer)*/ static void scx_bypass (struct scx_sched *sch,bool bypass) { -static DEFINE_RAW_SPINLOCK (bypass_lock); +struct scx_sched *pos;unsigned long flags;int cpu; -raw_spin_lock_irqsave (&bypass_lock, flags); +raw_spin_lock_irqsave (&scx_bypass_lock, flags);if (bypass) {u32 intv_us; -WRITE_ONCE (sch->bypass_depth, sch->bypass_depth +1 ); -WARN_ON_ONCE (sch->bypass_depth <=0 ); -if (sch->bypass_depth !=1 ) +if (!inc_bypass_depth (sch)) goto unlock; -WRITE_ONCE (sch->slice_dfl, scx_slice_bypass_us *NSEC_PER_USEC ); - sch->bypass_timestamp =ktime_get_ns (); -scx_add_event (sch,SCX_EV_BYPASS_ACTIVATE ,1 ); intv_us =READ_ONCE (scx_bypass_lb_intv_us);if (intv_us && !timer_pending (&sch->bypass_lb_timer)) {
@@ -4439,15 +4465,25 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)add_timer_global (&sch->bypass_lb_timer); } }else { -WRITE_ONCE (sch->bypass_depth, sch->bypass_depth -1 ); -WARN_ON_ONCE (sch->bypass_depth <0 ); -if (sch->bypass_depth !=0 ) +if (!dec_bypass_depth (sch)) goto unlock; -WRITE_ONCE (sch->slice_dfl,SCX_SLICE_DFL ); -scx_add_event (sch,SCX_EV_BYPASS_DURATION , -ktime_get_ns () - sch->bypass_timestamp); } +/* +* Bypass state is propagated to all descendants - an scx_sched bypasses +* if itself or any of its ancestors are in bypass mode. +*/ +raw_spin_lock (&scx_sched_lock); +scx_for_each_descendant_pre (pos, sch) { +if (pos == sch) +continue ; +if (bypass) +inc_bypass_depth (pos); +else +dec_bypass_depth (pos); + } +raw_spin_unlock (&scx_sched_lock); +/* * No task property is changing. We just need to make sure all currently * queued tasks are re-queued according to the new scx_bypassing()
@@ -4459,18 +4495,20 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)*/ for_each_possible_cpu (cpu) {struct rq *rq =cpu_rq (cpu); -struct scx_sched_pcpu *pcpu =per_cpu_ptr (sch->pcpu, cpu);struct task_struct *p, *n;raw_spin_rq_lock (rq); -if (bypass) { -WARN_ON_ONCE (pcpu->flags &SCX_SCHED_PCPU_BYPASSING ); - pcpu->flags |=SCX_SCHED_PCPU_BYPASSING ; - }else { -WARN_ON_ONCE (!(pcpu->flags &SCX_SCHED_PCPU_BYPASSING )); - pcpu->flags &= ~SCX_SCHED_PCPU_BYPASSING ; +raw_spin_lock (&scx_sched_lock); +scx_for_each_descendant_pre (pos, sch) { +struct scx_sched_pcpu *pcpu =per_cpu_ptr (pos->pcpu, cpu); + +if (pos->bypass_depth) + pcpu->flags |=SCX_SCHED_PCPU_BYPASSING ; +else + pcpu->flags &= ~SCX_SCHED_PCPU_BYPASSING ; } +raw_spin_unlock (&scx_sched_lock);/* * We need to guarantee that no tasks are on the BPF scheduler
@@ -4491,6 +4529,9 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)*/ list_for_each_entry_safe_reverse (p, n, &rq->scx.runnable_list, scx.runnable_node) { +if (!scx_is_descendant (scx_task_sched (p), sch)) +continue ; +/* cycling deq/enq is enough, see the function comment */ scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {/* nothing */ ;
@@ -4505,7 +4546,7 @@ static void scx_bypass(struct scx_sched *sch, bool bypass) } unlock: -raw_spin_unlock_irqrestore (&bypass_lock, flags); +raw_spin_unlock_irqrestore (&scx_bypass_lock, flags); }static void free_exit_info (struct scx_exit_info *ei)
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 20/34 sched_ext: Factor out scx_dispatch_sched()
In reply to Tejun HeoIn preparation of multiple scheduler support, factor out scx_dispatch_sched() from balance_one(). The function boundary makes remembering $prev_on_scx and $prev_on_rq less useful. Open code $prev_on_scx in balance_one() and $prev_on_rq in both balance_one() and scx_dispatch_sched().
No functional changes.
--- kernel/sched/ext.c | 123 ++++++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 58 deletions(-)
kernel/sched/ext.c +65 -58
@@ -2384,67 +2384,22 @@ static inline void maybe_queue_balance_callback(struct rq *rq) rq->scx.flags &= ~SCX_RQ_BAL_CB_PENDING ; } -static int balance_one (struct rq *rq,struct task_struct *prev) +static bool scx_dispatch_sched (struct scx_sched *sch,struct rq *rq, +struct task_struct *prev) { -struct scx_sched *sch = scx_root;struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx);bool prev_on_scx = prev->sched_class == &ext_sched_class; -bool prev_on_rq = prev->scx.flags &SCX_TASK_QUEUED ;int nr_loops =SCX_DSP_MAX_LOOPS ;s32 cpu =cpu_of (rq); -lockdep_assert_rq_held (rq); - rq->scx.flags |=SCX_RQ_IN_BALANCE ; - rq->scx.flags &= ~SCX_RQ_BAL_KEEP ; - -if ((sch->ops.flags &SCX_OPS_HAS_CPU_PREEMPT ) && -unlikely (rq->scx.cpu_released)) { -/* -* If the previous sched_class for the current CPU was not SCX, -* notify the BPF scheduler that it again has control of the -* core. This callback complements ->cpu_release(), which is -* emitted in switch_class(). -*/ -if (SCX_HAS_OP (sch, cpu_acquire)) -SCX_CALL_OP (sch,SCX_KF_REST , cpu_acquire, rq, cpu,NULL ); - rq->scx.cpu_released = false; - } - -if (prev_on_scx) { -update_curr_scx (rq); - -/* -* If @prev is runnable & has slice left, it has priority and -* fetching more just increases latency for the fetched tasks. -* Tell pick_task_scx() to keep running @prev. If the BPF -* scheduler wants to handle this explicitly, it should -* implement ->cpu_release(). -* -* See scx_disable_workfn() for the explanation on the bypassing -* test. -*/ -if (prev_on_rq && prev->scx.slice && !scx_bypassing (sch, cpu)) { - rq->scx.flags |=SCX_RQ_BAL_KEEP ; - goto has_tasks; - } - } - -/* if there already are tasks to run, nothing to do */ -if (rq->scx.local_dsq.nr) - goto has_tasks; -if (consume_global_dsq (sch, rq)) - goto has_tasks; +return true; -if (scx_bypassing (sch, cpu)) { -if (consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu))) - goto has_tasks; -else - goto no_tasks; - } +if (scx_bypassing (sch, cpu)) +return consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu));if (unlikely (!SCX_HAS_OP (sch, dispatch)) || !scx_rq_online (rq)) - goto no_tasks; +return false; dspc->rq = rq;
@@ -2463,14 +2418,14 @@ static int balance_one(struct rq *rq, struct task_struct *prev)flush_dispatch_buf (sch, rq); -if (prev_on_rq && prev->scx.slice) { +if ((prev->scx.flags &SCX_TASK_QUEUED ) && prev->scx.slice) { rq->scx.flags |=SCX_RQ_BAL_KEEP ; - goto has_tasks; +return true; }if (rq->scx.local_dsq.nr) - goto has_tasks; +return true;if (consume_global_dsq (sch, rq)) - goto has_tasks; +return true;/* * ops.dispatch() can trap us in this loop by repeatedly
@@ -2479,7 +2434,7 @@ static int balance_one(struct rq *rq, struct task_struct *prev)* balance(), we want to complete this scheduling cycle and then * start a new one. IOW, we want to call resched_curr() on the * next, most likely idle, task, not the current one. Use -* scx_kick_cpu() for deferred kicking. +* __scx_bpf_kick_cpu() for deferred kicking. */ if (unlikely (!--nr_loops)) {scx_kick_cpu (sch, cpu,0 );
@@ -2487,12 +2442,64 @@ static int balance_one(struct rq *rq, struct task_struct *prev) } }while (dspc->nr_tasks); -no_tasks: +return false; +} + +static int balance_one (struct rq *rq,struct task_struct *prev) +{ +struct scx_sched *sch = scx_root; +s32 cpu =cpu_of (rq); + +lockdep_assert_rq_held (rq); + rq->scx.flags |=SCX_RQ_IN_BALANCE ; + rq->scx.flags &= ~SCX_RQ_BAL_KEEP ; + +if ((sch->ops.flags &SCX_OPS_HAS_CPU_PREEMPT ) && +unlikely (rq->scx.cpu_released)) { +/* +* If the previous sched_class for the current CPU was not SCX, +* notify the BPF scheduler that it again has control of the +* core. This callback complements ->cpu_release(), which is +* emitted in switch_class(). +*/ +if (SCX_HAS_OP (sch, cpu_acquire)) +SCX_CALL_OP (sch,SCX_KF_REST , cpu_acquire, rq, cpu,NULL ); + rq->scx.cpu_released = false; + } + +if (prev->sched_class == &ext_sched_class) { +update_curr_scx (rq); + +/* +* If @prev is runnable & has slice left, it has priority and +* fetching more just increases latency for the fetched tasks. +* Tell pick_task_scx() to keep running @prev. If the BPF +* scheduler wants to handle this explicitly, it should +* implement ->cpu_release(). +* +* See scx_disable_workfn() for the explanation on the bypassing +* test. +*/ +if ((prev->scx.flags &SCX_TASK_QUEUED ) && prev->scx.slice && + !scx_bypassing (sch, cpu)) { + rq->scx.flags |=SCX_RQ_BAL_KEEP ; + goto has_tasks; + } + } + +/* if there already are tasks to run, nothing to do */ +if (rq->scx.local_dsq.nr) + goto has_tasks; + +/* dispatch @sch */ +if (scx_dispatch_sched (sch, rq, prev)) + goto has_tasks; +/* * Didn't find another task to run. Keep running @prev unless * %SCX_OPS_ENQ_LAST is in effect. */ -if (prev_on_rq && +if ((prev->scx.flags &SCX_TASK_QUEUED ) && (!(sch->ops.flags &SCX_OPS_ENQ_LAST ) ||scx_bypassing (sch, cpu))) { rq->scx.flags |=SCX_RQ_BAL_KEEP ;__scx_add_event (sch,SCX_EV_DISPATCH_KEEP_LAST ,1 );
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 21/34 sched_ext: When calling ops.dispatch() @prev must be on the same scx_sched
In reply to Tejun HeoThe @prev parameter passed into ops.dispatch() is expected to be on the same sched. Passing in @prev which isn't on the sched can spuriously trigger failures that can kill the scheduler. Pass in @prev iff it's on the same sched.
--- kernel/sched/ext.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/ext.c +3 -2
@@ -2388,9 +2388,10 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, struct task_struct *prev) {struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx); -bool prev_on_scx = prev->sched_class == &ext_sched_class;int nr_loops =SCX_DSP_MAX_LOOPS ;s32 cpu =cpu_of (rq); +bool prev_on_sch = (prev->sched_class == &ext_sched_class) && + sch ==rcu_access_pointer (prev->scx.sched);if (consume_global_dsq (sch, rq))return true;
@@ -2414,7 +2415,7 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, dspc->nr_tasks =0 ;SCX_CALL_OP (sch,SCX_KF_DISPATCH , dispatch, rq, cpu, - prev_on_scx ? prev :NULL ); + prev_on_sch ? prev :NULL );flush_dispatch_buf (sch, rq);
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 22/34 sched_ext: Separate bypass dispatch enabling from bypass depth tracking
In reply to Tejun HeoThe bypass_depth field tracks nesting of bypass operations but is also used to determine whether the bypass dispatch path should be active. With hierarchical scheduling, child schedulers may need to activate their parent's bypass dispatch path without affecting the parent's bypass_depth, requiring separation of these concerns.
Add bypass_dsp_enable_depth and bypass_dsp_claim to independently control bypass dispatch path activation. The new enable_bypass_dsp() and disable_bypass_dsp() functions manage this state with proper claim semantics to prevent races. The bypass dispatch path now only activates when bypass_dsp_enabled() returns true, which checks the new enable_depth counter.
The disable operation is carefully ordered after all tasks are moved out of bypass DSQs to ensure they are drained before the dispatch path is disabled. During scheduler teardown, disable_bypass_dsp() is called explicitly to ensure cleanup even if bypass mode was never entered normally.
--- kernel/sched/ext.c | 74 ++++++++++++++++++++++++++++++++----- kernel/sched/ext_internal.h | 5 +++ 2 files changed, 69 insertions(+), 10 deletions(-)
kernel/sched/ext.c +64 -10
@@ -357,6 +357,26 @@ static struct scx_dispatch_q *bypass_dsq(struct scx_sched *sch, s32 cpu)return &per_cpu_ptr (sch->pcpu, cpu)->bypass_dsq; } +/** +* bypass_dsp_enabled - Check if bypass dispatch path is enabled +* @sch: scheduler to check +* +* When a descendant scheduler enters bypass mode, bypassed tasks are scheduled +* by the nearest non-bypassing ancestor, or the root scheduler if all ancestors +* are bypassing. In the former case, the ancestor is not itself bypassing but +* its bypass DSQs will be populated with bypassed tasks from descendants. Thus, +* the ancestor's bypass dispatch path must be active even though its own +* bypass_depth remains zero. +* +* This function checks bypass_dsp_enable_depth which is managed separately from +* bypass_depth to enable this decoupling. See enable_bypass_dsp() and +* disable_bypass_dsp(). +*/ +static bool bypass_dsp_enabled (struct scx_sched *sch) +{ +return unlikely (atomic_read (&sch->bypass_dsp_enable_depth)); +} +/* * scx_kf_mask enforcement. Some kfuncs can only be called from specific SCX * ops. When invoking SCX ops, SCX_CALL_OP[_RET]() should be used to indicate
@@ -2396,7 +2416,7 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,if (consume_global_dsq (sch, rq))return true; -if (scx_bypassing (sch, cpu)) +if (bypass_dsp_enabled (sch) &&scx_bypassing (sch, cpu))return consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu));if (unlikely (!SCX_HAS_OP (sch, dispatch)) || !scx_rq_online (rq))
@@ -4379,7 +4399,7 @@ static void scx_bypass_lb_timerfn(struct timer_list *timer)int node;u32 intv_us; -if (!READ_ONCE (sch->bypass_depth)) +if (!bypass_dsp_enabled (sch))return ;for_each_node_with_cpus (node)
@@ -4420,6 +4440,42 @@ static bool dec_bypass_depth(struct scx_sched *sch)return true; } +static void enable_bypass_dsp (struct scx_sched *sch) +{ +u32 intv_us =READ_ONCE (scx_bypass_lb_intv_us); +s32 ret; + +/* +* @sch->bypass_depth transitioning from 0 to 1 triggers enabling. +* Shouldn't stagger. +*/ +if (WARN_ON_ONCE (test_and_set_bit (0 , &sch->bypass_dsp_claim))) +return ; + +/* +* The LB timer will stop running if bypass_arm_depth is 0. Increment +* before starting the LB timer. +*/ + ret =atomic_inc_return (&sch->bypass_dsp_enable_depth); +WARN_ON_ONCE (ret <=0 ); + +if (intv_us && !timer_pending (&sch->bypass_lb_timer)) +mod_timer (&sch->bypass_lb_timer, + jiffies +usecs_to_jiffies (intv_us)); +} + +/* may be called without holding scx_bypass_lock */ +static void disable_bypass_dsp (struct scx_sched *sch) +{ +s32 ret; + +if (!test_and_clear_bit (0 , &sch->bypass_dsp_claim)) +return ; + + ret =atomic_dec_return (&sch->bypass_dsp_enable_depth); +WARN_ON_ONCE (ret <0 ); +} +/** * scx_bypass - [Un]bypass scx_ops and guarantee forward progress * @sch: sched to bypass
@@ -4461,17 +4517,10 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)raw_spin_lock_irqsave (&scx_bypass_lock, flags);if (bypass) { -u32 intv_us; -if (!inc_bypass_depth (sch)) goto unlock; - intv_us =READ_ONCE (scx_bypass_lb_intv_us); -if (intv_us && !timer_pending (&sch->bypass_lb_timer)) { - sch->bypass_lb_timer.expires = - jiffies +usecs_to_jiffies (intv_us); -add_timer_global (&sch->bypass_lb_timer); - } +enable_bypass_dsp (sch); }else {if (!dec_bypass_depth (sch)) goto unlock;
@@ -4553,6 +4602,9 @@ static void scx_bypass(struct scx_sched *sch, bool bypass)raw_spin_rq_unlock (rq); } +/* disarming must come after moving all tasks out of the bypass DSQs */ +if (!bypass) +disable_bypass_dsp (sch); unlock:raw_spin_unlock_irqrestore (&scx_bypass_lock, flags); }
@@ -4654,6 +4706,8 @@ static void scx_sub_disable(struct scx_sched *sch)scx_cgroup_unlock ();percpu_up_write (&scx_fork_rwsem); +disable_bypass_dsp (sch); +raw_spin_lock_irq (&scx_sched_lock);list_del_init (&sch->sibling);list_del_rcu (&sch->all);
kernel/sched/ext_internal.h +5 -0
@@ -961,6 +961,11 @@ struct scx_sched {u64 slice_dfl;u64 bypass_timestamp;s32 bypass_depth; + +/* bypass dispatch path enable state, see bypass_dsp_enabled() */ +unsigned long bypass_dsp_claim; +atomic_t bypass_dsp_enable_depth; +bool aborting;s32 level;
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 23/34 sched_ext: Implement hierarchical bypass mode
In reply to Tejun HeoWhen a sub-scheduler enters bypass mode, its tasks must be scheduled by an ancestor to guarantee forward progress. Tasks from bypassing descendants are queued in the bypass DSQs of the nearest non-bypassing ancestor, or the root scheduler if all ancestors are bypassing. This requires coordination between bypassing schedulers and their hosts.
Add bypass_enq_target_dsq() to find the correct bypass DSQ by walking up the hierarchy until reaching a non-bypassing ancestor. When a sub-scheduler starts bypassing, all its runnable tasks are re-enqueued after scx_bypassing() is set, ensuring proper migration to ancestor bypass DSQs.
Update scx_dispatch_sched() to handle hosting bypassed descendants. When a scheduler is not bypassing but has bypassing descendants, it must schedule both its own tasks and bypassed descendant tasks. A simple policy is implemented where every Nth dispatch attempt (SCX_BYPASS_HOST_NTH=2) consumes from the bypass DSQ. A fallback consumption is also added at the end of dispatch to ensure bypassed tasks make progress even when normal scheduling is idle.
Update enable_bypass_dsp() and disable_bypass_dsp() to increment bypass_dsp_enable_depth on both the bypassing scheduler and its parent host, ensuring both can detect that bypass dispatch is active through bypass_dsp_enabled().
Add SCX_EV_SUB_BYPASS_DISPATCH event counter to track scheduling of bypassed descendant tasks.
--- kernel/sched/ext.c | 96 ++++++++++++++++++++++++++++++++++--- kernel/sched/ext_internal.h | 11 +++++ 2 files changed, 100 insertions(+), 7 deletions(-)
kernel/sched/ext.c +89 -7
@@ -357,6 +357,27 @@ static struct scx_dispatch_q *bypass_dsq(struct scx_sched *sch, s32 cpu)return &per_cpu_ptr (sch->pcpu, cpu)->bypass_dsq; } +static struct scx_dispatch_q *bypass_enq_target_dsq (struct scx_sched *sch,s32 cpu) +{ +#ifdef CONFIG_EXT_SUB_SCHED +/* +* If @sch is a sub-sched which is bypassing, its tasks should go into +* the bypass DSQs of the nearest ancestor which is not bypassing. The +* not-bypassing ancestor is responsible for scheduling all tasks from +* bypassing sub-trees. If all ancestors including root are bypassing, +* @p should go to the root's bypass DSQs. +* +* Whenever a sched starts bypassing, all runnable tasks in its subtree +* are re-enqueued after scx_bypassing() is turned on, guaranteeing that +* all tasks are transferred to the right DSQs. +*/ +while (scx_parent (sch) &&scx_bypassing (sch, cpu)) + sch =scx_parent (sch); +#endif /* CONFIG_EXT_SUB_SCHED */ + +return bypass_dsq (sch, cpu); +} +/** * bypass_dsp_enabled - Check if bypass dispatch path is enabled * @sch: scheduler to check
@@ -1646,7 +1667,7 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags, dsq =find_global_dsq (sch, p); goto enqueue; bypass: - dsq =bypass_dsq (sch,task_cpu (p)); + dsq =bypass_enq_target_dsq (sch,task_cpu (p)); goto enqueue; enqueue:
@@ -2416,8 +2437,31 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,if (consume_global_dsq (sch, rq))return true; -if (bypass_dsp_enabled (sch) &&scx_bypassing (sch, cpu)) -return consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu)); +if (bypass_dsp_enabled (sch)) { +struct scx_sched_pcpu *pcpu =per_cpu_ptr (sch->pcpu, cpu); + +/* if @sch is bypassing, only the bypass DSQs are active */ +if (scx_bypassing (sch, cpu)) +return consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu)); + +/* +* If @sch isn't bypassing but its children are, @sch is +* responsible for making forward progress for both its own +* tasks that aren't bypassing and the bypassing descendants' +* tasks. The following implements a simple built-in behavior - +* let each CPU try to run the bypass DSQ every Nth time. +* +* Later, if necessary, we can add an ops flag to suppress the +* auto-consumption and a kfunc to consume the bypass DSQ and, +* so that the BPF scheduler can fully control scheduling of +* bypassed tasks. +*/ +if (!(pcpu->bypass_host_seq++ %SCX_BYPASS_HOST_NTH ) && +consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu))) { +__scx_add_event (sch,SCX_EV_SUB_BYPASS_DISPATCH ,1 ); +return true; + } + }if (unlikely (!SCX_HAS_OP (sch, dispatch)) || !scx_rq_online (rq))return false;
@@ -2463,6 +2507,14 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, } }while (dspc->nr_tasks); +/* +* Prevent the CPU from going idle while bypassed descendants have tasks +* queued. Without this fallback, bypassed tasks could stall if the host +* scheduler's ops.dispatch() doesn't yield any tasks. +*/ +if (bypass_dsp_enabled (sch)) +return consume_dispatch_q (sch, rq,bypass_dsq (sch, cpu)); +return false; }
@@ -4069,6 +4121,7 @@ static ssize_t scx_attr_events_show(struct kobject *kobj, at +=scx_attr_event_show (buf, at, &events,SCX_EV_BYPASS_DISPATCH ); at +=scx_attr_event_show (buf, at, &events,SCX_EV_BYPASS_ACTIVATE ); at +=scx_attr_event_show (buf, at, &events,SCX_EV_INSERT_NOT_OWNED ); + at +=scx_attr_event_show (buf, at, &events,SCX_EV_SUB_BYPASS_DISPATCH );return at; }SCX_ATTR (events);
@@ -4429,6 +4482,7 @@ static bool dec_bypass_depth(struct scx_sched *sch) {lockdep_assert_held (&scx_bypass_lock); +WARN_ON_ONCE (sch->bypass_depth <1 );WRITE_ONCE (sch->bypass_depth, sch->bypass_depth -1 );if (sch->bypass_depth !=0 )
@@ -4442,6 +4496,7 @@ static bool dec_bypass_depth(struct scx_sched *sch)static void enable_bypass_dsp (struct scx_sched *sch) { +struct scx_sched *host =scx_parent (sch) ?: sch;u32 intv_us =READ_ONCE (scx_bypass_lb_intv_us);s32 ret;
@@ -4453,14 +4508,35 @@ static void enable_bypass_dsp(struct scx_sched *sch)return ;/* -* The LB timer will stop running if bypass_arm_depth is 0. Increment -* before starting the LB timer. +* When a sub-sched bypasses, its tasks are queued on the bypass DSQs of +* the nearest non-bypassing ancestor or root. As enable_bypass_dsp() is +* called iff @sch is not already bypassed due to an ancestor bypassing, +* we can assume that the parent is not bypassing and thus will be the +* host of the bypass DSQs. +* +* While the situation may change in the future, the following +* guarantees that the nearest non-bypassing ancestor or root has bypass +* dispatch enabled while a descendant is bypassing, which is all that's +* required. +* +* bypass_dsp_enabled() test is used to detemrine whether to enter the +* bypass dispatch handling path from both bypassing and hosting scheds. +* Bump enable depth on both @sch and bypass dispatch host. */ ret =atomic_inc_return (&sch->bypass_dsp_enable_depth);WARN_ON_ONCE (ret <=0 ); -if (intv_us && !timer_pending (&sch->bypass_lb_timer)) -mod_timer (&sch->bypass_lb_timer, +if (host != sch) { + ret =atomic_inc_return (&host->bypass_dsp_enable_depth); +WARN_ON_ONCE (ret <=0 ); + } + +/* +* The LB timer will stop running if bypass dispatch is disabled. Start +* after enabling bypass dispatch. +*/ +if (intv_us && !timer_pending (&host->bypass_lb_timer)) +mod_timer (&host->bypass_lb_timer, jiffies +usecs_to_jiffies (intv_us)); }
@@ -4474,6 +4550,11 @@ static void disable_bypass_dsp(struct scx_sched *sch) ret =atomic_dec_return (&sch->bypass_dsp_enable_depth);WARN_ON_ONCE (ret <0 ); + +if (scx_parent (sch)) { + ret =atomic_dec_return (&scx_parent (sch)->bypass_dsp_enable_depth); +WARN_ON_ONCE (ret <0 ); + } }/**
@@ -5248,6 +5329,7 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len)scx_dump_event (s, &events,SCX_EV_BYPASS_DISPATCH );scx_dump_event (s, &events,SCX_EV_BYPASS_ACTIVATE );scx_dump_event (s, &events,SCX_EV_INSERT_NOT_OWNED ); +scx_dump_event (s, &events,SCX_EV_SUB_BYPASS_DISPATCH );if (seq_buf_has_overflowed (&s) && dump_len >=sizeof (trunc_marker)) memcpy(ei->dump + dump_len - sizeof(trunc_marker),
kernel/sched/ext_internal.h +11 -0
@@ -24,6 +24,8 @@ enum scx_consts {*/ SCX_TASK_ITER_BATCH =32 , +SCX_BYPASS_HOST_NTH =2 , +SCX_BYPASS_LB_DFL_INTV_US =500 *USEC_PER_MSEC ,SCX_BYPASS_LB_DONOR_PCT =125 , SCX_BYPASS_LB_MIN_DELTA_DIV = 4,
@@ -923,6 +925,12 @@ struct scx_event_stats {* scheduler. */ s64 SCX_EV_INSERT_NOT_OWNED ; + +/* +* The number of times tasks from bypassing descendants are scheduled +* from sub_bypass_dsq's. +*/ +s64 SCX_EV_SUB_BYPASS_DISPATCH ; };enum scx_sched_pcpu_flags {
@@ -940,6 +948,9 @@ struct scx_sched_pcpu {struct scx_event_stats event_stats;struct scx_dispatch_q bypass_dsq; +#ifdef CONFIG_EXT_SUB_SCHED +u32 bypass_host_seq; +#endif };struct scx_sched {
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 24/34 sched_ext: Dispatch from all scx_sched instances
In reply to Tejun HeoThe cgroup sub-sched support involves invasive changes to many areas of sched_ext. The overall scaffolding is now in place and the next step is implementing sub-sched enable/disable.
To enable partial testing and verification, update balance_one() to dispatch from all scx_sched instances until it finds a task to run. This should keep scheduling working when sub-scheds are enabled with tasks on them. This will be replaced by BPF-driven hierarchical operation.
--- kernel/sched/ext.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-)
kernel/sched/ext.c +8 -4
@@ -2520,7 +2520,7 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,static int balance_one (struct rq *rq,struct task_struct *prev) { -struct scx_sched *sch = scx_root; +struct scx_sched *sch = scx_root, *pos;s32 cpu =cpu_of (rq);lockdep_assert_rq_held (rq);
@@ -2564,9 +2564,13 @@ static int balance_one(struct rq *rq, struct task_struct *prev)if (rq->scx.local_dsq.nr) goto has_tasks; -/* dispatch @sch */ -if (scx_dispatch_sched (sch, rq, prev)) - goto has_tasks; +/* +* TEMPORARY - Dispatch all scheds. This will be replaced by BPF-driven +* hierarchical operation. +*/ +list_for_each_entry_rcu (pos, &scx_sched_all, all) +if (scx_dispatch_sched (pos, rq, prev)) + goto has_tasks;/* * Didn't find another task to run. Keep running @prev unless
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 25/34 sched_ext: Move scx_dsp_ctx and scx_dsp_max_batch into scx_sched
In reply to Tejun Heoscx_dsp_ctx and scx_dsp_max_batch are global variables used in the dispatch path. In prepration for multiple scheduler support, move the former into scx_sched_pcpu and the latter into scx_sched. No user-visible behavior changes intended.
--- kernel/sched/ext.c | 55 ++++++++++--------------------------- kernel/sched/ext_internal.h | 19 +++++++++++++ 2 files changed, 34 insertions(+), 40 deletions(-)
kernel/sched/ext.c +15 -40
@@ -106,25 +106,6 @@ static const struct rhashtable_params dsq_hash_params = {static LLIST_HEAD (dsqs_to_free); -/* dispatch buf */ -struct scx_dsp_buf_ent { -struct task_struct *task; -unsigned long qseq; -u64 dsq_id; -u64 enq_flags; -}; - -static u32 scx_dsp_max_batch; - -struct scx_dsp_ctx { -struct rq *rq; -u32 cursor; -u32 nr_tasks; -struct scx_dsp_buf_ent buf[]; -}; - -static struct scx_dsp_ctx __percpu *scx_dsp_ctx; -/* string formatting from BPF */ struct scx_bstr_buf {u64 data[MAX_BPRINTF_VARARGS ];
@@ -2398,7 +2379,7 @@ static void finish_dispatch(struct scx_sched *sch, struct rq *rq,static void flush_dispatch_buf (struct scx_sched *sch,struct rq *rq) { -struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx); +struct scx_dsp_ctx *dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx;u32 u;for (u =0 ; u < dspc->cursor; u++) {
@@ -2428,7 +2409,7 @@ static inline void maybe_queue_balance_callback(struct rq *rq)static bool scx_dispatch_sched (struct scx_sched *sch,struct rq *rq,struct task_struct *prev) { -struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx); +struct scx_dsp_ctx *dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx;int nr_loops =SCX_DSP_MAX_LOOPS ;s32 cpu =cpu_of (rq); bool prev_on_sch = (prev->sched_class == &ext_sched_class) &&
@@ -4953,9 +4934,6 @@ static void scx_root_disable(struct scx_sched *sch)*/ kobject_del (&sch->kobj); -free_percpu (scx_dsp_ctx); - scx_dsp_ctx =NULL ; - scx_dsp_max_batch =0 ;free_kick_syncs ();mutex_unlock (&scx_enable_mutex);
@@ -5450,7 +5428,10 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops, sch->global_dsqs[node] = dsq; } - sch->pcpu = alloc_percpu(struct scx_sched_pcpu); + sch->dsp_max_batch = ops->dispatch_max_batch ?:SCX_DSP_DFL_MAX_BATCH ; + sch->pcpu = __alloc_percpu(struct_size_t(struct scx_sched_pcpu, + dsp_ctx.buf, sch->dsp_max_batch), + __alignof__(struct scx_sched_pcpu ));if (!sch->pcpu) { ret = -ENOMEM ; goto err_free_gdsqs;
@@ -5688,16 +5669,6 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)if (ret) goto err_disable; -WARN_ON_ONCE (scx_dsp_ctx); - scx_dsp_max_batch = ops->dispatch_max_batch ?:SCX_DSP_DFL_MAX_BATCH ; - scx_dsp_ctx = __alloc_percpu(struct_size_t(struct scx_dsp_ctx, buf, - scx_dsp_max_batch), - __alignof__(struct scx_dsp_ctx )); -if (!scx_dsp_ctx) { - ret = -ENOMEM ; - goto err_disable; - } -if (ops->timeout_ms) timeout =msecs_to_jiffies (ops->timeout_ms);else
@@ -6636,7 +6607,7 @@ static bool scx_dsq_insert_preamble(struct scx_sched *sch, struct task_struct *pstatic void scx_dsq_insert_commit (struct scx_sched *sch,struct task_struct *p,u64 dsq_id,u64 enq_flags) { -struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx); +struct scx_dsp_ctx *dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx;struct task_struct *ddsp_task; ddsp_task =__this_cpu_read (direct_dispatch_task);
@@ -6645,7 +6616,7 @@ static void scx_dsq_insert_commit(struct scx_sched *sch, struct task_struct *p,return ; } -if (unlikely (dspc->cursor >= scx_dsp_max_batch)) { +if (unlikely (dspc->cursor >= sch->dsp_max_batch)) {scx_error (sch,"dispatch buffer overflow" );return ; }
@@ -6963,7 +6934,7 @@ __bpf_kfunc u32 scx_bpf_dispatch_nr_slots(const struct bpf_prog_aux *aux)if (!scx_kf_allowed (sch,SCX_KF_DISPATCH ))return 0 ; -return scx_dsp_max_batch -__this_cpu_read (scx_dsp_ctx->cursor); +return sch->dsp_max_batch -__this_cpu_read (sch->pcpu->dsp_ctx.cursor); }/**
@@ -6975,8 +6946,8 @@ __bpf_kfunc u32 scx_bpf_dispatch_nr_slots(const struct bpf_prog_aux *aux)*/ __bpf_kfunc void scx_bpf_dispatch_cancel(const struct bpf_prog_aux *aux) { -struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx);struct scx_sched *sch; +struct scx_dsp_ctx *dspc;guard (rcu)();
@@ -6987,6 +6958,8 @@ __bpf_kfunc void scx_bpf_dispatch_cancel(const struct bpf_prog_aux *aux)if (!scx_kf_allowed (sch,SCX_KF_DISPATCH ))return ; + dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx; +if (dspc->cursor >0 ) dspc->cursor--;else
@@ -7010,9 +6983,9 @@ __bpf_kfunc void scx_bpf_dispatch_cancel(const struct bpf_prog_aux *aux)*/ __bpf_kfunc bool scx_bpf_dsq_move_to_local(u64 dsq_id, const struct bpf_prog_aux *aux) { -struct scx_dsp_ctx *dspc =this_cpu_ptr (scx_dsp_ctx);struct scx_dispatch_q *dsq;struct scx_sched *sch; +struct scx_dsp_ctx *dspc;guard (rcu)();
@@ -7023,6 +6996,8 @@ __bpf_kfunc bool scx_bpf_dsq_move_to_local(u64 dsq_id, const struct bpf_prog_auxif (!scx_kf_allowed (sch,SCX_KF_DISPATCH ))return false; + dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx; +flush_dispatch_buf (sch, dspc->rq); dsq =find_user_dsq (sch, dsq_id);
kernel/sched/ext_internal.h +19 -0
@@ -937,6 +937,21 @@ enum scx_sched_pcpu_flags { SCX_SCHED_PCPU_BYPASSING = 1LLU << 0, }; +/* dispatch buf */ +struct scx_dsp_buf_ent { +struct task_struct *task; +unsigned long qseq; +u64 dsq_id; +u64 enq_flags; +}; + +struct scx_dsp_ctx { +struct rq *rq; +u32 cursor; +u32 nr_tasks; +struct scx_dsp_buf_ent buf[]; +}; +struct scx_sched_pcpu {u64 flags;/* protected by rq lock */
@@ -951,6 +966,9 @@ struct scx_sched_pcpu {#ifdef CONFIG_EXT_SUB_SCHED u32 bypass_host_seq;#endif + +/* must be the last entry - contains flex array */ +struct scx_dsp_ctx dsp_ctx; };struct scx_sched {
@@ -978,6 +996,7 @@ struct scx_sched {atomic_t bypass_dsp_enable_depth;bool aborting; +u32 dsp_max_batch;s32 level;/*
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 26/34 sched_ext: Make watchdog sub-sched aware
In reply to Tejun HeoCurrently, the watchdog checks all tasks as if they are all on scx_root. Move scx_watchdog_timeout inside scx_sched and make check_rq_for_timeouts() use the timeout from the scx_sched associated with each task. refresh_watchdog() is added, which determines the timer interval as half of the shortest watchdog timeouts of all scheds and arms or disarms it as necessary. Every scx_sched instance has equivalent or better detection latency while sharing the same timer.
--- kernel/sched/ext.c | 74 ++++++++++++++++++++++++------------- kernel/sched/ext_internal.h | 7 ++++ 2 files changed, 56 insertions(+), 25 deletions(-)
kernel/sched/ext.c +49 -25
@@ -59,11 +59,10 @@ static atomic_long_t scx_hotplug_seq = ATOMIC_LONG_INIT(0);static atomic_long_t scx_enable_seq =ATOMIC_LONG_INIT (0 );/* -* The maximum amount of time in jiffies that a task may be runnable without -* being scheduled on a CPU. If this timeout is exceeded, it will trigger -* scx_error(). +* Watchdog interval. All scx_sched's share a single watchdog timer and the +* interval is half of the shortest sch->watchdog_timeout. */ -static unsigned long scx_watchdog_timeout; +static unsigned long scx_watchdog_interval;/* * The last time the delayed work was run. This delayed work relies on
@@ -3032,10 +3031,11 @@ static bool check_rq_for_timeouts(struct rq *rq) goto out_unlock;list_for_each_entry (p, &rq->scx.runnable_list, scx.runnable_node) { +struct scx_sched *sch =scx_task_sched (p);unsigned long last_runnable = p->scx.runnable_at;if (unlikely (time_after (jiffies, - last_runnable + scx_watchdog_timeout))) { + last_runnable + sch->watchdog_timeout))) {u32 dur_ms =jiffies_to_msecs (jiffies - last_runnable); scx_exit(sch, SCX_EXIT_ERROR_STALL, 0,
@@ -3052,6 +3052,7 @@ static bool check_rq_for_timeouts(struct rq *rq)static void scx_watchdog_workfn (struct work_struct *work) { +unsigned long intv;int cpu;WRITE_ONCE (scx_watchdog_timestamp, jiffies);
@@ -3062,28 +3063,31 @@ static void scx_watchdog_workfn(struct work_struct *work)cond_resched (); } -queue_delayed_work (system_unbound_wq,to_delayed_work (work), - scx_watchdog_timeout /2 ); + + intv =READ_ONCE (scx_watchdog_interval); +if (intv <ULONG_MAX ) +queue_delayed_work (system_unbound_wq,to_delayed_work (work), + intv); }void scx_tick (struct rq *rq) { -struct scx_sched *sch; +struct scx_sched *root;unsigned long last_check;if (!scx_enabled ())return ; - sch =rcu_dereference_bh (scx_root); -if (unlikely (!sch)) + root =rcu_dereference_bh (scx_root); +if (unlikely (!root))return ; last_check =READ_ONCE (scx_watchdog_timestamp);if (unlikely (time_after (jiffies, - last_check +READ_ONCE (scx_watchdog_timeout)))) { + last_check +READ_ONCE (root->watchdog_timeout)))) {u32 dur_ms =jiffies_to_msecs (jiffies - last_check); -scx_exit (sch,SCX_EXIT_ERROR_STALL ,0 , +scx_exit (root,SCX_EXIT_ERROR_STALL ,0 ,"watchdog failed to check in for %u.%03us" , dur_ms /1000 , dur_ms %1000 ); }
@@ -4741,6 +4745,26 @@ static void free_kick_syncs(void) } } +static void refresh_watchdog (void ) +{ +struct scx_sched *sch; +unsigned long intv =ULONG_MAX ; + +/* take the shortest timeout and use its half for watchdog interval */ +rcu_read_lock (); +list_for_each_entry_rcu (sch, &scx_sched_all, all) + intv =max (min (intv, sch->watchdog_timeout /2 ),1 ); +rcu_read_unlock (); + +WRITE_ONCE (scx_watchdog_timestamp, jiffies); +WRITE_ONCE (scx_watchdog_interval, intv); + +if (intv <ULONG_MAX ) +mod_delayed_work (system_unbound_wq, &scx_watchdog_work, intv); +else +cancel_delayed_work_sync (&scx_watchdog_work); +} +#ifdef CONFIG_EXT_SUB_SCHED static DECLARE_WAIT_QUEUE_HEAD (scx_unlink_waitq);
@@ -4779,6 +4803,8 @@ static void scx_sub_disable(struct scx_sched *sch)list_del_rcu (&sch->all);raw_spin_unlock_irq (&scx_sched_lock); +refresh_watchdog (); +mutex_unlock (&scx_enable_mutex);/*
@@ -4913,12 +4939,12 @@ static void scx_root_disable(struct scx_sched *sch)if (sch->ops.exit)SCX_CALL_OP (sch,SCX_KF_UNLOCKED , exit,NULL , ei); -cancel_delayed_work_sync (&scx_watchdog_work); -raw_spin_lock_irq (&scx_sched_lock);list_del_rcu (&sch->all);raw_spin_unlock_irq (&scx_sched_lock); +refresh_watchdog (); +/* * scx_root clearing must be inside cpus_read_lock(). See * handle_hotplug().
@@ -5454,6 +5480,11 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops, sch->ancestors[level] = sch; sch->level = level; +if (ops->timeout_ms) + sch->watchdog_timeout =msecs_to_jiffies (ops->timeout_ms); +else + sch->watchdog_timeout =SCX_WATCHDOG_MAX_TIMEOUT ; + sch->slice_dfl =SCX_SLICE_DFL ;atomic_set (&sch->exit_kind,SCX_EXIT_NONE );init_irq_work (&sch->error_irq_work, scx_error_irq_workfn);
@@ -5581,7 +5612,6 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)struct scx_sched *sch;struct scx_task_iter sti;struct task_struct *p; -unsigned long timeout;s32 i, cpu, ret; if (!cpumask_equal(housekeeping_cpumask(HK_TYPE_DOMAIN),
@@ -5639,6 +5669,8 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)list_add_tail_rcu (&sch->all, &scx_sched_all);raw_spin_unlock_irq (&scx_sched_lock); +refresh_watchdog (); +scx_idle_enable (ops);if (sch->ops.init) {
@@ -5669,16 +5701,6 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)if (ret) goto err_disable; -if (ops->timeout_ms) - timeout =msecs_to_jiffies (ops->timeout_ms); -else - timeout =SCX_WATCHDOG_MAX_TIMEOUT ; - -WRITE_ONCE (scx_watchdog_timeout, timeout); -WRITE_ONCE (scx_watchdog_timestamp, jiffies); -queue_delayed_work (system_unbound_wq, &scx_watchdog_work, - scx_watchdog_timeout /2 ); -/* * Once __scx_enabled is set, %current can be switched to SCX anytime. * This can lead to stalls as some BPF schedulers (e.g. userspace
@@ -5896,6 +5918,8 @@ static s32 scx_sub_enable(struct sched_ext_ops *ops, struct bpf_link *link)list_add_tail_rcu (&sch->all, &scx_sched_all);raw_spin_unlock_irq (&scx_sched_lock); +refresh_watchdog (); +if (sch->level >=SCX_SUB_MAX_DEPTH ) {scx_error (sch,"max nesting depth %d violated" ,SCX_SUB_MAX_DEPTH );
kernel/sched/ext_internal.h +7 -0
@@ -1019,6 +1019,13 @@ struct scx_sched {bool sub_attached;#endif /* CONFIG_EXT_SUB_SCHED */ +/* +* The maximum amount of time in jiffies that a task may be runnable +* without being scheduled on a CPU. If this timeout is exceeded, it +* will trigger scx_error(). +*/ +unsigned long watchdog_timeout; +atomic_t exit_kind;struct scx_exit_info *exit_info;
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 27/34 sched_ext: Convert scx_dump_state() spinlock to raw spinlock
In reply to Tejun HeoThe scx_dump_state() function uses a regular spinlock to serialize access. In a subsequent patch, this function will be called while holding scx_sched_lock, which is a raw spinlock, creating a lock nesting violation.
Convert the dump_lock to a raw spinlock and use the guard macro for cleaner lock management.
--- kernel/sched/ext.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
kernel/sched/ext.c +2 -5
@@ -5201,7 +5201,7 @@ static void scx_dump_task(struct seq_buf *s, struct scx_dump_ctx *dctx,static void scx_dump_state (struct scx_exit_info *ei,size_t dump_len) { -static DEFINE_SPINLOCK (dump_lock); +static DEFINE_RAW_SPINLOCK (dump_lock);static const char trunc_marker[] ="\n\n~~~~ TRUNCATED ~~~~\n" ;struct scx_sched *sch = scx_root;struct scx_dump_ctx dctx = {
@@ -5213,11 +5213,10 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len) };struct seq_buf s;struct scx_event_stats events; -unsigned long flags;char *buf;int cpu; -spin_lock_irqsave (&dump_lock, flags); +guard (raw_spinlock_irqsave)(&dump_lock);seq_buf_init (&s, ei->dump, dump_len);
@@ -5342,8 +5341,6 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len)if (seq_buf_has_overflowed (&s) && dump_len >=sizeof (trunc_marker))memcpy (ei->dump + dump_len -sizeof (trunc_marker), trunc_marker,sizeof (trunc_marker)); - -spin_unlock_irqrestore (&dump_lock, flags); }static void scx_error_irq_workfn (struct irq_work *irq_work)
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 28/34 sched_ext: Support dumping multiple schedulers and add scheduler identification
In reply to Tejun HeoExtend scx_dump_state() to support multiple schedulers and improve task identification in dumps. The function now takes a specific scheduler to dump and can optionally filter tasks by scheduler.
scx_dump_task() now displays which scheduler each task belongs to, using "*" to mark tasks owned by the scheduler being dumped. Sub-schedulers are identified with their level and cgroup ID.
The SysRq-D handler now iterates through all active schedulers under scx_sched_lock and dumps each one separately. For SysRq-D dumps, only tasks owned by each scheduler are dumped to avoid redundancy since all schedulers are being dumped. Error-triggered dumps continue to dump all tasks since only that specific scheduler is being dumped.
--- kernel/sched/ext.c | 54 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 11 deletions(-)
kernel/sched/ext.c +43 -11
@@ -5156,22 +5156,34 @@ static void ops_dump_exit(void) scx_dump_data.cpu =-1 ; } -static void scx_dump_task (struct seq_buf *s,struct scx_dump_ctx *dctx, +static void scx_dump_task (struct scx_sched *sch, +struct seq_buf *s,struct scx_dump_ctx *dctx,struct task_struct *p,char marker) {static unsigned long bt[SCX_EXIT_BT_LEN ]; -struct scx_sched *sch = scx_root; +struct scx_sched *task_sch =scx_task_sched (p); +const char *own_marker; +char sch_id_buf[32 ];char dsq_id_buf[19 ] ="(n/a)" ;unsigned long ops_state =atomic_long_read (&p->scx.ops_state);unsigned int bt_len =0 ; + own_marker = task_sch == sch ?"*" :"" ; + +if (task_sch->level ==0 ) +scnprintf (sch_id_buf,sizeof (sch_id_buf),"root" ); +else +scnprintf (sch_id_buf,sizeof (sch_id_buf),"sub%d-%llu" , + task_sch->level, task_sch->ops.sub_cgroup_id); +if (p->scx.dsq)scnprintf (dsq_id_buf,sizeof (dsq_id_buf),"0x%llx" , (unsigned long long )p->scx.dsq->id);dump_newline (s); -dump_line (s," %c%c %s[%d] %+ldms" , +dump_line (s," %c%c %s[%d] %s%s %+ldms" , marker,task_state_to_char (p), p->comm, p->pid, + own_marker, sch_id_buf,jiffies_delta_msecs (p->scx.runnable_at, dctx->at_jiffies)); dump_line(s, " scx_state/flags=%u/0x%x dsq_flags=0x%x ops_state/qseq=%lu/%lu", scx_get_task_state(p), p->scx.flags & ~SCX_TASK_STATE_MASK,
@@ -5199,11 +5211,18 @@ static void scx_dump_task(struct seq_buf *s, struct scx_dump_ctx *dctx, } } -static void scx_dump_state (struct scx_exit_info *ei,size_t dump_len) +/* +* Dump scheduler state. If @dump_all_tasks is true, dump all tasks regardless +* of which scheduler they belong to. If false, only dump tasks owned by @sch. +* For SysRq-D dumps, @dump_all_tasks=false since all schedulers are dumped +* separately. For error dumps, @dump_all_tasks=true since only the failing +* scheduler is dumped. +*/ +static void scx_dump_state (struct scx_sched *sch,struct scx_exit_info *ei, +size_t dump_len,bool dump_all_tasks) {static DEFINE_RAW_SPINLOCK (dump_lock);static const char trunc_marker[] ="\n\n~~~~ TRUNCATED ~~~~\n" ; -struct scx_sched *sch = scx_root;struct scx_dump_ctx dctx = { .kind = ei->kind, .exit_code = ei->exit_code,
@@ -5220,6 +5239,14 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len)seq_buf_init (&s, ei->dump, dump_len); +#ifdef CONFIG_EXT_SUB_SCHED +if (sch->level ==0 ) +dump_line (&s,"%s: root" , sch->ops.name); +else +dump_line (&s,"%s: sub%d-%llu %s" , + sch->ops.name, sch->level, sch->ops.sub_cgroup_id, + sch->cgrp_path); +#endif if (ei->kind ==SCX_EXIT_NONE ) {dump_line (&s,"Debug dump triggered by %s" , ei->reason); }else {
@@ -5312,11 +5339,13 @@ static void scx_dump_state(struct scx_exit_info *ei, size_t dump_len)seq_buf_set_overflow (&s); } -if (rq->curr->sched_class == &ext_sched_class) -scx_dump_task (&s, &dctx, rq->curr,'*' ); +if (rq->curr->sched_class == &ext_sched_class && + (dump_all_tasks ||scx_task_on_sched (sch, rq->curr))) +scx_dump_task (sch, &s, &dctx, rq->curr,'*' );list_for_each_entry (p, &rq->scx.runnable_list, scx.runnable_node) -scx_dump_task (&s, &dctx, p,' ' ); +if (dump_all_tasks ||scx_task_on_sched (sch, p)) +scx_dump_task (sch, &s, &dctx, p,' ' ); next:rq_unlock_irqrestore (rq, &rf); }
@@ -5349,7 +5378,7 @@ static void scx_error_irq_workfn(struct irq_work *irq_work)struct scx_exit_info *ei = sch->exit_info;if (ei->kind >=SCX_EXIT_ERROR ) -scx_dump_state (ei, sch->ops.exit_dump_len); +scx_dump_state (sch, ei, sch->ops.exit_dump_len, true);kthread_queue_work (sch->helper, &sch->disable_work); }
@@ -6333,9 +6362,12 @@ static const struct sysrq_key_op sysrq_sched_ext_reset_op = {static void sysrq_handle_sched_ext_dump (u8 key) {struct scx_exit_info ei = { .kind =SCX_EXIT_NONE , .reason ="SysRq-D" }; +struct scx_sched *sch; -if (scx_enabled ()) -scx_dump_state (&ei,0 ); +guard (raw_spinlock_irqsave)(&scx_sched_lock); + +list_for_each_entry_rcu (sch, &scx_sched_all, all) +scx_dump_state (sch, &ei,0 , false); }static const struct sysrq_key_op sysrq_sched_ext_dump_op = {
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 29/34 sched_ext: Implement cgroup sub-sched enabling and disabling
In reply to Tejun HeoThe preceding changes implemented the framework to support cgroup sub-scheds and updated scheduling paths and kfuncs so that they have minimal but working support for sub-scheds. However, actual sub-sched enabling/disabling hasn't been implemented yet and all tasks stayed on scx_root.
Implement cgroup sub-sched enabling and disabling to actually activate sub-scheds:
- Both enable and disable operations bypass only the tasks in the subtree of the child being enabled or disabled to limit disruptions.
- When enabling, all candidate tasks are first initialized for the child sched. Once that succeeds, the tasks are exited for the parent and then switched over to the child. This adds a bit of complication but guarantees that child scheduler failures are always contained.
- Disabling works the same way in the other direction. However, when the parent may fail to initialize a task, disabling is propagated up to the parent. While this means that a parent sched fail due to a child sched event, the failure can only originate from the parent itself (its ops.init_task()). The only effect a malfunctioning child can have on the parent is attempting to move the tasks back to the parent.
After this change, although not all the necessary mechanisms are in place yet, sub-scheds can take control of their tasks and schedule them.
--- include/linux/sched/ext.h | 1 + kernel/sched/ext.c | 278 +++++++++++++++++++++++++++++++++++++- 2 files changed, 273 insertions(+), 6 deletions(-)
include/linux/sched/ext.h +1 -0
@@ -88,6 +88,7 @@ enum scx_ent_flags {SCX_TASK_IN_CUSTODY =1 <<1 ,/* in custody, needs ops.dequeue() when leaving */ SCX_TASK_RESET_RUNNABLE_AT =1 <<2 ,/* runnable_at should be reset */ SCX_TASK_DEQD_FOR_SLEEP =1 <<3 ,/* last dequeue was for SLEEP */ +SCX_TASK_SUB_INIT =1 <<4 ,/* task being initialized for a sub sched */ SCX_TASK_STATE_SHIFT =8 ,/* bit 8 and 9 are used to carry scx_task_state */ SCX_TASK_STATE_BITS = 2,
kernel/sched/ext.c +272 -6
370 lines not on this page · raw message
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 30/34 sched_ext: Add scx_sched back pointer to scx_sched_pcpu
In reply to Tejun HeoAdd a back pointer from scx_sched_pcpu to scx_sched. This will be used by the next patch to make scx_bpf_reenqueue_local() sub-sched aware.
--- kernel/sched/ext.c | 3 +++ kernel/sched/ext_internal.h | 3 +++ 2 files changed, 6 insertions(+)
kernel/sched/ext.c +3 -0
@@ -5631,6 +5631,9 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops,for_each_possible_cpu (cpu)init_dsq (bypass_dsq (sch, cpu),SCX_DSQ_BYPASS , sch); +for_each_possible_cpu (cpu) +per_cpu_ptr (sch->pcpu, cpu)->sch = sch; + sch->helper =kthread_run_worker (0 ,"sched_ext_helper" );if (IS_ERR (sch->helper)) { ret =PTR_ERR (sch->helper);
kernel/sched/ext_internal.h +3 -0
@@ -933,6 +933,8 @@ struct scx_event_stats {s64 SCX_EV_SUB_BYPASS_DISPATCH ; }; +struct scx_sched ; +enum scx_sched_pcpu_flags {SCX_SCHED_PCPU_BYPASSING =1LLU <<0 , };
@@ -953,6 +955,7 @@ struct scx_dsp_ctx { };struct scx_sched_pcpu { +struct scx_sched *sch;u64 flags;/* protected by rq lock */ /*
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 31/34 sched_ext: Make scx_bpf_reenqueue_local() sub-sched aware
In reply to Tejun Heoscx_bpf_reenqueue_local() currently re-enqueues all tasks on the local DSQ regardless of which sub-scheduler owns them. With multiple sub-schedulers, each should only re-enqueue tasks it owns or are owned by its descendants.
Replace the per-rq boolean flag with a lock-free linked list to track per-scheduler reenqueue requests. Filter tasks in reenq_local() using hierarchical ownership checks and block deferrals during bypass to prevent use on dead schedulers.
--- kernel/sched/ext.c | 73 ++++++++++++++++++++++++++++++------- kernel/sched/ext_internal.h | 1 + kernel/sched/sched.h | 2 +- 3 files changed, 62 insertions(+), 14 deletions(-)
kernel/sched/ext.c +60 -13
@@ -183,7 +183,7 @@ MODULE_PARM_DESC(bypass_lb_intv_us, "bypass load balance interval in microsecondstatic void process_ddsp_deferred_locals (struct rq *rq);static bool task_dead_and_done (struct task_struct *p); -static u32 reenq_local (struct rq *rq); +static u32 reenq_local (struct scx_sched *sch,struct rq *rq);static void scx_kick_cpu (struct scx_sched *sch,s32 cpu,u64 flags);static void scx_disable (struct scx_sched *sch,enum scx_exit_kind kind); static bool scx_vexit(struct scx_sched *sch, enum scx_exit_kind kind,
@@ -989,9 +989,16 @@ static void run_deferred(struct rq *rq) {process_ddsp_deferred_locals (rq); -if (local_read (&rq->scx.reenq_local_deferred)) { -local_set (&rq->scx.reenq_local_deferred,0 ); -reenq_local (rq); +if (!llist_empty (&rq->scx.deferred_reenq_locals)) { +struct llist_node *llist = +llist_del_all (&rq->scx.deferred_reenq_locals); +struct scx_sched_pcpu *pos, *next; + +llist_for_each_entry_safe (pos, next, llist, + deferred_reenq_locals_node) { +init_llist_node (&pos->deferred_reenq_locals_node); +reenq_local (pos->sch, rq); + } } }
@@ -4061,7 +4068,7 @@ static void scx_sched_free_rcu_work(struct work_struct *work) struct scx_sched *sch = container_of(rcu_work, struct scx_sched, rcu_work);struct rhashtable_iter rht_iter;struct scx_dispatch_q *dsq; -int node; +int cpu, node;irq_work_sync (&sch->error_irq_work);kthread_destroy_worker (sch->helper);
@@ -4073,6 +4080,17 @@ static void scx_sched_free_rcu_work(struct work_struct *work)cgroup_put (sch->cgrp);#endif /* CONFIG_EXT_SUB_SCHED */ +/* +* $sch would have entered bypass mode before the RCU grace period. As +* that blocks new deferrals, all deferred_reenq_locals_node's must be +* off-list by now. +*/ +for_each_possible_cpu (cpu) { +struct scx_sched_pcpu *pcpu =per_cpu_ptr (sch->pcpu, cpu); + +WARN_ON_ONCE (llist_on_list (&pcpu->deferred_reenq_locals_node)); + } +free_percpu (sch->pcpu);for_each_node_state (node,N_POSSIBLE )
@@ -5631,8 +5649,12 @@ static struct scx_sched *scx_alloc_and_add_sched(struct sched_ext_ops *ops,for_each_possible_cpu (cpu)init_dsq (bypass_dsq (sch, cpu),SCX_DSQ_BYPASS , sch); -for_each_possible_cpu (cpu) -per_cpu_ptr (sch->pcpu, cpu)->sch = sch; +for_each_possible_cpu (cpu) { +struct scx_sched_pcpu *pcpu =per_cpu_ptr (sch->pcpu, cpu); + + pcpu->sch = sch; +init_llist_node (&pcpu->deferred_reenq_locals_node); + } sch->helper =kthread_run_worker (0 ,"sched_ext_helper" );if (IS_ERR (sch->helper)) {
@@ -6883,6 +6905,7 @@ void __init init_sched_ext_class(void)BUG_ON (!zalloc_cpumask_var_node (&rq->scx.cpus_to_kick_if_idle,GFP_KERNEL , n));BUG_ON (!zalloc_cpumask_var_node (&rq->scx.cpus_to_preempt,GFP_KERNEL , n));BUG_ON (!zalloc_cpumask_var_node (&rq->scx.cpus_to_wait,GFP_KERNEL , n)); +init_llist_head (&rq->scx.deferred_reenq_locals); rq->scx.deferred_irq_work =IRQ_WORK_INIT_HARD (deferred_irq_workfn); rq->scx.kick_cpus_irq_work =IRQ_WORK_INIT_HARD (kick_cpus_irq_workfn);
@@ -7454,7 +7477,7 @@ static const struct btf_kfunc_id_set scx_kfunc_set_dispatch = { .set = &scx_kfunc_ids_dispatch, }; -static u32 reenq_local (struct rq *rq) +static u32 reenq_local (struct scx_sched *sch,struct rq *rq) {LIST_HEAD (tasks);u32 nr_enqueued =0 ;
@@ -7469,6 +7492,8 @@ static u32 reenq_local(struct rq *rq)*/ list_for_each_entry_safe (p, n, &rq->scx.local_dsq.list, scx.dsq_list.node) { +struct scx_sched *task_sch =scx_task_sched (p); +/* * If @p is being migrated, @p's current CPU may not agree with * its allowed CPUs and the migration_cpu_stop is about to
@@ -7483,6 +7508,9 @@ static u32 reenq_local(struct rq *rq)if (p->migration_pending)continue ; +if (!scx_is_descendant (task_sch, sch)) +continue ; +dispatch_dequeue (rq, p);list_add_tail (&p->scx.dsq_list.node, &tasks); }
@@ -7525,7 +7553,7 @@ __bpf_kfunc u32 scx_bpf_reenqueue_local(const struct bpf_prog_aux *aux) rq =cpu_rq (smp_processor_id ());lockdep_assert_rq_held (rq); -return reenq_local (rq); +return reenq_local (sch, rq); }__bpf_kfunc_end_defs ();
@@ -8096,20 +8124,39 @@ __bpf_kfunc void scx_bpf_dump_bstr(char *fmt, unsigned long long *data,/** * scx_bpf_reenqueue_local - Re-enqueue tasks on a local DSQ +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs * * Iterate over all of the tasks currently enqueued on the local DSQ of the * caller's CPU, and re-enqueue them in the BPF scheduler. Can be called from * anywhere. */ -__bpf_kfunc void scx_bpf_reenqueue_local___v2(void) +__bpf_kfunc void scx_bpf_reenqueue_local___v2(const struct bpf_prog_aux *aux) { +unsigned long flags; +struct scx_sched *sch;struct rq *rq; +struct llist_node *lnode; -guard (preempt)(); +raw_local_irq_save (flags); + + sch =scx_prog_sched (aux); +if (unlikely (!sch)) + goto out_irq_restore; + +/* +* Allowing reenqueue-locals doesn't make sense while bypassing. This +* also blocks from new reenqueues to be scheduled on dead scheds. +*/ +if (unlikely (sch->bypass_depth)) + goto out_irq_restore; rq =this_rq (); -local_set (&rq->scx.reenq_local_deferred,1 ); + lnode = &this_cpu_ptr (sch->pcpu)->deferred_reenq_locals_node; +if (!llist_on_list (lnode)) +llist_add (lnode, &rq->scx.deferred_reenq_locals);schedule_deferred (rq); +out_irq_restore: +raw_local_irq_restore (flags); }/**
@@ -8534,7 +8581,7 @@ BTF_ID_FLAGS(func, bpf_iter_scx_dsq_destroy, KF_ITER_DESTROY) BTF_ID_FLAGS(func, scx_bpf_exit_bstr, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_error_bstr, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_dump_bstr, KF_IMPLICIT_ARGS) -BTF_ID_FLAGS(func, scx_bpf_reenqueue_local___v2) +BTF_ID_FLAGS(func, scx_bpf_reenqueue_local___v2, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_cpuperf_cap, KF_IMPLICIT_ARGS) BTF_ID_FLAGS(func, scx_bpf_cpuperf_cur, KF_IMPLICIT_ARGS)BTF_ID_FLAGS (func, scx_bpf_cpuperf_set,KF_IMPLICIT_ARGS )
kernel/sched/ext_internal.h +1 -0
@@ -965,6 +965,7 @@ struct scx_sched_pcpu {*/ struct scx_event_stats event_stats; +struct llist_node deferred_reenq_locals_node;struct scx_dispatch_q bypass_dsq;#ifdef CONFIG_EXT_SUB_SCHED u32 bypass_host_seq;
kernel/sched/sched.h +1 -1
@@ -805,7 +805,7 @@ struct scx_rq {cpumask_var_t cpus_to_preempt;cpumask_var_t cpus_to_wait;unsigned long kick_sync; -local_t reenq_local_deferred; +struct llist_head deferred_reenq_locals;struct balance_callback deferred_bal_cb;struct irq_work deferred_irq_work;struct irq_work kick_cpus_irq_work;
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 32/34 sched_ext: Factor out scx_link_sched() and scx_unlink_sched()
In reply to Tejun HeoFactor out scx_link_sched() and scx_unlink_sched() functions to reduce code duplication in the scheduler enable/disable paths.
No functional change.
--- kernel/sched/ext.c | 53 +++++++++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 22 deletions(-)
kernel/sched/ext.c +31 -22
@@ -4806,6 +4806,33 @@ static void refresh_watchdog(void)cancel_delayed_work_sync (&scx_watchdog_work); } +static void scx_link_sched (struct scx_sched *sch) +{ + scoped_guard(raw_spinlock_irq, &scx_sched_lock) { +#ifdef CONFIG_EXT_SUB_SCHED +struct scx_sched *parent =scx_parent (sch); +if (parent) +list_add_tail (&sch->sibling, &parent->children); +#endif /* CONFIG_EXT_SUB_SCHED */ +list_add_tail_rcu (&sch->all, &scx_sched_all); + } + +refresh_watchdog (); +} + +static void scx_unlink_sched (struct scx_sched *sch) +{ + scoped_guard(raw_spinlock_irq, &scx_sched_lock) { +#ifdef CONFIG_EXT_SUB_SCHED +if (scx_parent (sch)) +list_del_init (&sch->sibling); +#endif /* CONFIG_EXT_SUB_SCHED */ +list_del_rcu (&sch->all); + } + +refresh_watchdog (); +} +#ifdef CONFIG_EXT_SUB_SCHED static DECLARE_WAIT_QUEUE_HEAD (scx_unlink_waitq);
@@ -4955,12 +4982,7 @@ static void scx_sub_disable(struct scx_sched *sch)synchronize_rcu_expedited ();disable_bypass_dsp (sch); -raw_spin_lock_irq (&scx_sched_lock); -list_del_init (&sch->sibling); -list_del_rcu (&sch->all); -raw_spin_unlock_irq (&scx_sched_lock); - -refresh_watchdog (); +scx_unlink_sched (sch);mutex_unlock (&scx_enable_mutex);
@@ -5096,11 +5118,7 @@ static void scx_root_disable(struct scx_sched *sch)if (sch->ops.exit)SCX_CALL_OP (sch,SCX_KF_UNLOCKED , exit,NULL , ei); -raw_spin_lock_irq (&scx_sched_lock); -list_del_rcu (&sch->all); -raw_spin_unlock_irq (&scx_sched_lock); - -refresh_watchdog (); +scx_unlink_sched (sch);/* * scx_root clearing must be inside cpus_read_lock(). See
@@ -5855,11 +5873,7 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)*/ rcu_assign_pointer (scx_root, sch); -raw_spin_lock_irq (&scx_sched_lock); -list_add_tail_rcu (&sch->all, &scx_sched_all); -raw_spin_unlock_irq (&scx_sched_lock); - -refresh_watchdog (); +scx_link_sched (sch);scx_idle_enable (ops);
@@ -6120,12 +6134,7 @@ static s32 scx_sub_enable(struct sched_ext_ops *ops, struct bpf_link *link) goto out_put_cgrp; } -raw_spin_lock_irq (&scx_sched_lock); -list_add_tail (&sch->sibling, &parent->children); -list_add_tail_rcu (&sch->all, &scx_sched_all); -raw_spin_unlock_irq (&scx_sched_lock); - -refresh_watchdog (); +scx_link_sched (sch);if (sch->level >=SCX_SUB_MAX_DEPTH ) { scx_error(sch, "max nesting depth %d violated",
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 33/34 sched_ext: Add rhashtable lookup for sub-schedulers
In reply to Tejun HeoAdd rhashtable-based lookup for sub-schedulers indexed by cgroup_id to enable efficient scheduler discovery in preparation for multiple scheduler support. The hash table allows quick lookup of the appropriate scheduler instance when processing tasks from different cgroups.
This extends scx_link_sched() to register sub-schedulers in the hash table and scx_unlink_sched() to remove them. A new scx_find_sub_sched() function provides the lookup interface.
--- kernel/sched/ext.c | 50 +++++++++++++++++++++++++++++++++---- kernel/sched/ext_internal.h | 2 ++ 2 files changed, 47 insertions(+), 5 deletions(-)
kernel/sched/ext.c +45 -5
@@ -27,6 +27,16 @@ struct scx_sched __rcu *scx_root;*/ static LIST_HEAD (scx_sched_all); +#ifdef CONFIG_EXT_SUB_SCHED +static const struct rhashtable_params scx_sched_hash_params = { + .key_len = sizeof_field(struct scx_sched, ops.sub_cgroup_id), + .key_offset = offsetof(struct scx_sched, ops.sub_cgroup_id), + .head_offset = offsetof(struct scx_sched , hash_node), +}; + +static struct rhashtable scx_sched_hash; +#endif +/* * During exit, a task may schedule after losing its PIDs. When disabling the * BPF scheduler, we need to be able to iterate tasks in every state to
@@ -285,6 +295,12 @@ static struct scx_sched *scx_next_descendant_pre(struct scx_sched *pos,return NULL ; } +static struct scx_sched *scx_find_sub_sched (u64 cgroup_id) +{ +return rhashtable_lookup (&scx_sched_hash, &cgroup_id, + scx_sched_hash_params); +} +static void scx_set_task_sched (struct task_struct *p,struct scx_sched *sch) {rcu_assign_pointer (p->scx.sched, sch);
@@ -292,6 +308,7 @@ static void scx_set_task_sched(struct task_struct *p, struct scx_sched *sch)#else /* CONFIG_EXT_SUB_SCHED */ static struct scx_sched *scx_parent (struct scx_sched *sch) {return NULL ; }static struct scx_sched *scx_next_descendant_pre (struct scx_sched *pos,struct scx_sched *root) {return pos ?NULL : root; } +static struct scx_sched *scx_find_sub_sched (u64 cgroup_id) {return NULL ; }static void scx_set_task_sched (struct task_struct *p,struct scx_sched *sch) {}#endif /* CONFIG_EXT_SUB_SCHED */
@@ -4806,26 +4823,41 @@ static void refresh_watchdog(void)cancel_delayed_work_sync (&scx_watchdog_work); } -static void scx_link_sched (struct scx_sched *sch) +static s32 scx_link_sched (struct scx_sched *sch) { scoped_guard(raw_spinlock_irq, &scx_sched_lock) {#ifdef CONFIG_EXT_SUB_SCHED struct scx_sched *parent =scx_parent (sch); -if (parent) +s32 ret; + +if (parent) { + ret =rhashtable_lookup_insert_fast (&scx_sched_hash, + &sch->hash_node, scx_sched_hash_params); +if (ret) { +scx_error (sch,"failed to insert into scx_sched_hash (%d)" , ret); +return ret; + } +list_add_tail (&sch->sibling, &parent->children); + }#endif /* CONFIG_EXT_SUB_SCHED */ +list_add_tail_rcu (&sch->all, &scx_sched_all); }refresh_watchdog (); +return 0 ; }static void scx_unlink_sched (struct scx_sched *sch) { scoped_guard(raw_spinlock_irq, &scx_sched_lock) {#ifdef CONFIG_EXT_SUB_SCHED -if (scx_parent (sch)) +if (scx_parent (sch)) { +rhashtable_remove_fast (&scx_sched_hash, &sch->hash_node, + scx_sched_hash_params);list_del_init (&sch->sibling); + }#endif /* CONFIG_EXT_SUB_SCHED */ list_del_rcu (&sch->all); }
@@ -5873,7 +5905,9 @@ static s32 scx_root_enable(struct sched_ext_ops *ops, struct bpf_link *link)*/ rcu_assign_pointer (scx_root, sch); -scx_link_sched (sch); + ret =scx_link_sched (sch); +if (ret) + goto err_disable;scx_idle_enable (ops);
@@ -6134,7 +6168,9 @@ static s32 scx_sub_enable(struct sched_ext_ops *ops, struct bpf_link *link) goto out_put_cgrp; } -scx_link_sched (sch); + ret =scx_link_sched (sch); +if (ret) + goto err_disable;if (sch->level >=SCX_SUB_MAX_DEPTH ) { scx_error(sch, "max nesting depth %d violated",
@@ -6925,6 +6961,10 @@ void __init init_sched_ext_class(void)register_sysrq_key ('S' , &sysrq_sched_ext_reset_op);register_sysrq_key ('D' , &sysrq_sched_ext_dump_op);INIT_DELAYED_WORK (&scx_watchdog_work, scx_watchdog_workfn); + +#ifdef CONFIG_EXT_SUB_SCHED +BUG_ON (rhashtable_init (&scx_sched_hash, &scx_sched_hash_params)); +#endif /* CONFIG_EXT_SUB_SCHED */ }
kernel/sched/ext_internal.h +2 -0
@@ -1014,6 +1014,8 @@ struct scx_sched {struct list_head all;#ifdef CONFIG_EXT_SUB_SCHED +struct rhash_head hash_node; +struct list_head children;struct list_head sibling;struct cgroup *cgrp;
Signature · 1 line
2.53.0
Tejun Heo
maintainerPATCH 34/34 sched_ext: Add basic building blocks for nested sub-scheduler dispatching
In reply to Tejun HeoThis is an early-stage partial implementation that demonstrates the core building blocks for nested sub-scheduler dispatching. While significant work remains in the enqueue path and other areas, this patch establishes the fundamental mechanisms needed for hierarchical scheduler operation.
The key building blocks introduced include:
- Private stack support for ops.dispatch() to prevent stack overflow when walking down nested schedulers during dispatch operations
- scx_bpf_sub_dispatch() kfunc that allows parent schedulers to trigger dispatch operations on their direct child schedulers
- Proper parent-child relationship validation to ensure dispatch requests are only made to legitimate child schedulers
- Updated scx_dispatch_sched() to handle both nested and non-nested invocations with appropriate kf_mask handling
The qmap scheduler is updated to demonstrate the functionality by calling scx_bpf_sub_dispatch() on registered child schedulers when it has no tasks in its own queues.
--- kernel/sched/ext.c | 120 ++++++++++++++++++++--- kernel/sched/sched.h | 3 + tools/sched_ext/include/scx/common.bpf.h | 1 + tools/sched_ext/scx_qmap.bpf.c | 37 ++++++- 4 files changed, 145 insertions(+), 16 deletions(-)
kernel/sched/ext.c +105 -15
@@ -2438,8 +2438,14 @@ static inline void maybe_queue_balance_callback(struct rq *rq) rq->scx.flags &= ~SCX_RQ_BAL_CB_PENDING ; } -static bool scx_dispatch_sched (struct scx_sched *sch,struct rq *rq, -struct task_struct *prev) +/* +* One user of this function is scx_bpf_dispatch() which can be called +* recursively as sub-sched dispatches nest. Always inline to reduce stack usage +* from the call frame. +*/ +static __always_inline bool +scx_dispatch_sched (struct scx_sched *sch,struct rq *rq, +struct task_struct *prev,bool nested) {struct scx_dsp_ctx *dspc = &this_cpu_ptr (sch->pcpu)->dsp_ctx;int nr_loops =SCX_DSP_MAX_LOOPS ;
@@ -2491,8 +2497,23 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq, do { dspc->nr_tasks = 0; - SCX_CALL_OP(sch, SCX_KF_DISPATCH, dispatch, rq, cpu, - prev_on_sch ? prev : NULL); + if (nested) { + /* + * If nested, don't update kf_mask as the originating + * invocation would already have set it up. + */ + SCX_CALL_OP(sch, 0, dispatch, rq, cpu, + prev_on_sch ? prev : NULL); + } else { + /* + * If not nested, stash @prev so that nested invocations + * can access it. + */ + rq->scx.sub_dispatch_prev = prev; + SCX_CALL_OP(sch, SCX_KF_DISPATCH, dispatch, rq, cpu, + prev_on_sch ? prev : NULL); + rq->scx.sub_dispatch_prev = NULL; + } flush_dispatch_buf(sch, rq);
@@ -2533,7 +2554,7 @@ static bool scx_dispatch_sched(struct scx_sched *sch, struct rq *rq,static int balance_one (struct rq *rq,struct task_struct *prev) { -struct scx_sched *sch = scx_root, *pos; +struct scx_sched *sch = scx_root;s32 cpu =cpu_of (rq);lockdep_assert_rq_held (rq);
@@ -2577,13 +2598,8 @@ static int balance_one(struct rq *rq, struct task_struct *prev)if (rq->scx.local_dsq.nr) goto has_tasks; -/* -* TEMPORARY - Dispatch all scheds. This will be replaced by BPF-driven -* hierarchical operation. -*/ -list_for_each_entry_rcu (pos, &scx_sched_all, all) -if (scx_dispatch_sched (pos, rq, prev)) - goto has_tasks; +if (scx_dispatch_sched (sch, rq, prev, false)) + goto has_tasks;/* * Didn't find another task to run. Keep running @prev unless
@@ -4918,9 +4934,8 @@ static void scx_sub_disable(struct scx_sched *sch)/* * Guarantee forward progress and wait for descendants to be disabled. -* To limit -* disruptions, $parent is not bypassed. Tasks are fully prepped and -* then inserted back into $parent. +* To limit disruptions, $parent is not bypassed. Tasks are fully +* prepped and then inserted back into $parent. */ scx_bypass (sch, true);drain_descendants (sch);
@@ -6500,6 +6515,20 @@ static int bpf_scx_init_member(const struct btf_type *t,return 0 ; } +#ifdef CONFIG_EXT_SUB_SCHED +static void scx_pstack_recursion_on_dispatch (struct bpf_prog *prog) +{ +struct scx_sched *sch; + +guard (rcu)(); + sch =scx_prog_sched (prog->aux); +if (unlikely (!sch)) +return ; + +scx_error (sch,"dispatch recursion detected" ); +} +#endif /* CONFIG_EXT_SUB_SCHED */ +static int bpf_scx_check_member (const struct btf_type *t,const struct btf_member *member,const struct bpf_prog *prog)
@@ -6525,6 +6554,22 @@ static int bpf_scx_check_member(const struct btf_type *t,return -EINVAL ; } +#ifdef CONFIG_EXT_SUB_SCHED +/* +* Enable private stack for operations that can nest along the +* hierarchy. +* +* XXX - Ideally, we should only do this for scheds that allow +* sub-scheds and sub-scheds themselves but I don't know how to access +* struct_ops from here. +*/ +switch (moff) { +case offsetof(struct sched_ext_ops , dispatch): + prog->aux->priv_stack_requested = true; + prog->aux->recursion_detected = scx_pstack_recursion_on_dispatch; + } +#endif /* CONFIG_EXT_SUB_SCHED */ +return 0 ; }
@@ -7509,6 +7554,48 @@ __bpf_kfunc bool scx_bpf_dsq_move_vtime(struct bpf_iter_scx_dsq *it__iter, p, dsq_id, enq_flags | SCX_ENQ_DSQ_PRIQ); } +#ifdef CONFIG_EXT_SUB_SCHED +/** +* scx_bpf_sub_dispatch - Trigger dispatching on a child scheduler +* @cgroup_id: cgroup ID of the child scheduler to dispatch +* @aux: implicit BPF argument to access bpf_prog_aux hidden from BPF progs +* +* Allows a parent scheduler to trigger dispatching on one of its direct +* child schedulers. The child scheduler runs its dispatch operation to +* move tasks from dispatch queues to the local runqueue. +* +* Returns: true on success, false if cgroup_id is invalid, not a direct +* child, or caller lacks dispatch permission. +*/ +__bpf_kfunc bool scx_bpf_sub_dispatch(u64 cgroup_id, const struct bpf_prog_aux *aux) +{ +struct rq *this_rq =this_rq (); +struct scx_sched *parent, *child; + +guard (rcu)(); + parent =scx_prog_sched (aux); +if (unlikely (!parent)) +return false; + +if (!scx_kf_allowed (parent,SCX_KF_DISPATCH )) +return false; + + child =scx_find_sub_sched (cgroup_id); + +if (unlikely (!child)) +return false; + +if (unlikely (scx_parent (child) != parent)) { +scx_error (parent,"trying to dispatch a distant sub-sched on cgroup %llu" , + cgroup_id); +return false; + } + +return scx_dispatch_sched (child, this_rq, this_rq->scx.sub_dispatch_prev, + true); +} +#endif /* CONFIG_EXT_SUB_SCHED */ +__bpf_kfunc_end_defs ();BTF_KFUNCS_START (scx_kfunc_ids_dispatch)
@@ -7519,6 +7606,9 @@ BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_slice, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move_set_vtime, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move, KF_RCU) BTF_ID_FLAGS(func, scx_bpf_dsq_move_vtime, KF_RCU) +#ifdef CONFIG_EXT_SUB_SCHED +BTF_ID_FLAGS(func, scx_bpf_sub_dispatch, KF_IMPLICIT_ARGS) +#endif BTF_KFUNCS_END (scx_kfunc_ids_dispatch) static const struct btf_kfunc_id_set scx_kfunc_set_dispatch = {
kernel/sched/sched.h +3 -0
@@ -805,6 +805,9 @@ struct scx_rq {cpumask_var_t cpus_to_preempt;cpumask_var_t cpus_to_wait;unsigned long kick_sync; + +struct task_struct *sub_dispatch_prev; +struct llist_head deferred_reenq_locals;struct balance_callback deferred_bal_cb;struct irq_work deferred_irq_work;
tools/sched_ext/include/scx/common.bpf.h +1 -0
@@ -101,6 +101,7 @@ struct rq *scx_bpf_locked_rq(void) __ksym;struct task_struct *scx_bpf_cpu_curr (s32 cpu) __ksym__weak ; u64 scx_bpf_now(void) __ksym __weak; void scx_bpf_events(struct scx_event_stats *events, size_t events__sz) __ksym __weak; +bool scx_bpf_sub_dispatch(u64 cgroup_id) __ksym __weak;/* * Use the following as @it__iter when calling scx_bpf_dsq_move[_vtime]() from
tools/sched_ext/scx_qmap.bpf.c +36 -1
@@ -48,6 +48,9 @@ const volatile bool suppress_dump;u64 nr_highpri_queued;u32 test_error_cnt; +#define MAX_SUB_SCHEDS 8 +u64 sub_sched_cgroup_ids[MAX_SUB_SCHEDS ]; +UEI_DEFINE (uei);struct qmap {
@@ -451,6 +454,12 @@ void BPF_STRUCT_OPS(qmap_dispatch, s32 cpu, struct task_struct *prev) cpuc->dsp_cnt =0 ; } +for (i =0 ; i <MAX_SUB_SCHEDS ; i++) { +if (sub_sched_cgroup_ids[i] && +scx_bpf_sub_dispatch (sub_sched_cgroup_ids[i])) +return ; + } +/* * No other tasks. @prev will keep running. Update its core_sched_seq as * if the task were enqueued and dispatched immediately.
@@ -895,7 +904,32 @@ void BPF_STRUCT_OPS(qmap_exit, struct scx_exit_info *ei)s32 BPF_STRUCT_OPS (qmap_sub_attach ,struct scx_sub_attach_args *args) { -return 0 ; +s32 i; + +for (i =0 ; i <MAX_SUB_SCHEDS ; i++) { +if (!sub_sched_cgroup_ids[i]) { + sub_sched_cgroup_ids[i] = args->ops->sub_cgroup_id; +bpf_printk ("attaching sub-sched[%d] on %s" , + i, args->cgroup_path); +return 0 ; + } + } + +return -ENOSPC ; +} + +void BPF_STRUCT_OPS (qmap_sub_detach ,struct scx_sub_detach_args *args) +{ +s32 i; + +for (i =0 ; i <MAX_SUB_SCHEDS ; i++) { +if (sub_sched_cgroup_ids[i] == args->ops->sub_cgroup_id) { + sub_sched_cgroup_ids[i] =0 ; +bpf_printk ("detaching sub-sched[%d] on %s" , + i, args->cgroup_path); +break ; + } + } } SCX_OPS_DEFINE(qmap_ops,
@@ -914,6 +948,7 @@ SCX_OPS_DEFINE(qmap_ops, .cgroup_set_weight = (void *)qmap_cgroup_set_weight, .cgroup_set_bandwidth = (void *)qmap_cgroup_set_bandwidth, .sub_attach = (void *)qmap_sub_attach, + .sub_detach = (void *)qmap_sub_detach, .cpu_online = (void *)qmap_cpu_online, .cpu_offline = (void *)qmap_cpu_offline, .init = (void *)qmap_init,
Signature · 1 line
2.53.0
Tejun Heo
maintainerPlease ignore this duplicate posting. The correct thread is:
http://lkml.kernel.org/r/[email protected]
Sorry for the mess.
--
tejun