[PATCH 06/16] sched_ext: Block proxy donors before taking control
From: Andrea Righi
Date: Tue Sep 22 2026 - 12:56:26 EST
Proxy execution retains mutex-blocked donors on the runqueue so their
scheduling context can execute a lock owner. sched_ext cannot safely
inherit such donors unless the BPF scheduler explicitly participates in
their admission and ordering.
Make sched_ext reject retained donors by default. Implement
scx_allow_proxy_exec() to force blocked EXT tasks through the regular
block path in schedule().
A donor retained by another scheduling class may already be queued when
sched_ext is enabled or when a task switches to SCHED_EXT. Fully block
the donor before sched_change_begin() records its queued state. This
prevents sched_ext from importing a proxy session which the BPF
scheduler did not admit, while leaving ordinary FAIR, RT and DL class
transitions untouched.
This is a preparatory change to support proxy execution with sched_ext.
Signed-off-by: Andrea Righi <arighi@xxxxxxxxxx>
---
kernel/sched/ext/ext.c | 37 +++++++++++++++++++++++++++++++++++--
kernel/sched/ext/ext.h | 2 ++
kernel/sched/syscalls.c | 2 ++
3 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index aaa6ee66917e8..288d479c71694 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -26,7 +26,22 @@ DEFINE_RAW_SPINLOCK(scx_sched_lock);
bool scx_allow_proxy_exec(const struct task_struct *p)
{
- return true;
+ return p->sched_class != &ext_sched_class;
+}
+
+/*
+ * End retained proxy execution before sched_ext takes ownership of @p.
+ * Called with @p's pi and rq locks held immediately before
+ * sched_change_begin(). The caller must pass DEQUEUE_NOCLOCK so the rq clock
+ * is updated only once.
+ */
+static void scx_prepare_task_sched_change(struct task_struct *p)
+{
+ lockdep_assert_held(&p->pi_lock);
+ lockdep_assert_rq_held(task_rq(p));
+
+ update_rq_clock(task_rq(p));
+ sched_proxy_block_task(task_rq(p), p);
}
/*
@@ -4370,7 +4385,7 @@ int scx_check_setscheduler(struct task_struct *p, int policy)
{
lockdep_assert_rq_held(task_rq(p));
- /* if disallow, reject transitioning into SCX */
+ /* If disallow, reject transitioning into SCX. */
if (scx_enabled() && READ_ONCE(p->scx.disallow) &&
p->policy != policy && policy == SCHED_EXT)
return -EACCES;
@@ -4378,6 +4393,20 @@ int scx_check_setscheduler(struct task_struct *p, int policy)
return 0;
}
+/*
+ * Don't carry a donor retained by another class into sched_ext. The caller
+ * has updated the rq clock and invokes this immediately before
+ * sched_change_begin() records the task's queued state.
+ */
+void scx_prepare_setscheduler(struct task_struct *p, int policy)
+{
+ lockdep_assert_held(&p->pi_lock);
+ lockdep_assert_rq_held(task_rq(p));
+
+ if (scx_enabled() && p->policy != policy && policy == SCHED_EXT)
+ sched_proxy_block_task(task_rq(p), p);
+}
+
static void process_ddsp_deferred_locals(struct rq *rq)
{
struct task_struct *p;
@@ -7892,6 +7921,10 @@ static void scx_root_enable_workfn(struct kthread_work *work)
if (old_class != new_class)
queue_flags |= DEQUEUE_CLASS;
+ if (new_class == &ext_sched_class) {
+ scx_prepare_task_sched_change(p);
+ queue_flags |= DEQUEUE_NOCLOCK;
+ }
scoped_guard (sched_change, p, queue_flags) {
scx_set_task_slice(p, READ_ONCE(sch->slice_dfl));
diff --git a/kernel/sched/ext/ext.h b/kernel/sched/ext/ext.h
index cca3f7c97b788..8348d6008651c 100644
--- a/kernel/sched/ext/ext.h
+++ b/kernel/sched/ext/ext.h
@@ -18,6 +18,7 @@ bool scx_can_stop_tick(struct rq *rq);
void scx_rq_activate(struct rq *rq);
void scx_rq_deactivate(struct rq *rq);
int scx_check_setscheduler(struct task_struct *p, int policy);
+void scx_prepare_setscheduler(struct task_struct *p, int policy);
bool task_should_scx(int policy);
bool scx_allow_ttwu_queue(const struct task_struct *p);
bool scx_allow_proxy_exec(const struct task_struct *p);
@@ -62,6 +63,7 @@ static inline bool scx_can_stop_tick(struct rq *rq) { return true; }
static inline void scx_rq_activate(struct rq *rq) {}
static inline void scx_rq_deactivate(struct rq *rq) {}
static inline int scx_check_setscheduler(struct task_struct *p, int policy) { return 0; }
+static inline void scx_prepare_setscheduler(struct task_struct *p, int policy) {}
static inline bool task_on_scx(const struct task_struct *p) { return false; }
static inline bool scx_allow_ttwu_queue(const struct task_struct *p) { return true; }
static inline bool scx_allow_proxy_exec(const struct task_struct *p) { return true; }
diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
index b215b0ead9a60..45a127deeadd9 100644
--- a/kernel/sched/syscalls.c
+++ b/kernel/sched/syscalls.c
@@ -678,6 +678,8 @@ int __sched_setscheduler(struct task_struct *p,
if (prev_class != next_class)
queue_flags |= DEQUEUE_CLASS;
+ scx_prepare_setscheduler(p, policy);
+
scoped_guard (sched_change, p, queue_flags) {
if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
--
2.55.0