[PATCH 09/20 v3] sched/fair: Optimize push task mechanism for fair

From: Vincent Guittot

Date: Fri Nov 06 2026 - 10:26:13 EST


Instead of always unlocking local rq in order to lock both task and rq
in a safe order, just try to lock the task. If the task is already locked
by something else its state is probably changing and the conditions used
to add it in the pushable list are probably not true anymore so skipping
the push sequence seems like a good choice.

Signed-off-by: Vincent Guittot <vincent.guittot@xxxxxxxxxx>
---
kernel/sched/core.c | 4 ++--
kernel/sched/fair.c | 44 +++++++++++++++++++++++---------------------
kernel/sched/sched.h | 3 ---
3 files changed, 25 insertions(+), 26 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 1096ea9cbc4c..d71da9069218 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2589,8 +2589,8 @@ static inline bool is_cpu_allowed(struct task_struct *p, int cpu)
*
* Returns (locked) new rq. Old rq's lock is released.
*/
-struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
- struct task_struct *p, int new_cpu)
+static struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
+ struct task_struct *p, int new_cpu)
__must_hold(__rq_lockp(rq))
{
lockdep_assert_rq_held(rq);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index e37f8b55a293..e2999d3f88e4 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -10148,8 +10148,7 @@ static bool fair_push_task(struct rq *rq)
{
struct task_struct *next_task;
int prev_cpu, new_cpu;
- struct rq_flags rf;
- struct rq *cur_rq;
+ struct rq *new_rq;

next_task = pick_next_pushable_fair_task(rq);
if (!next_task)
@@ -10158,38 +10157,41 @@ static bool fair_push_task(struct rq *rq)
if (is_migration_disabled(next_task))
return true;

- /* We might release rq lock */
- get_task_struct(next_task);
-
prev_cpu = rq->cpu;

/*
- * We need to release rq lock and take both task and rq w/o
- * triggering a deadlock.
+ * The safe lock ordering for task and rq is task 1st then rq but we
+ * already get the rq so just try to get task too. If task is already
+ * locked, it is waiting for the rq's lock and it is about to change
+ * task state so skipping the push sequence in order to speed up the
+ * release of the lock is the best choice.
*/
- raw_spin_rq_unlock(rq);
-
- cur_rq = task_rq_lock(next_task, &rf);
-
- /* Task already migrated */
- if (cur_rq->cpu != prev_cpu)
- goto skip_push;
+ if (!raw_spin_trylock(&next_task->pi_lock))
+ return true;

new_cpu = select_task_rq_fair(next_task, prev_cpu, 0);

/* Task doesn't need to migrate */
if (new_cpu == prev_cpu)
- goto skip_push;
+ goto no_push;
+
+ new_rq = cpu_rq(new_cpu);

- update_rq_clock(cur_rq);
- cur_rq = move_queued_task(cur_rq, &rf, next_task, new_cpu);
+ deactivate_task(rq, next_task, 0);
+ set_task_cpu(next_task, new_cpu);
+ raw_spin_rq_unlock(rq);

-skip_push:
- task_rq_unlock(cur_rq, next_task, &rf);
+ raw_spin_rq_lock(new_rq);
+ WARN_ON_ONCE(task_cpu(next_task) != new_cpu);
+ activate_task(new_rq, next_task, 0);
+ wakeup_preempt(new_rq, next_task, 0);
+ raw_spin_rq_unlock(new_rq);

- /* Restore rq state */
+ /* Restore rq lock state */
raw_spin_rq_lock(rq);
- put_task_struct(next_task);
+
+no_push:
+ raw_spin_unlock(&next_task->pi_lock);

return true;
}
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 74e68d458509..b88857dd3941 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4223,9 +4223,6 @@ void move_queued_task_locked(struct rq *src_rq, struct rq *dst_rq, struct task_s
wakeup_preempt(dst_rq, task, 0);
}

-struct rq *move_queued_task(struct rq *rq, struct rq_flags *rf,
- struct task_struct *p, int new_cpu);
-
static inline
bool task_is_pushable(struct rq *rq, struct task_struct *p, int cpu)
{
--
2.53.0