summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/sched/ext.c110
-rw-r--r--kernel/sched/ext_internal.h7
2 files changed, 108 insertions, 9 deletions
diff --git a/kernel/sched/ext.c b/kernel/sched/ext.c
index 044bb2168dd0..d5e688b9acc0 100644
--- a/kernel/sched/ext.c
+++ b/kernel/sched/ext.c
@@ -986,12 +986,45 @@ static void refill_task_slice_dfl(struct scx_sched *sch, struct task_struct *p)
__scx_add_event(sch, SCX_EV_REFILL_SLICE_DFL, 1);
}
+/*
+ * Return true if @p is moving due to an internal SCX migration, false
+ * otherwise.
+ */
+static inline bool task_scx_migrating(struct task_struct *p)
+{
+ /*
+ * We only need to check sticky_cpu: it is set to the destination
+ * CPU in move_remote_task_to_local_dsq() before deactivate_task()
+ * and cleared when the task is enqueued on the destination, so it
+ * is only non-negative during an internal SCX migration.
+ */
+ return p->scx.sticky_cpu >= 0;
+}
+
+/*
+ * Call ops.dequeue() if the task is in BPF custody and not migrating.
+ * Clears %SCX_TASK_IN_CUSTODY when the callback is invoked.
+ */
+static void call_task_dequeue(struct scx_sched *sch, struct rq *rq,
+ struct task_struct *p, u64 deq_flags)
+{
+ if (!(p->scx.flags & SCX_TASK_IN_CUSTODY) || task_scx_migrating(p))
+ return;
+
+ if (SCX_HAS_OP(sch, dequeue))
+ SCX_CALL_OP_TASK(sch, SCX_KF_REST, dequeue, rq, p, deq_flags);
+
+ p->scx.flags &= ~SCX_TASK_IN_CUSTODY;
+}
+
static void local_dsq_post_enq(struct scx_dispatch_q *dsq, struct task_struct *p,
u64 enq_flags)
{
struct rq *rq = container_of(dsq, struct rq, scx.local_dsq);
bool preempt = false;
+ call_task_dequeue(scx_root, rq, p, 0);
+
/*
* If @rq is in balance, the CPU is already vacant and looking for the
* next task to run. No need to preempt or trigger resched after moving
@@ -1116,16 +1149,33 @@ static void dispatch_enqueue(struct scx_sched *sch, struct rq *rq,
p->scx.ddsp_enq_flags = 0;
/*
+ * Update custody and call ops.dequeue() before clearing ops_state:
+ * once ops_state is cleared, waiters in ops_dequeue() can proceed
+ * and dequeue_task_scx() will RMW p->scx.flags. If we clear
+ * ops_state first, both sides would modify p->scx.flags
+ * concurrently in a non-atomic way.
+ */
+ if (is_local) {
+ local_dsq_post_enq(dsq, p, enq_flags);
+ } else {
+ /*
+ * Task on global/bypass DSQ: leave custody, task on
+ * non-terminal DSQ: enter custody.
+ */
+ if (dsq->id == SCX_DSQ_GLOBAL || dsq->id == SCX_DSQ_BYPASS)
+ call_task_dequeue(sch, rq, p, 0);
+ else
+ p->scx.flags |= SCX_TASK_IN_CUSTODY;
+
+ raw_spin_unlock(&dsq->lock);
+ }
+
+ /*
* We're transitioning out of QUEUEING or DISPATCHING. store_release to
* match waiters' load_acquire.
*/
if (enq_flags & SCX_ENQ_CLEAR_OPSS)
atomic_long_set_release(&p->scx.ops_state, SCX_OPSS_NONE);
-
- if (is_local)
- local_dsq_post_enq(dsq, p, enq_flags);
- else
- raw_spin_unlock(&dsq->lock);
}
static void task_unlink_from_dsq(struct task_struct *p,
@@ -1406,6 +1456,12 @@ static void do_enqueue_task(struct rq *rq, struct task_struct *p, u64 enq_flags,
goto direct;
/*
+ * Task is now in BPF scheduler's custody. Set %SCX_TASK_IN_CUSTODY
+ * so ops.dequeue() is called when it leaves custody.
+ */
+ p->scx.flags |= SCX_TASK_IN_CUSTODY;
+
+ /*
* If not directly dispatched, QUEUEING isn't clear yet and dispatch or
* dequeue may be waiting. The store_release matches their load_acquire.
*/
@@ -1522,6 +1578,14 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags)
{
struct scx_sched *sch = scx_root;
unsigned long opss;
+ u64 op_deq_flags = deq_flags;
+
+ /*
+ * Set %SCX_DEQ_SCHED_CHANGE when the dequeue is due to a property
+ * change (not sleep or core-sched pick).
+ */
+ if (!(op_deq_flags & (DEQUEUE_SLEEP | SCX_DEQ_CORE_SCHED_EXEC)))
+ op_deq_flags |= SCX_DEQ_SCHED_CHANGE;
/* dequeue is always temporary, don't reset runnable_at */
clr_task_runnable(p, false);
@@ -1539,10 +1603,8 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags)
*/
BUG();
case SCX_OPSS_QUEUED:
- if (SCX_HAS_OP(sch, dequeue))
- SCX_CALL_OP_TASK(sch, SCX_KF_REST, dequeue, rq,
- p, deq_flags);
-
+ /* A queued task must always be in BPF scheduler's custody */
+ WARN_ON_ONCE(!(p->scx.flags & SCX_TASK_IN_CUSTODY));
if (atomic_long_try_cmpxchg(&p->scx.ops_state, &opss,
SCX_OPSS_NONE))
break;
@@ -1565,6 +1627,22 @@ static void ops_dequeue(struct rq *rq, struct task_struct *p, u64 deq_flags)
BUG_ON(atomic_long_read(&p->scx.ops_state) != SCX_OPSS_NONE);
break;
}
+
+ /*
+ * Call ops.dequeue() if the task is still in BPF custody.
+ *
+ * The code that clears ops_state to %SCX_OPSS_NONE does not always
+ * clear %SCX_TASK_IN_CUSTODY: in dispatch_to_local_dsq(), when
+ * we're moving a task that was in %SCX_OPSS_DISPATCHING to a
+ * remote CPU's local DSQ, we only set ops_state to %SCX_OPSS_NONE
+ * so that a concurrent dequeue can proceed, but we clear
+ * %SCX_TASK_IN_CUSTODY only when we later enqueue or move the
+ * task. So we can see NONE + IN_CUSTODY here and we must handle
+ * it. Similarly, after waiting on %SCX_OPSS_DISPATCHING we see
+ * NONE but the task may still have %SCX_TASK_IN_CUSTODY set until
+ * it is enqueued on the destination.
+ */
+ call_task_dequeue(sch, rq, p, op_deq_flags);
}
static bool dequeue_task_scx(struct rq *rq, struct task_struct *p, int deq_flags)
@@ -2936,6 +3014,13 @@ static void scx_enable_task(struct task_struct *p)
lockdep_assert_rq_held(rq);
/*
+ * Verify the task is not in BPF scheduler's custody. If flag
+ * transitions are consistent, the flag should always be clear
+ * here.
+ */
+ WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
+
+ /*
* Set the weight before calling ops.enable() so that the scheduler
* doesn't see a stale value if they inspect the task struct.
*/
@@ -2966,6 +3051,13 @@ static void scx_disable_task(struct task_struct *p)
if (SCX_HAS_OP(sch, disable))
SCX_CALL_OP_TASK(sch, SCX_KF_REST, disable, rq, p);
scx_set_task_state(p, SCX_TASK_READY);
+
+ /*
+ * Verify the task is not in BPF scheduler's custody. If flag
+ * transitions are consistent, the flag should always be clear
+ * here.
+ */
+ WARN_ON_ONCE(p->scx.flags & SCX_TASK_IN_CUSTODY);
}
static void scx_exit_task(struct task_struct *p)
diff --git a/kernel/sched/ext_internal.h b/kernel/sched/ext_internal.h
index 386c677e4c9a..befa9a5d6e53 100644
--- a/kernel/sched/ext_internal.h
+++ b/kernel/sched/ext_internal.h
@@ -982,6 +982,13 @@ enum scx_deq_flags {
* it hasn't been dispatched yet. Dequeue from the BPF side.
*/
SCX_DEQ_CORE_SCHED_EXEC = 1LLU << 32,
+
+ /*
+ * The task is being dequeued due to a property change (e.g.,
+ * sched_setaffinity(), sched_setscheduler(), set_user_nice(),
+ * etc.).
+ */
+ SCX_DEQ_SCHED_CHANGE = 1LLU << 33,
};
enum scx_pick_idle_cpu_flags {