Skip to contentSkip to conversation

sched: Cleanup the change-pattern and related locking

PATCH Applied Peter Zijlstra 14 participants 74 messages Oct 6, 2025 – Jan 15

Subsystems (4)
lore
Share & tools
lore

Peter Zijlstra

author maintainer
#0 lore raw

cover sched: Cleanup the change-pattern and related locking

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

I would like to queue them into sched/core after the merge window.

Also in:

git://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git sched/cleanup

---
 include/linux/cleanup.h  |   5 +
 include/linux/sched.h    |   4 +-
 kernel/cgroup/cpuset.c   |   2 +-
 kernel/kthread.c         |  15 +--
 kernel/sched/core.c      | 327 ++++++++++++++++++-----------------------------
 kernel/sched/deadline.c  |  20 +--
 kernel/sched/ext.c       |  47 +++----
 kernel/sched/fair.c      |  15 ++-
 kernel/sched/idle.c      |   9 +-
 kernel/sched/rt.c        |   7 +-
 kernel/sched/sched.h     | 198 ++++++++++++++++++++--------
 kernel/sched/stats.h     |   2 +-
 kernel/sched/stop_task.c |   9 +-
 kernel/sched/syscalls.c  |  84 ++++--------
 14 files changed, 373 insertions(+), 371 deletions(-)
author maintainer
#1 lore raw

PATCH 1/12 sched: Employ sched_change guards

As proposed a long while ago -- and half done by scx -- wrap the scheduler's 'change' pattern in a guard helper.

---
 include/linux/cleanup.h |    5 +
 kernel/sched/core.c     |  157 ++++++++++++++++++------------------------------
 kernel/sched/ext.c      |   39 +++++------
 kernel/sched/sched.h    |   21 +++---
 kernel/sched/syscalls.c |   65 +++++++------------
 5 files changed, 116 insertions(+), 171 deletions(-)
include/linux/cleanup.h +5 -0
@@ -340,6 +340,11 @@ _label:
 #define __DEFINE_CLASS_IS_CONDITIONAL(_name, _is_cond)	\
 static __maybe_unused const bool class_##_name##_is_conditional = _is_cond
 
+#define DEFINE_CLASS_IS_UNCONDITIONAL(_name)		\
+	__DEFINE_CLASS_IS_CONDITIONAL(_name, false);	\
+	static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \
+	{ return (void *)1; }
+
 #define __GUARD_IS_ERR(_ptr)                                       \
 	({                                                         \
 		unsigned long _rc = (__force unsigned long)(_ptr); \
kernel/sched/core.c +60 -97

219 lines not on this page · raw message

kernel/sched/ext.c +18 -21
@@ -3780,11 +3780,10 @@ static void scx_bypass(bool bypass)
 		 */
 		list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list,
 						 scx.runnable_node) {
-			struct sched_enq_and_set_ctx ctx;
-
 			/* cycling deq/enq is enough, see the function comment */
-			sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
-			sched_enq_and_set_task(&ctx);
+			scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
+				/* nothing */ ;
+			}
 		}
 
 		/* resched to restore ticks and idle state */
@@ -3916,17 +3915,16 @@ static void scx_disable_workfn(struct kt
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
-		struct sched_enq_and_set_ctx ctx;
-
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
 
-		sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
+		update_rq_clock(task_rq(p));
 
-		p->sched_class = new_class;
-		check_class_changing(task_rq(p), p, old_class);
+		if (old_class != new_class && p->se.sched_delayed)
+			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		sched_enq_and_set_task(&ctx);
+		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+			p->sched_class = new_class;
+			check_class_changing(task_rq(p), p, old_class);
+		}
 
 		check_class_changed(task_rq(p), p, old_class, p->prio);
 		scx_exit_task(p);
@@ -4660,21 +4658,20 @@ static int scx_enable(struct sched_ext_o
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
-		struct sched_enq_and_set_ctx ctx;
 
 		if (!tryget_task_struct(p))
 			continue;
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
-
-		sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
+		update_rq_clock(task_rq(p));
 
-		p->scx.slice = SCX_SLICE_DFL;
-		p->sched_class = new_class;
-		check_class_changing(task_rq(p), p, old_class);
+		if (old_class != new_class && p->se.sched_delayed)
+			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		sched_enq_and_set_task(&ctx);
+		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+			p->scx.slice = SCX_SLICE_DFL;
+			p->sched_class = new_class;
+			check_class_changing(task_rq(p), p, old_class);
+		}
 
 		check_class_changed(task_rq(p), p, old_class, p->prio);
 		put_task_struct(p);
kernel/sched/sched.h +10 -11
@@ -3885,23 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
-#ifdef CONFIG_SCHED_CLASS_EXT
-/*
- * Used by SCX in the enable/disable paths to move tasks between sched_classes
- * and establish invariants.
- */
-struct sched_enq_and_set_ctx {
+struct sched_change_ctx {
 	struct task_struct	*p;
-	int			queue_flags;
+	int			flags;
 	bool			queued;
 	bool			running;
 };
 
-void sched_deq_and_put_task(struct task_struct *p, int queue_flags,
-			    struct sched_enq_and_set_ctx *ctx);
-void sched_enq_and_set_task(struct sched_enq_and_set_ctx *ctx);
+struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags);
+void sched_change_end(struct sched_change_ctx *ctx);
 
-#endif /* CONFIG_SCHED_CLASS_EXT */
+DEFINE_CLASS(sched_change, struct sched_change_ctx *,
+	     sched_change_end(_T),
+	     sched_change_begin(p, flags),
+	     struct task_struct *p, unsigned int flags)
+
+DEFINE_CLASS_IS_UNCONDITIONAL(sched_change)
 
 #include "ext.h"
 
kernel/sched/syscalls.c +23 -42

92 lines not on this page · raw message

reviewer
#2 lore raw

Re: [PATCH 01/12] sched: Employ sched_change guards

Hi Peter,

Quoted 6 lines · Peter Zijlstra

As proposed a long while ago -- and half done by scx -- wrap the scheduler's 'change' pattern in a guard helper.

---

...

kernel/sched/sched.h extern void check_class_changed(struct r · line 3893

Quoted 12 lines
kernel/sched/sched.h +0 -6
@@ -3885,23 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
-#ifdef CONFIG_SCHED_CLASS_EXT
-/*
- * Used by SCX in the enable/disable paths to move tasks between sched_classes
- * and establish invariants.
- */
-struct sched_enq_and_set_ctx {

Not necessarily for this patch, we can add it later, but I kinda liked the comment that briefly explained how the context is used. Maybe having something along these lines could be helpful?

/*
* Used to ensure the correct sequence of task state transitions, such as
* switching between sched_classes, changing CPU affinity, priority, or
* updating the queued/running state.
*/

Quoted 114 lines
kernel/sched/sched.h +10 -5
+struct sched_change_ctx {
 	struct task_struct	*p;
-	int			queue_flags;
+	int			flags;
 	bool			queued;
 	bool			running;
 };
 
-void sched_deq_and_put_task(struct task_struct *p, int queue_flags,
-			    struct sched_enq_and_set_ctx *ctx);
-void sched_enq_and_set_task(struct sched_enq_and_set_ctx *ctx);
+struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags);
+void sched_change_end(struct sched_change_ctx *ctx);
 
-#endif /* CONFIG_SCHED_CLASS_EXT */
+DEFINE_CLASS(sched_change, struct sched_change_ctx *,
+	     sched_change_end(_T),
+	     sched_change_begin(p, flags),
+	     struct task_struct *p, unsigned int flags)
+
+DEFINE_CLASS_IS_UNCONDITIONAL(sched_change)
 
 #include "ext.h"
 
kernel/sched/syscalls.c +19 -40
@@ -64,7 +64,6 @@ static int effective_prio(struct task_st
 
 void set_user_nice(struct task_struct *p, long nice)
 {
-	bool queued, running;
 	struct rq *rq;
 	int old_prio;
 
@@ -90,22 +89,12 @@ void set_user_nice(struct task_struct *p
 		return;
 	}
 
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);
-	if (queued)
-		dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
-	if (running)
-		put_prev_task(rq, p);
-
-	p->static_prio = NICE_TO_PRIO(nice);
-	set_load_weight(p, true);
-	old_prio = p->prio;
-	p->prio = effective_prio(p);
-
-	if (queued)
-		enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);
-	if (running)
-		set_next_task(rq, p);
+	scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK) {
+		p->static_prio = NICE_TO_PRIO(nice);
+		set_load_weight(p, true);
+		old_prio = p->prio;
+		p->prio = effective_prio(p);
+	}
 
 	/*
 	 * If the task increased its priority or is running and
@@ -515,7 +504,7 @@ int __sched_setscheduler(struct task_str
 			 bool user, bool pi)
 {
 	int oldpolicy = -1, policy = attr->sched_policy;
-	int retval, oldprio, newprio, queued, running;
+	int retval, oldprio, newprio;
 	const struct sched_class *prev_class, *next_class;
 	struct balance_callback *head;
 	struct rq_flags rf;
@@ -698,33 +687,25 @@ int __sched_setscheduler(struct task_str
 	if (prev_class != next_class && p->se.sched_delayed)
 		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);
-	if (queued)
-		dequeue_task(rq, p, queue_flags);
-	if (running)
-		put_prev_task(rq, p);
-
-	if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
-		__setscheduler_params(p, attr);
-		p->sched_class = next_class;
-		p->prio = newprio;
-	}
-	__setscheduler_uclamp(p, attr);
-	check_class_changing(rq, p, prev_class);
+	scoped_guard (sched_change, p, queue_flags) {
 
-	if (queued) {
-		/*
-		 * We enqueue to tail when the priority of a task is
-		 * increased (user space view).
-		 */
-		if (oldprio < p->prio)
-			queue_flags |= ENQUEUE_HEAD;
+		if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
+			__setscheduler_params(p, attr);
+			p->sched_class = next_class;
+			p->prio = newprio;
+		}
+		__setscheduler_uclamp(p, attr);
+		check_class_changing(rq, p, prev_class);
 
-		enqueue_task(rq, p, queue_flags);
+		if (scope->queued) {
+			/*
+			 * We enqueue to tail when the priority of a task is
+			 * increased (user space view).

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 711

Quoted 10 lines
kernel/sched/syscalls.c +4 -2
+			 */
+			if (oldprio < p->prio)
+				scope->flags |= ENQUEUE_HEAD;
+		}
 	}
-	if (running)
-		set_next_task(rq, p);
 
 	check_class_changed(rq, p, prev_class, oldprio);
 

Thanks,
-Andrea

author maintainer
#3 lore raw

Re: [PATCH 01/12] sched: Employ sched_change guards

Quoted 16 lines · Andrea Righi

Hi Peter,

Quoted 6 lines · Peter Zijlstra

As proposed a long while ago -- and half done by scx -- wrap the scheduler's 'change' pattern in a guard helper.

---

...

Quoted 6 lines
kernel/sched/sched.h +0 -0
@@ -3885,23 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
Quoted 10 lines

kernel/sched/sched.h extern void check_class_changed(struct r · line 3893

Quoted 6 lines
kernel/sched/sched.h +0 -6
-#ifdef CONFIG_SCHED_CLASS_EXT
-/*
- * Used by SCX in the enable/disable paths to move tasks between sched_classes
- * and establish invariants.
- */
-struct sched_enq_and_set_ctx {

Not necessarily for this patch, we can add it later, but I kinda liked the comment that briefly explained how the context is used. Maybe having something along these lines could be helpful?

I have changed it thus:

kernel/sched/sched.h +16 -0
@@ -3885,6 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
+/*
+ * The 'sched_change' pattern is the safe, easy and slow way of changing a
+ * task's scheduling properties. It dequeues a task, such that the scheduler
+ * is fully unaware of it; at which point its properties can be modified;
+ * after which it is enqueued again.
+ *
+ * Typically this must be called while holding task_rq_lock, since most/all
+ * properties are serialized under those locks. There is currently one
+ * exception to this rule in sched/ext which only holds rq->lock.
+ */
+
+/*
+ * This structure is a temporary, used to preserve/convey the queueing state
+ * of the task between sched_change_begin() and sched_change_end(). Ensuring
+ * the task's queueing state is idempotent across the operation.
+ */
 struct sched_change_ctx {
 	struct task_struct	*p;
 	int			flags;
reviewer
#4 lore raw

Re: [PATCH 01/12] sched: Employ sched_change guards

Hi Peter,

Quoted 43 lines · Peter Zijlstra
Quoted 16 lines · Andrea Righi

Hi Peter,

Quoted 6 lines · Peter Zijlstra

As proposed a long while ago -- and half done by scx -- wrap the scheduler's 'change' pattern in a guard helper.

---

...

Quoted 6 lines
kernel/sched/sched.h +0 -0
@@ -3885,23 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
Quoted 10 lines

kernel/sched/sched.h extern void check_class_changed(struct r · line 3893

Quoted 6 lines
kernel/sched/sched.h +0 -6
-#ifdef CONFIG_SCHED_CLASS_EXT
-/*
- * Used by SCX in the enable/disable paths to move tasks between sched_classes
- * and establish invariants.
- */
-struct sched_enq_and_set_ctx {

Not necessarily for this patch, we can add it later, but I kinda liked the comment that briefly explained how the context is used. Maybe having something along these lines could be helpful?

I have changed it thus:

kernel/sched/sched.h +6 -0
@@ -3885,6 +3885,22 @@ extern void check_class_changed(struct r
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
+/*
+ * The 'sched_change' pattern is the safe, easy and slow way of changing a
+ * task's scheduling properties. It dequeues a task, such that the scheduler
+ * is fully unaware of it; at which point its properties can be modified;
+ * after which it is enqueued again.
+ *

kernel/sched/sched.h extern void check_class_changed(struct r · line 3903

Quoted 10 lines
kernel/sched/sched.h +10 -0
+ * Typically this must be called while holding task_rq_lock, since most/all
+ * properties are serialized under those locks. There is currently one
+ * exception to this rule in sched/ext which only holds rq->lock.
+ */
+
+/*
+ * This structure is a temporary, used to preserve/convey the queueing state
+ * of the task between sched_change_begin() and sched_change_end(). Ensuring
+ * the task's queueing state is idempotent across the operation.
+ */

Looks great and very clear, thanks!

-Andrea

#5 lore raw

Re: [PATCH 01/12] sched: Employ sched_change guards

In reply to Peter Zijlstra

kernel/sched/core.c void rt_mutex_setprio(struct task_struct · line 7395

Quoted 6 lines · Peter Zijlstra
kernel/sched/core.c +0 -2
@@ -7391,52 +7391,42 @@ void rt_mutex_setprio(struct task_struct
      if (prev_class != next_class && p->se.sched_delayed)
              dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);

I'm not sure how that plays with sched_ext, but for the "standard" change pattern such as this one & the others in core.c, that becomes a task_current() per sched_change_begin(). I'm guessing we want to make sched_change_begin() use task_current_donor() instead?

author maintainer
#6 lore raw

Re: [PATCH 01/12] sched: Employ sched_change guards

Quoted 5 lines · Valentin Schneider
Quoted 4 lines · Peter Zijlstra
kernel/sched/core.c +0 -0
@@ -7391,52 +7391,42 @@ void rt_mutex_setprio(struct task_struct
      if (prev_class != next_class && p->se.sched_delayed)
              dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
Quoted 8 lines

Review void rt_mutex_setprio(struct task_struct · line 7395

Quoted 2 lines
(quoted diff) +0 -2
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);

I'm not sure how that plays with sched_ext, but for the "standard" change pattern such as this one & the others in core.c, that becomes a task_current() per sched_change_begin(). I'm guessing we want to make sched_change_begin() use task_current_donor() instead?

Argh yeah, rebase fail. Let me go fix.

merge bot Applied
#7 lore raw

[tip: sched/core] sched: Employ sched_change guards

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     e9139f765ac7048cadc9981e962acdf8b08eabf3
Gitweb:        https://git.kernel.org/tip/e9139f765ac7048cadc9981e962acdf8b08eabf3
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 30 Oct 2024 13:43:43 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:50 +02:00

sched: Employ sched_change guards

As proposed a long while ago -- and half done by scx -- wrap the scheduler's 'change' pattern in a guard helper.

---
 include/linux/cleanup.h |   5 +-
 kernel/sched/core.c     | 159 ++++++++++++++-------------------------
 kernel/sched/ext.c      |  39 ++++------
 kernel/sched/sched.h    |  33 +++++---
 kernel/sched/syscalls.c |  65 +++++-----------
 5 files changed, 131 insertions(+), 170 deletions(-)
include/linux/cleanup.h +5 -0
index 2573585..ae38167 100644
@@ -340,6 +340,11 @@ _label:                                                         \
 #define __DEFINE_CLASS_IS_CONDITIONAL(_name, _is_cond)	\
 static __maybe_unused const bool class_##_name##_is_conditional = _is_cond
 
+#define DEFINE_CLASS_IS_UNCONDITIONAL(_name)		\
+	__DEFINE_CLASS_IS_CONDITIONAL(_name, false);	\
+	static inline void * class_##_name##_lock_ptr(class_##_name##_t *_T) \
+	{ return (void *)1; }
+
 #define __GUARD_IS_ERR(_ptr)                                       \
 	({                                                         \
 		unsigned long _rc = (__force unsigned long)(_ptr); \
kernel/sched/core.c +61 -98

220 lines not on this page · raw message

kernel/sched/ext.c +18 -21
index 2b0e882..4566a7c 100644
@@ -3780,11 +3780,10 @@ static void scx_bypass(bool bypass)
 		 */
 		list_for_each_entry_safe_reverse(p, n, &rq->scx.runnable_list,
 						 scx.runnable_node) {
-			struct sched_enq_and_set_ctx ctx;
-
 			/* cycling deq/enq is enough, see the function comment */
-			sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
-			sched_enq_and_set_task(&ctx);
+			scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE) {
+				/* nothing */ ;
+			}
 		}
 
 		/* resched to restore ticks and idle state */
@@ -3916,17 +3915,16 @@ static void scx_disable_workfn(struct kthread_work *work)
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
-		struct sched_enq_and_set_ctx ctx;
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
+		update_rq_clock(task_rq(p));
 
-		sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
-
-		p->sched_class = new_class;
-		check_class_changing(task_rq(p), p, old_class);
+		if (old_class != new_class && p->se.sched_delayed)
+			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		sched_enq_and_set_task(&ctx);
+		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+			p->sched_class = new_class;
+			check_class_changing(task_rq(p), p, old_class);
+		}
 
 		check_class_changed(task_rq(p), p, old_class, p->prio);
 		scx_exit_task(p);
@@ -4660,21 +4658,20 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
-		struct sched_enq_and_set_ctx ctx;
 
 		if (!tryget_task_struct(p))
 			continue;
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
-
-		sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
+		update_rq_clock(task_rq(p));
 
-		p->scx.slice = SCX_SLICE_DFL;
-		p->sched_class = new_class;
-		check_class_changing(task_rq(p), p, old_class);
+		if (old_class != new_class && p->se.sched_delayed)
+			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		sched_enq_and_set_task(&ctx);
+		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+			p->scx.slice = SCX_SLICE_DFL;
+			p->sched_class = new_class;
+			check_class_changing(task_rq(p), p, old_class);
+		}
 
 		check_class_changed(task_rq(p), p, old_class, p->prio);
 		put_task_struct(p);
kernel/sched/sched.h +24 -9
index 1f5d070..6546849 100644
@@ -3885,23 +3885,38 @@ extern void check_class_changed(struct rq *rq, struct task_struct *p,
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
-#ifdef CONFIG_SCHED_CLASS_EXT
 /*
- * Used by SCX in the enable/disable paths to move tasks between sched_classes
- * and establish invariants.
+ * The 'sched_change' pattern is the safe, easy and slow way of changing a
+ * task's scheduling properties. It dequeues a task, such that the scheduler
+ * is fully unaware of it; at which point its properties can be modified;
+ * after which it is enqueued again.
+ *
+ * Typically this must be called while holding task_rq_lock, since most/all
+ * properties are serialized under those locks. There is currently one
+ * exception to this rule in sched/ext which only holds rq->lock.
+ */
+
+/*
+ * This structure is a temporary, used to preserve/convey the queueing state
+ * of the task between sched_change_begin() and sched_change_end(). Ensuring
+ * the task's queueing state is idempotent across the operation.
  */
-struct sched_enq_and_set_ctx {
+struct sched_change_ctx {
 	struct task_struct	*p;
-	int			queue_flags;
+	int			flags;
 	bool			queued;
 	bool			running;
 };
 
-void sched_deq_and_put_task(struct task_struct *p, int queue_flags,
-			    struct sched_enq_and_set_ctx *ctx);
-void sched_enq_and_set_task(struct sched_enq_and_set_ctx *ctx);
+struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int flags);
+void sched_change_end(struct sched_change_ctx *ctx);
 
-#endif /* CONFIG_SCHED_CLASS_EXT */
+DEFINE_CLASS(sched_change, struct sched_change_ctx *,
+	     sched_change_end(_T),
+	     sched_change_begin(p, flags),
+	     struct task_struct *p, unsigned int flags)
+
+DEFINE_CLASS_IS_UNCONDITIONAL(sched_change)
 
 #include "ext.h"
 
kernel/sched/syscalls.c +23 -42

92 lines not on this page · raw message

author maintainer
#8 lore raw

PATCH 2/12 sched: Re-arrange the {EN,DE}QUEUE flags

In reply to Peter Zijlstra

Ensure the matched flags are in the low word while the unmatched flags go into the second word.

---
 kernel/sched/sched.h |   45 ++++++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 21 deletions(-)
kernel/sched/sched.h +24 -21
@@ -2362,27 +2362,30 @@ extern const u32		sched_prio_to_wmult[40
  *
  */
 
-#define DEQUEUE_SLEEP		0x01 /* Matches ENQUEUE_WAKEUP */
-#define DEQUEUE_SAVE		0x02 /* Matches ENQUEUE_RESTORE */
-#define DEQUEUE_MOVE		0x04 /* Matches ENQUEUE_MOVE */
-#define DEQUEUE_NOCLOCK		0x08 /* Matches ENQUEUE_NOCLOCK */
-#define DEQUEUE_SPECIAL		0x10
-#define DEQUEUE_MIGRATING	0x100 /* Matches ENQUEUE_MIGRATING */
-#define DEQUEUE_DELAYED		0x200 /* Matches ENQUEUE_DELAYED */
-#define DEQUEUE_THROTTLE	0x800
-
-#define ENQUEUE_WAKEUP		0x01
-#define ENQUEUE_RESTORE		0x02
-#define ENQUEUE_MOVE		0x04
-#define ENQUEUE_NOCLOCK		0x08
-
-#define ENQUEUE_HEAD		0x10
-#define ENQUEUE_REPLENISH	0x20
-#define ENQUEUE_MIGRATED	0x40
-#define ENQUEUE_INITIAL		0x80
-#define ENQUEUE_MIGRATING	0x100
-#define ENQUEUE_DELAYED		0x200
-#define ENQUEUE_RQ_SELECTED	0x400
+#define DEQUEUE_SLEEP		0x0001 /* Matches ENQUEUE_WAKEUP */
+#define DEQUEUE_SAVE		0x0002 /* Matches ENQUEUE_RESTORE */
+#define DEQUEUE_MOVE		0x0004 /* Matches ENQUEUE_MOVE */
+#define DEQUEUE_NOCLOCK		0x0008 /* Matches ENQUEUE_NOCLOCK */
+
+#define DEQUEUE_MIGRATING	0x0010 /* Matches ENQUEUE_MIGRATING */
+#define DEQUEUE_DELAYED		0x0020 /* Matches ENQUEUE_DELAYED */
+
+#define DEQUEUE_SPECIAL		0x00010000
+#define DEQUEUE_THROTTLE	0x00020000
+
+#define ENQUEUE_WAKEUP		0x0001
+#define ENQUEUE_RESTORE		0x0002
+#define ENQUEUE_MOVE		0x0004
+#define ENQUEUE_NOCLOCK		0x0008
+
+#define ENQUEUE_MIGRATING	0x0010
+#define ENQUEUE_DELAYED		0x0020
+
+#define ENQUEUE_HEAD		0x00010000
+#define ENQUEUE_REPLENISH	0x00020000
+#define ENQUEUE_MIGRATED	0x00040000
+#define ENQUEUE_INITIAL		0x00080000
+#define ENQUEUE_RQ_SELECTED	0x00100000
 
 #define RETRY_TASK		((void *)-1UL)
 
merge bot Applied
#9 lore raw

[tip: sched/core] sched: Re-arrange the {EN,DE}QUEUE flags

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     376f8963bbda5fee838eb1823b07562368104024
Gitweb:        https://git.kernel.org/tip/376f8963bbda5fee838eb1823b07562368104024
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 30 Oct 2024 13:52:05 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:50 +02:00

sched: Re-arrange the {EN,DE}QUEUE flags

Ensure the matched flags are in the low word while the unmatched flags go into the second word.

---
 kernel/sched/sched.h | 45 ++++++++++++++++++++++---------------------
 1 file changed, 24 insertions(+), 21 deletions(-)
kernel/sched/sched.h +24 -21
index 6546849..24b3c6c 100644
@@ -2362,27 +2362,30 @@ extern const u32		sched_prio_to_wmult[40];
  *
  */
 
-#define DEQUEUE_SLEEP		0x01 /* Matches ENQUEUE_WAKEUP */
-#define DEQUEUE_SAVE		0x02 /* Matches ENQUEUE_RESTORE */
-#define DEQUEUE_MOVE		0x04 /* Matches ENQUEUE_MOVE */
-#define DEQUEUE_NOCLOCK		0x08 /* Matches ENQUEUE_NOCLOCK */
-#define DEQUEUE_SPECIAL		0x10
-#define DEQUEUE_MIGRATING	0x100 /* Matches ENQUEUE_MIGRATING */
-#define DEQUEUE_DELAYED		0x200 /* Matches ENQUEUE_DELAYED */
-#define DEQUEUE_THROTTLE	0x800
-
-#define ENQUEUE_WAKEUP		0x01
-#define ENQUEUE_RESTORE		0x02
-#define ENQUEUE_MOVE		0x04
-#define ENQUEUE_NOCLOCK		0x08
-
-#define ENQUEUE_HEAD		0x10
-#define ENQUEUE_REPLENISH	0x20
-#define ENQUEUE_MIGRATED	0x40
-#define ENQUEUE_INITIAL		0x80
-#define ENQUEUE_MIGRATING	0x100
-#define ENQUEUE_DELAYED		0x200
-#define ENQUEUE_RQ_SELECTED	0x400
+#define DEQUEUE_SLEEP		0x0001 /* Matches ENQUEUE_WAKEUP */
+#define DEQUEUE_SAVE		0x0002 /* Matches ENQUEUE_RESTORE */
+#define DEQUEUE_MOVE		0x0004 /* Matches ENQUEUE_MOVE */
+#define DEQUEUE_NOCLOCK		0x0008 /* Matches ENQUEUE_NOCLOCK */
+
+#define DEQUEUE_MIGRATING	0x0010 /* Matches ENQUEUE_MIGRATING */
+#define DEQUEUE_DELAYED		0x0020 /* Matches ENQUEUE_DELAYED */
+
+#define DEQUEUE_SPECIAL		0x00010000
+#define DEQUEUE_THROTTLE	0x00020000
+
+#define ENQUEUE_WAKEUP		0x0001
+#define ENQUEUE_RESTORE		0x0002
+#define ENQUEUE_MOVE		0x0004
+#define ENQUEUE_NOCLOCK		0x0008
+
+#define ENQUEUE_MIGRATING	0x0010
+#define ENQUEUE_DELAYED		0x0020
+
+#define ENQUEUE_HEAD		0x00010000
+#define ENQUEUE_REPLENISH	0x00020000
+#define ENQUEUE_MIGRATED	0x00040000
+#define ENQUEUE_INITIAL		0x00080000
+#define ENQUEUE_RQ_SELECTED	0x00100000
 
 #define RETRY_TASK		((void *)-1UL)
 
author maintainer
#10 lore raw

PATCH 3/12 sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

In reply to Peter Zijlstra

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

---
 kernel/sched/core.c      |   56 +++++++++++++++++++++--------------------------
 kernel/sched/ext.c       |   26 ++++++++++++++++-----
 kernel/sched/idle.c      |    4 +--
 kernel/sched/rt.c        |    2 -
 kernel/sched/sched.h     |   22 ++++++------------
 kernel/sched/stop_task.c |    4 +--
 kernel/sched/syscalls.c  |    9 +++++--
 7 files changed, 66 insertions(+), 57 deletions(-)
kernel/sched/core.c +26 -30
@@ -2169,34 +2169,9 @@ inline int task_curr(const struct task_s
 	return cpu_curr(task_cpu(p)) == p;
 }
 
-/*
- * ->switching_to() is called with the pi_lock and rq_lock held and must not
- * mess with locking.
- */
-void check_class_changing(struct rq *rq, struct task_struct *p,
-			  const struct sched_class *prev_class)
+void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio)
 {
-	if (prev_class != p->sched_class && p->sched_class->switching_to)
-		p->sched_class->switching_to(rq, p);
-}
-
-/*
- * switched_from, switched_to and prio_changed must _NOT_ drop rq->lock,
- * use the balance_callback list if you want balancing.
- *
- * this means any call to check_class_changed() must be followed by a call to
- * balance_callback().
- */
-void check_class_changed(struct rq *rq, struct task_struct *p,
-			 const struct sched_class *prev_class,
-			 int oldprio)
-{
-	if (prev_class != p->sched_class) {
-		if (prev_class->switched_from)
-			prev_class->switched_from(rq, p);
-
-		p->sched_class->switched_to(rq, p);
-	} else if (oldprio != p->prio || dl_task(p))
+	if (oldprio != p->prio || dl_task(p))
 		p->sched_class->prio_changed(rq, p, oldprio);
 }
 
@@ -7388,6 +7363,11 @@ void rt_mutex_setprio(struct task_struct
 	prev_class = p->sched_class;
 	next_class = __setscheduler_class(p->policy, prio);
 
+	if (prev_class != next_class) {
+		queue_flag |= DEQUEUE_CLASS;
+		queue_flag &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
+	}
+
 	if (prev_class != next_class && p->se.sched_delayed)
 		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
@@ -7424,11 +7404,10 @@ void rt_mutex_setprio(struct task_struct
 
 		p->sched_class = next_class;
 		p->prio = prio;
-
-		check_class_changing(rq, p, prev_class);
 	}
 
-	check_class_changed(rq, p, prev_class, oldprio);
+	if (!(queue_flag & DEQUEUE_CLASS))
+		check_prio_changed(rq, p, oldprio);
 out_unlock:
 	/* Avoid rq from going away on us: */
 	preempt_disable();
@@ -10862,6 +10841,14 @@ struct sched_change_ctx *sched_change_be
 
 	lockdep_assert_rq_held(rq);
 
+	if (flags & DEQUEUE_CLASS) {
+		if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
+			flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
+
+		if (p->sched_class->switching_from)
+			p->sched_class->switching_from(rq, p);
+	}
+
 	*ctx = (struct sched_change_ctx){
 		.p = p,
 		.flags = flags,
@@ -10874,6 +10861,9 @@ struct sched_change_ctx *sched_change_be
 	if (ctx->running)
 		put_prev_task(rq, p);
 
+	if ((flags & DEQUEUE_CLASS) && p->sched_class->switched_from)
+		p->sched_class->switched_from(rq, p);
+
 	return ctx;
 }
 
@@ -10884,8 +10874,14 @@ void sched_change_end(struct sched_chang
 
 	lockdep_assert_rq_held(rq);
 
+	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switching_to)
+		p->sched_class->switching_to(rq, p);
+
 	if (ctx->queued)
 		enqueue_task(rq, p, ctx->flags | ENQUEUE_NOCLOCK);
 	if (ctx->running)
 		set_next_task(rq, p);
+
+	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switched_to)
+		p->sched_class->switched_to(rq, p);
 }
kernel/sched/ext.c +20 -6
@@ -3912,21 +3912,28 @@ static void scx_disable_workfn(struct kt
 
 	scx_task_iter_start(&sti);
 	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;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
 
 		update_rq_clock(task_rq(p));
 
+		if (old_class != new_class) {
+			queue_flags |= DEQUEUE_CLASS;
+			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
+		}
+
 		if (old_class != new_class && p->se.sched_delayed)
 			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+		scoped_guard (sched_change, p, queue_flags) {
 			p->sched_class = new_class;
-			check_class_changing(task_rq(p), p, old_class);
 		}
 
-		check_class_changed(task_rq(p), p, old_class, p->prio);
+		if (!(queue_flags & DEQUEUE_CLASS))
+			check_prio_changed(task_rq(p), p, p->prio);
+
 		scx_exit_task(p);
 	}
 	scx_task_iter_stop(&sti);
@@ -4655,6 +4662,7 @@ static int scx_enable(struct sched_ext_o
 	percpu_down_write(&scx_fork_rwsem);
 	scx_task_iter_start(&sti);
 	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;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
@@ -4664,16 +4672,22 @@ static int scx_enable(struct sched_ext_o
 
 		update_rq_clock(task_rq(p));
 
+		if (old_class != new_class) {
+			queue_flags |= DEQUEUE_CLASS;
+			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
+		}
+
 		if (old_class != new_class && p->se.sched_delayed)
 			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+		scoped_guard (sched_change, p, queue_flags) {
 			p->scx.slice = SCX_SLICE_DFL;
 			p->sched_class = new_class;
-			check_class_changing(task_rq(p), p, old_class);
 		}
 
-		check_class_changed(task_rq(p), p, old_class, p->prio);
+		if (!(queue_flags & DEQUEUE_CLASS))
+			check_prio_changed(task_rq(p), p, p->prio);
+
 		put_task_struct(p);
 	}
 	scx_task_iter_stop(&sti);
kernel/sched/idle.c +2 -2
@@ -498,7 +498,7 @@ static void task_tick_idle(struct rq *rq
 {
 }
 
-static void switched_to_idle(struct rq *rq, struct task_struct *p)
+static void switching_to_idle(struct rq *rq, struct task_struct *p)
 {
 	BUG();
 }
@@ -536,6 +536,6 @@ DEFINE_SCHED_CLASS(idle) = {
 	.task_tick		= task_tick_idle,
 
 	.prio_changed		= prio_changed_idle,
-	.switched_to		= switched_to_idle,
+	.switching_to		= switching_to_idle,
 	.update_curr		= update_curr_idle,
 };
kernel/sched/rt.c +1 -1
@@ -2589,8 +2589,8 @@ DEFINE_SCHED_CLASS(rt) = {
 
 	.get_rr_interval	= get_rr_interval_rt,
 
-	.prio_changed		= prio_changed_rt,
 	.switched_to		= switched_to_rt,
+	.prio_changed		= prio_changed_rt,
 
 	.update_curr		= update_curr_rt,
 
kernel/sched/sched.h +8 -14
@@ -20,7 +20,6 @@
 #include <linux/sched/task_flags.h>
 #include <linux/sched/task.h>
 #include <linux/sched/topology.h>
-
 #include <linux/atomic.h>
 #include <linux/bitmap.h>
 #include <linux/bug.h>
@@ -2369,6 +2368,7 @@ extern const u32		sched_prio_to_wmult[40
 
 #define DEQUEUE_MIGRATING	0x0010 /* Matches ENQUEUE_MIGRATING */
 #define DEQUEUE_DELAYED		0x0020 /* Matches ENQUEUE_DELAYED */
+#define DEQUEUE_CLASS		0x0040 /* Matches ENQUEUE_CLASS */
 
 #define DEQUEUE_SPECIAL		0x00010000
 #define DEQUEUE_THROTTLE	0x00020000
@@ -2380,6 +2380,7 @@ extern const u32		sched_prio_to_wmult[40
 
 #define ENQUEUE_MIGRATING	0x0010
 #define ENQUEUE_DELAYED		0x0020
+#define ENQUEUE_CLASS		0x0040
 
 #define ENQUEUE_HEAD		0x00010000
 #define ENQUEUE_REPLENISH	0x00020000
@@ -2443,14 +2444,11 @@ struct sched_class {
 	void (*task_fork)(struct task_struct *p);
 	void (*task_dead)(struct task_struct *p);
 
-	/*
-	 * The switched_from() call is allowed to drop rq->lock, therefore we
-	 * cannot assume the switched_from/switched_to pair is serialized by
-	 * rq->lock. They are however serialized by p->pi_lock.
-	 */
-	void (*switching_to) (struct rq *this_rq, struct task_struct *task);
-	void (*switched_from)(struct rq *this_rq, struct task_struct *task);
-	void (*switched_to)  (struct rq *this_rq, struct task_struct *task);
+	void (*switching_from)(struct rq *this_rq, struct task_struct *task);
+	void (*switched_from) (struct rq *this_rq, struct task_struct *task);
+	void (*switching_to)  (struct rq *this_rq, struct task_struct *task);
+	void (*switched_to)   (struct rq *this_rq, struct task_struct *task);
+
 	void (*reweight_task)(struct rq *this_rq, struct task_struct *task,
 			      const struct load_weight *lw);
 	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
@@ -3879,11 +3877,7 @@ extern void set_load_weight(struct task_
 extern void enqueue_task(struct rq *rq, struct task_struct *p, int flags);
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
-extern void check_class_changing(struct rq *rq, struct task_struct *p,
-				 const struct sched_class *prev_class);
-extern void check_class_changed(struct rq *rq, struct task_struct *p,
-				const struct sched_class *prev_class,
-				int oldprio);
+extern void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio);
 
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
kernel/sched/stop_task.c +2 -2
@@ -75,7 +75,7 @@ static void task_tick_stop(struct rq *rq
 {
 }
 
-static void switched_to_stop(struct rq *rq, struct task_struct *p)
+static void switching_to_stop(struct rq *rq, struct task_struct *p)
 {
 	BUG(); /* its impossible to change to this class */
 }
@@ -112,6 +112,6 @@ DEFINE_SCHED_CLASS(stop) = {
 	.task_tick		= task_tick_stop,
 
 	.prio_changed		= prio_changed_stop,
-	.switched_to		= switched_to_stop,
+	.switching_to		= switching_to_stop,
 	.update_curr		= update_curr_stop,
 };
kernel/sched/syscalls.c +7 -2
@@ -684,6 +684,11 @@ int __sched_setscheduler(struct task_str
 	prev_class = p->sched_class;
 	next_class = __setscheduler_class(policy, newprio);
 
+	if (prev_class != next_class) {
+		queue_flags |= DEQUEUE_CLASS;
+		queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
+	}
+
 	if (prev_class != next_class && p->se.sched_delayed)
 		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
@@ -695,7 +700,6 @@ int __sched_setscheduler(struct task_str
 			p->prio = newprio;
 		}
 		__setscheduler_uclamp(p, attr);
-		check_class_changing(rq, p, prev_class);
 
 		if (scope->queued) {
 			/*
@@ -707,7 +711,8 @@ int __sched_setscheduler(struct task_str
 		}
 	}
 
-	check_class_changed(rq, p, prev_class, oldprio);
+	if (!(queue_flags & DEQUEUE_CLASS))
+		check_prio_changed(rq, p, oldprio);
 
 	/* Avoid rq from going away on us: */
 	preempt_disable();
#11 lore raw

Re: [PATCH 03/12] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Quoted 21 lines · Peter Zijlstra

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|
Quoted 7 lines

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

This one causes a DL bw related warning when I run a simple 1 DL task rt-app workload:

# rt-app ./rt-app/dl10.json 

[rt-app] <notice> thread_data_set_unique_name 0 thread0-0 [rt-app] <notice> [0] starting thread ...

[rt-app] <notice> [0] Starting with SCHED_DEADLINE policy with priority 0

[   16.390272] sched: DL replenish lagged too much
[   16.390327] ------------[ cut here ]------------
[   16.390329] WARNING: CPU: 2 PID: 591 at kernel/sched/deadline.c:239 sub_running_bw.isra.0+0xf4/0x150
[   16.391849] Modules linked in:
[   16.392107] CPU: 2 UID: 0 PID: 591 Comm: thread0-0 Not tainted 6.17.0-rc4-00020-ga6b63e5ce187 #46 PREEMPT 
[   16.392885] Hardware name: linux,dummy-virt (DT)
[   16.393265] pstate: 014000c5 (nzcv daIF +PAN -UAO -TCO +DIT -SSBS BTYPE=--)
[   16.393783] pc : sub_running_bw.isra.0+0xf4/0x150
[   16.394153] lr : sub_running_bw.isra.0+0x118/0x150
[   16.394636] sp : ffff80008137bb10
[   16.394864] x29: ffff80008137bb10 x28: ffff0000ff7b39c0 x27: ffff0000ce73dd60
[   16.395333] x26: 0000000000000000 x25: ffffa1134d945000 x24: ffff0000ff7b42c8
[   16.395805] x23: ffffa1134d944000 x22: ffffa1134d944000 x21: 000000000000cccc
[   16.396267] x20: 0000000000060000 x19: ffff0000ff7b42c8 x18: fffffffffffe6f58
[   16.396742] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000001
[   16.397202] x14: fffffffffffc6f57 x13: 0a6863756d206f6f x12: ffffa1134e743f60
[   16.397674] x11: 00000000000000c0 x10: 0000000000000001 x9 : 0000000000000000
[   16.398130] x8 : ffff0000c001e490 x7 : 0000000000000008 x6 : ffff0000c0029968
[   16.398883] x5 : 00000000ffffffff x4 : 0000000000000064 x3 : ffff0000c0029fa8
[   16.399432] x2 : ffff5eedb1f6e000 x1 : 000000000000cccc x0 : fffffffffffacccc
[   16.399962] Call trace:
[   16.400147]  sub_running_bw.isra.0+0xf4/0x150 (P)
[   16.400510]  task_non_contending+0x248/0x2ac
[   16.400831]  dequeue_task_dl+0x178/0x2d4
[   16.401122]  __schedule+0x6ac/0x1038
[   16.401401]  schedule+0x4c/0x164
[   16.401627]  do_nanosleep+0x6c/0x190
[   16.401862]  hrtimer_nanosleep+0xbc/0x200
[   16.402156]  common_nsleep_timens+0x50/0x90
[   16.402522]  __arm64_sys_clock_nanosleep+0xd0/0x150
[   16.402813]  invoke_syscall+0x48/0x104
[   16.403043]  el0_svc_common.constprop.0+0x40/0xe0
[   16.403327]  do_el0_svc+0x1c/0x28
[   16.403520]  el0_svc+0x4c/0x160
[   16.403711]  el0t_64_sync_handler+0xa0/0xf0
[   16.403950]  el0t_64_sync+0x198/0x19c
[   16.404226] irq event stamp: 196
[   16.404451] hardirqs last  enabled at (195): [<ffffa1134c8021d8>] _raw_spin_unlock_irqrestore+0x6c/0x74
[   16.405086] hardirqs last disabled at (196): [<ffffa1134c7f7850>] __schedule+0x4e8/0x1038
[   16.405629] softirqs last  enabled at (154): [<ffffa1134b4e157c>] handle_softirqs+0x44c/0x498
[   16.406218] softirqs last disabled at (145): [<ffffa1134b410774>] __do_softirq+0x14/0x20

with extra logging and removing underflow WARN_ON_ONCE:

# rt-app ./rt-app/dl10.json 

[rt-app] <notice> thread_data_set_unique_name 0 thread0-0 [rt-app] <notice> [0] starting thread ...

[rt-app] <notice> [0] Starting with SCHED_DEADLINE policy with priority 0

[   18.494469] sched: DL replenish lagged too much
[   18.494483] cpu=3 p->comm=thread0-0 p->pid=592
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[   18.494486] __sub_running_bw() cpu=3 dl_rq->running_bw=18446744073709210828 dl_bw=393216 old=52428
                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                        dl_rq->running_bw underflow in task_non_contending()
[   18.494492] CPU: 3 UID: 0 PID: 592 Comm: thread0-0 Not tainted 6.17.0-rc4-00020-ga6b63e5ce187-dirty #44 PREEMPT 
[   18.494495] Hardware name: linux,dummy-virt (DT)
[   18.494497] Call trace:
[   18.494498]  show_stack+0x18/0x24 (C)
[   18.494510]  dump_stack_lvl+0x70/0x98
[   18.494514]  dump_stack+0x18/0x24
[   18.494516]  sub_running_bw.isra.0+0x164/0x180
[   18.494539]  task_non_contending+0x298/0x2e8
[   18.494541]  dequeue_task_dl+0x188/0x31c
[   18.494544]  __schedule+0x6ac/0x1038
[   18.494574]  schedule+0x4c/0x164
[   18.494578]  do_nanosleep+0x6c/0x190
[   18.494580]  hrtimer_nanosleep+0xbc/0x200
[   18.494594]  common_nsleep_timens+0x50/0x90
[   18.494599]  __arm64_sys_clock_nanosleep+0xd0/0x150
[   18.494602]  invoke_syscall+0x48/0x104
[   18.494610]  el0_svc_common.constprop.0+0x40/0xe0
[   18.494612]  do_el0_svc+0x1c/0x28
[   18.494615]  el0_svc+0x4c/0x160
[   18.494617]  el0t_64_sync_handler+0xa0/0xf0
[   18.494620]  el0t_64_sync+0x198/0x19c

Not sure yet how this is related to switched_from_dl() being now called earlier?

[...]

author maintainer
#12 lore raw

Re: [PATCH 03/12] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Quoted 27 lines · Dietmar Eggemann
Quoted 26 lines · Peter Zijlstra

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Quoted 7 lines
Quoted 2 lines

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

This one causes a DL bw related warning when I run a simple 1 DL task rt-app workload:

Not sure yet how this is related to switched_from_dl() being now called earlier?

Ooh, I might see a problem. task_non_contending() uses dl_task(), which uses p->prio. The move above means it is now called using the 'old' prio, whereas it used to run with the 'new' prio.

I suppose it does this to distinguish 'real' DL tasks from PI boosted DL tasks.

Let me see if I can figure out something for this.

author maintainer
#13 lore raw

Re: [PATCH 03/12] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Quoted 34 lines · Peter Zijlstra
Quoted 32 lines · Dietmar Eggemann
Quoted 21 lines · Peter Zijlstra

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|
Quoted 7 lines

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

This one causes a DL bw related warning when I run a simple 1 DL task rt-app workload:

Quoted 7 lines
Quoted 1 line

Not sure yet how this is related to switched_from_dl() being now called earlier?

Ooh, I might see a problem. task_non_contending() uses dl_task(), which uses p->prio. The move above means it is now called using the 'old' prio, whereas it used to run with the 'new' prio.

Let me see if I can figure out something for this.

Does this help? /me goes find rt-app.

kernel/sched/deadline.c +5 -5
index 615411a0a881..fe2272c812b2 100644
@@ -405,7 +405,7 @@ static void __dl_clear_params(struct sched_dl_entity *dl_se);
  * up, and checks if the task is still in the "ACTIVE non contending"
  * state or not (in the second case, it updates running_bw).
  */
-static void task_non_contending(struct sched_dl_entity *dl_se)
+static void task_non_contending(struct sched_dl_entity *dl_se, bool dl_task)
 {
 	struct hrtimer *timer = &dl_se->inactive_timer;
 	struct rq *rq = rq_of_dl_se(dl_se);
@@ -444,10 +444,10 @@ static void task_non_contending(struct sched_dl_entity *dl_se)
 		} else {
 			struct task_struct *p = dl_task_of(dl_se);
 
-			if (dl_task(p))
+			if (dl_task)
 				sub_running_bw(dl_se, dl_rq);
 
-			if (!dl_task(p) || READ_ONCE(p->__state) == TASK_DEAD) {
+			if (!dl_task || READ_ONCE(p->__state) == TASK_DEAD) {
 				struct dl_bw *dl_b = dl_bw_of(task_cpu(p));
 
 				if (READ_ONCE(p->__state) == TASK_DEAD)
@@ -2045,7 +2045,7 @@ static void dequeue_dl_entity(struct sched_dl_entity *dl_se, int flags)
 	 * or "inactive")
 	 */
 	if (flags & DEQUEUE_SLEEP)
-		task_non_contending(dl_se);
+		task_non_contending(dl_se, true);
 }
 
 static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags)
@@ -2970,7 +2970,7 @@ static void switched_from_dl(struct rq *rq, struct task_struct *p)
 	 * will reset the task parameters.
 	 */
 	if (task_on_rq_queued(p) && p->dl.dl_runtime)
-		task_non_contending(&p->dl);
+		task_non_contending(&p->dl, false);
 
 	/*
 	 * In case a task is setscheduled out from SCHED_DEADLINE we need to
#14 lore raw

Re: [PATCH 03/12] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Quoted 37 lines · Peter Zijlstra
Quoted 36 lines · Peter Zijlstra
Quoted 34 lines · Dietmar Eggemann
Quoted 21 lines · Peter Zijlstra

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|
Quoted 7 lines

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

This one causes a DL bw related warning when I run a simple 1 DL task rt-app workload:

Not sure yet how this is related to switched_from_dl() being now called earlier?

Quoted 7 lines
Quoted 5 lines

Ooh, I might see a problem. task_non_contending() uses dl_task(), which uses p->prio. The move above means it is now called using the 'old' prio, whereas it used to run with the 'new' prio.

Let me see if I can figure out something for this.

Does this help? /me goes find rt-app.

Yes, but there seems to be more ... missing DEQUEUE_SAVE (a.k.a. ENQUEUE_RESTORE) in

enqueue_dl_entity()

    if (flags & (ENQUEUE_RESTORE|ENQUEUE_MIGRATING))
                 ^^^^^^^^^^^^^^^
      ...
      add_running_bw(dl_se, dl_rq)

and

__sched_setscheduler()

    ...
    if (prev_class != next_class)
      queue_flags |= DEQUEUE_CLASS;
      queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                       ^^^^^^^^^^^^

as well as

sched_change_begin()

    ...
    if (flags & DEQUEUE_CLASS) {
      if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
        flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                   ^^^^^^^^^^^^

With your patch and this the issue went away:

kernel/sched/core.c +0 -0
index 884926d3dd95..35074799e9ad 100644
@@ -10844,9 +10844,6 @@ struct sched_change_ctx

*sched_change_begin(struct task_struct *p, unsigned int

        lockdep_assert_rq_held(rq);

        if (flags & DEQUEUE_CLASS) {
-               if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
-                       flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
-
                if (p->sched_class->switching_from)
                        p->sched_class->switching_from(rq, p);
        }
kernel/sched/syscalls.c +1 -3
index 007d1440374b..bcef5c72d287 100644
@@ -684,10 +684,8 @@ int __sched_setscheduler(struct task_struct *p,
        prev_class = p->sched_class;
        next_class = __setscheduler_class(policy, newprio);
 
-       if (prev_class != next_class) {
+       if (prev_class != next_class)
                queue_flags |= DEQUEUE_CLASS;
-               queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
-       }
author maintainer
#15 lore raw

Re: [PATCH 03/12] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Quoted 22 lines · Dietmar Eggemann

Yes, but there seems to be more ... missing DEQUEUE_SAVE (a.k.a. ENQUEUE_RESTORE) in

enqueue_dl_entity()

    if (flags & (ENQUEUE_RESTORE|ENQUEUE_MIGRATING))
                 ^^^^^^^^^^^^^^^
      ...
      add_running_bw(dl_se, dl_rq)

and

__sched_setscheduler()

    ...
    if (prev_class != next_class)
      queue_flags |= DEQUEUE_CLASS;
      queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                       ^^^^^^^^^^^^

as well as

Quoted 8 lines

sched_change_begin()

    ...
    if (flags & DEQUEUE_CLASS) {
      if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
        flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                   ^^^^^^^^^^^^

Urgh.. SAVE/RESTORE while changing CLASS is so weird.

But yeah, let me take that bit out for now -- I'll make a note in a comment that we should look at perhaps cleaning that up instead.

merge bot Applied
#16 lore raw

[tip: sched/core] sched/deadline: Prepare for switched_from() change

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     5e42d4c123ba9b89ce19b3aa7e22b7684cbfa49c
Gitweb:        https://git.kernel.org/tip/5e42d4c123ba9b89ce19b3aa7e22b7684cbfa49c
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 09 Oct 2025 16:09:25 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:51 +02:00

sched/deadline: Prepare for switched_from() change

Prepare for the sched_class::switch*() methods getting folded into the change pattern. As a result of that, the location of switched_from will change slightly. SCHED_DEADLINE is affected by this change in location:

OLD NEW

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|

Notably, where switched_from() was called *after* the change to the task, it will get called before it. Specifically, switched_from_dl() uses dl_task(p) which uses p->prio; which is changed when switching class (it might be the reason to switch class in case of PI).

When switched_from_dl() gets called, the task will have left the deadline class and dl_task() must be false, while when doing dequeue_dl_entity() the task must be a dl_task(), otherwise we'd have called a different dequeue method.

---
 kernel/sched/deadline.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
kernel/sched/deadline.c +5 -5
index 933bd1f..fd147a7 100644
@@ -405,7 +405,7 @@ static void __dl_clear_params(struct sched_dl_entity *dl_se);
  * up, and checks if the task is still in the "ACTIVE non contending"
  * state or not (in the second case, it updates running_bw).
  */
-static void task_non_contending(struct sched_dl_entity *dl_se)
+static void task_non_contending(struct sched_dl_entity *dl_se, bool dl_task)
 {
 	struct hrtimer *timer = &dl_se->inactive_timer;
 	struct rq *rq = rq_of_dl_se(dl_se);
@@ -444,10 +444,10 @@ static void task_non_contending(struct sched_dl_entity *dl_se)
 		} else {
 			struct task_struct *p = dl_task_of(dl_se);
 
-			if (dl_task(p))
+			if (dl_task)
 				sub_running_bw(dl_se, dl_rq);
 
-			if (!dl_task(p) || READ_ONCE(p->__state) == TASK_DEAD) {
+			if (!dl_task || READ_ONCE(p->__state) == TASK_DEAD) {
 				struct dl_bw *dl_b = dl_bw_of(task_cpu(p));
 
 				if (READ_ONCE(p->__state) == TASK_DEAD)
@@ -2045,7 +2045,7 @@ static void dequeue_dl_entity(struct sched_dl_entity *dl_se, int flags)
 	 * or "inactive")
 	 */
 	if (flags & DEQUEUE_SLEEP)
-		task_non_contending(dl_se);
+		task_non_contending(dl_se, true);
 }
 
 static void enqueue_task_dl(struct rq *rq, struct task_struct *p, int flags)
@@ -2970,7 +2970,7 @@ static void switched_from_dl(struct rq *rq, struct task_struct *p)
 	 * will reset the task parameters.
 	 */
 	if (task_on_rq_queued(p) && p->dl.dl_runtime)
-		task_non_contending(&p->dl);
+		task_non_contending(&p->dl, false);
 
 	/*
 	 * In case a task is setscheduled out from SCHED_DEADLINE we need to
merge bot Applied
#17 lore raw

[tip: sched/core] sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     637b0682821b144d5993211cf0a768b322138a69
Gitweb:        https://git.kernel.org/tip/637b0682821b144d5993211cf0a768b322138a69
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 30 Oct 2024 15:08:15 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:51 +02:00

sched: Fold sched_class::switch{ing,ed}_{to,from}() into the change pattern

Add {DE,EN}QUEUE_CLASS and fold the sched_class::switch* methods into the change pattern. This completes and makes the pattern more symmetric.

This changes the order of callbacks slightly:

OLD NEW

				|
				|  switching_from()
  dequeue_task();		|  dequeue_task()
  put_prev_task();		|  put_prev_task()
				|  switched_from()
				|
  ... change task ...		|  ... change task ...
				|
  switching_to();		|  switching_to()
  enqueue_task();		|  enqueue_task()
  set_next_task();		|  set_next_task()
  prev_class->switched_from()	|
  switched_to()			|  switched_to()
				|

Notably, it moves the switched_from() callback right after the dequeue/put. Existing implementations don't appear to be affected by this change in location -- specifically the task isn't enqueued on the class in question in either location.

Make (CLASS)^(SAVE|MOVE), because there is nothing to save-restore when changing scheduling classes.

---
 kernel/sched/core.c      | 51 ++++++++++++++++-----------------------
 kernel/sched/ext.c       | 22 ++++++++++++-----
 kernel/sched/idle.c      |  4 +--
 kernel/sched/rt.c        |  2 +-
 kernel/sched/sched.h     | 22 ++++++-----------
 kernel/sched/stop_task.c |  4 +--
 kernel/sched/syscalls.c  |  7 +++--
 7 files changed, 55 insertions(+), 57 deletions(-)
kernel/sched/core.c +21 -30
index eca40df..4dbd206 100644
@@ -2169,34 +2169,9 @@ inline int task_curr(const struct task_struct *p)
 	return cpu_curr(task_cpu(p)) == p;
 }
 
-/*
- * ->switching_to() is called with the pi_lock and rq_lock held and must not
- * mess with locking.
- */
-void check_class_changing(struct rq *rq, struct task_struct *p,
-			  const struct sched_class *prev_class)
-{
-	if (prev_class != p->sched_class && p->sched_class->switching_to)
-		p->sched_class->switching_to(rq, p);
-}
-
-/*
- * switched_from, switched_to and prio_changed must _NOT_ drop rq->lock,
- * use the balance_callback list if you want balancing.
- *
- * this means any call to check_class_changed() must be followed by a call to
- * balance_callback().
- */
-void check_class_changed(struct rq *rq, struct task_struct *p,
-			 const struct sched_class *prev_class,
-			 int oldprio)
+void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio)
 {
-	if (prev_class != p->sched_class) {
-		if (prev_class->switched_from)
-			prev_class->switched_from(rq, p);
-
-		p->sched_class->switched_to(rq, p);
-	} else if (oldprio != p->prio || dl_task(p))
+	if (oldprio != p->prio || dl_task(p))
 		p->sched_class->prio_changed(rq, p, oldprio);
 }
 
@@ -7388,6 +7363,9 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 	prev_class = p->sched_class;
 	next_class = __setscheduler_class(p->policy, prio);
 
+	if (prev_class != next_class)
+		queue_flag |= DEQUEUE_CLASS;
+
 	if (prev_class != next_class && p->se.sched_delayed)
 		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
@@ -7424,11 +7402,10 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 
 		p->sched_class = next_class;
 		p->prio = prio;
-
-		check_class_changing(rq, p, prev_class);
 	}
 
-	check_class_changed(rq, p, prev_class, oldprio);
+	if (!(queue_flag & DEQUEUE_CLASS))
+		check_prio_changed(rq, p, oldprio);
 out_unlock:
 	/* Avoid rq from going away on us: */
 	preempt_disable();
@@ -10862,6 +10839,11 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int 
 
 	lockdep_assert_rq_held(rq);
 
+	if (flags & DEQUEUE_CLASS) {
+		if (p->sched_class->switching_from)
+			p->sched_class->switching_from(rq, p);
+	}
+
 	*ctx = (struct sched_change_ctx){
 		.p = p,
 		.flags = flags,
@@ -10874,6 +10856,9 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int 
 	if (ctx->running)
 		put_prev_task(rq, p);
 
+	if ((flags & DEQUEUE_CLASS) && p->sched_class->switched_from)
+		p->sched_class->switched_from(rq, p);
+
 	return ctx;
 }
 
@@ -10884,8 +10869,14 @@ void sched_change_end(struct sched_change_ctx *ctx)
 
 	lockdep_assert_rq_held(rq);
 
+	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switching_to)
+		p->sched_class->switching_to(rq, p);
+
 	if (ctx->queued)
 		enqueue_task(rq, p, ctx->flags | ENQUEUE_NOCLOCK);
 	if (ctx->running)
 		set_next_task(rq, p);
+
+	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switched_to)
+		p->sched_class->switched_to(rq, p);
 }
kernel/sched/ext.c +16 -6
index 4566a7c..a408c39 100644
@@ -3912,21 +3912,26 @@ static void scx_disable_workfn(struct kthread_work *work)
 
 	scx_task_iter_start(&sti);
 	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;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
 
 		update_rq_clock(task_rq(p));
 
+		if (old_class != new_class)
+			queue_flags |= DEQUEUE_CLASS;
+
 		if (old_class != new_class && p->se.sched_delayed)
 			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+		scoped_guard (sched_change, p, queue_flags) {
 			p->sched_class = new_class;
-			check_class_changing(task_rq(p), p, old_class);
 		}
 
-		check_class_changed(task_rq(p), p, old_class, p->prio);
+		if (!(queue_flags & DEQUEUE_CLASS))
+			check_prio_changed(task_rq(p), p, p->prio);
+
 		scx_exit_task(p);
 	}
 	scx_task_iter_stop(&sti);
@@ -4655,6 +4660,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 	percpu_down_write(&scx_fork_rwsem);
 	scx_task_iter_start(&sti);
 	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;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
@@ -4664,16 +4670,20 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 
 		update_rq_clock(task_rq(p));
 
+		if (old_class != new_class)
+			queue_flags |= DEQUEUE_CLASS;
+
 		if (old_class != new_class && p->se.sched_delayed)
 			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
-		scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK) {
+		scoped_guard (sched_change, p, queue_flags) {
 			p->scx.slice = SCX_SLICE_DFL;
 			p->sched_class = new_class;
-			check_class_changing(task_rq(p), p, old_class);
 		}
 
-		check_class_changed(task_rq(p), p, old_class, p->prio);
+		if (!(queue_flags & DEQUEUE_CLASS))
+			check_prio_changed(task_rq(p), p, p->prio);
+
 		put_task_struct(p);
 	}
 	scx_task_iter_stop(&sti);
kernel/sched/idle.c +2 -2
index c39b089..f02dced 100644
@@ -498,7 +498,7 @@ static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued)
 {
 }
 
-static void switched_to_idle(struct rq *rq, struct task_struct *p)
+static void switching_to_idle(struct rq *rq, struct task_struct *p)
 {
 	BUG();
 }
@@ -536,6 +536,6 @@ DEFINE_SCHED_CLASS(idle) = {
 	.task_tick		= task_tick_idle,
 
 	.prio_changed		= prio_changed_idle,
-	.switched_to		= switched_to_idle,
+	.switching_to		= switching_to_idle,
 	.update_curr		= update_curr_idle,
 };
kernel/sched/rt.c +1 -1
index 7936d43..6b2e811 100644
@@ -2589,8 +2589,8 @@ DEFINE_SCHED_CLASS(rt) = {
 
 	.get_rr_interval	= get_rr_interval_rt,
 
-	.prio_changed		= prio_changed_rt,
 	.switched_to		= switched_to_rt,
+	.prio_changed		= prio_changed_rt,
 
 	.update_curr		= update_curr_rt,
 
kernel/sched/sched.h +8 -14
index 24b3c6c..e3f4215 100644
@@ -20,7 +20,6 @@
 #include <linux/sched/task_flags.h>
 #include <linux/sched/task.h>
 #include <linux/sched/topology.h>
-
 #include <linux/atomic.h>
 #include <linux/bitmap.h>
 #include <linux/bug.h>
@@ -2369,6 +2368,7 @@ extern const u32		sched_prio_to_wmult[40];
 
 #define DEQUEUE_MIGRATING	0x0010 /* Matches ENQUEUE_MIGRATING */
 #define DEQUEUE_DELAYED		0x0020 /* Matches ENQUEUE_DELAYED */
+#define DEQUEUE_CLASS		0x0040 /* Matches ENQUEUE_CLASS */
 
 #define DEQUEUE_SPECIAL		0x00010000
 #define DEQUEUE_THROTTLE	0x00020000
@@ -2380,6 +2380,7 @@ extern const u32		sched_prio_to_wmult[40];
 
 #define ENQUEUE_MIGRATING	0x0010
 #define ENQUEUE_DELAYED		0x0020
+#define ENQUEUE_CLASS		0x0040
 
 #define ENQUEUE_HEAD		0x00010000
 #define ENQUEUE_REPLENISH	0x00020000
@@ -2443,14 +2444,11 @@ struct sched_class {
 	void (*task_fork)(struct task_struct *p);
 	void (*task_dead)(struct task_struct *p);
 
-	/*
-	 * The switched_from() call is allowed to drop rq->lock, therefore we
-	 * cannot assume the switched_from/switched_to pair is serialized by
-	 * rq->lock. They are however serialized by p->pi_lock.
-	 */
-	void (*switching_to) (struct rq *this_rq, struct task_struct *task);
-	void (*switched_from)(struct rq *this_rq, struct task_struct *task);
-	void (*switched_to)  (struct rq *this_rq, struct task_struct *task);
+	void (*switching_from)(struct rq *this_rq, struct task_struct *task);
+	void (*switched_from) (struct rq *this_rq, struct task_struct *task);
+	void (*switching_to)  (struct rq *this_rq, struct task_struct *task);
+	void (*switched_to)   (struct rq *this_rq, struct task_struct *task);
+
 	void (*reweight_task)(struct rq *this_rq, struct task_struct *task,
 			      const struct load_weight *lw);
 	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
@@ -3879,11 +3877,7 @@ extern void set_load_weight(struct task_struct *p, bool update_load);
 extern void enqueue_task(struct rq *rq, struct task_struct *p, int flags);
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
-extern void check_class_changing(struct rq *rq, struct task_struct *p,
-				 const struct sched_class *prev_class);
-extern void check_class_changed(struct rq *rq, struct task_struct *p,
-				const struct sched_class *prev_class,
-				int oldprio);
+extern void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio);
 
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
kernel/sched/stop_task.c +2 -2
index 2d4e279..fcc4c54 100644
@@ -75,7 +75,7 @@ static void task_tick_stop(struct rq *rq, struct task_struct *curr, int queued)
 {
 }
 
-static void switched_to_stop(struct rq *rq, struct task_struct *p)
+static void switching_to_stop(struct rq *rq, struct task_struct *p)
 {
 	BUG(); /* its impossible to change to this class */
 }
@@ -112,6 +112,6 @@ DEFINE_SCHED_CLASS(stop) = {
 	.task_tick		= task_tick_stop,
 
 	.prio_changed		= prio_changed_stop,
-	.switched_to		= switched_to_stop,
+	.switching_to		= switching_to_stop,
 	.update_curr		= update_curr_stop,
 };
kernel/sched/syscalls.c +5 -2
index 09ffe91..bcef5c7 100644
@@ -684,6 +684,9 @@ change:
 	prev_class = p->sched_class;
 	next_class = __setscheduler_class(policy, newprio);
 
+	if (prev_class != next_class)
+		queue_flags |= DEQUEUE_CLASS;
+
 	if (prev_class != next_class && p->se.sched_delayed)
 		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
 
@@ -695,7 +698,6 @@ change:
 			p->prio = newprio;
 		}
 		__setscheduler_uclamp(p, attr);
-		check_class_changing(rq, p, prev_class);
 
 		if (scope->queued) {
 			/*
@@ -707,7 +709,8 @@ change:
 		}
 	}
 
-	check_class_changed(rq, p, prev_class, oldprio);
+	if (!(queue_flags & DEQUEUE_CLASS))
+		check_prio_changed(rq, p, oldprio);
 
 	/* Avoid rq from going away on us: */
 	preempt_disable();
author maintainer
#18 lore raw

PATCH 4/12 sched: Cleanup sched_delayed handling for class switches

In reply to Peter Zijlstra

Use the new sched_class::switching_from() method to dequeue delayed tasks before switching to another class.

---
 kernel/sched/core.c     |   12 ++++++++----
 kernel/sched/ext.c      |    6 ------
 kernel/sched/fair.c     |    7 +++++++
 kernel/sched/syscalls.c |    3 ---
 4 files changed, 15 insertions(+), 13 deletions(-)
kernel/sched/core.c +8 -4
@@ -7368,9 +7368,6 @@ void rt_mutex_setprio(struct task_struct
 		queue_flag &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 	}
 
-	if (prev_class != next_class && p->se.sched_delayed)
-		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 	scoped_guard (sched_change, p, queue_flag) {
 		/*
 		 * Boosting condition are:
@@ -10845,8 +10842,15 @@ struct sched_change_ctx *sched_change_be
 		if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
 			flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 
-		if (p->sched_class->switching_from)
+		if (p->sched_class->switching_from) {
+			/*
+			 * switching_from_fair() assumes CLASS implies NOCLOCK;
+			 * fixing this assumption would mean switching_from()
+			 * would need to be able to change flags.
+			 */
+			WARN_ON(!(flags & DEQUEUE_NOCLOCK));
 			p->sched_class->switching_from(rq, p);
+		}
 	}
 
 	*ctx = (struct sched_change_ctx){
kernel/sched/ext.c +0 -6
@@ -3924,9 +3924,6 @@ static void scx_disable_workfn(struct kt
 			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 		}
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 		scoped_guard (sched_change, p, queue_flags) {
 			p->sched_class = new_class;
 		}
@@ -4677,9 +4674,6 @@ static int scx_enable(struct sched_ext_o
 			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 		}
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 		scoped_guard (sched_change, p, queue_flags) {
 			p->scx.slice = SCX_SLICE_DFL;
 			p->sched_class = new_class;
kernel/sched/fair.c +7 -0
@@ -13237,6 +13237,12 @@ static void attach_task_cfs_rq(struct ta
 	attach_entity_cfs_rq(se);
 }
 
+static void switching_from_fair(struct rq *rq, struct task_struct *p)
+{
+	if (p->se.sched_delayed)
+		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
+}
+
 static void switched_from_fair(struct rq *rq, struct task_struct *p)
 {
 	detach_task_cfs_rq(p);
@@ -13638,6 +13644,7 @@ DEFINE_SCHED_CLASS(fair) = {
 
 	.reweight_task		= reweight_task_fair,
 	.prio_changed		= prio_changed_fair,
+	.switching_from		= switching_from_fair,
 	.switched_from		= switched_from_fair,
 	.switched_to		= switched_to_fair,
 
kernel/sched/syscalls.c +0 -3
@@ -689,9 +689,6 @@ int __sched_setscheduler(struct task_str
 		queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 	}
 
-	if (prev_class != next_class && p->se.sched_delayed)
-		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 	scoped_guard (sched_change, p, queue_flags) {
 
 		if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
maintainer Reviewed-by on v1 4/12
#19 lore raw

Re: [PATCH 04/12] sched: Cleanup sched_delayed handling for class switches

Quoted 6 lines · Peter Zijlstra

Use the new sched_class::switching_from() method to dequeue delayed tasks before switching to another class.

Quoted 95 lines
---
 kernel/sched/core.c     |   12 ++++++++----
 kernel/sched/ext.c      |    6 ------
 kernel/sched/fair.c     |    7 +++++++
 kernel/sched/syscalls.c |    3 ---
 4 files changed, 15 insertions(+), 13 deletions(-)
kernel/sched/core.c +8 -4
@@ -7368,9 +7368,6 @@ void rt_mutex_setprio(struct task_struct
                queue_flag &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
        }
 
-       if (prev_class != next_class && p->se.sched_delayed)
-               dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
        scoped_guard (sched_change, p, queue_flag) {
                /*
                 * Boosting condition are:
@@ -10845,8 +10842,15 @@ struct sched_change_ctx *sched_change_be
                if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
                        flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 
-               if (p->sched_class->switching_from)
+               if (p->sched_class->switching_from) {
+                       /*
+                        * switching_from_fair() assumes CLASS implies NOCLOCK;
+                        * fixing this assumption would mean switching_from()
+                        * would need to be able to change flags.
+                        */
+                       WARN_ON(!(flags & DEQUEUE_NOCLOCK));
                        p->sched_class->switching_from(rq, p);
+               }
        }
 
        *ctx = (struct sched_change_ctx){
kernel/sched/ext.c +0 -6
@@ -3924,9 +3924,6 @@ static void scx_disable_workfn(struct kt
                        queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                }
 
-               if (old_class != new_class && p->se.sched_delayed)
-                       dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
                scoped_guard (sched_change, p, queue_flags) {
                        p->sched_class = new_class;
                }
@@ -4677,9 +4674,6 @@ static int scx_enable(struct sched_ext_o
                        queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
                }
 
-               if (old_class != new_class && p->se.sched_delayed)
-                       dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
                scoped_guard (sched_change, p, queue_flags) {
                        p->scx.slice = SCX_SLICE_DFL;
                        p->sched_class = new_class;
kernel/sched/fair.c +7 -0
@@ -13237,6 +13237,12 @@ static void attach_task_cfs_rq(struct ta
        attach_entity_cfs_rq(se);
 }
 
+static void switching_from_fair(struct rq *rq, struct task_struct *p)
+{
+       if (p->se.sched_delayed)
+               dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
+}
+
 static void switched_from_fair(struct rq *rq, struct task_struct *p)
 {
        detach_task_cfs_rq(p);
@@ -13638,6 +13644,7 @@ DEFINE_SCHED_CLASS(fair) = {
 
        .reweight_task          = reweight_task_fair,
        .prio_changed           = prio_changed_fair,
+       .switching_from         = switching_from_fair,
        .switched_from          = switched_from_fair,
        .switched_to            = switched_to_fair,
 
kernel/sched/syscalls.c +0 -3
@@ -689,9 +689,6 @@ int __sched_setscheduler(struct task_str
                queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
        }
 
-       if (prev_class != next_class && p->se.sched_delayed)
-               dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
        scoped_guard (sched_change, p, queue_flags) {
 
                if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
merge bot Applied
#20 lore raw

[tip: sched/core] sched: Cleanup sched_delayed handling for class switches

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     1ae5f5dfe5adc64a90b1b0ab5bd9bd7c9d140c28
Gitweb:        https://git.kernel.org/tip/1ae5f5dfe5adc64a90b1b0ab5bd9bd7c9d140c28
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 30 Oct 2024 15:47:46 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:51 +02:00

sched: Cleanup sched_delayed handling for class switches

Use the new sched_class::switching_from() method to dequeue delayed tasks before switching to another class.

---
 kernel/sched/core.c     | 12 ++++++++----
 kernel/sched/ext.c      |  6 ------
 kernel/sched/fair.c     |  7 +++++++
 kernel/sched/syscalls.c |  3 ---
 4 files changed, 15 insertions(+), 13 deletions(-)
kernel/sched/core.c +8 -4
index 4dbd206..bd2c551 100644
@@ -7366,9 +7366,6 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 	if (prev_class != next_class)
 		queue_flag |= DEQUEUE_CLASS;
 
-	if (prev_class != next_class && p->se.sched_delayed)
-		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 	scoped_guard (sched_change, p, queue_flag) {
 		/*
 		 * Boosting condition are:
@@ -10840,8 +10837,15 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int 
 	lockdep_assert_rq_held(rq);
 
 	if (flags & DEQUEUE_CLASS) {
-		if (p->sched_class->switching_from)
+		if (p->sched_class->switching_from) {
+			/*
+			 * switching_from_fair() assumes CLASS implies NOCLOCK;
+			 * fixing this assumption would mean switching_from()
+			 * would need to be able to change flags.
+			 */
+			WARN_ON(!(flags & DEQUEUE_NOCLOCK));
 			p->sched_class->switching_from(rq, p);
+		}
 	}
 
 	*ctx = (struct sched_change_ctx){
kernel/sched/ext.c +0 -6
index a408c39..b0a1e2a 100644
@@ -3922,9 +3922,6 @@ static void scx_disable_workfn(struct kthread_work *work)
 		if (old_class != new_class)
 			queue_flags |= DEQUEUE_CLASS;
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 		scoped_guard (sched_change, p, queue_flags) {
 			p->sched_class = new_class;
 		}
@@ -4673,9 +4670,6 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 		if (old_class != new_class)
 			queue_flags |= DEQUEUE_CLASS;
 
-		if (old_class != new_class && p->se.sched_delayed)
-			dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 		scoped_guard (sched_change, p, queue_flags) {
 			p->scx.slice = SCX_SLICE_DFL;
 			p->sched_class = new_class;
kernel/sched/fair.c +7 -0
index ac881df..6c462e4 100644
@@ -13249,6 +13249,12 @@ static void attach_task_cfs_rq(struct task_struct *p)
 	attach_entity_cfs_rq(se);
 }
 
+static void switching_from_fair(struct rq *rq, struct task_struct *p)
+{
+	if (p->se.sched_delayed)
+		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
+}
+
 static void switched_from_fair(struct rq *rq, struct task_struct *p)
 {
 	detach_task_cfs_rq(p);
@@ -13650,6 +13656,7 @@ DEFINE_SCHED_CLASS(fair) = {
 
 	.reweight_task		= reweight_task_fair,
 	.prio_changed		= prio_changed_fair,
+	.switching_from		= switching_from_fair,
 	.switched_from		= switched_from_fair,
 	.switched_to		= switched_to_fair,
 
kernel/sched/syscalls.c +0 -3
index bcef5c7..6583faf 100644
@@ -687,9 +687,6 @@ change:
 	if (prev_class != next_class)
 		queue_flags |= DEQUEUE_CLASS;
 
-	if (prev_class != next_class && p->se.sched_delayed)
-		dequeue_task(rq, p, DEQUEUE_SLEEP | DEQUEUE_DELAYED | DEQUEUE_NOCLOCK);
-
 	scoped_guard (sched_change, p, queue_flags) {
 
 		if (!(attr->sched_flags & SCHED_FLAG_KEEP_PARAMS)) {
author maintainer
#21 lore raw

PATCH 5/12 sched: Move sched_class::prio_changed() into the change pattern

In reply to Peter Zijlstra

Move sched_class::prio_changed() into the change pattern.

And while there, extend it with sched_class::get_prio() in order to fix the deadline sitation.

---
 kernel/sched/core.c      |   24 +++++++++++++-----------
 kernel/sched/deadline.c  |   20 +++++++++++---------
 kernel/sched/ext.c       |    8 +-------
 kernel/sched/fair.c      |    8 ++++++--
 kernel/sched/idle.c      |    5 ++++-
 kernel/sched/rt.c        |    5 ++++-
 kernel/sched/sched.h     |    7 ++++---
 kernel/sched/stop_task.c |    5 ++++-
 kernel/sched/syscalls.c  |    9 ---------
 9 files changed, 47 insertions(+), 44 deletions(-)
kernel/sched/core.c +13 -11
@@ -2169,12 +2169,6 @@ inline int task_curr(const struct task_s
 	return cpu_curr(task_cpu(p)) == p;
 }
 
-void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio)
-{
-	if (oldprio != p->prio || dl_task(p))
-		p->sched_class->prio_changed(rq, p, oldprio);
-}
-
 void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
 {
 	struct task_struct *donor = rq->donor;
@@ -7402,9 +7396,6 @@ void rt_mutex_setprio(struct task_struct
 		p->sched_class = next_class;
 		p->prio = prio;
 	}
-
-	if (!(queue_flag & DEQUEUE_CLASS))
-		check_prio_changed(rq, p, oldprio);
 out_unlock:
 	/* Avoid rq from going away on us: */
 	preempt_disable();
@@ -10860,6 +10851,13 @@ struct sched_change_ctx *sched_change_be
 		.running = task_current(rq, p),
 	};
 
+	if (!(flags & DEQUEUE_CLASS)) {
+		if (p->sched_class->get_prio)
+			ctx->prio = p->sched_class->get_prio(rq, p);
+		else
+			ctx->prio = p->prio;
+	}
+
 	if (ctx->queued)
 		dequeue_task(rq, p, flags);
 	if (ctx->running)
@@ -10886,6 +10884,10 @@ void sched_change_end(struct sched_chang
 	if (ctx->running)
 		set_next_task(rq, p);
 
-	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switched_to)
-		p->sched_class->switched_to(rq, p);
+	if (ctx->flags & ENQUEUE_CLASS) {
+		if (p->sched_class->switched_to)
+			p->sched_class->switched_to(rq, p);
+	} else {
+		p->sched_class->prio_changed(rq, p, ctx->prio);
+	}
 }
kernel/sched/deadline.c +11 -9
@@ -3042,23 +3042,24 @@ static void switched_to_dl(struct rq *rq
 	}
 }
 
+static u64 get_prio_dl(struct rq *rq, struct task_struct *p)
+{
+	return p->dl.deadline;
+}
+
 /*
  * If the scheduling parameters of a -deadline task changed,
  * a push or pull operation might be needed.
  */
-static void prio_changed_dl(struct rq *rq, struct task_struct *p,
-			    int oldprio)
+static void prio_changed_dl(struct rq *rq, struct task_struct *p, u64 old_deadline)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
-	/*
-	 * This might be too much, but unfortunately
-	 * we don't have the old deadline value, and
-	 * we can't argue if the task is increasing
-	 * or lowering its prio, so...
-	 */
-	if (!rq->dl.overloaded)
+	if (p->dl.deadline == old_deadline)
+		return;
+
+	if (dl_time_before(old_deadline, p->dl.deadline))
 		deadline_queue_pull_task(rq);
 
 	if (task_current_donor(rq, p)) {
@@ -3113,6 +3114,7 @@ DEFINE_SCHED_CLASS(dl) = {
 	.task_tick		= task_tick_dl,
 	.task_fork              = task_fork_dl,
 
+	.get_prio		= get_prio_dl,
 	.prio_changed           = prio_changed_dl,
 	.switched_from		= switched_from_dl,
 	.switched_to		= switched_to_dl,
kernel/sched/ext.c +1 -7
@@ -2961,7 +2961,7 @@ static void reweight_task_scx(struct rq
 				 p, p->scx.weight);
 }
 
-static void prio_changed_scx(struct rq *rq, struct task_struct *p, int oldprio)
+static void prio_changed_scx(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 }
 
@@ -3928,9 +3928,6 @@ static void scx_disable_workfn(struct kt
 			p->sched_class = new_class;
 		}
 
-		if (!(queue_flags & DEQUEUE_CLASS))
-			check_prio_changed(task_rq(p), p, p->prio);
-
 		scx_exit_task(p);
 	}
 	scx_task_iter_stop(&sti);
@@ -4679,9 +4676,6 @@ static int scx_enable(struct sched_ext_o
 			p->sched_class = new_class;
 		}
 
-		if (!(queue_flags & DEQUEUE_CLASS))
-			check_prio_changed(task_rq(p), p, p->prio);
-
 		put_task_struct(p);
 	}
 	scx_task_iter_stop(&sti);
kernel/sched/fair.c +6 -2
@@ -13138,11 +13138,14 @@ static void task_fork_fair(struct task_s
  * the current task.
  */
 static void
-prio_changed_fair(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_fair(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
+	if (p->prio == oldprio)
+		return;
+
 	if (rq->cfs.nr_queued == 1)
 		return;
 
@@ -13154,8 +13157,9 @@ prio_changed_fair(struct rq *rq, struct
 	if (task_current_donor(rq, p)) {
 		if (p->prio > oldprio)
 			resched_curr(rq);
-	} else
+	} else {
 		wakeup_preempt(rq, p, 0);
+	}
 }
 
 #ifdef CONFIG_FAIR_GROUP_SCHED
kernel/sched/idle.c +4 -1
@@ -504,8 +504,11 @@ static void switching_to_idle(struct rq
 }
 
 static void
-prio_changed_idle(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_idle(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
+	if (p->prio == oldprio)
+		return;
+
 	BUG();
 }
 
kernel/sched/rt.c +4 -1
@@ -2437,11 +2437,14 @@ static void switched_to_rt(struct rq *rq
  * us to initiate a push or pull.
  */
 static void
-prio_changed_rt(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_rt(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
+	if (p->prio == oldprio)
+		return;
+
 	if (task_current_donor(rq, p)) {
 		/*
 		 * If our priority decreases while running, we
kernel/sched/sched.h +4 -3
@@ -2451,8 +2451,10 @@ struct sched_class {
 
 	void (*reweight_task)(struct rq *this_rq, struct task_struct *task,
 			      const struct load_weight *lw);
+
+	u64  (*get_prio)     (struct rq *this_rq, struct task_struct *task);
 	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
-			      int oldprio);
+			      u64 oldprio);
 
 	unsigned int (*get_rr_interval)(struct rq *rq,
 					struct task_struct *task);
@@ -3877,12 +3879,11 @@ extern void set_load_weight(struct task_
 extern void enqueue_task(struct rq *rq, struct task_struct *p, int flags);
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
-extern void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio);
-
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
 struct sched_change_ctx {
+	u64			prio;
 	struct task_struct	*p;
 	int			flags;
 	bool			queued;
kernel/sched/stop_task.c +4 -1
@@ -81,8 +81,11 @@ static void switching_to_stop(struct rq
 }
 
 static void
-prio_changed_stop(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_stop(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
+	if (p->prio == oldprio)
+		return;
+
 	BUG(); /* how!?, what priority? */
 }
 
kernel/sched/syscalls.c +0 -9
@@ -95,12 +95,6 @@ void set_user_nice(struct task_struct *p
 		old_prio = p->prio;
 		p->prio = effective_prio(p);
 	}
-
-	/*
-	 * If the task increased its priority or is running and
-	 * lowered its priority, then reschedule its CPU:
-	 */
-	p->sched_class->prio_changed(rq, p, old_prio);
 }
 EXPORT_SYMBOL(set_user_nice);
 
@@ -708,9 +702,6 @@ int __sched_setscheduler(struct task_str
 		}
 	}
 
-	if (!(queue_flags & DEQUEUE_CLASS))
-		check_prio_changed(rq, p, oldprio);
-
 	/* Avoid rq from going away on us: */
 	preempt_disable();
 	head = splice_balance_callbacks(rq);
merge bot Applied
#22 lore raw

[tip: sched/core] sched: Move sched_class::prio_changed() into the change pattern

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     6455ad5346c9cf755fa9dda6e326c4028fb3c853
Gitweb:        https://git.kernel.org/tip/6455ad5346c9cf755fa9dda6e326c4028fb3c853
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Fri, 01 Nov 2024 14:16:10 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:52 +02:00

sched: Move sched_class::prio_changed() into the change pattern

Move sched_class::prio_changed() into the change pattern.

And while there, extend it with sched_class::get_prio() in order to fix the deadline sitation.

---
 kernel/sched/core.c      | 24 +++++++++++++-----------
 kernel/sched/deadline.c  | 20 +++++++++++---------
 kernel/sched/ext.c       |  8 +-------
 kernel/sched/fair.c      |  8 ++++++--
 kernel/sched/idle.c      |  5 ++++-
 kernel/sched/rt.c        |  5 ++++-
 kernel/sched/sched.h     |  7 ++++---
 kernel/sched/stop_task.c |  5 ++++-
 kernel/sched/syscalls.c  |  9 ---------
 9 files changed, 47 insertions(+), 44 deletions(-)
kernel/sched/core.c +13 -11
index bd2c551..4a4dbce 100644
@@ -2169,12 +2169,6 @@ inline int task_curr(const struct task_struct *p)
 	return cpu_curr(task_cpu(p)) == p;
 }
 
-void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio)
-{
-	if (oldprio != p->prio || dl_task(p))
-		p->sched_class->prio_changed(rq, p, oldprio);
-}
-
 void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
 {
 	struct task_struct *donor = rq->donor;
@@ -7400,9 +7394,6 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 		p->sched_class = next_class;
 		p->prio = prio;
 	}
-
-	if (!(queue_flag & DEQUEUE_CLASS))
-		check_prio_changed(rq, p, oldprio);
 out_unlock:
 	/* Avoid rq from going away on us: */
 	preempt_disable();
@@ -10855,6 +10846,13 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int 
 		.running = task_current_donor(rq, p),
 	};
 
+	if (!(flags & DEQUEUE_CLASS)) {
+		if (p->sched_class->get_prio)
+			ctx->prio = p->sched_class->get_prio(rq, p);
+		else
+			ctx->prio = p->prio;
+	}
+
 	if (ctx->queued)
 		dequeue_task(rq, p, flags);
 	if (ctx->running)
@@ -10881,6 +10879,10 @@ void sched_change_end(struct sched_change_ctx *ctx)
 	if (ctx->running)
 		set_next_task(rq, p);
 
-	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switched_to)
-		p->sched_class->switched_to(rq, p);
+	if (ctx->flags & ENQUEUE_CLASS) {
+		if (p->sched_class->switched_to)
+			p->sched_class->switched_to(rq, p);
+	} else {
+		p->sched_class->prio_changed(rq, p, ctx->prio);
+	}
 }
kernel/sched/deadline.c +11 -9
index fd147a7..1f94994 100644
@@ -3042,23 +3042,24 @@ static void switched_to_dl(struct rq *rq, struct task_struct *p)
 	}
 }
 
+static u64 get_prio_dl(struct rq *rq, struct task_struct *p)
+{
+	return p->dl.deadline;
+}
+
 /*
  * If the scheduling parameters of a -deadline task changed,
  * a push or pull operation might be needed.
  */
-static void prio_changed_dl(struct rq *rq, struct task_struct *p,
-			    int oldprio)
+static void prio_changed_dl(struct rq *rq, struct task_struct *p, u64 old_deadline)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
-	/*
-	 * This might be too much, but unfortunately
-	 * we don't have the old deadline value, and
-	 * we can't argue if the task is increasing
-	 * or lowering its prio, so...
-	 */
-	if (!rq->dl.overloaded)
+	if (p->dl.deadline == old_deadline)
+		return;
+
+	if (dl_time_before(old_deadline, p->dl.deadline))
 		deadline_queue_pull_task(rq);
 
 	if (task_current_donor(rq, p)) {
@@ -3113,6 +3114,7 @@ DEFINE_SCHED_CLASS(dl) = {
 	.task_tick		= task_tick_dl,
 	.task_fork              = task_fork_dl,
 
+	.get_prio		= get_prio_dl,
 	.prio_changed           = prio_changed_dl,
 	.switched_from		= switched_from_dl,
 	.switched_to		= switched_to_dl,
kernel/sched/ext.c +1 -7
index b0a1e2a..ad371b6 100644
@@ -2961,7 +2961,7 @@ static void reweight_task_scx(struct rq *rq, struct task_struct *p,
 				 p, p->scx.weight);
 }
 
-static void prio_changed_scx(struct rq *rq, struct task_struct *p, int oldprio)
+static void prio_changed_scx(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 }
 
@@ -3926,9 +3926,6 @@ static void scx_disable_workfn(struct kthread_work *work)
 			p->sched_class = new_class;
 		}
 
-		if (!(queue_flags & DEQUEUE_CLASS))
-			check_prio_changed(task_rq(p), p, p->prio);
-
 		scx_exit_task(p);
 	}
 	scx_task_iter_stop(&sti);
@@ -4675,9 +4672,6 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 			p->sched_class = new_class;
 		}
 
-		if (!(queue_flags & DEQUEUE_CLASS))
-			check_prio_changed(task_rq(p), p, p->prio);
-
 		put_task_struct(p);
 	}
 	scx_task_iter_stop(&sti);
kernel/sched/fair.c +6 -2
index 6c462e4..77a713e 100644
@@ -13150,11 +13150,14 @@ static void task_fork_fair(struct task_struct *p)
  * the current task.
  */
 static void
-prio_changed_fair(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_fair(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
+	if (p->prio == oldprio)
+		return;
+
 	if (rq->cfs.nr_queued == 1)
 		return;
 
@@ -13166,8 +13169,9 @@ prio_changed_fair(struct rq *rq, struct task_struct *p, int oldprio)
 	if (task_current_donor(rq, p)) {
 		if (p->prio > oldprio)
 			resched_curr(rq);
-	} else
+	} else {
 		wakeup_preempt(rq, p, 0);
+	}
 }
 
 #ifdef CONFIG_FAIR_GROUP_SCHED
kernel/sched/idle.c +4 -1
index f02dced..dee6e01 100644
@@ -504,8 +504,11 @@ static void switching_to_idle(struct rq *rq, struct task_struct *p)
 }
 
 static void
-prio_changed_idle(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_idle(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
+	if (p->prio == oldprio)
+		return;
+
 	BUG();
 }
 
kernel/sched/rt.c +4 -1
index 6b2e811..c2347e4 100644
@@ -2437,11 +2437,14 @@ static void switched_to_rt(struct rq *rq, struct task_struct *p)
  * us to initiate a push or pull.
  */
 static void
-prio_changed_rt(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_rt(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
 	if (!task_on_rq_queued(p))
 		return;
 
+	if (p->prio == oldprio)
+		return;
+
 	if (task_current_donor(rq, p)) {
 		/*
 		 * If our priority decreases while running, we
kernel/sched/sched.h +4 -3
index e3f4215..bcde43d 100644
@@ -2451,8 +2451,10 @@ struct sched_class {
 
 	void (*reweight_task)(struct rq *this_rq, struct task_struct *task,
 			      const struct load_weight *lw);
+
+	u64  (*get_prio)     (struct rq *this_rq, struct task_struct *task);
 	void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
-			      int oldprio);
+			      u64 oldprio);
 
 	unsigned int (*get_rr_interval)(struct rq *rq,
 					struct task_struct *task);
@@ -3877,8 +3879,6 @@ extern void set_load_weight(struct task_struct *p, bool update_load);
 extern void enqueue_task(struct rq *rq, struct task_struct *p, int flags);
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
-extern void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio);
-
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
@@ -3899,6 +3899,7 @@ extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
  * the task's queueing state is idempotent across the operation.
  */
 struct sched_change_ctx {
+	u64			prio;
 	struct task_struct	*p;
 	int			flags;
 	bool			queued;
kernel/sched/stop_task.c +4 -1
index fcc4c54..73aa8de 100644
@@ -81,8 +81,11 @@ static void switching_to_stop(struct rq *rq, struct task_struct *p)
 }
 
 static void
-prio_changed_stop(struct rq *rq, struct task_struct *p, int oldprio)
+prio_changed_stop(struct rq *rq, struct task_struct *p, u64 oldprio)
 {
+	if (p->prio == oldprio)
+		return;
+
 	BUG(); /* how!?, what priority? */
 }
 
kernel/sched/syscalls.c +0 -9
index 6583faf..20af564 100644
@@ -95,12 +95,6 @@ void set_user_nice(struct task_struct *p, long nice)
 		old_prio = p->prio;
 		p->prio = effective_prio(p);
 	}
-
-	/*
-	 * If the task increased its priority or is running and
-	 * lowered its priority, then reschedule its CPU:
-	 */
-	p->sched_class->prio_changed(rq, p, old_prio);
 }
 EXPORT_SYMBOL(set_user_nice);
 
@@ -706,9 +700,6 @@ change:
 		}
 	}
 
-	if (!(queue_flags & DEQUEUE_CLASS))
-		check_prio_changed(rq, p, oldprio);
-
 	/* Avoid rq from going away on us: */
 	preempt_disable();
 	head = splice_balance_callbacks(rq);
#23 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

In reply to Peter Zijlstra

Hello Peter,

It seems this patch:
6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern") is triggering the following warning:
rq_pin_lock()
\-WARN_ON_ONCE(rq->balance_callback && rq->balance_callback != &balance_push_callback);

On an arm64 Juno, it can be reproduced by creating and killing a deadline task:
chrt -d -T 1000000 -P 1000000 0 yes > /dev/null

[   49.518832] Hardware name: ARM LTD ARM Juno Development Platform/ARM Juno Development Platform, BIOS EDK II Jul 11 2025
[   49.518838] Call trace:
[   49.518842]  show_stack (arch/arm64/kernel/stacktrace.c:501) (C)
[   49.518864]  dump_stack_lvl (lib/dump_stack.c:122)
[   49.518878]  dump_stack (lib/dump_stack.c:130)
[   49.518889]  prio_changed_dl (kernel/sched/deadline.c:0 kernel/sched/deadline.c:3343)
[   49.518903]  sched_change_end (kernel/sched/core.c:0)
[   49.518916]  sched_move_task (kernel/sched/core.c:9167)
[   49.518927]  sched_autogroup_exit_task (kernel/sched/autogroup.c:157)
[   49.518940]  do_exit (kernel/exit.c:975)
[   49.518950]  do_group_exit (kernel/exit.c:0)
[   49.518960]  get_signal (kernel/signal.c:0)
[   49.518970]  arch_do_signal_or_restart (arch/arm64/kernel/signal.c:1619)
[   49.518983]  exit_to_user_mode_loop (kernel/entry/common.c:43 kernel/entry/common.c:75)
[   49.518994]  el0_svc (./include/linux/irq-entry-common.h:0 ./include/linux/irq-entry-common.h:242 arch/arm64/kernel/entry-common.c:81 arch/arm64/kernel/entry-common.c:725) [   49.519009]  el0t_64_sync_handler (arch/arm64/kernel/entry-common.c:0)
[   49.519023]  el0t_64_sync (arch/arm64/kernel/entry.S:596)
[   49.519119] ------------[ cut here ]------------
[   49.519124] WARNING: kernel/sched/sched.h:1829 at __schedule+0x404/0xf78, CPU#1: yes/326
[   49.612674] Modules linked in:
[   49.615737] CPU: 1 UID: 0 PID: 326 Comm: yes Not tainted 6.19.0-rc4-next-20260109-g8be7ad74b7e4 #261 PREEMPT
[   49.625670] Hardware name: ARM LTD ARM Juno Development Platform/ARM Juno Development Platform, BIOS EDK II Jul 11 2025
[   49.636470] pstate: 800000c5 (Nzcv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[   49.643443] pc : __schedule (kernel/sched/core.c:0 kernel/sched/sched.h:1907 kernel/sched/core.c:6798)
[   49.647287] lr : __schedule (kernel/sched/sched.h:1827 kernel/sched/sched.h:1907 kernel/sched/core.c:6798)
[   49.651130] sp : ffff800081d739e0
[   49.654445] x29: ffff800081d73a40 x28: ffff000809548908 x27: ffffddc6d7c532e8
[   49.661604] x26: ffff000809548000 x25: 00000000400004d8 x24: 0000000000000009
[   49.668762] x23: 0000000000000001 x22: ffffddc6d7bf8500 x21: ffffddc6d5b9bdb0
[   49.675919] x20: ffff00097681c500 x19: ffff000809548000 x18: ffff800081d735b8
[   49.683076] x17: 0000000000000063 x16: 0000000000000000 x15: 0000000000000004
[   49.690233] x14: ffff000809548aa0 x13: 000000000dc48bda x12: 000000002edb68e5
[   49.697391] x11: 0000000000000000 x10: 0000000000000001 x9 : ffffddc6d7c7b388
[   49.704548] x8 : ffff000976636420 x7 : ffffddc6d5b9ae64 x6 : 0000000000000000
[   49.711704] x5 : 0000000000000001 x4 : 0000000000000001 x3 : 0000000000000000
[   49.718861] x2 : 0000000000000008 x1 : ffff00097681c518 x0 : 0000000000008629
[   49.726017] Call trace:
[   49.728462]  __schedule (kernel/sched/core.c:0 kernel/sched/sched.h:1907 kernel/sched/core.c:6798) (P)
[   49.732308]  preempt_schedule_common (./arch/arm64/include/asm/preempt.h:53 kernel/sched/core.c:7080)
[   49.736762]  preempt_schedule (kernel/sched/core.c:0)
[   49.740606]  _raw_spin_unlock_irqrestore (./include/linux/spinlock_api_smp.h:0 kernel/locking/spinlock.c:194)
[   49.745410]  sched_move_task (kernel/sched/sched.h:0)
[   49.749341]  sched_autogroup_exit_task (kernel/sched/autogroup.c:157)
[   49.753969]  do_exit (kernel/exit.c:975)
[   49.757202]  do_group_exit (kernel/exit.c:0)
[   49.760782]  get_signal (kernel/signal.c:0)
[   49.764277]  arch_do_signal_or_restart (arch/arm64/kernel/signal.c:1619)
[   49.769078]  exit_to_user_mode_loop (kernel/entry/common.c:43 kernel/entry/common.c:75)
[   49.773530]  el0_svc (./include/linux/irq-entry-common.h:0 ./include/linux/irq-entry-common.h:242 arch/arm64/kernel/entry-common.c:81 arch/arm64/kernel/entry-common.c:725) [   49.776767]  el0t_64_sync_handler (arch/arm64/kernel/entry-common.c:0)
[   49.781048]  el0t_64_sync (arch/arm64/kernel/entry.S:596)
[   49.784716] irq event stamp: 80194
[   49.788118] hardirqs last  enabled at (80193): irqentry_exit (kernel/entry/common.c:0)
[   49.796575] hardirqs last disabled at (80194): __schedule (kernel/sched/core.c:6755)
[   49.804858] softirqs last  enabled at (77126): handle_softirqs (./arch/arm64/include/asm/preempt.h:12 kernel/softirq.c:469 kernel/softirq.c:654) [   49.813575] softirqs last disabled at (77121): __do_softirq (kernel/softirq.c:661)
[   49.821856] ---[ end trace 0000000000000000 ]---

The first stack dump comes from this:

kernel/sched/deadline.c +0 -0
index 1f94994984038..4647fea76d748 100644
@@ -632,11 +640,17 @@ static inline void deadline_queue_push_tasks(struct rq *rq)

        if (!has_pushable_dl_tasks(rq))
                return;

+       if (sysctl_sched_debug_local)
+               dump_stack();
+
        queue_balance_callback(rq, &per_cpu(dl_push_head, rq->cpu), push_dl_tasks); }

 static inline void deadline_queue_pull_task(struct rq *rq)
 {
+       if (sysctl_sched_debug_local)
+               dump_stack();
+
        queue_balance_callback(rq, &per_cpu(dl_pull_head, rq->cpu), pull_dl_task); }

Quoted 36 lines · Peter Zijlstra

Move sched_class::prio_changed() into the change pattern.

And while there, extend it with sched_class::get_prio() in order to fix the deadline sitation.

---
  kernel/sched/core.c      |   24 +++++++++++++-----------
  kernel/sched/deadline.c  |   20 +++++++++++---------
  kernel/sched/ext.c       |    8 +-------
  kernel/sched/fair.c      |    8 ++++++--
  kernel/sched/idle.c      |    5 ++++-
  kernel/sched/rt.c        |    5 ++++-
  kernel/sched/sched.h     |    7 ++++---
  kernel/sched/stop_task.c |    5 ++++-
  kernel/sched/syscalls.c  |    9 ---------
  9 files changed, 47 insertions(+), 44 deletions(-)
kernel/sched/core.c +0 -6
@@ -2169,12 +2169,6 @@ inline int task_curr(const struct task_s
  	return cpu_curr(task_cpu(p)) == p;
  }
 
-void check_prio_changed(struct rq *rq, struct task_struct *p, int oldprio)
-{
-	if (oldprio != p->prio || dl_task(p))
-		p->sched_class->prio_changed(rq, p, oldprio);
-}
-
  void wakeup_preempt(struct rq *rq, struct task_struct *p, int flags)
  {
  	struct task_struct *donor = rq->donor;

kernel/sched/core.c void rt_mutex_setprio(struct task_struct · line 7401

Quoted 9 lines
kernel/sched/core.c +0 -3
@@ -7402,9 +7396,6 @@ void rt_mutex_setprio(struct task_struct
  		p->sched_class = next_class;
  		p->prio = prio;
  	}
-
-	if (!(queue_flag & DEQUEUE_CLASS))
-		check_prio_changed(rq, p, oldprio);
  out_unlock:
  	/* Avoid rq from going away on us: */
  	preempt_disable();

The cause might be the above. This used to call __balance_callbacks() while holding the rq lock.

Quoted 16 lines
kernel/sched/core.c +7 -0
@@ -10860,6 +10851,13 @@ struct sched_change_ctx *sched_change_be
  		.running = task_current(rq, p),
  	};
 
+	if (!(flags & DEQUEUE_CLASS)) {
+		if (p->sched_class->get_prio)
+			ctx->prio = p->sched_class->get_prio(rq, p);
+		else
+			ctx->prio = p->prio;
+	}
+
  	if (ctx->queued)
  		dequeue_task(rq, p, flags);
  	if (ctx->running)
@@ -10886,6 +10884,10 @@ void sched_change_end(struct sched_chang
  	if (ctx->running)

kernel/sched/core.c void sched_change_end(struct sched_chang · line 10892

Quoted 10 lines
kernel/sched/core.c +6 -2
  		set_next_task(rq, p);
 
-	if ((ctx->flags & ENQUEUE_CLASS) && p->sched_class->switched_to)
-		p->sched_class->switched_to(rq, p);
+	if (ctx->flags & ENQUEUE_CLASS) {
+		if (p->sched_class->switched_to)
+			p->sched_class->switched_to(rq, p);
+	} else {
+		p->sched_class->prio_changed(rq, p, ctx->prio);
+	}

Now this is not the case anymore it seems. prio_changed_dl() sets the balance_callback and rq_pin_lock() is called with a non-NULL value.

#24 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hello Pierre,

Quoted 7 lines · Pierre Gondois

Hello Peter,

It seems this patch:
6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern") is triggering the following warning:
rq_pin_lock()
\-WARN_ON_ONCE(rq->balance_callback && rq->balance_callback != &balance_push_callback);

Can you check if the following solution helps your case too:
https://lore.kernel.org/all/[email protected]/

Signature · 2 lines
Thanks and Regards,
Prateek
#25 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hello Prateek,

Quoted 5 lines · K Prateek Nayak

Hello Pierre,

Quoted 2 lines · Pierre Gondois

Hello Peter,

Quoted 8 lines
Quoted 5 lines

It seems this patch:
6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern") is triggering the following warning:
rq_pin_lock()
\-WARN_ON_ONCE(rq->balance_callback && rq->balance_callback != &balance_push_callback);

Can you check if the following solution helps your case too:
https://lore.kernel.org/all/[email protected]/

I can still see the issue.
It seems the task deadline is also updated in:
sched_change_end()
\-enqueue_task_dl()
  \-enqueue_dl_entity()
    \-setup_new_dl_entity()
      \-replenish_dl_new_period()
if the task's period finished.

So in sched_change_end(), the task priority (i.e. p->dl.deadline) is updated. This results in having an old_deadline earlier than the new p->dl.deadline. Thus the rq->balance_callback:

prio_changed_dl() {
...
if (dl_time_before(old_deadline, p->dl.deadline))
  deadline_queue_pull_task(rq);
...
}

#26 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hello Pierre,

Quoted 16 lines · Pierre Gondois

Hello Prateek,

Quoted 5 lines · K Prateek Nayak

Hello Pierre,

Quoted 2 lines · Pierre Gondois

Hello Peter,

Quoted 8 lines
Quoted 5 lines

It seems this patch:
6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern") is triggering the following warning:
rq_pin_lock()
\-WARN_ON_ONCE(rq->balance_callback && rq->balance_callback != &balance_push_callback);

Can you check if the following solution helps your case too:
https://lore.kernel.org/all/[email protected]/

Quoted 8 lines

I can still see the issue.
It seems the task deadline is also updated in:
sched_change_end()
\-enqueue_task_dl()
  \-enqueue_dl_entity()
    \-setup_new_dl_entity()
      \-replenish_dl_new_period()
if the task's period finished.

Ah! Got it. Thank you for testing the fix.

I'm curious, why is setup_new_dl_entity() doing an update_rq_clock()? That can advance the rq->clock and make it look like we need a replenish.

Does enabling WARN_DOUBLE_CLOCK warn of a double clock update before hitting this warning?

Quoted 5 lines

So in sched_change_end(), the task priority (i.e. p->dl.deadline) is updated. This results in having an old_deadline earlier than the new p->dl.deadline. Thus the rq->balance_callback:

Quoted 7 lines

prio_changed_dl() {
...
if (dl_time_before(old_deadline, p->dl.deadline))
  deadline_queue_pull_task(rq);
...
}

Thank you for your analysis.

Signature · 2 lines
Thanks and Regards,
Prateek
author maintainer
#27 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 2 lines · K Prateek Nayak

Does enabling WARN_DOUBLE_CLOCK warn of a double clock update before hitting this warning?

setup_new_dl_entity() -> update_rq_clock() seems like it will trip that in this case.

author maintainer
#28 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 7 lines · Peter Zijlstra
Quoted 2 lines · K Prateek Nayak

Does enabling WARN_DOUBLE_CLOCK warn of a double clock update before hitting this warning?

setup_new_dl_entity() -> update_rq_clock() seems like it will trip that in this case.

Something like so to fix: 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP")

kernel/sched/deadline.c +1 -2
@@ -752,8 +752,6 @@ static inline void setup_new_dl_entity(s
 	struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
 	struct rq *rq = rq_of_dl_rq(dl_rq);
 
-	update_rq_clock(rq);
-
 	WARN_ON(is_dl_boosted(dl_se));
 	WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
 
@@ -1834,6 +1832,7 @@ void sched_init_dl_servers(void)
 		rq = cpu_rq(cpu);
 
 		guard(rq_lock_irq)(rq);
+		update_rq_clock(rq);
 
 		dl_se = &rq->fair_server;
 
#29 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 22 lines · Peter Zijlstra
Quoted 6 lines · Peter Zijlstra
Quoted 2 lines · K Prateek Nayak

Does enabling WARN_DOUBLE_CLOCK warn of a double clock update before hitting this warning?

setup_new_dl_entity() -> update_rq_clock() seems like it will trip that in this case.

Something like so to fix: 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP")

kernel/sched/deadline.c +0 -2
@@ -752,8 +752,6 @@ static inline void setup_new_dl_entity(s
  	struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
  	struct rq *rq = rq_of_dl_rq(dl_rq);
 
-	update_rq_clock(rq);
-
  	WARN_ON(is_dl_boosted(dl_se));
  	WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
 

kernel/sched/deadline.c void sched_init_dl_servers(void) · line 1838

Quoted 7 lines
kernel/sched/deadline.c +1 -0
@@ -1834,6 +1832,7 @@ void sched_init_dl_servers(void)
  		rq = cpu_rq(cpu);
 
  		guard(rq_lock_irq)(rq);
+		update_rq_clock(rq);
 
  		dl_se = &rq->fair_server;
 

Yes right, enabling WARN_DOUBLE_CLOCK detects the double clock update and this fixes it.

#30 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

In reply to Peter Zijlstra
Quoted 22 lines · Peter Zijlstra
Quoted 6 lines · Peter Zijlstra
Quoted 2 lines · K Prateek Nayak

Does enabling WARN_DOUBLE_CLOCK warn of a double clock update before hitting this warning?

setup_new_dl_entity() -> update_rq_clock() seems like it will trip that in this case.

Something like so to fix: 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP")

kernel/sched/deadline.c +0 -2
@@ -752,8 +752,6 @@ static inline void setup_new_dl_entity(s
  	struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
  	struct rq *rq = rq_of_dl_rq(dl_rq);
 
-	update_rq_clock(rq);
-
  	WARN_ON(is_dl_boosted(dl_se));
  	WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
 

kernel/sched/deadline.c void sched_init_dl_servers(void) · line 1838

Quoted 7 lines
kernel/sched/deadline.c +1 -0
@@ -1834,6 +1832,7 @@ void sched_init_dl_servers(void)
  		rq = cpu_rq(cpu);
 
  		guard(rq_lock_irq)(rq);
+		update_rq_clock(rq);
 
  		dl_se = &rq->fair_server;
 

Yes right, enabling WARN_DOUBLE_CLOCK detects the double clock update and this fixes it.

merge bot Applied
#31 lore raw

[tip: sched/urgent] sched/deadline: Avoid double update_rq_clock()

In reply to Peter Zijlstra
The following commit has been merged into the sched/urgent branch of tip:

The following commit has been merged into the sched/urgent branch of tip:

Commit-ID:     4de9ff76067b40c3660df73efaea57389e62ea7a
Gitweb:        https://git.kernel.org/tip/4de9ff76067b40c3660df73efaea57389e62ea7a
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Tue, 13 Jan 2026 12:57:14 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 15 Jan 2026 21:57:52 +01:00

sched/deadline: Avoid double update_rq_clock()

When setup_new_dl_entity() is called from enqueue_task_dl() -> enqueue_dl_entity(), the rq-clock should already be updated, and calling update_rq_clock() again is not right.

Move the update_rq_clock() to the one other caller of setup_new_dl_entity(): sched_init_dl_server().

Fixes 9f239df55546 ("sched/deadline: Initialize dl_servers after SMP")
---
 kernel/sched/deadline.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)
kernel/sched/deadline.c +1 -2
index b7acf74..5d6f3cc 100644
@@ -752,8 +752,6 @@ static inline void setup_new_dl_entity(struct sched_dl_entity *dl_se)
 	struct dl_rq *dl_rq = dl_rq_of_se(dl_se);
 	struct rq *rq = rq_of_dl_rq(dl_rq);
 
-	update_rq_clock(rq);
-
 	WARN_ON(is_dl_boosted(dl_se));
 	WARN_ON(dl_time_before(rq_clock(rq), dl_se->deadline));
 
@@ -1839,6 +1837,7 @@ void sched_init_dl_servers(void)
 		rq = cpu_rq(cpu);
 
 		guard(rq_lock_irq)(rq);
+		update_rq_clock(rq);
 
 		dl_se = &rq->fair_server;
 
author maintainer
#32 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

In reply to Pierre Gondois
Quoted 30 lines · Pierre Gondois

Hello Prateek,

Quoted 5 lines · K Prateek Nayak

Hello Pierre,

Quoted 2 lines · Pierre Gondois

Hello Peter,

Quoted 8 lines
Quoted 5 lines

It seems this patch:
6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern") is triggering the following warning:
rq_pin_lock()
\-WARN_ON_ONCE(rq->balance_callback && rq->balance_callback != &balance_push_callback);

Can you check if the following solution helps your case too:
https://lore.kernel.org/all/[email protected]/

I can still see the issue.
It seems the task deadline is also updated in:
sched_change_end()
\-enqueue_task_dl()
  \-enqueue_dl_entity()
    \-setup_new_dl_entity()
      \-replenish_dl_new_period()
if the task's period finished.

So in sched_change_end(), the task priority (i.e. p->dl.deadline) is updated.
This results in having an old_deadline earlier than the new p->dl.deadline. Thus the rq->balance_callback:

Quoted 6 lines

prio_changed_dl() {
...
if (dl_time_before(old_deadline, p->dl.deadline))
  deadline_queue_pull_task(rq);
...
}

Hum... so this one is a little more tricky.

So the normal rules are that DEQUEUE_SAVE + ENQUEUE_RESTORE should be as invariant as possible.

But what I think happens here is that at the point of dequeue we are effectively ready to throttle/replenish, but we don't.

Then at enqueue, we do. The replenish changes the deadline and we're up a creek.

Let me think about this for a bit...

#33 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hello Peter,

Quoted 10 lines · Peter Zijlstra

Hum... so this one is a little more tricky.

So the normal rules are that DEQUEUE_SAVE + ENQUEUE_RESTORE should be as invariant as possible.

But what I think happens here is that at the point of dequeue we are effectively ready to throttle/replenish, but we don't.

Then at enqueue, we do. The replenish changes the deadline and we're up a creek.

I've the following data from the scenario in which I observe the same splat as Pierre splat wit the two fixes on top of tip:

    yes-4108    [194] d..2.    53.396872: get_prio_dl: get_prio_dl: clock(53060728757)
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: update_curr_dl_se: past throttle label
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: dl_throttled(0) dl_overrun(0) timer_queued(0) server?(0)
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: dl_se->runtime(190623) rq->dl.overloaded(0)
    yes-4108    [194] d..2.    53.396874: get_prio_dl: get_prio_dl: deadline(53060017809)
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: ENQUEUE_RESTORE update_dl_entity
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: setup_new_dl_entity
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: Replenish: Old: 53060017809 dl_deadline(1000000)
    yes-4108    [194] d..2.    53.396879: enqueue_dl_entity: Replenish: New: 53061728757
    yes-4108    [194] d..2.    53.396882: prio_changed_dl.part.0: Woops! prio_changed_dl: CPU(194) clock(53060728757) overloaded(0): Task: yes(4108), Curr: yes(4108) deadline: 53060017809 -> 53061728757

get_prio_dl() sees "deadline < rq->clock" but dl_se->runtime is still positive so update_curr_dl_se() doesn't fiddle with the deadline.

ENQUEUE_RESTORE sees "deadline" before "rq->clock" and calls setup_new_dl_entity() which calls replenish.

sched_change_end() will call prio_changed() with the old deadline from get_prio_dl() but enqueue advanced the deadline so we land in a pickle.

Quoted 2 lines

Let me think about this for a bit...

Should prio_changed_dl() care about "dl_se->dl_deadline" having changed within the sched_change guard since that is the attribute that can be changed using sched_setattr() right?

Signature · 2 lines
Thanks and Regards,
Prateek
author maintainer
#34 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 29 lines · K Prateek Nayak

Hello Peter,

Quoted 10 lines · Peter Zijlstra

Hum... so this one is a little more tricky.

So the normal rules are that DEQUEUE_SAVE + ENQUEUE_RESTORE should be as invariant as possible.

But what I think happens here is that at the point of dequeue we are effectively ready to throttle/replenish, but we don't.

Then at enqueue, we do. The replenish changes the deadline and we're up a creek.

I've the following data from the scenario in which I observe the same splat as Pierre splat wit the two fixes on top of tip:

    yes-4108    [194] d..2.    53.396872: get_prio_dl: get_prio_dl: clock(53060728757)
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: update_curr_dl_se: past throttle label
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: dl_throttled(0) dl_overrun(0) timer_queued(0) server?(0)
    yes-4108    [194] d..2.    53.396873: update_curr_dl_se: dl_se->runtime(190623) rq->dl.overloaded(0)
    yes-4108    [194] d..2.    53.396874: get_prio_dl: get_prio_dl: deadline(53060017809)
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: ENQUEUE_RESTORE update_dl_entity
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: setup_new_dl_entity
    yes-4108    [194] d..2.    53.396878: enqueue_dl_entity: Replenish: Old: 53060017809 dl_deadline(1000000)
    yes-4108    [194] d..2.    53.396879: enqueue_dl_entity: Replenish: New: 53061728757
    yes-4108    [194] d..2.    53.396882: prio_changed_dl.part.0: Woops! prio_changed_dl: CPU(194) clock(53060728757) overloaded(0): Task: yes(4108), Curr: yes(4108) deadline: 53060017809 -> 53061728757
Quoted 5 lines

get_prio_dl() sees "deadline < rq->clock" but dl_se->runtime is still positive so update_curr_dl_se() doesn't fiddle with the deadline.

ENQUEUE_RESTORE sees "deadline" before "rq->clock" and calls setup_new_dl_entity() which calls replenish.

Right this. That's more or less where I ended up as well. Just don't know what to do about that. It doesn't feel right.

That is, it means that a task behaves differently depending on if a (unrelated) sched_change comes in between.

If undisturbed it will be allowed to exhaust its runtime, irrespective of it missing its deadline (valid for G-EDF); while when it gets disturbed it will be forced to replenish.

Juri, Luca, I'm tempted to suggest to simply remove the replenish on RESTORE entirely -- that would allow the task to continue as it had been, irrespective of it being 'late'.

Something like so -- what would this break?

kernel/sched/deadline.c +0 -4
@@ -2214,10 +2214,6 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
-		   !is_dl_boosted(dl_se) &&
-		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
-		setup_new_dl_entity(dl_se);
 	}
 
 	/*
Quoted 5 lines
Quoted 1 line

Let me think about this for a bit...

Should prio_changed_dl() care about "dl_se->dl_deadline" having changed within the sched_change guard since that is the attribute that can be changed using sched_setattr() right?

__setparam_dl() changes dl_se->dl_deadline, as you say, but that does not immediately affect the current dl_se->deadline. It will take effect the next replenish.

That is, changing dl task attributes changes the next activation, not the current. And since DL is a dynamic priority scheme, it doesn't affect the current priority.

author maintainer
#35 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 9 lines · Peter Zijlstra

Juri, Luca, I'm tempted to suggest to simply remove the replenish on RESTORE entirely -- that would allow the task to continue as it had been, irrespective of it being 'late'.

Something like so -- what would this break?

kernel/sched/deadline.c enqueue_dl_entity(struct sched_dl_entity · line 2219

Quoted 10 lines
kernel/sched/deadline.c +0 -4
@@ -2214,10 +2214,6 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
-		   !is_dl_boosted(dl_se) &&
-		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
-		setup_new_dl_entity(dl_se);
 	}
 
 	/*

Ah, this is de-boost, right? Boosting allows one to break the CBS rules and then we have to rein in the excesses.

But we have {DE,EN}QUEUE_MOVE for this, that explicitly allows priority to change and is set for rt_mutex_setprio() (among others).

So doing s/RESTORE/MOVE/ above.

The corollary to all this is that everybody that sets MOVE must be able to deal with balance callbacks, so audit that too.

This then gives something like so.. which builds and boots for me, but clearly I haven't been able to trigger these funny cases.

---

kernel/sched/core.c +14 -7
@@ -4969,9 +4969,13 @@ struct balance_callback *splice_balance_
 	return __splice_balance_callbacks(rq, true);
 }
 
-static void __balance_callbacks(struct rq *rq)
+void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
 {
+	if (rf)
+		rq_unpin_lock(rq, rf);
 	do_balance_callbacks(rq, __splice_balance_callbacks(rq, false));
+	if (rf)
+		rq_repin_lock(rq, rf);
 }
 
 void balance_callbacks(struct rq *rq, struct balance_callback *head)
@@ -5018,7 +5022,7 @@ static inline void finish_lock_switch(st
 	 * prev into current:
 	 */
 	spin_acquire(&__rq_lockp(rq)->dep_map, 0, 0, _THIS_IP_);
-	__balance_callbacks(rq);
+	__balance_callbacks(rq, NULL);
 	raw_spin_rq_unlock_irq(rq);
 }
 
@@ -6901,7 +6905,7 @@ static void __sched notrace __schedule(i
 			proxy_tag_curr(rq, next);
 
 		rq_unpin_lock(rq, &rf);
-		__balance_callbacks(rq);
+		__balance_callbacks(rq, NULL);
 		raw_spin_rq_unlock_irq(rq);
 	}
 	trace_sched_exit_tp(is_switch);
@@ -7350,7 +7354,7 @@ void rt_mutex_setprio(struct task_struct
 	trace_sched_pi_setprio(p, pi_task);
 	oldprio = p->prio;
 
-	if (oldprio == prio)
+	if (oldprio == prio && !dl_prio(prio))
 		queue_flag &= ~DEQUEUE_MOVE;
 
 	prev_class = p->sched_class;
@@ -7396,9 +7400,7 @@ void rt_mutex_setprio(struct task_struct
 out_unlock:
 	/* Caller holds task_struct::pi_lock, IRQs are still disabled */
 
-	rq_unpin_lock(rq, &rf);
-	__balance_callbacks(rq);
-	rq_repin_lock(rq, &rf);
+	__balance_callbacks(rq, &rf);
 	__task_rq_unlock(rq, p, &rf);
 }
 #endif /* CONFIG_RT_MUTEXES */
@@ -9167,6 +9169,8 @@ void sched_move_task(struct task_struct
 
 	if (resched)
 		resched_curr(rq);
+
+	__balance_callbacks(rq, &rq_guard.rf);
 }
 
 static struct cgroup_subsys_state *
@@ -10891,6 +10895,9 @@ void sched_change_end(struct sched_chang
 				resched_curr(rq);
 		}
 	} else {
+		/*
+		 * XXX validate prio only really changed when ENQUEUE_MOVE is set.
+		 */
 		p->sched_class->prio_changed(rq, p, ctx->prio);
 	}
 }
kernel/sched/deadline.c +6 -1
@@ -2214,9 +2214,14 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
+	} else if ((flags & ENQUEUE_MOVE) &&
 		   !is_dl_boosted(dl_se) &&
 		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
+		/*
+		 * Deals with the de-boost case, and ENQUEUE_MOVE explicitly
+		 * allows us to change priority. Callers are expected to deal
+		 * with balance_callbacks.
+		 */
 		setup_new_dl_entity(dl_se);
 	}
 
kernel/sched/ext.c +1 -0
@@ -545,6 +545,7 @@ static void scx_task_iter_start(struct s
 static void __scx_task_iter_rq_unlock(struct scx_task_iter *iter)
 {
 	if (iter->locked_task) {
+		__balance_callbacks(iter->rq, &iter->rf);
 		task_rq_unlock(iter->rq, iter->locked_task, &iter->rf);
 		iter->locked_task = NULL;
 	}
kernel/sched/sched.h +4 -1
@@ -2430,7 +2430,8 @@ extern const u32		sched_prio_to_wmult[40
  *                should preserve as much state as possible.
  *
  * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location
- *        in the runqueue.
+ *        in the runqueue. IOW the priority is allowed to change. Callers
+ *        must expect to deal with balance callbacks.
  *
  * NOCLOCK - skip the update_rq_clock() (avoids double updates)
  *
@@ -4019,6 +4020,8 @@ extern void enqueue_task(struct rq *rq,
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
+
+extern void __balance_callbacks(struct rq *rq, struct rq_flags *rf);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
 /*
kernel/sched/syscalls.c +1 -1
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}
 
#36 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hi Peter,

Quoted 21 lines · On Wed, 14 Jan 2026 14:05:28 +0100 Peter Zijlstra <[email protected]> wrote:
Quoted 19 lines · Peter Zijlstra

Juri, Luca, I'm tempted to suggest to simply remove the replenish on RESTORE entirely -- that would allow the task to continue as it had been, irrespective of it being 'late'.

Something like so -- what would this break?

kernel/sched/deadline.c +0 -4
@@ -2214,10 +2214,6 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
-		   !is_dl_boosted(dl_se) &&
-		   dl_time_before(dl_se->deadline,
 rq_clock(rq_of_dl_se(dl_se)))) {
-		setup_new_dl_entity(dl_se);
 	}
 
Quoted 4 lines
Quoted 1 line

/*

Ah, this is de-boost, right? Boosting allows one to break the CBS rules and then we have to rein in the excesses.

Sorry, I am missing a little bit of context (I am trying to catch up reading the mailing list archives)... But I agree that the call to setup_new_dl_entity() mentioned above does not make too much sense.

I suspect the hunk above could be directly removed, as you originally suggested (on de-boosting(), the task returns to its original deadline, which is larger than the inherited one, so I am not sure whether we should generate a new deadline or just leave it as it is, even if it has been missed).

Luca

Quoted 155 lines

But we have {DE,EN}QUEUE_MOVE for this, that explicitly allows priority to change and is set for rt_mutex_setprio() (among others).

So doing s/RESTORE/MOVE/ above.

The corollary to all this is that everybody that sets MOVE must be able to deal with balance callbacks, so audit that too.

This then gives something like so.. which builds and boots for me, but clearly I haven't been able to trigger these funny cases.

---

kernel/sched/core.c +5 -1
@@ -4969,9 +4969,13 @@ struct balance_callback *splice_balance_
 	return __splice_balance_callbacks(rq, true);
 }
 
-static void __balance_callbacks(struct rq *rq)
+void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
 {
+	if (rf)
+		rq_unpin_lock(rq, rf);
 	do_balance_callbacks(rq, __splice_balance_callbacks(rq,
 false));
+	if (rf)
+		rq_repin_lock(rq, rf);
 }
 

void balance_callbacks(struct rq *rq, struct balance_callback *head)

kernel/sched/core.c +3 -3
@@ -5018,7 +5022,7 @@ static inline void finish_lock_switch(st
 	 * prev into current:
 	 */
 	spin_acquire(&__rq_lockp(rq)->dep_map, 0, 0, _THIS_IP_);
-	__balance_callbacks(rq);
+	__balance_callbacks(rq, NULL);
 	raw_spin_rq_unlock_irq(rq);
 }
 
@@ -6901,7 +6905,7 @@ static void __sched notrace __schedule(i
 			proxy_tag_curr(rq, next);
 
 		rq_unpin_lock(rq, &rf);
-		__balance_callbacks(rq);
+		__balance_callbacks(rq, NULL);
 		raw_spin_rq_unlock_irq(rq);
 	}
 	trace_sched_exit_tp(is_switch);
@@ -7350,7 +7354,7 @@ void rt_mutex_setprio(struct task_struct
 	trace_sched_pi_setprio(p, pi_task);
 	oldprio = p->prio;
 
-	if (oldprio == prio)
+	if (oldprio == prio && !dl_prio(prio))
 		queue_flag &= ~DEQUEUE_MOVE;
 
 	prev_class = p->sched_class;
@@ -7396,9 +7400,7 @@ void rt_mutex_setprio(struct task_struct
 out_unlock:
 	/* Caller holds task_struct::pi_lock, IRQs are still

disabled */

kernel/sched/core.c +5 -3
-	rq_unpin_lock(rq, &rf);
-	__balance_callbacks(rq);
-	rq_repin_lock(rq, &rf);
+	__balance_callbacks(rq, &rf);
 	__task_rq_unlock(rq, p, &rf);
 }
 #endif /* CONFIG_RT_MUTEXES */
@@ -9167,6 +9169,8 @@ void sched_move_task(struct task_struct
 
 	if (resched)
 		resched_curr(rq);
+
+	__balance_callbacks(rq, &rq_guard.rf);
 }
 
 static struct cgroup_subsys_state *
@@ -10891,6 +10895,9 @@ void sched_change_end(struct sched_chang
 				resched_curr(rq);
 		}
 	} else {
+		/*
+		 * XXX validate prio only really changed when

ENQUEUE_MOVE is set.

kernel/sched/core.c +1 -0
+		 */
 		p->sched_class->prio_changed(rq, p, ctx->prio);
 	}
 }
kernel/sched/deadline.c +3 -1
@@ -2214,9 +2214,14 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
+	} else if ((flags & ENQUEUE_MOVE) &&
 		   !is_dl_boosted(dl_se) &&
 		   dl_time_before(dl_se->deadline,
 rq_clock(rq_of_dl_se(dl_se)))) {
+		/*
+		 * Deals with the de-boost case, and ENQUEUE_MOVE

explicitly

kernel/sched/deadline.c +1 -0
+		 * allows us to change priority. Callers are

expected to deal

kernel/sched/deadline.c +2 -0
+		 * with balance_callbacks.
+		 */
 		setup_new_dl_entity(dl_se);
 	}
kernel/sched/ext.c +1 -0
@@ -545,6 +545,7 @@ static void scx_task_iter_start(struct s
 static void __scx_task_iter_rq_unlock(struct scx_task_iter *iter)
 {
 	if (iter->locked_task) {
+		__balance_callbacks(iter->rq, &iter->rf);
 		task_rq_unlock(iter->rq, iter->locked_task,
 &iter->rf); iter->locked_task = NULL;
 	}
kernel/sched/sched.h +0 -0
@@ -2430,7 +2430,8 @@ extern const u32

sched_prio_to_wmult[40
* should preserve as much state as possible.
*
* MOVE - paired with SAVE/RESTORE, explicitly does not preserve the
location
- * in the runqueue.
+ * in the runqueue. IOW the priority is allowed to change.
Callers
+ * must expect to deal with balance callbacks.
*
* NOCLOCK - skip the update_rq_clock() (avoids double updates) *

kernel/sched/sched.h +1 -0
@@ -4019,6 +4020,8 @@ extern void enqueue_task(struct rq *rq,
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int
 flags); 
 extern struct balance_callback *splice_balance_callbacks(struct rq
 *rq); +
+extern void __balance_callbacks(struct rq *rq, struct rq_flags *rf);
 extern void balance_callbacks(struct rq *rq, struct balance_callback
 *head); 
 /*
kernel/sched/syscalls.c +1 -1
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}
 
maintainer
#37 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

In reply to Peter Zijlstra
Quoted 163 lines · Peter Zijlstra
Quoted 9 lines · Peter Zijlstra

Juri, Luca, I'm tempted to suggest to simply remove the replenish on RESTORE entirely -- that would allow the task to continue as it had been, irrespective of it being 'late'.

Something like so -- what would this break?

kernel/sched/deadline.c enqueue_dl_entity(struct sched_dl_entity · line 2219

Quoted 10 lines
kernel/sched/deadline.c +0 -4
@@ -2214,10 +2214,6 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
-		   !is_dl_boosted(dl_se) &&
-		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
-		setup_new_dl_entity(dl_se);
 	}
 
 	/*

Ah, this is de-boost, right? Boosting allows one to break the CBS rules and then we have to rein in the excesses.

But we have {DE,EN}QUEUE_MOVE for this, that explicitly allows priority to change and is set for rt_mutex_setprio() (among others).

So doing s/RESTORE/MOVE/ above.

The corollary to all this is that everybody that sets MOVE must be able to deal with balance callbacks, so audit that too.

This then gives something like so.. which builds and boots for me, but clearly I haven't been able to trigger these funny cases.

---

kernel/sched/core.c +14 -7
@@ -4969,9 +4969,13 @@ struct balance_callback *splice_balance_
 	return __splice_balance_callbacks(rq, true);
 }
 
-static void __balance_callbacks(struct rq *rq)
+void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
 {
+	if (rf)
+		rq_unpin_lock(rq, rf);
 	do_balance_callbacks(rq, __splice_balance_callbacks(rq, false));
+	if (rf)
+		rq_repin_lock(rq, rf);
 }
 
 void balance_callbacks(struct rq *rq, struct balance_callback *head)
@@ -5018,7 +5022,7 @@ static inline void finish_lock_switch(st
 	 * prev into current:
 	 */
 	spin_acquire(&__rq_lockp(rq)->dep_map, 0, 0, _THIS_IP_);
-	__balance_callbacks(rq);
+	__balance_callbacks(rq, NULL);
 	raw_spin_rq_unlock_irq(rq);
 }
 
@@ -6901,7 +6905,7 @@ static void __sched notrace __schedule(i
 			proxy_tag_curr(rq, next);
 
 		rq_unpin_lock(rq, &rf);
-		__balance_callbacks(rq);
+		__balance_callbacks(rq, NULL);
 		raw_spin_rq_unlock_irq(rq);
 	}
 	trace_sched_exit_tp(is_switch);
@@ -7350,7 +7354,7 @@ void rt_mutex_setprio(struct task_struct
 	trace_sched_pi_setprio(p, pi_task);
 	oldprio = p->prio;
 
-	if (oldprio == prio)
+	if (oldprio == prio && !dl_prio(prio))
 		queue_flag &= ~DEQUEUE_MOVE;
 
 	prev_class = p->sched_class;
@@ -7396,9 +7400,7 @@ void rt_mutex_setprio(struct task_struct
 out_unlock:
 	/* Caller holds task_struct::pi_lock, IRQs are still disabled */
 
-	rq_unpin_lock(rq, &rf);
-	__balance_callbacks(rq);
-	rq_repin_lock(rq, &rf);
+	__balance_callbacks(rq, &rf);
 	__task_rq_unlock(rq, p, &rf);
 }
 #endif /* CONFIG_RT_MUTEXES */
@@ -9167,6 +9169,8 @@ void sched_move_task(struct task_struct
 
 	if (resched)
 		resched_curr(rq);
+
+	__balance_callbacks(rq, &rq_guard.rf);
 }
 
 static struct cgroup_subsys_state *
@@ -10891,6 +10895,9 @@ void sched_change_end(struct sched_chang
 				resched_curr(rq);
 		}
 	} else {
+		/*
+		 * XXX validate prio only really changed when ENQUEUE_MOVE is set.
+		 */
 		p->sched_class->prio_changed(rq, p, ctx->prio);
 	}
 }
kernel/sched/deadline.c +6 -1
@@ -2214,9 +2214,14 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
+	} else if ((flags & ENQUEUE_MOVE) &&
 		   !is_dl_boosted(dl_se) &&
 		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
+		/*
+		 * Deals with the de-boost case, and ENQUEUE_MOVE explicitly
+		 * allows us to change priority. Callers are expected to deal
+		 * with balance_callbacks.
+		 */
 		setup_new_dl_entity(dl_se);
 	}
 
kernel/sched/ext.c +1 -0
@@ -545,6 +545,7 @@ static void scx_task_iter_start(struct s
 static void __scx_task_iter_rq_unlock(struct scx_task_iter *iter)
 {
 	if (iter->locked_task) {
+		__balance_callbacks(iter->rq, &iter->rf);
 		task_rq_unlock(iter->rq, iter->locked_task, &iter->rf);
 		iter->locked_task = NULL;
 	}
kernel/sched/sched.h +4 -1
@@ -2430,7 +2430,8 @@ extern const u32		sched_prio_to_wmult[40
  *                should preserve as much state as possible.
  *
  * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location
- *        in the runqueue.
+ *        in the runqueue. IOW the priority is allowed to change. Callers
+ *        must expect to deal with balance callbacks.
  *
  * NOCLOCK - skip the update_rq_clock() (avoids double updates)
  *
@@ -4019,6 +4020,8 @@ extern void enqueue_task(struct rq *rq,
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
+
+extern void __balance_callbacks(struct rq *rq, struct rq_flags *rf);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
 /*

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 7 lines
kernel/sched/syscalls.c +1 -1
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE. So, from a quick first look, it does sound good to me.

Thanks!
Juri

#38 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hi Juri,

On Wed, 14 Jan 2026 15:20:48 +0100
Juri Lelli <[email protected]> wrote:
[...]

Quoted 14 lines
Quoted 14 lines
Quoted 14 lines
kernel/sched/deadline.c +0 -4
@@ -2214,10 +2214,6 @@ enqueue_dl_entity(struct sched_dl_entity
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
-		   !is_dl_boosted(dl_se) &&
-		   dl_time_before(dl_se->deadline,
 rq_clock(rq_of_dl_se(dl_se)))) {
-		setup_new_dl_entity(dl_se);
 	}
 

/*

[...]

Quoted 6 lines
Quoted 6 lines
kernel/sched/syscalls.c +0 -0
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
Quoted 8 lines

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 4 lines
kernel/sched/syscalls.c +1 -1
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}  

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE.

You are right: double thinking about it, I seem to remember that the "flags & ENQUEUE_RESTORE" check above was introduced to fix tasks switching to SCHED_DEADLINE...

So, I agree that changing "ENQUEUE_RESTORE" to "ENQUEUE_MOVE" should be the right thing to do

Luca

author maintainer
#39 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

In reply to Juri Lelli
Quoted 7 lines · Juri Lelli
Quoted 7 lines
kernel/sched/syscalls.c +0 -1
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
Quoted 8 lines

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 3 lines
kernel/sched/syscalls.c +1 -0
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE. So, from a quick first look, it does sound good to me.

If this is strictly about tasks coming into SCHED_DEADLINE there are a number of alternative options:

- there are the sched_class::switch{ing,ed}_to() callbacks;
- there is (the fairly recent) ENQUEUE_CLASS.

Anyway, let me break up this one patch into individual bits and write changelogs. I'll stick them in queue/sched/urgent for now; hopefully Pierre can given them a spin and report back if it all sorts his problem).

author maintainer
#40 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 21 lines · Peter Zijlstra
Quoted 7 lines · Juri Lelli
Quoted 7 lines
kernel/sched/syscalls.c +0 -1
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
Quoted 8 lines

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 3 lines
kernel/sched/syscalls.c +1 -0
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE. So, from a quick first look, it does sound good to me.

If this is strictly about tasks coming into SCHED_DEADLINE there are a number of alternative options:

Quoted 7 lines

- there are the sched_class::switch{ing,ed}_to() callbacks;
- there is (the fairly recent) ENQUEUE_CLASS.

Anyway, let me break up this one patch into individual bits and write changelogs. I'll stick them in queue/sched/urgent for now; hopefully Pierre can given them a spin and report back if it all sorts his problem).

Now live at:

https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=sched/urgent

Please test.

#41 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Hello Peter,

Quoted 27 lines · Peter Zijlstra
Quoted 19 lines · Peter Zijlstra
Quoted 6 lines · Juri Lelli
Quoted 6 lines
kernel/sched/syscalls.c +0 -0
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
  		 * itself.
  		 */
  		newprio = rt_effective_prio(p, newprio);
Quoted 8 lines

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 4 lines
kernel/sched/syscalls.c +1 -1
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
  			queue_flags &= ~DEQUEUE_MOVE;
  	}

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE. So, from a quick first look, it does sound good to me.

If this is strictly about tasks coming into SCHED_DEADLINE there are a number of alternative options:

Quoted 7 lines

- there are the sched_class::switch{ing,ed}_to() callbacks;
- there is (the fairly recent) ENQUEUE_CLASS.

Anyway, let me break up this one patch into individual bits and write changelogs. I'll stick them in queue/sched/urgent for now; hopefully Pierre can given them a spin and report back if it all sorts his problem).

Quoted 5 lines

Now live at:

https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=sched/urgent

Please test.

I don't see the balance_callback or the double clock update warnings anymore.

Thanks for the branch,
Regards,
Pierre

maintainer
#42 lore raw

Re: [PATCH 05/12] sched: Move sched_class::prio_changed() into the change pattern

Quoted 30 lines · Pierre Gondois

Hello Peter,

Quoted 27 lines · Peter Zijlstra
Quoted 26 lines · Peter Zijlstra
Quoted 6 lines · Juri Lelli
Quoted 6 lines
kernel/sched/syscalls.c +0 -0
@@ -639,7 +639,7 @@ int __sched_setscheduler(struct task_str
  		 * itself.
  		 */
  		newprio = rt_effective_prio(p, newprio);
Quoted 8 lines

kernel/sched/syscalls.c int __sched_setscheduler(struct task_str · line 644

Quoted 4 lines
kernel/sched/syscalls.c +1 -1
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
  			queue_flags &= ~DEQUEUE_MOVE;
  	}

We have been using (improperly?) ENQUEUE_SAVE also to know when a new entity gets setscheduled to DEADLINE (or its parameters are changed) and it looks like this keeps that happening with DEQUEUE_MOVE. So, from a quick first look, it does sound good to me.

If this is strictly about tasks coming into SCHED_DEADLINE there are a number of alternative options:

- there are the sched_class::switch{ing,ed}_to() callbacks;
- there is (the fairly recent) ENQUEUE_CLASS.

Anyway, let me break up this one patch into individual bits and write changelogs. I'll stick them in queue/sched/urgent for now; hopefully Pierre can given them a spin and report back if it all sorts his problem).

Quoted 7 lines
Quoted 5 lines

Now live at:

https://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git/log/?h=sched/urgent

Please test.

I don't see the balance_callback or the double clock update warnings anymore.

FWIW (as I wasn't seeing the reported issue) I had a look as well and tested locally. Patches look good and nothing to report on the test side.

Thanks!
Juri

merge bot Applied
#43 lore raw

[tip: sched/urgent] sched/deadline: Use ENQUEUE_MOVE to allow priority change

In reply to Peter Zijlstra
The following commit has been merged into the sched/urgent branch of tip:

The following commit has been merged into the sched/urgent branch of tip:

Commit-ID:     627cc25f84466d557d86e5dc67b43a4eea604c80
Gitweb:        https://git.kernel.org/tip/627cc25f84466d557d86e5dc67b43a4eea604c80
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 15 Jan 2026 09:27:22 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 15 Jan 2026 21:57:53 +01:00

sched/deadline: Use ENQUEUE_MOVE to allow priority change

Pierre reported hitting balance callback warnings for deadline tasks after commit 6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern").

It turns out that DEQUEUE_SAVE+ENQUEUE_RESTORE does not preserve DL priority and subsequently trips a balance pass -- where one was not expected.

Quoted 1 line

From discussion with Juri and Luca, the purpose of this clause was to

deal with tasks new to DL and all those sites will have MOVE set (as well as CLASS, but MOVE is move conservative at this point).

Per the previous patches MOVE is audited to always run the balance callbacks, so switch enqueue_dl_entity() to use MOVE for this case.

Fixes 6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern")
---
 kernel/sched/deadline.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
kernel/sched/deadline.c +1 -1
index 5d6f3cc..c509f2e 100644
@@ -2214,7 +2214,7 @@ enqueue_dl_entity(struct sched_dl_entity *dl_se, int flags)
 		update_dl_entity(dl_se);
 	} else if (flags & ENQUEUE_REPLENISH) {
 		replenish_dl_entity(dl_se);
-	} else if ((flags & ENQUEUE_RESTORE) &&
+	} else if ((flags & ENQUEUE_MOVE) &&
 		   !is_dl_boosted(dl_se) &&
 		   dl_time_before(dl_se->deadline, rq_clock(rq_of_dl_se(dl_se)))) {
 		setup_new_dl_entity(dl_se);
merge bot Applied
#44 lore raw

[tip: sched/urgent] sched: Deadline has dynamic priority

In reply to Peter Zijlstra
The following commit has been merged into the sched/urgent branch of tip:

The following commit has been merged into the sched/urgent branch of tip:

Commit-ID:     e008ec6c7904ed99d3b2cb634b6545b008a99288
Gitweb:        https://git.kernel.org/tip/e008ec6c7904ed99d3b2cb634b6545b008a99288
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 15 Jan 2026 09:25:37 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 15 Jan 2026 21:57:53 +01:00

sched: Deadline has dynamic priority

While FIFO/RR have static priority, DEADLINE is a dynamic priority scheme. Notably it has static priority -1. Do not assume the priority doesn't change for deadline tasks just because the static priority doesn't change.

This ensures DL always sees {DE,EN}QUEUE_MOVE where appropriate.

Fixes ff77e4685359 ("sched/rt: Fix PI handling vs. sched_setscheduler()")
---
 kernel/sched/core.c     | 2 +-
 kernel/sched/syscalls.c | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)
kernel/sched/core.c +1 -1
index 4d925d7..045f83a 100644
@@ -7320,7 +7320,7 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 	trace_sched_pi_setprio(p, pi_task);
 	oldprio = p->prio;
 
-	if (oldprio == prio)
+	if (oldprio == prio && !dl_prio(prio))
 		queue_flag &= ~DEQUEUE_MOVE;
 
 	prev_class = p->sched_class;
kernel/sched/syscalls.c +1 -1
index cb337de..6f10db3 100644
@@ -639,7 +639,7 @@ change:
 		 * itself.
 		 */
 		newprio = rt_effective_prio(p, newprio);
-		if (newprio == oldprio)
+		if (newprio == oldprio && !dl_prio(newprio))
 			queue_flags &= ~DEQUEUE_MOVE;
 	}
 
merge bot Applied
#45 lore raw

[tip: sched/urgent] sched: Audit MOVE vs balance_callbacks

In reply to Peter Zijlstra
The following commit has been merged into the sched/urgent branch of tip:

The following commit has been merged into the sched/urgent branch of tip:

Commit-ID:     53439363c0a111f11625982b69c88ee2ce8608ec
Gitweb:        https://git.kernel.org/tip/53439363c0a111f11625982b69c88ee2ce8608ec
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 15 Jan 2026 09:17:49 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 15 Jan 2026 21:57:53 +01:00

sched: Audit MOVE vs balance_callbacks

The {DE,EN}QUEUE_MOVE flag indicates a task is allowed to change priority, which means there could be balance callbacks queued.

Therefore audit all MOVE users and make sure they do run balance callbacks before dropping rq-lock.

Fixes 6455ad5346c9 ("sched: Move sched_class::prio_changed() into the change pattern")
---
 kernel/sched/core.c  | 4 +++-
 kernel/sched/ext.c   | 1 +
 kernel/sched/sched.h | 5 ++++-
 3 files changed, 8 insertions(+), 2 deletions(-)
kernel/sched/core.c +3 -1
index 842a3ad..4d925d7 100644
@@ -4950,7 +4950,7 @@ struct balance_callback *splice_balance_callbacks(struct rq *rq)
 	return __splice_balance_callbacks(rq, true);
 }
 
-static void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
+void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
 {
 	if (rf)
 		rq_unpin_lock(rq, rf);
@@ -9126,6 +9126,8 @@ void sched_move_task(struct task_struct *tsk, bool for_autogroup)
 
 	if (resched)
 		resched_curr(rq);
+
+	__balance_callbacks(rq, &rq_guard.rf);
 }
 
 static struct cgroup_subsys_state *
kernel/sched/ext.c +1 -0
index 8f6d8d7..afe28c0 100644
@@ -545,6 +545,7 @@ static void scx_task_iter_start(struct scx_task_iter *iter)
 static void __scx_task_iter_rq_unlock(struct scx_task_iter *iter)
 {
 	if (iter->locked_task) {
+		__balance_callbacks(iter->rq, &iter->rf);
 		task_rq_unlock(iter->rq, iter->locked_task, &iter->rf);
 		iter->locked_task = NULL;
 	}
kernel/sched/sched.h +4 -1
index e885a93..93fce4b 100644
@@ -2388,7 +2388,8 @@ extern const u32		sched_prio_to_wmult[40];
  *                should preserve as much state as possible.
  *
  * MOVE - paired with SAVE/RESTORE, explicitly does not preserve the location
- *        in the runqueue.
+ *        in the runqueue. IOW the priority is allowed to change. Callers
+ *        must expect to deal with balance callbacks.
  *
  * NOCLOCK - skip the update_rq_clock() (avoids double updates)
  *
@@ -3969,6 +3970,8 @@ extern void enqueue_task(struct rq *rq, struct task_struct *p, int flags);
 extern bool dequeue_task(struct rq *rq, struct task_struct *p, int flags);
 
 extern struct balance_callback *splice_balance_callbacks(struct rq *rq);
+
+extern void __balance_callbacks(struct rq *rq, struct rq_flags *rf);
 extern void balance_callbacks(struct rq *rq, struct balance_callback *head);
 
 /*
merge bot Applied
#46 lore raw

[tip: sched/urgent] sched: Fold rq-pin swizzle into __balance_callbacks()

In reply to Peter Zijlstra
The following commit has been merged into the sched/urgent branch of tip:

The following commit has been merged into the sched/urgent branch of tip:

Commit-ID:     49041e87f9cd3e6be8926b80b3fee71e89323e1c
Gitweb:        https://git.kernel.org/tip/49041e87f9cd3e6be8926b80b3fee71e89323e1c
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 15 Jan 2026 09:16:44 +01:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 15 Jan 2026 21:57:52 +01:00

sched: Fold rq-pin swizzle into __balance_callbacks()

Prepare for more users needing the rq-pin swizzle.

---
 kernel/sched/core.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)
kernel/sched/core.c +8 -6
index 60afadb..842a3ad 100644
@@ -4950,9 +4950,13 @@ struct balance_callback *splice_balance_callbacks(struct rq *rq)
 	return __splice_balance_callbacks(rq, true);
 }
 
-static void __balance_callbacks(struct rq *rq)
+static void __balance_callbacks(struct rq *rq, struct rq_flags *rf)
 {
+	if (rf)
+		rq_unpin_lock(rq, rf);
 	do_balance_callbacks(rq, __splice_balance_callbacks(rq, false));
+	if (rf)
+		rq_repin_lock(rq, rf);
 }
 
 void balance_callbacks(struct rq *rq, struct balance_callback *head)
@@ -4991,7 +4995,7 @@ static inline void finish_lock_switch(struct rq *rq)
 	 * prev into current:
 	 */
 	spin_acquire(&__rq_lockp(rq)->dep_map, 0, 0, _THIS_IP_);
-	__balance_callbacks(rq);
+	__balance_callbacks(rq, NULL);
 	raw_spin_rq_unlock_irq(rq);
 }
 
@@ -6867,7 +6871,7 @@ keep_resched:
 			proxy_tag_curr(rq, next);
 
 		rq_unpin_lock(rq, &rf);
-		__balance_callbacks(rq);
+		__balance_callbacks(rq, NULL);
 		raw_spin_rq_unlock_irq(rq);
 	}
 	trace_sched_exit_tp(is_switch);
@@ -7362,9 +7366,7 @@ void rt_mutex_setprio(struct task_struct *p, struct task_struct *pi_task)
 out_unlock:
 	/* Caller holds task_struct::pi_lock, IRQs are still disabled */
 
-	rq_unpin_lock(rq, &rf);
-	__balance_callbacks(rq);
-	rq_repin_lock(rq, &rf);
+	__balance_callbacks(rq, &rf);
 	__task_rq_unlock(rq, p, &rf);
 }
 #endif /* CONFIG_RT_MUTEXES */
author maintainer
#47 lore raw

PATCH 6/12 sched: Fix migrate_disable_switch() locking

In reply to Peter Zijlstra

For some reason migrate_disable_switch() was more complicated than it needs to be, resulting in mind bending locking of dubious quality.

Recognise that migrate_disable_switch() must be called before a context switch, but any place before that switch is equally good. Since the current place results in troubled locking, simply move the thing before taking rq->lock.

---
 kernel/sched/core.c |   27 ++++++---------------------
 1 file changed, 6 insertions(+), 21 deletions(-)
kernel/sched/core.c +6 -21
@@ -2346,10 +2346,10 @@ static void migrate_disable_switch(struc
 	if (p->cpus_ptr != &p->cpus_mask)
 		return;
 
-	/*
-	 * Violates locking rules! See comment in __do_set_cpus_allowed().
-	 */
-	__do_set_cpus_allowed(p, &ac);
+	scoped_guard (task_rq_lock, p) {
+		update_rq_clock(scope.rq);
+		__do_set_cpus_allowed(p, &ac);
+	}
 }
 
 void ___migrate_enable(void)
@@ -2667,22 +2667,7 @@ __do_set_cpus_allowed(struct task_struct
 	struct rq *rq = task_rq(p);
 	bool queued, running;
 
-	/*
-	 * This here violates the locking rules for affinity, since we're only
-	 * supposed to change these variables while holding both rq->lock and
-	 * p->pi_lock.
-	 *
-	 * HOWEVER, it magically works, because ttwu() is the only code that
-	 * accesses these variables under p->pi_lock and only does so after
-	 * smp_cond_load_acquire(&p->on_cpu, !VAL), and we're in __schedule()
-	 * before finish_task().
-	 *
-	 * XXX do further audits, this smells like something putrid.
-	 */
-	if (ctx->flags & SCA_MIGRATE_DISABLE)
-		WARN_ON_ONCE(!p->on_cpu);
-	else
-		lockdep_assert_held(&p->pi_lock);
+	lockdep_assert_held(&p->pi_lock);
 
 	queued = task_on_rq_queued(p);
 	running = task_current_donor(rq, p);
@@ -6781,6 +6766,7 @@ static void __sched notrace __schedule(i
 
 	local_irq_disable();
 	rcu_note_context_switch(preempt);
+	migrate_disable_switch(rq, prev);
 
 	/*
 	 * Make sure that signal_pending_state()->signal_pending() below
@@ -6887,7 +6873,6 @@ static void __sched notrace __schedule(i
 		 */
 		++*switch_count;
 
-		migrate_disable_switch(rq, prev);
 		psi_account_irqtime(rq, prev, next);
 		psi_sched_switch(prev, next, !task_on_rq_queued(prev) ||
 					     prev->se.sched_delayed);
merge bot Applied
#48 lore raw

[tip: sched/core] sched: Fix migrate_disable_switch() locking

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     942b8db965006cf655d356162f7091a9238da94e
Gitweb:        https://git.kernel.org/tip/942b8db965006cf655d356162f7091a9238da94e
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 10 Sep 2025 09:46:44 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:52 +02:00

sched: Fix migrate_disable_switch() locking

For some reason migrate_disable_switch() was more complicated than it needs to be, resulting in mind bending locking of dubious quality.

Recognise that migrate_disable_switch() must be called before a context switch, but any place before that switch is equally good. Since the current place results in troubled locking, simply move the thing before taking rq->lock.

---
 kernel/sched/core.c | 27 ++++++---------------------
 1 file changed, 6 insertions(+), 21 deletions(-)
kernel/sched/core.c +6 -21
index 4a4dbce..f2d16d1 100644
@@ -2346,10 +2346,10 @@ static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 	if (p->cpus_ptr != &p->cpus_mask)
 		return;
 
-	/*
-	 * Violates locking rules! See comment in __do_set_cpus_allowed().
-	 */
-	__do_set_cpus_allowed(p, &ac);
+	scoped_guard (task_rq_lock, p) {
+		update_rq_clock(scope.rq);
+		__do_set_cpus_allowed(p, &ac);
+	}
 }
 
 void ___migrate_enable(void)
@@ -2667,22 +2667,7 @@ __do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 	struct rq *rq = task_rq(p);
 	bool queued, running;
 
-	/*
-	 * This here violates the locking rules for affinity, since we're only
-	 * supposed to change these variables while holding both rq->lock and
-	 * p->pi_lock.
-	 *
-	 * HOWEVER, it magically works, because ttwu() is the only code that
-	 * accesses these variables under p->pi_lock and only does so after
-	 * smp_cond_load_acquire(&p->on_cpu, !VAL), and we're in __schedule()
-	 * before finish_task().
-	 *
-	 * XXX do further audits, this smells like something putrid.
-	 */
-	if (ctx->flags & SCA_MIGRATE_DISABLE)
-		WARN_ON_ONCE(!p->on_cpu);
-	else
-		lockdep_assert_held(&p->pi_lock);
+	lockdep_assert_held(&p->pi_lock);
 
 	queued = task_on_rq_queued(p);
 	running = task_current_donor(rq, p);
@@ -6781,6 +6766,7 @@ static void __sched notrace __schedule(int sched_mode)
 
 	local_irq_disable();
 	rcu_note_context_switch(preempt);
+	migrate_disable_switch(rq, prev);
 
 	/*
 	 * Make sure that signal_pending_state()->signal_pending() below
@@ -6887,7 +6873,6 @@ keep_resched:
 		 */
 		++*switch_count;
 
-		migrate_disable_switch(rq, prev);
 		psi_account_irqtime(rq, prev, next);
 		psi_sched_switch(prev, next, !task_on_rq_queued(prev) ||
 					     prev->se.sched_delayed);
author maintainer
#49 lore raw

PATCH 7/12 sched: Fix do_set_cpus_allowed() locking

In reply to Peter Zijlstra

All callers of do_set_cpus_allowed() only take p->pi_lock, which is not sufficient to actually change the cpumask. Again, this is mostly ok in these cases, but it results in unnecessarily complicated reasoning.

Furthermore, there is no reason what so ever to not just take all the required locks, so do just that.

---
 kernel/kthread.c     |   15 +++++----------
 kernel/sched/core.c  |   21 +++++++--------------
 kernel/sched/sched.h |    5 +++++
 3 files changed, 17 insertions(+), 24 deletions(-)
kernel/kthread.c +5 -10
@@ -593,18 +593,16 @@ EXPORT_SYMBOL(kthread_create_on_node);
 
 static void __kthread_bind_mask(struct task_struct *p, const struct cpumask *mask, unsigned int state)
 {
-	unsigned long flags;
-
 	if (!wait_task_inactive(p, state)) {
 		WARN_ON(1);
 		return;
 	}
 
+	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
+		do_set_cpus_allowed(p, mask);
+
 	/* It's safe because the task is inactive. */
-	raw_spin_lock_irqsave(&p->pi_lock, flags);
-	do_set_cpus_allowed(p, mask);
 	p->flags |= PF_NO_SETAFFINITY;
-	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
 }
 
 static void __kthread_bind(struct task_struct *p, unsigned int cpu, unsigned int state)
@@ -857,7 +855,6 @@ int kthread_affine_preferred(struct task
 {
 	struct kthread *kthread = to_kthread(p);
 	cpumask_var_t affinity;
-	unsigned long flags;
 	int ret = 0;
 
 	if (!wait_task_inactive(p, TASK_UNINTERRUPTIBLE) || kthread->started) {
@@ -882,10 +879,8 @@ int kthread_affine_preferred(struct task
 	list_add_tail(&kthread->hotplug_node, &kthreads_hotplug);
 	kthread_fetch_affinity(kthread, affinity);
 
-	/* It's safe because the task is inactive. */
-	raw_spin_lock_irqsave(&p->pi_lock, flags);
-	do_set_cpus_allowed(p, affinity);
-	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
+	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
+		do_set_cpus_allowed(p, affinity);
 
 	mutex_unlock(&kthreads_hotplug_lock);
 out:
kernel/sched/core.c +7 -14
@@ -2668,18 +2668,14 @@ __do_set_cpus_allowed(struct task_struct
 	bool queued, running;
 
 	lockdep_assert_held(&p->pi_lock);
+	lockdep_assert_rq_held(rq);
 
 	queued = task_on_rq_queued(p);
 	running = task_current_donor(rq, p);
 
-	if (queued) {
-		/*
-		 * Because __kthread_bind() calls this on blocked tasks without
-		 * holding rq->lock.
-		 */
-		lockdep_assert_rq_held(rq);
+	if (queued)
 		dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
-	}
+
 	if (running)
 		put_prev_task(rq, p);
 
@@ -2708,7 +2704,10 @@ void do_set_cpus_allowed(struct task_str
 		struct rcu_head rcu;
 	};
 
-	__do_set_cpus_allowed(p, &ac);
+	scoped_guard (__task_rq_lock, p) {
+		update_rq_clock(scope.rq);
+		__do_set_cpus_allowed(p, &ac);
+	}
 
 	/*
 	 * Because this is called with p->pi_lock held, it is not possible
@@ -3483,12 +3482,6 @@ static int select_fallback_rq(int cpu, s
 			}
 			fallthrough;
 		case possible:
-			/*
-			 * XXX When called from select_task_rq() we only
-			 * hold p->pi_lock and again violate locking order.
-			 *
-			 * More yuck to audit.
-			 */
 			do_set_cpus_allowed(p, task_cpu_fallback_mask(p));
 			state = fail;
 			break;
kernel/sched/sched.h +5 -0
@@ -1847,6 +1847,11 @@ DEFINE_LOCK_GUARD_1(task_rq_lock, struct
 		    task_rq_unlock(_T->rq, _T->lock, &_T->rf),
 		    struct rq *rq; struct rq_flags rf)
 
+DEFINE_LOCK_GUARD_1(__task_rq_lock, struct task_struct,
+		    _T->rq = __task_rq_lock(_T->lock, &_T->rf),
+		    __task_rq_unlock(_T->rq, &_T->rf),
+		    struct rq *rq; struct rq_flags rf)
+
 static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf)
 	__acquires(rq->lock)
 {
merge bot Applied
#50 lore raw

[tip: sched/core] sched: Fix do_set_cpus_allowed() locking

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     abfc01077df66593f128d966fdad1d042facc9ac
Gitweb:        https://git.kernel.org/tip/abfc01077df66593f128d966fdad1d042facc9ac
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 10 Sep 2025 09:51:06 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:52 +02:00

sched: Fix do_set_cpus_allowed() locking

All callers of do_set_cpus_allowed() only take p->pi_lock, which is not sufficient to actually change the cpumask. Again, this is mostly ok in these cases, but it results in unnecessarily complicated reasoning.

Furthermore, there is no reason what so ever to not just take all the required locks, so do just that.

---
 kernel/kthread.c     | 15 +++++----------
 kernel/sched/core.c  | 21 +++++++--------------
 kernel/sched/sched.h |  5 +++++
 3 files changed, 17 insertions(+), 24 deletions(-)
kernel/kthread.c +5 -10
index 31b072e..832bd2a 100644
@@ -593,18 +593,16 @@ EXPORT_SYMBOL(kthread_create_on_node);
 
 static void __kthread_bind_mask(struct task_struct *p, const struct cpumask *mask, unsigned int state)
 {
-	unsigned long flags;
-
 	if (!wait_task_inactive(p, state)) {
 		WARN_ON(1);
 		return;
 	}
 
+	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
+		do_set_cpus_allowed(p, mask);
+
 	/* It's safe because the task is inactive. */
-	raw_spin_lock_irqsave(&p->pi_lock, flags);
-	do_set_cpus_allowed(p, mask);
 	p->flags |= PF_NO_SETAFFINITY;
-	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
 }
 
 static void __kthread_bind(struct task_struct *p, unsigned int cpu, unsigned int state)
@@ -857,7 +855,6 @@ int kthread_affine_preferred(struct task_struct *p, const struct cpumask *mask)
 {
 	struct kthread *kthread = to_kthread(p);
 	cpumask_var_t affinity;
-	unsigned long flags;
 	int ret = 0;
 
 	if (!wait_task_inactive(p, TASK_UNINTERRUPTIBLE) || kthread->started) {
@@ -882,10 +879,8 @@ int kthread_affine_preferred(struct task_struct *p, const struct cpumask *mask)
 	list_add_tail(&kthread->hotplug_node, &kthreads_hotplug);
 	kthread_fetch_affinity(kthread, affinity);
 
-	/* It's safe because the task is inactive. */
-	raw_spin_lock_irqsave(&p->pi_lock, flags);
-	do_set_cpus_allowed(p, affinity);
-	raw_spin_unlock_irqrestore(&p->pi_lock, flags);
+	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
+		do_set_cpus_allowed(p, affinity);
 
 	mutex_unlock(&kthreads_hotplug_lock);
 out:
kernel/sched/core.c +7 -14
index f2d16d1..805e650 100644
@@ -2668,18 +2668,14 @@ __do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 	bool queued, running;
 
 	lockdep_assert_held(&p->pi_lock);
+	lockdep_assert_rq_held(rq);
 
 	queued = task_on_rq_queued(p);
 	running = task_current_donor(rq, p);
 
-	if (queued) {
-		/*
-		 * Because __kthread_bind() calls this on blocked tasks without
-		 * holding rq->lock.
-		 */
-		lockdep_assert_rq_held(rq);
+	if (queued)
 		dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
-	}
+
 	if (running)
 		put_prev_task(rq, p);
 
@@ -2708,7 +2704,10 @@ void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
 		struct rcu_head rcu;
 	};
 
-	__do_set_cpus_allowed(p, &ac);
+	scoped_guard (__task_rq_lock, p) {
+		update_rq_clock(scope.rq);
+		__do_set_cpus_allowed(p, &ac);
+	}
 
 	/*
 	 * Because this is called with p->pi_lock held, it is not possible
@@ -3483,12 +3482,6 @@ static int select_fallback_rq(int cpu, struct task_struct *p)
 			}
 			fallthrough;
 		case possible:
-			/*
-			 * XXX When called from select_task_rq() we only
-			 * hold p->pi_lock and again violate locking order.
-			 *
-			 * More yuck to audit.
-			 */
 			do_set_cpus_allowed(p, task_cpu_fallback_mask(p));
 			state = fail;
 			break;
kernel/sched/sched.h +5 -0
index bcde43d..b23ce9c 100644
@@ -1847,6 +1847,11 @@ DEFINE_LOCK_GUARD_1(task_rq_lock, struct task_struct,
 		    task_rq_unlock(_T->rq, _T->lock, &_T->rf),
 		    struct rq *rq; struct rq_flags rf)
 
+DEFINE_LOCK_GUARD_1(__task_rq_lock, struct task_struct,
+		    _T->rq = __task_rq_lock(_T->lock, &_T->rf),
+		    __task_rq_unlock(_T->rq, &_T->rf),
+		    struct rq *rq; struct rq_flags rf)
+
 static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf)
 	__acquires(rq->lock)
 {
#51 lore raw

[REGRESSION] Deadlock during CPU hotplug caused by abfc01077df6

In reply to Peter Zijlstra

We've identified a regression introduced by commit abfc01077df6 ("sched: Fix do_set_cpus_allowed() locking") that causes a reproducible deadlock during CPU hotplug testing on s390x.

While running the cpuhotplug02.sh test from LTP, which dynamically offlines and onlines CPUs, the system consistently enters a stalled state.

Observed behavior:
- migration/N attempts to migrate a task currently executing on another CPU.
- Concurrently, rcu_sched tries to complete an RCU grace period.
- Both threads are blocked on spinlocks (e.g., arch_spin_lock_wait), likely due to lock contention.
- Neither thread progresses; the grace period stalls.
- The kernel detects the stall and triggers a crash dump.

Sys info:

	RELEASE: 6.18.0-20251021.rc2.git224.fe45352cd106.63.fc42.s390x+next
	CPUS: 32
	TASKS: 623
	MEMORY: 16 GB

Crash log excerpt:

    [ 6146.992159] rcu: INFO: rcu_sched detected stalls on CPUs/tasks:
    [ 6146.992173] rcu:     1-...0: (5 ticks this GP) idle=cea4/1/0x4000000000000000 softirq=1055899/1055901 fqs=4769
    [ 6146.992236] rcu:     (detected by 3, t=240013 jiffies, g=2041729, q=14778 ncpus=32)
    [ 6146.992240] Task dump for CPU 1:
    [ 6146.992241] task:migration/1     state:R  running task     stack:0     pid:22    tgid:22    ppid:2      task_flags:0x4208040 flags:0x00000000
    [ 6146.992246] Stopper: __balance_push_cpu_stop+0x0/0x230 <- balance_push+0xea/0x170
    [ 6146.992254] Call Trace:
    [ 6146.992255]  [<000000009d9e2300>] 0x9d9e2300
    [ 6146.992280] rcu: rcu_sched kthread starved for 210010 jiffies! g2041729 f0x2 RCU_GP_DOING_FQS(6) ->state=0x0 ->cpu=23
    [ 6146.992287] rcu:     Unless rcu_sched kthread gets sufficient CPU time, OOM is now expected behavior.
    [ 6146.992288] rcu: RCU grace-period kthread stack dump:
    [ 6146.992289] task:rcu_sched       state:R  running task     stack:0     pid:16    tgid:16    ppid:2      task_flags:0x208040 flags:0x00000010
    [ 6146.992294] Call Trace:
    [ 6146.992295]  [<0700000000000001>] 0x700000000000001
    [ 6146.992298]  [<000002e1fb072998>] arch_spin_lock_wait+0xc8/0x110
    [ 6146.992303]  [<000002e1fa239d06>] raw_spin_rq_lock_nested+0x96/0xc0
    [ 6146.992306]  [<000002e1fa23bc90>] resched_cpu+0x50/0xc0
    [ 6146.992309]  [<000002e1fa29d646>] force_qs_rnp+0x306/0x3e0
    [ 6146.992314]  [<000002e1fa29ed30>] rcu_gp_fqs_loop+0x430/0x6e0
    [ 6146.992316]  [<000002e1fa2a1b0e>] rcu_gp_kthread+0x1ee/0x270
    [ 6146.992320]  [<000002e1fa228edc>] kthread+0x12c/0x250
    [ 6146.992323]  [<000002e1fa19ccfc>] __ret_from_fork+0x3c/0x150
    [ 6146.992328]  [<000002e1fb0800ba>] ret_from_fork+0xa/0x30
author maintainer
#52 lore raw

PATCH 8/12 sched: Rename do_set_cpus_allowed()

In reply to Peter Zijlstra

Hopefully saner naming.

---
 include/linux/sched.h  |    4 ++--
 kernel/cgroup/cpuset.c |    2 +-
 kernel/kthread.c       |    4 ++--
 kernel/sched/core.c    |   16 ++++++++--------
 kernel/sched/sched.h   |    2 +-
 5 files changed, 14 insertions(+), 14 deletions(-)
include/linux/sched.h +2 -2
@@ -1861,8 +1861,8 @@ extern int task_can_attach(struct task_s
 extern int dl_bw_alloc(int cpu, u64 dl_bw);
 extern void dl_bw_free(int cpu, u64 dl_bw);
 
-/* do_set_cpus_allowed() - consider using set_cpus_allowed_ptr() instead */
-extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask);
+/* set_cpus_allowed_force() - consider using set_cpus_allowed_ptr() instead */
+extern void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask);
 
 /**
  * set_cpus_allowed_ptr - set CPU affinity mask of a task
kernel/cgroup/cpuset.c +1 -1
@@ -4180,7 +4180,7 @@ bool cpuset_cpus_allowed_fallback(struct
 	rcu_read_lock();
 	cs_mask = task_cs(tsk)->cpus_allowed;
 	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask)) {
-		do_set_cpus_allowed(tsk, cs_mask);
+		set_cpus_allowed_force(tsk, cs_mask);
 		changed = true;
 	}
 	rcu_read_unlock();
kernel/kthread.c +2 -2
@@ -599,7 +599,7 @@ static void __kthread_bind_mask(struct t
 	}
 
 	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
-		do_set_cpus_allowed(p, mask);
+		set_cpus_allowed_force(p, mask);
 
 	/* It's safe because the task is inactive. */
 	p->flags |= PF_NO_SETAFFINITY;
@@ -880,7 +880,7 @@ int kthread_affine_preferred(struct task
 	kthread_fetch_affinity(kthread, affinity);
 
 	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
-		do_set_cpus_allowed(p, affinity);
+		set_cpus_allowed_force(p, affinity);
 
 	mutex_unlock(&kthreads_hotplug_lock);
 out:
kernel/sched/core.c +8 -8
@@ -2331,7 +2331,7 @@ unsigned long wait_task_inactive(struct
 }
 
 static void
-__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx);
+do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx);
 
 static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 {
@@ -2348,7 +2348,7 @@ static void migrate_disable_switch(struc
 
 	scoped_guard (task_rq_lock, p) {
 		update_rq_clock(scope.rq);
-		__do_set_cpus_allowed(p, &ac);
+		do_set_cpus_allowed(p, &ac);
 	}
 }
 
@@ -2662,7 +2662,7 @@ void set_cpus_allowed_common(struct task
 }
 
 static void
-__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
+do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
 	struct rq *rq = task_rq(p);
 	bool queued, running;
@@ -2692,7 +2692,7 @@ __do_set_cpus_allowed(struct task_struct
  * Used for kthread_bind() and select_fallback_rq(), in both cases the user
  * affinity (if any) should be destroyed too.
  */
-void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
+void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask)
 {
 	struct affinity_context ac = {
 		.new_mask  = new_mask,
@@ -2706,7 +2706,7 @@ void do_set_cpus_allowed(struct task_str
 
 	scoped_guard (__task_rq_lock, p) {
 		update_rq_clock(scope.rq);
-		__do_set_cpus_allowed(p, &ac);
+		do_set_cpus_allowed(p, &ac);
 	}
 
 	/*
@@ -2745,7 +2745,7 @@ int dup_user_cpus_ptr(struct task_struct
 	 * Use pi_lock to protect content of user_cpus_ptr
 	 *
 	 * Though unlikely, user_cpus_ptr can be reset to NULL by a concurrent
-	 * do_set_cpus_allowed().
+	 * set_cpus_allowed_force().
 	 */
 	raw_spin_lock_irqsave(&src->pi_lock, flags);
 	if (src->user_cpus_ptr) {
@@ -3073,7 +3073,7 @@ static int __set_cpus_allowed_ptr_locked
 		goto out;
 	}
 
-	__do_set_cpus_allowed(p, ctx);
+	do_set_cpus_allowed(p, ctx);
 
 	return affine_move_task(rq, p, rf, dest_cpu, ctx->flags);
 
@@ -3482,7 +3482,7 @@ static int select_fallback_rq(int cpu, s
 			}
 			fallthrough;
 		case possible:
-			do_set_cpus_allowed(p, task_cpu_fallback_mask(p));
+			set_cpus_allowed_force(p, task_cpu_fallback_mask(p));
 			state = fail;
 			break;
 		case fail:
kernel/sched/sched.h +1 -1
@@ -2617,7 +2617,7 @@ static inline bool task_allowed_on_cpu(s
 static inline cpumask_t *alloc_user_cpus_ptr(int node)
 {
 	/*
-	 * See do_set_cpus_allowed() above for the rcu_head usage.
+	 * See set_cpus_allowed_force() above for the rcu_head usage.
 	 */
 	int size = max_t(int, cpumask_size(), sizeof(struct rcu_head));
 
merge bot Applied
#53 lore raw

[tip: sched/core] sched: Rename do_set_cpus_allowed()

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     b079d93796528053cde322f2ca838c2d21c297e7
Gitweb:        https://git.kernel.org/tip/b079d93796528053cde322f2ca838c2d21c297e7
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Wed, 10 Sep 2025 10:08:05 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:53 +02:00

sched: Rename do_set_cpus_allowed()

Hopefully saner naming.

---
 include/linux/sched.h  |  4 ++--
 kernel/cgroup/cpuset.c |  2 +-
 kernel/kthread.c       |  4 ++--
 kernel/sched/core.c    | 16 ++++++++--------
 kernel/sched/sched.h   |  2 +-
 5 files changed, 14 insertions(+), 14 deletions(-)
include/linux/sched.h +2 -2
index cbb7340..77426c3 100644
@@ -1861,8 +1861,8 @@ extern int task_can_attach(struct task_struct *p);
 extern int dl_bw_alloc(int cpu, u64 dl_bw);
 extern void dl_bw_free(int cpu, u64 dl_bw);
 
-/* do_set_cpus_allowed() - consider using set_cpus_allowed_ptr() instead */
-extern void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask);
+/* set_cpus_allowed_force() - consider using set_cpus_allowed_ptr() instead */
+extern void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask);
 
 /**
  * set_cpus_allowed_ptr - set CPU affinity mask of a task
kernel/cgroup/cpuset.c +1 -1
index 52468d2..185e820 100644
@@ -4180,7 +4180,7 @@ bool cpuset_cpus_allowed_fallback(struct task_struct *tsk)
 	rcu_read_lock();
 	cs_mask = task_cs(tsk)->cpus_allowed;
 	if (is_in_v2_mode() && cpumask_subset(cs_mask, possible_mask)) {
-		do_set_cpus_allowed(tsk, cs_mask);
+		set_cpus_allowed_force(tsk, cs_mask);
 		changed = true;
 	}
 	rcu_read_unlock();
kernel/kthread.c +2 -2
index 832bd2a..99a3808 100644
@@ -599,7 +599,7 @@ static void __kthread_bind_mask(struct task_struct *p, const struct cpumask *mas
 	}
 
 	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
-		do_set_cpus_allowed(p, mask);
+		set_cpus_allowed_force(p, mask);
 
 	/* It's safe because the task is inactive. */
 	p->flags |= PF_NO_SETAFFINITY;
@@ -880,7 +880,7 @@ int kthread_affine_preferred(struct task_struct *p, const struct cpumask *mask)
 	kthread_fetch_affinity(kthread, affinity);
 
 	scoped_guard (raw_spinlock_irqsave, &p->pi_lock)
-		do_set_cpus_allowed(p, affinity);
+		set_cpus_allowed_force(p, affinity);
 
 	mutex_unlock(&kthreads_hotplug_lock);
 out:
kernel/sched/core.c +8 -8
index 805e650..638bffd 100644
@@ -2331,7 +2331,7 @@ unsigned long wait_task_inactive(struct task_struct *p, unsigned int match_state
 }
 
 static void
-__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx);
+do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx);
 
 static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 {
@@ -2348,7 +2348,7 @@ static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 
 	scoped_guard (task_rq_lock, p) {
 		update_rq_clock(scope.rq);
-		__do_set_cpus_allowed(p, &ac);
+		do_set_cpus_allowed(p, &ac);
 	}
 }
 
@@ -2662,7 +2662,7 @@ void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx
 }
 
 static void
-__do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
+do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
 	struct rq *rq = task_rq(p);
 	bool queued, running;
@@ -2692,7 +2692,7 @@ __do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
  * Used for kthread_bind() and select_fallback_rq(), in both cases the user
  * affinity (if any) should be destroyed too.
  */
-void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
+void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mask)
 {
 	struct affinity_context ac = {
 		.new_mask  = new_mask,
@@ -2706,7 +2706,7 @@ void do_set_cpus_allowed(struct task_struct *p, const struct cpumask *new_mask)
 
 	scoped_guard (__task_rq_lock, p) {
 		update_rq_clock(scope.rq);
-		__do_set_cpus_allowed(p, &ac);
+		do_set_cpus_allowed(p, &ac);
 	}
 
 	/*
@@ -2745,7 +2745,7 @@ int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src,
 	 * Use pi_lock to protect content of user_cpus_ptr
 	 *
 	 * Though unlikely, user_cpus_ptr can be reset to NULL by a concurrent
-	 * do_set_cpus_allowed().
+	 * set_cpus_allowed_force().
 	 */
 	raw_spin_lock_irqsave(&src->pi_lock, flags);
 	if (src->user_cpus_ptr) {
@@ -3073,7 +3073,7 @@ static int __set_cpus_allowed_ptr_locked(struct task_struct *p,
 		goto out;
 	}
 
-	__do_set_cpus_allowed(p, ctx);
+	do_set_cpus_allowed(p, ctx);
 
 	return affine_move_task(rq, p, rf, dest_cpu, ctx->flags);
 
@@ -3482,7 +3482,7 @@ static int select_fallback_rq(int cpu, struct task_struct *p)
 			}
 			fallthrough;
 		case possible:
-			do_set_cpus_allowed(p, task_cpu_fallback_mask(p));
+			set_cpus_allowed_force(p, task_cpu_fallback_mask(p));
 			state = fail;
 			break;
 		case fail:
kernel/sched/sched.h +1 -1
index b23ce9c..ea2ea8f 100644
@@ -2617,7 +2617,7 @@ static inline bool task_allowed_on_cpu(struct task_struct *p, int cpu)
 static inline cpumask_t *alloc_user_cpus_ptr(int node)
 {
 	/*
-	 * See do_set_cpus_allowed() above for the rcu_head usage.
+	 * See set_cpus_allowed_force() above for the rcu_head usage.
 	 */
 	int size = max_t(int, cpumask_size(), sizeof(struct rcu_head));
 
author maintainer
#54 lore raw

PATCH 9/12 sched: Make __do_set_cpus_allowed() use the sched_change pattern

In reply to Peter Zijlstra

Now that do_set_cpus_allowed() holds all the regular locks, convert it to use the sched_change pattern helper.

---
 kernel/sched/core.c |   26 +++++---------------------
 1 file changed, 5 insertions(+), 21 deletions(-)
kernel/sched/core.c +5 -21
@@ -2664,28 +2664,12 @@ void set_cpus_allowed_common(struct task
 static void
 do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
-	struct rq *rq = task_rq(p);
-	bool queued, running;
+	u32 flags = DEQUEUE_SAVE | DEQUEUE_NOCLOCK;
 
-	lockdep_assert_held(&p->pi_lock);
-	lockdep_assert_rq_held(rq);
-
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);
-
-	if (queued)
-		dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
-
-	if (running)
-		put_prev_task(rq, p);
-
-	p->sched_class->set_cpus_allowed(p, ctx);
-	mm_set_cpus_allowed(p->mm, ctx->new_mask);
-
-	if (queued)
-		enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);
-	if (running)
-		set_next_task(rq, p);
+	scoped_guard (sched_change, p, flags) {
+		p->sched_class->set_cpus_allowed(p, ctx);
+		mm_set_cpus_allowed(p->mm, ctx->new_mask);
+	}
 }
 
 /*
merge bot Applied
#55 lore raw

[tip: sched/core] sched: Make __do_set_cpus_allowed() use the sched_change pattern

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     650952d3fb3889b04cbda722351b5d6090a1c10b
Gitweb:        https://git.kernel.org/tip/650952d3fb3889b04cbda722351b5d6090a1c10b
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Tue, 09 Sep 2025 13:16:23 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:53 +02:00

sched: Make __do_set_cpus_allowed() use the sched_change pattern

Now that do_set_cpus_allowed() holds all the regular locks, convert it to use the sched_change pattern helper.

---
 kernel/sched/core.c | 26 +++++---------------------
 1 file changed, 5 insertions(+), 21 deletions(-)
kernel/sched/core.c +5 -21
index 638bffd..e932439 100644
@@ -2664,28 +2664,12 @@ void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx
 static void
 do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
-	struct rq *rq = task_rq(p);
-	bool queued, running;
-
-	lockdep_assert_held(&p->pi_lock);
-	lockdep_assert_rq_held(rq);
-
-	queued = task_on_rq_queued(p);
-	running = task_current_donor(rq, p);
-
-	if (queued)
-		dequeue_task(rq, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK);
+	u32 flags = DEQUEUE_SAVE | DEQUEUE_NOCLOCK;
 
-	if (running)
-		put_prev_task(rq, p);
-
-	p->sched_class->set_cpus_allowed(p, ctx);
-	mm_set_cpus_allowed(p->mm, ctx->new_mask);
-
-	if (queued)
-		enqueue_task(rq, p, ENQUEUE_RESTORE | ENQUEUE_NOCLOCK);
-	if (running)
-		set_next_task(rq, p);
+	scoped_guard (sched_change, p, flags) {
+		p->sched_class->set_cpus_allowed(p, ctx);
+		mm_set_cpus_allowed(p->mm, ctx->new_mask);
+	}
 }
 
 /*
author maintainer
#56 lore raw

PATCH 10/12 sched: Add locking comments to sched_class methods

In reply to Peter Zijlstra

'Document' the locking context the various sched_class methods are called under.

---
 kernel/sched/core.c  |    6 +-
 kernel/sched/sched.h |  106 ++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 103 insertions(+), 9 deletions(-)
kernel/sched/core.c +3 -3
@@ -583,8 +583,8 @@ EXPORT_SYMBOL(__trace_set_current_state)
  *
  * p->on_rq <- { 0, 1 = TASK_ON_RQ_QUEUED, 2 = TASK_ON_RQ_MIGRATING }:
  *
- *   is set by activate_task() and cleared by deactivate_task(), under
- *   rq->lock. Non-zero indicates the task is runnable, the special
+ *   is set by activate_task() and cleared by deactivate_task()/block_task(),
+ *   under rq->lock. Non-zero indicates the task is runnable, the special
  *   ON_RQ_MIGRATING state is used for migration without holding both
  *   rq->locks. It indicates task_cpu() is not stable, see task_rq_lock().
  *
@@ -4162,7 +4162,7 @@ int try_to_wake_up(struct task_struct *p
 		 * __schedule().  See the comment for smp_mb__after_spinlock().
 		 *
 		 * Form a control-dep-acquire with p->on_rq == 0 above, to ensure
-		 * schedule()'s deactivate_task() has 'happened' and p will no longer
+		 * schedule()'s block_task() has 'happened' and p will no longer
 		 * care about it's own p->state. See the comment in __schedule().
 		 */
 		smp_acquire__after_ctrl_dep();
kernel/sched/sched.h +100 -6

176 lines not on this page · raw message

maintainer
#57 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Hi Peter,

Quoted 5 lines · Peter Zijlstra

'Document' the locking context the various sched_class methods are called under.

---

...

Quoted 14 lines
kernel/sched/sched.h +1 -2
@@ -2345,8 +2345,7 @@ extern const u32		sched_prio_to_wmult[40
 /*
  * {de,en}queue flags:
  *
- * DEQUEUE_SLEEP  - task is no longer runnable
- * ENQUEUE_WAKEUP - task just became runnable
+ * SLEEP/WAKEUP - task is no-longer/just-became runnable
  *
  * SAVE/RESTORE - an otherwise spurious dequeue/enqueue, done to ensure tasks
  *                are in a known state which allows modification. Such pairs
@@ -2359,6 +2358,11 @@ extern const u32		sched_prio_to_wmult[40
  *

kernel/sched/sched.h extern const u32 sched_prio_to_wmult[40 · line 2368

Quoted 10 lines
kernel/sched/sched.h +5 -0
  * MIGRATION - p->on_rq == TASK_ON_RQ_MIGRATING (used for DEADLINE)
  *
+ * DELAYED - de/re-queue a sched_delayed task
+ *
+ * CLASS - going to update p->sched_class; makes sched_change call the
+ *         various switch methods.
+ *
  * ENQUEUE_HEAD      - place at front of runqueue (tail if not specified)
  * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline)
  * ENQUEUE_MIGRATED  - the task was migrated during wakeup

Not for this patch, but I wondered if, while we are at it, we wanted to complete documentation of these flags. My new AI friend is suggesting the following, is it very much garbage? :)

Thanks,
Juri

---

From: Claude <[email protected]>
Date: Mon, 7 Oct 2025 12:44:13 +0200
Subject: sched: Document remaining DEQUEUE/ENQUEUE flags

Complete the flag documentation by adding descriptions for the three previously undocumented flags: DEQUEUE_SPECIAL, DEQUEUE_THROTTLE, and ENQUEUE_INITIAL.

DEQUEUE_SPECIAL is used when dequeuing tasks in special states (stopped, traced, parked, dead, or frozen) that don't use the normal wait-loop pattern and must not use delayed dequeue.

DEQUEUE_THROTTLE is used when removing tasks from the runqueue due to CFS bandwidth throttling, preventing delayed dequeue to ensure proper throttling behavior.

ENQUEUE_INITIAL is used when enqueueing newly created tasks in wake_up_new_task(), allowing the fair scheduler to give them preferential initial placement (half vslice when PLACE_DEADLINE_INITIAL is enabled).

Not-so-sure-yet: Juri Lelli <[email protected]>

---
 kernel/sched/sched.h | 10 ++++++++++
 1 file changed, 10 insertions(+)
kernel/sched/sched.h +10 -0
index 4c222fa8f908..1a2b3c8d9e4f 100644
@@ -2364,10 +2364,20 @@ extern const u32		sched_prio_to_wmult[40];
  * CLASS - going to update p->sched_class; makes sched_change call the
  *         various switch methods.
  *
+ * DEQUEUE_SPECIAL - task is in a special state (STOPPED, TRACED, PARKED,
+ *                   DEAD, FROZEN) that doesn't use the normal wait-loop;
+ *                   disables delayed dequeue.
+ *
+ * DEQUEUE_THROTTLE - dequeuing due to CFS bandwidth throttling; disables
+ *                    delayed dequeue to ensure proper throttling.
+ *
  * ENQUEUE_HEAD      - place at front of runqueue (tail if not specified)
  * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline)
  * ENQUEUE_MIGRATED  - the task was migrated during wakeup
+ * ENQUEUE_INITIAL   - enqueuing a newly created task in wake_up_new_task();
+ *                     fair scheduler may give preferential initial placement
+ *                     (e.g., half vslice with PLACE_DEADLINE_INITIAL).
  * ENQUEUE_RQ_SELECTED - ->select_task_rq() was called
  *
  */
 
author maintainer
#58 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Quoted 3 lines · Juri Lelli

Not for this patch, but I wondered if, while we are at it, we wanted to complete documentation of these flags. My new AI friend is suggesting the following, is it very much garbage? :)

Heh; its not terrible. I've been playing with local LLMs, but mostly I've found they struggle with getting enough context to not be utterly demented. And when you up the context window, they get unusable slow :/

Setting up and configuring the whole pile of subtly interlocking stacks of software to get anything useful out of this stuff is non-trivial (it reminds me of the sendmail m4 days).

Quoted 18 lines

---

From: Claude <[email protected]>
Date: Mon, 7 Oct 2025 12:44:13 +0200
Subject: sched: Document remaining DEQUEUE/ENQUEUE flags

Complete the flag documentation by adding descriptions for the three previously undocumented flags: DEQUEUE_SPECIAL, DEQUEUE_THROTTLE, and ENQUEUE_INITIAL.

DEQUEUE_SPECIAL is used when dequeuing tasks in special states (stopped, traced, parked, dead, or frozen) that don't use the normal wait-loop pattern and must not use delayed dequeue.

DEQUEUE_THROTTLE is used when removing tasks from the runqueue due to CFS bandwidth throttling, preventing delayed dequeue to ensure proper throttling behavior.

Quoted 6 lines

ENQUEUE_INITIAL is used when enqueueing newly created tasks in wake_up_new_task(), allowing the fair scheduler to give them preferential initial placement (half vslice when PLACE_DEADLINE_INITIAL is enabled).

Not-so-sure-yet: Juri Lelli <[email protected]>

Is this the generally acceptable form of attribution for these things? I'm not sure what the official guidance is on using these AI tools.

Greg, you have any insights here?

Quoted 29 lines
---
 kernel/sched/sched.h | 10 ++++++++++
 1 file changed, 10 insertions(+)
kernel/sched/sched.h +10 -0
index 4c222fa8f908..1a2b3c8d9e4f 100644
@@ -2364,10 +2364,20 @@ extern const u32		sched_prio_to_wmult[40];
  * CLASS - going to update p->sched_class; makes sched_change call the
  *         various switch methods.
  *
+ * DEQUEUE_SPECIAL - task is in a special state (STOPPED, TRACED, PARKED,
+ *                   DEAD, FROZEN) that doesn't use the normal wait-loop;
+ *                   disables delayed dequeue.
+ *
+ * DEQUEUE_THROTTLE - dequeuing due to CFS bandwidth throttling; disables
+ *                    delayed dequeue to ensure proper throttling.
+ *
  * ENQUEUE_HEAD      - place at front of runqueue (tail if not specified)
  * ENQUEUE_REPLENISH - CBS (replenish runtime and postpone deadline)
  * ENQUEUE_MIGRATED  - the task was migrated during wakeup
+ * ENQUEUE_INITIAL   - enqueuing a newly created task in wake_up_new_task();
+ *                     fair scheduler may give preferential initial placement
+ *                     (e.g., half vslice with PLACE_DEADLINE_INITIAL).
  * ENQUEUE_RQ_SELECTED - ->select_task_rq() was called
  *
  */
 
#59 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Quoted 36 lines · Peter Zijlstra
Quoted 3 lines · Juri Lelli

Not for this patch, but I wondered if, while we are at it, we wanted to complete documentation of these flags. My new AI friend is suggesting the following, is it very much garbage? :)

Heh; its not terrible. I've been playing with local LLMs, but mostly I've found they struggle with getting enough context to not be utterly demented. And when you up the context window, they get unusable slow :/

Setting up and configuring the whole pile of subtly interlocking stacks of software to get anything useful out of this stuff is non-trivial (it reminds me of the sendmail m4 days).

Quoted 22 lines

---

From: Claude <[email protected]>
Date: Mon, 7 Oct 2025 12:44:13 +0200
Subject: sched: Document remaining DEQUEUE/ENQUEUE flags

Complete the flag documentation by adding descriptions for the three previously undocumented flags: DEQUEUE_SPECIAL, DEQUEUE_THROTTLE, and ENQUEUE_INITIAL.

DEQUEUE_SPECIAL is used when dequeuing tasks in special states (stopped, traced, parked, dead, or frozen) that don't use the normal wait-loop pattern and must not use delayed dequeue.

DEQUEUE_THROTTLE is used when removing tasks from the runqueue due to CFS bandwidth throttling, preventing delayed dequeue to ensure proper throttling behavior.

ENQUEUE_INITIAL is used when enqueueing newly created tasks in wake_up_new_task(), allowing the fair scheduler to give them preferential initial placement (half vslice when PLACE_DEADLINE_INITIAL is enabled).

Quoted 7 lines
Quoted 2 lines

Not-so-sure-yet: Juri Lelli <[email protected]>

Is this the generally acceptable form of attribution for these things? I'm not sure what the official guidance is on using these AI tools.

Greg, you have any insights here?

First off, Claude can NOT sign off on anything, so that's a non-starter. All Red Hat people should know that :)

Otherwise, there is a draft of something that was going to address stuff like this floating around by Dave Hansen, I'll go poke him to see what the status of that is.

thanks,

greg k-h

maintainer
#60 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Quoted 40 lines · Greg Kroah-Hartman
Quoted 39 lines · Peter Zijlstra
Quoted 3 lines · Juri Lelli

Not for this patch, but I wondered if, while we are at it, we wanted to complete documentation of these flags. My new AI friend is suggesting the following, is it very much garbage? :)

Heh; its not terrible. I've been playing with local LLMs, but mostly I've found they struggle with getting enough context to not be utterly demented. And when you up the context window, they get unusable slow :/

Setting up and configuring the whole pile of subtly interlocking stacks of software to get anything useful out of this stuff is non-trivial (it reminds me of the sendmail m4 days).

Quoted 24 lines

---

From: Claude <[email protected]>
Date: Mon, 7 Oct 2025 12:44:13 +0200
Subject: sched: Document remaining DEQUEUE/ENQUEUE flags

Complete the flag documentation by adding descriptions for the three previously undocumented flags: DEQUEUE_SPECIAL, DEQUEUE_THROTTLE, and ENQUEUE_INITIAL.

DEQUEUE_SPECIAL is used when dequeuing tasks in special states (stopped, traced, parked, dead, or frozen) that don't use the normal wait-loop pattern and must not use delayed dequeue.

DEQUEUE_THROTTLE is used when removing tasks from the runqueue due to CFS bandwidth throttling, preventing delayed dequeue to ensure proper throttling behavior.

ENQUEUE_INITIAL is used when enqueueing newly created tasks in wake_up_new_task(), allowing the fair scheduler to give them preferential initial placement (half vslice when PLACE_DEADLINE_INITIAL is enabled).

Not-so-sure-yet: Juri Lelli <[email protected]>

Quoted 7 lines
Quoted 4 lines

Is this the generally acceptable form of attribution for these things? I'm not sure what the official guidance is on using these AI tools.

Greg, you have any insights here?

First off, Claude can NOT sign off on anything, so that's a non-starter. All Red Hat people should know that :)

Yep, knew that. But I felt guilty nontheless as I didn't touch the change at all. Current SoB was kind of a (silly) joke. :)

Quoted 3 lines

Otherwise, there is a draft of something that was going to address stuff like this floating around by Dave Hansen, I'll go poke him to see what the status of that is.

I believe it was suggested something like Co-developed-by: <model> and then Signed-off-by: <human>, but indeed curious to know how that discussion ended.

Thanks!
Juri

#61 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Quoted 52 lines · Juri Lelli
Quoted 40 lines · Greg Kroah-Hartman
Quoted 39 lines · Peter Zijlstra
Quoted 3 lines · Juri Lelli

Not for this patch, but I wondered if, while we are at it, we wanted to complete documentation of these flags. My new AI friend is suggesting the following, is it very much garbage? :)

Heh; its not terrible. I've been playing with local LLMs, but mostly I've found they struggle with getting enough context to not be utterly demented. And when you up the context window, they get unusable slow :/

Setting up and configuring the whole pile of subtly interlocking stacks of software to get anything useful out of this stuff is non-trivial (it reminds me of the sendmail m4 days).

Quoted 24 lines

---

From: Claude <[email protected]>
Date: Mon, 7 Oct 2025 12:44:13 +0200
Subject: sched: Document remaining DEQUEUE/ENQUEUE flags

Complete the flag documentation by adding descriptions for the three previously undocumented flags: DEQUEUE_SPECIAL, DEQUEUE_THROTTLE, and ENQUEUE_INITIAL.

DEQUEUE_SPECIAL is used when dequeuing tasks in special states (stopped, traced, parked, dead, or frozen) that don't use the normal wait-loop pattern and must not use delayed dequeue.

DEQUEUE_THROTTLE is used when removing tasks from the runqueue due to CFS bandwidth throttling, preventing delayed dequeue to ensure proper throttling behavior.

ENQUEUE_INITIAL is used when enqueueing newly created tasks in wake_up_new_task(), allowing the fair scheduler to give them preferential initial placement (half vslice when PLACE_DEADLINE_INITIAL is enabled).

Not-so-sure-yet: Juri Lelli <[email protected]>

Quoted 7 lines
Quoted 4 lines

Is this the generally acceptable form of attribution for these things? I'm not sure what the official guidance is on using these AI tools.

Greg, you have any insights here?

First off, Claude can NOT sign off on anything, so that's a non-starter. All Red Hat people should know that :)

Yep, knew that. But I felt guilty nontheless as I didn't touch the change at all. Current SoB was kind of a (silly) joke. :)

Quoted 7 lines
Quoted 3 lines

Otherwise, there is a draft of something that was going to address stuff like this floating around by Dave Hansen, I'll go poke him to see what the status of that is.

I believe it was suggested something like Co-developed-by: <model> and then Signed-off-by: <human>, but indeed curious to know how that discussion ended.

The general answer is "you better know the copyright ownership information of the output of the tool you use" before you do anything with any of these tools. Be careful about this, because adding your signed-off-by to a patch like makes it your responsibility :)

After that, treat it like any other tool that you use to generate a patch, document what you used and why/how, and you should be fine. You have to do this today if you were to use any type of tool, so in that way, "AI" is no different, with the exception of the ownership of the output result (again, consult the terms of the tool used.)

Hopefully documentation updates to our process documents will reflect this in the near future.

thanks,

greg k-h

#62 lore raw

Re: [PATCH 10/12] sched: Add locking comments to sched_class methods

Quoted 4 lines · On Wed, 8 Oct 2025 12:06:56 +0200 Greg Kroah-Hartman…

The general answer is "you better know the copyright ownership information of the output of the tool you use" before you do anything with any of these tools. Be careful about this, because adding your signed-off-by to a patch like makes it your responsibility :)

And there are a lot of copyright battles going on in courts wrt AI right now. It's best to see how that plays out too.

Quoted 9 lines

After that, treat it like any other tool that you use to generate a patch, document what you used and why/how, and you should be fine. You have to do this today if you were to use any type of tool, so in that way, "AI" is no different, with the exception of the ownership of the output result (again, consult the terms of the tool used.)

Hopefully documentation updates to our process documents will reflect this in the near future.

Yeah, I need to help Dave on that too.

Thanks for the reminder,

-- Steve

merge bot Applied
#63 lore raw

[tip: sched/core] sched: Add locking comments to sched_class methods

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     46a177fb01e52ec0e3f9eab9b217a0f7c8909eeb
Gitweb:        https://git.kernel.org/tip/46a177fb01e52ec0e3f9eab9b217a0f7c8909eeb
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Tue, 09 Sep 2025 11:58:02 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:53 +02:00

sched: Add locking comments to sched_class methods

'Document' the locking context the various sched_class methods are called under.

---
 kernel/sched/core.c  |   6 +-
 kernel/sched/sched.h | 108 +++++++++++++++++++++++++++++++++++++++---
 2 files changed, 105 insertions(+), 9 deletions(-)
kernel/sched/core.c +3 -3
index e932439..8c55740 100644
@@ -583,8 +583,8 @@ EXPORT_SYMBOL(__trace_set_current_state);
  *
  * p->on_rq <- { 0, 1 = TASK_ON_RQ_QUEUED, 2 = TASK_ON_RQ_MIGRATING }:
  *
- *   is set by activate_task() and cleared by deactivate_task(), under
- *   rq->lock. Non-zero indicates the task is runnable, the special
+ *   is set by activate_task() and cleared by deactivate_task()/block_task(),
+ *   under rq->lock. Non-zero indicates the task is runnable, the special
  *   ON_RQ_MIGRATING state is used for migration without holding both
  *   rq->locks. It indicates task_cpu() is not stable, see task_rq_lock().
  *
@@ -4162,7 +4162,7 @@ int try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
 		 * __schedule().  See the comment for smp_mb__after_spinlock().
 		 *
 		 * Form a control-dep-acquire with p->on_rq == 0 above, to ensure
-		 * schedule()'s deactivate_task() has 'happened' and p will no longer
+		 * schedule()'s block_task() has 'happened' and p will no longer
 		 * care about it's own p->state. See the comment in __schedule().
 		 */
 		smp_acquire__after_ctrl_dep();
kernel/sched/sched.h +102 -6

183 lines not on this page · raw message

author maintainer
#64 lore raw

PATCH 11/12 sched: Match __task_rq_{,un}lock()

In reply to Peter Zijlstra

In preparation to adding more rules to __task_rq_lock(), such that __task_rq_unlock() will no longer be requivalent to rq_unlock(), make sure every __task_rq_lock() is matched by a __task_rq_unlock() and vice-versa.

---
 kernel/sched/core.c  |   13 ++++++++-----
 kernel/sched/sched.h |    8 ++++----
 kernel/sched/stats.h |    2 +-
 3 files changed, 13 insertions(+), 10 deletions(-)
kernel/sched/core.c +8 -5
@@ -2582,7 +2582,8 @@ static int migration_cpu_stop(void *data
 		 */
 		WARN_ON_ONCE(!pending->stop_pending);
 		preempt_disable();
-		task_rq_unlock(rq, p, &rf);
+		rq_unlock(rq, &rf);
+		raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 		stop_one_cpu_nowait(task_cpu(p), migration_cpu_stop,
 				    &pending->arg, &pending->stop_work);
 		preempt_enable();
@@ -2591,7 +2592,8 @@ static int migration_cpu_stop(void *data
 out:
 	if (pending)
 		pending->stop_pending = false;
-	task_rq_unlock(rq, p, &rf);
+	rq_unlock(rq, &rf);
+	raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 
 	if (complete)
 		complete_all(&pending->done);
@@ -3708,7 +3710,7 @@ static int ttwu_runnable(struct task_str
 		ttwu_do_wakeup(p);
 		ret = 1;
 	}
-	__task_rq_unlock(rq, &rf);
+	__task_rq_unlock(rq, p, &rf);
 
 	return ret;
 }
@@ -4301,7 +4303,7 @@ int task_call_func(struct task_struct *p
 	ret = func(p, arg);
 
 	if (rq)
-		rq_unlock(rq, &rf);
+		__task_rq_unlock(rq, p, &rf);
 
 	raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 	return ret;
@@ -7364,7 +7366,8 @@ void rt_mutex_setprio(struct task_struct
 
 	rq_unpin_lock(rq, &rf);
 	__balance_callbacks(rq);
-	raw_spin_rq_unlock(rq);
+	rq_repin_lock(rq, &rf);
+	__task_rq_unlock(rq, p, &rf);
 
 	preempt_enable();
 }
kernel/sched/sched.h +4 -4
@@ -1825,7 +1825,8 @@ struct rq *task_rq_lock(struct task_stru
 	__acquires(p->pi_lock)
 	__acquires(rq->lock);
 
-static inline void __task_rq_unlock(struct rq *rq, struct rq_flags *rf)
+static inline void
+__task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf)
 	__releases(rq->lock)
 {
 	rq_unpin_lock(rq, rf);
@@ -1837,8 +1838,7 @@ task_rq_unlock(struct rq *rq, struct tas
 	__releases(rq->lock)
 	__releases(p->pi_lock)
 {
-	rq_unpin_lock(rq, rf);
-	raw_spin_rq_unlock(rq);
+	__task_rq_unlock(rq, p, rf);
 	raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags);
 }
 
@@ -1849,7 +1849,7 @@ DEFINE_LOCK_GUARD_1(task_rq_lock, struct
 
 DEFINE_LOCK_GUARD_1(__task_rq_lock, struct task_struct,
 		    _T->rq = __task_rq_lock(_T->lock, &_T->rf),
-		    __task_rq_unlock(_T->rq, &_T->rf),
+		    __task_rq_unlock(_T->rq, _T->lock, &_T->rf),
 		    struct rq *rq; struct rq_flags rf)
 
 static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf)
kernel/sched/stats.h +1 -1
@@ -206,7 +206,7 @@ static inline void psi_ttwu_dequeue(stru
 
 		rq = __task_rq_lock(p, &rf);
 		psi_task_change(p, p->psi_flags, 0);
-		__task_rq_unlock(rq, &rf);
+		__task_rq_unlock(rq, p, &rf);
 	}
 }
 
maintainer
#65 lore raw

Re: [PATCH 11/12] sched: Match __task_rq_{,un}lock()

Quoted 2 lines · Peter Zijlstra

In preparation to adding more rules to __task_rq_lock(), such that __task_rq_unlock() will no longer be requivalent to rq_unlock(),

                                       ^
                                       typo

Thanks.

Signature · 1 line
tejun
merge bot Applied
#66 lore raw

[tip: sched/core] sched: Match __task_rq_{,un}lock()

In reply to Peter Zijlstra
The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     5892cbd85dbf9059b8a3a7dd8ab64c0fce671029
Gitweb:        https://git.kernel.org/tip/5892cbd85dbf9059b8a3a7dd8ab64c0fce671029
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 25 Sep 2025 11:26:22 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:54 +02:00

sched: Match __task_rq_{,un}lock()

In preparation to adding more rules to __task_rq_lock(), such that __task_rq_unlock() will no longer be equivalent to rq_unlock(), make sure every __task_rq_lock() is matched by a __task_rq_unlock() and vice-versa.

---
 kernel/sched/core.c  | 13 ++++++++-----
 kernel/sched/sched.h |  8 ++++----
 kernel/sched/stats.h |  2 +-
 3 files changed, 13 insertions(+), 10 deletions(-)
kernel/sched/core.c +8 -5
index 8c55740..e715147 100644
@@ -2582,7 +2582,8 @@ static int migration_cpu_stop(void *data)
 		 */
 		WARN_ON_ONCE(!pending->stop_pending);
 		preempt_disable();
-		task_rq_unlock(rq, p, &rf);
+		rq_unlock(rq, &rf);
+		raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 		stop_one_cpu_nowait(task_cpu(p), migration_cpu_stop,
 				    &pending->arg, &pending->stop_work);
 		preempt_enable();
@@ -2591,7 +2592,8 @@ static int migration_cpu_stop(void *data)
 out:
 	if (pending)
 		pending->stop_pending = false;
-	task_rq_unlock(rq, p, &rf);
+	rq_unlock(rq, &rf);
+	raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 
 	if (complete)
 		complete_all(&pending->done);
@@ -3708,7 +3710,7 @@ static int ttwu_runnable(struct task_struct *p, int wake_flags)
 		ttwu_do_wakeup(p);
 		ret = 1;
 	}
-	__task_rq_unlock(rq, &rf);
+	__task_rq_unlock(rq, p, &rf);
 
 	return ret;
 }
@@ -4301,7 +4303,7 @@ int task_call_func(struct task_struct *p, task_call_f func, void *arg)
 	ret = func(p, arg);
 
 	if (rq)
-		rq_unlock(rq, &rf);
+		__task_rq_unlock(rq, p, &rf);
 
 	raw_spin_unlock_irqrestore(&p->pi_lock, rf.flags);
 	return ret;
@@ -7362,7 +7364,8 @@ out_unlock:
 
 	rq_unpin_lock(rq, &rf);
 	__balance_callbacks(rq);
-	raw_spin_rq_unlock(rq);
+	rq_repin_lock(rq, &rf);
+	__task_rq_unlock(rq, p, &rf);
 
 	preempt_enable();
 }
kernel/sched/sched.h +4 -4
index 3462145..e3d2710 100644
@@ -1825,7 +1825,8 @@ struct rq *task_rq_lock(struct task_struct *p, struct rq_flags *rf)
 	__acquires(p->pi_lock)
 	__acquires(rq->lock);
 
-static inline void __task_rq_unlock(struct rq *rq, struct rq_flags *rf)
+static inline void
+__task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf)
 	__releases(rq->lock)
 {
 	rq_unpin_lock(rq, rf);
@@ -1837,8 +1838,7 @@ task_rq_unlock(struct rq *rq, struct task_struct *p, struct rq_flags *rf)
 	__releases(rq->lock)
 	__releases(p->pi_lock)
 {
-	rq_unpin_lock(rq, rf);
-	raw_spin_rq_unlock(rq);
+	__task_rq_unlock(rq, p, rf);
 	raw_spin_unlock_irqrestore(&p->pi_lock, rf->flags);
 }
 
@@ -1849,7 +1849,7 @@ DEFINE_LOCK_GUARD_1(task_rq_lock, struct task_struct,
 
 DEFINE_LOCK_GUARD_1(__task_rq_lock, struct task_struct,
 		    _T->rq = __task_rq_lock(_T->lock, &_T->rf),
-		    __task_rq_unlock(_T->rq, &_T->rf),
+		    __task_rq_unlock(_T->rq, _T->lock, &_T->rf),
 		    struct rq *rq; struct rq_flags rf)
 
 static inline void rq_lock_irqsave(struct rq *rq, struct rq_flags *rf)
kernel/sched/stats.h +1 -1
index 26f3fd4..cbf7206 100644
@@ -206,7 +206,7 @@ static inline void psi_ttwu_dequeue(struct task_struct *p)
 
 		rq = __task_rq_lock(p, &rf);
 		psi_task_change(p, p->psi_flags, 0);
-		__task_rq_unlock(rq, &rf);
+		__task_rq_unlock(rq, p, &rf);
 	}
 }
 
author maintainer
#67 lore raw

PATCH 12/12 sched: Cleanup the sched_change NOCLOCK usage

In reply to Peter Zijlstra

Teach the sched_change pattern how to do update_rq_clock(); this allows for some simplifications / cleanups.

---
 kernel/sched/core.c     |   33 +++++++++++----------------------
 kernel/sched/ext.c      |    8 ++------
 kernel/sched/syscalls.c |    8 ++------
 3 files changed, 15 insertions(+), 34 deletions(-)
kernel/sched/core.c +11 -22
@@ -2346,10 +2346,8 @@ static void migrate_disable_switch(struc
 	if (p->cpus_ptr != &p->cpus_mask)
 		return;
 
-	scoped_guard (task_rq_lock, p) {
-		update_rq_clock(scope.rq);
+	scoped_guard (task_rq_lock, p)
 		do_set_cpus_allowed(p, &ac);
-	}
 }
 
 void ___migrate_enable(void)
@@ -2666,9 +2664,7 @@ void set_cpus_allowed_common(struct task
 static void
 do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
-	u32 flags = DEQUEUE_SAVE | DEQUEUE_NOCLOCK;
-
-	scoped_guard (sched_change, p, flags) {
+	scoped_guard (sched_change, p, DEQUEUE_SAVE) {
 		p->sched_class->set_cpus_allowed(p, ctx);
 		mm_set_cpus_allowed(p->mm, ctx->new_mask);
 	}
@@ -2690,10 +2686,8 @@ void set_cpus_allowed_force(struct task_
 		struct rcu_head rcu;
 	};
 
-	scoped_guard (__task_rq_lock, p) {
-		update_rq_clock(scope.rq);
+	scoped_guard (__task_rq_lock, p)
 		do_set_cpus_allowed(p, &ac);
-	}
 
 	/*
 	 * Because this is called with p->pi_lock held, it is not possible
@@ -9110,16 +9104,13 @@ static void sched_change_group(struct ta
  */
 void sched_move_task(struct task_struct *tsk, bool for_autogroup)
 {
-	unsigned int queue_flags =
-		DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
+	unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE;
 	bool resched = false;
 	struct rq *rq;
 
 	CLASS(task_rq_lock, rq_guard)(tsk);
 	rq = rq_guard.rq;
 
-	update_rq_clock(rq);
-
 	scoped_guard (sched_change, tsk, queue_flags) {
 		sched_change_group(tsk);
 		if (!for_autogroup)
@@ -10794,19 +10785,17 @@ struct sched_change_ctx *sched_change_be
 
 	lockdep_assert_rq_held(rq);
 
+	if (!(flags & DEQUEUE_NOCLOCK)) {
+		update_rq_clock(rq);
+		flags |= DEQUEUE_NOCLOCK;
+	}
+
 	if (flags & DEQUEUE_CLASS) {
 		if (WARN_ON_ONCE(flags & (DEQUEUE_SAVE | DEQUEUE_MOVE)))
 			flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
 
-		if (p->sched_class->switching_from) {
-			/*
-			 * switching_from_fair() assumes CLASS implies NOCLOCK;
-			 * fixing this assumption would mean switching_from()
-			 * would need to be able to change flags.
-			 */
-			WARN_ON(!(flags & DEQUEUE_NOCLOCK));
+		if (p->sched_class->switching_from)
 			p->sched_class->switching_from(rq, p);
-		}
 	}
 
 	*ctx = (struct sched_change_ctx){
@@ -10845,7 +10834,7 @@ void sched_change_end(struct sched_chang
 		p->sched_class->switching_to(rq, p);
 
 	if (ctx->queued)
-		enqueue_task(rq, p, ctx->flags | ENQUEUE_NOCLOCK);
+		enqueue_task(rq, p, ctx->flags);
 	if (ctx->running)
 		set_next_task(rq, p);
 
kernel/sched/ext.c +2 -6
@@ -3912,13 +3912,11 @@ static void scx_disable_workfn(struct kt
 
 	scx_task_iter_start(&sti);
 	while ((p = scx_task_iter_next_locked(&sti))) {
-		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
+		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE;
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
 
-		update_rq_clock(task_rq(p));
-
 		if (old_class != new_class) {
 			queue_flags |= DEQUEUE_CLASS;
 			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
@@ -4656,7 +4654,7 @@ static int scx_enable(struct sched_ext_o
 	percpu_down_write(&scx_fork_rwsem);
 	scx_task_iter_start(&sti);
 	while ((p = scx_task_iter_next_locked(&sti))) {
-		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
+		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE;
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
@@ -4664,8 +4662,6 @@ static int scx_enable(struct sched_ext_o
 		if (!tryget_task_struct(p))
 			continue;
 
-		update_rq_clock(task_rq(p));
-
 		if (old_class != new_class) {
 			queue_flags |= DEQUEUE_CLASS;
 			queue_flags &= ~(DEQUEUE_SAVE | DEQUEUE_MOVE);
kernel/sched/syscalls.c +2 -6
@@ -64,7 +64,6 @@ static int effective_prio(struct task_st
 
 void set_user_nice(struct task_struct *p, long nice)
 {
-	struct rq *rq;
 	int old_prio;
 
 	if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)
@@ -73,10 +72,7 @@ void set_user_nice(struct task_struct *p
 	 * We have to be careful, if called from sys_setpriority(),
 	 * the task might be in the middle of scheduling on another CPU.
 	 */
-	CLASS(task_rq_lock, rq_guard)(p);
-	rq = rq_guard.rq;
-
-	update_rq_clock(rq);
+	guard(task_rq_lock)(p);
 
 	/*
 	 * The RT priorities are set via sched_setscheduler(), but we still
@@ -89,7 +85,7 @@ void set_user_nice(struct task_struct *p
 		return;
 	}
 
-	scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK) {
+	scoped_guard (sched_change, p, DEQUEUE_SAVE) {
 		p->static_prio = NICE_TO_PRIO(nice);
 		set_load_weight(p, true);
 		old_prio = p->prio;
merge bot Applied
#68 lore raw

[tip: sched/core] sched: Cleanup the sched_change NOCLOCK usage

The following commit has been merged into the sched/core branch of tip:

The following commit has been merged into the sched/core branch of tip:

Commit-ID:     d4c64207b88a60dd15a38c790bb73c0b6f9a8c40
Gitweb:        https://git.kernel.org/tip/d4c64207b88a60dd15a38c790bb73c0b6f9a8c40
Author:        Peter Zijlstra <[email protected]>
AuthorDate:    Thu, 11 Sep 2025 12:09:19 +02:00
Committer:     Peter Zijlstra <[email protected]>
CommitterDate: Thu, 16 Oct 2025 11:13:54 +02:00

sched: Cleanup the sched_change NOCLOCK usage

Teach the sched_change pattern how to do update_rq_clock(); this allows for some simplifications / cleanups.

---
 kernel/sched/core.c     | 33 +++++++++++----------------------
 kernel/sched/ext.c      |  4 +---
 kernel/sched/syscalls.c |  8 ++------
 3 files changed, 14 insertions(+), 31 deletions(-)
kernel/sched/core.c +11 -22
index e715147..3d5659f 100644
@@ -2346,10 +2346,8 @@ static void migrate_disable_switch(struct rq *rq, struct task_struct *p)
 	if (p->cpus_ptr != &p->cpus_mask)
 		return;
 
-	scoped_guard (task_rq_lock, p) {
-		update_rq_clock(scope.rq);
+	scoped_guard (task_rq_lock, p)
 		do_set_cpus_allowed(p, &ac);
-	}
 }
 
 void ___migrate_enable(void)
@@ -2666,9 +2664,7 @@ void set_cpus_allowed_common(struct task_struct *p, struct affinity_context *ctx
 static void
 do_set_cpus_allowed(struct task_struct *p, struct affinity_context *ctx)
 {
-	u32 flags = DEQUEUE_SAVE | DEQUEUE_NOCLOCK;
-
-	scoped_guard (sched_change, p, flags) {
+	scoped_guard (sched_change, p, DEQUEUE_SAVE) {
 		p->sched_class->set_cpus_allowed(p, ctx);
 		mm_set_cpus_allowed(p->mm, ctx->new_mask);
 	}
@@ -2690,10 +2686,8 @@ void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *new_mas
 		struct rcu_head rcu;
 	};
 
-	scoped_guard (__task_rq_lock, p) {
-		update_rq_clock(scope.rq);
+	scoped_guard (__task_rq_lock, p)
 		do_set_cpus_allowed(p, &ac);
-	}
 
 	/*
 	 * Because this is called with p->pi_lock held, it is not possible
@@ -9108,16 +9102,13 @@ static void sched_change_group(struct task_struct *tsk)
  */
 void sched_move_task(struct task_struct *tsk, bool for_autogroup)
 {
-	unsigned int queue_flags =
-		DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
+	unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE;
 	bool resched = false;
 	struct rq *rq;
 
 	CLASS(task_rq_lock, rq_guard)(tsk);
 	rq = rq_guard.rq;
 
-	update_rq_clock(rq);
-
 	scoped_guard (sched_change, tsk, queue_flags) {
 		sched_change_group(tsk);
 		if (!for_autogroup)
@@ -10792,16 +10783,14 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int 
 
 	lockdep_assert_rq_held(rq);
 
+	if (!(flags & DEQUEUE_NOCLOCK)) {
+		update_rq_clock(rq);
+		flags |= DEQUEUE_NOCLOCK;
+	}
+
 	if (flags & DEQUEUE_CLASS) {
-		if (p->sched_class->switching_from) {
-			/*
-			 * switching_from_fair() assumes CLASS implies NOCLOCK;
-			 * fixing this assumption would mean switching_from()
-			 * would need to be able to change flags.
-			 */
-			WARN_ON(!(flags & DEQUEUE_NOCLOCK));
+		if (p->sched_class->switching_from)
 			p->sched_class->switching_from(rq, p);
-		}
 	}
 
 	*ctx = (struct sched_change_ctx){
@@ -10840,7 +10829,7 @@ void sched_change_end(struct sched_change_ctx *ctx)
 		p->sched_class->switching_to(rq, p);
 
 	if (ctx->queued)
-		enqueue_task(rq, p, ctx->flags | ENQUEUE_NOCLOCK);
+		enqueue_task(rq, p, ctx->flags);
 	if (ctx->running)
 		set_next_task(rq, p);
 
kernel/sched/ext.c +1 -3
index ad371b6..5717042 100644
@@ -4654,7 +4654,7 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 	percpu_down_write(&scx_fork_rwsem);
 	scx_task_iter_start(&sti);
 	while ((p = scx_task_iter_next_locked(&sti))) {
-		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE | DEQUEUE_NOCLOCK;
+		unsigned int queue_flags = DEQUEUE_SAVE | DEQUEUE_MOVE;
 		const struct sched_class *old_class = p->sched_class;
 		const struct sched_class *new_class =
 			__setscheduler_class(p->policy, p->prio);
@@ -4662,8 +4662,6 @@ static int scx_enable(struct sched_ext_ops *ops, struct bpf_link *link)
 		if (!tryget_task_struct(p))
 			continue;
 
-		update_rq_clock(task_rq(p));
-
 		if (old_class != new_class)
 			queue_flags |= DEQUEUE_CLASS;
 
kernel/sched/syscalls.c +2 -6
index 20af564..8f0f603 100644
@@ -64,7 +64,6 @@ static int effective_prio(struct task_struct *p)
 
 void set_user_nice(struct task_struct *p, long nice)
 {
-	struct rq *rq;
 	int old_prio;
 
 	if (task_nice(p) == nice || nice < MIN_NICE || nice > MAX_NICE)
@@ -73,10 +72,7 @@ void set_user_nice(struct task_struct *p, long nice)
 	 * We have to be careful, if called from sys_setpriority(),
 	 * the task might be in the middle of scheduling on another CPU.
 	 */
-	CLASS(task_rq_lock, rq_guard)(p);
-	rq = rq_guard.rq;
-
-	update_rq_clock(rq);
+	guard(task_rq_lock)(p);
 
 	/*
 	 * The RT priorities are set via sched_setscheduler(), but we still
@@ -89,7 +85,7 @@ void set_user_nice(struct task_struct *p, long nice)
 		return;
 	}
 
-	scoped_guard (sched_change, p, DEQUEUE_SAVE | DEQUEUE_NOCLOCK) {
+	scoped_guard (sched_change, p, DEQUEUE_SAVE) {
 		p->static_prio = NICE_TO_PRIO(nice);
 		set_load_weight(p, true);
 		old_prio = p->prio;
reviewer Acked-by on the v1 series
#69 lore raw
In reply to Peter Zijlstra

Hi Peter,

Quoted 8 lines · Peter Zijlstra

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

I would like to queue them into sched/core after the merge window.

They all look sane changes to me. I've also stress-tested this quite a bit, even with the sched_ext dl_server patch set applied, and everything appears to work well.

Therefore, from a sched_ext perspective:

Thanks!
-Andrea

Quoted 24 lines

Also in:

git://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git sched/cleanup

---
 include/linux/cleanup.h  |   5 +
 include/linux/sched.h    |   4 +-
 kernel/cgroup/cpuset.c   |   2 +-
 kernel/kthread.c         |  15 +--
 kernel/sched/core.c      | 327 ++++++++++++++++++-----------------------------
 kernel/sched/deadline.c  |  20 +--
 kernel/sched/ext.c       |  47 +++----
 kernel/sched/fair.c      |  15 ++-
 kernel/sched/idle.c      |   9 +-
 kernel/sched/rt.c        |   7 +-
 kernel/sched/sched.h     | 198 ++++++++++++++++++++--------
 kernel/sched/stats.h     |   2 +-
 kernel/sched/stop_task.c |   9 +-
 kernel/sched/syscalls.c  |  84 ++++--------
 14 files changed, 373 insertions(+), 371 deletions(-)
maintainer Reviewed-by Acked-by on the v1 series
#70 lore raw
In reply to Peter Zijlstra

Hi Peter,

Quoted 8 lines · Peter Zijlstra

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

I would like to queue them into sched/core after the merge window.

The set looks good to me.

For the DEADLINE bits also

Best,
Juri

maintainer Acked-by on the v1 series
#71 lore raw
In reply to Peter Zijlstra
Quoted 9 lines · Peter Zijlstra

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

I would like to queue them into sched/core after the merge window.

in addition to the reviewed-by for patch 4

Quoted 25 lines

Also in:

git://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git sched/cleanup

---
 include/linux/cleanup.h  |   5 +
 include/linux/sched.h    |   4 +-
 kernel/cgroup/cpuset.c   |   2 +-
 kernel/kthread.c         |  15 +--
 kernel/sched/core.c      | 327 ++++++++++++++++++-----------------------------
 kernel/sched/deadline.c  |  20 +--
 kernel/sched/ext.c       |  47 +++----
 kernel/sched/fair.c      |  15 ++-
 kernel/sched/idle.c      |   9 +-
 kernel/sched/rt.c        |   7 +-
 kernel/sched/sched.h     | 198 ++++++++++++++++++++--------
 kernel/sched/stats.h     |   2 +-
 kernel/sched/stop_task.c |   9 +-
 kernel/sched/syscalls.c  |  84 ++++--------
 14 files changed, 373 insertions(+), 371 deletions(-)
maintainer Acked-by on the v1 series
#72 lore raw
In reply to Peter Zijlstra
Quoted 8 lines · Peter Zijlstra

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

I would like to queue them into sched/core after the merge window.

FWIW, all look good to me.

Thanks.

Signature · 1 line
tejun
reviewer Reviewed-by on the v1 series
#73 lore raw
In reply to Peter Zijlstra
Quoted 6 lines · Peter Zijlstra

Hi,

There here patches clean up the scheduler 'change' pattern and related locking some. They are the less controversial bit of some proposed sched_ext changes and stand on their own.

Quoted 7 lines

I would like to queue them into sched/core after the merge window.

Also in:

git://git.kernel.org/pub/scm/linux/kernel/git/peterz/queue.git sched/cleanup

Other than what's already been said, that LGTM. It's good to finally have a canonical change pattern... pattern? :-)