[PATCH v3] sched: Restart fair hrtick after same-task repicks

Shubhang Kaushik (Ampere) posted 1 patch 1 week, 2 days ago
There is a newer version of this series
kernel/sched/core.c      |  2 +-
kernel/sched/deadline.c  |  7 +++++--
kernel/sched/ext/ext.c   |  5 ++++-
kernel/sched/fair.c      | 15 ++++++++++-----
kernel/sched/idle.c      |  5 ++++-
kernel/sched/rt.c        |  7 +++++--
kernel/sched/sched.h     | 16 ++++++++++++----
kernel/sched/stop_task.c |  5 ++++-
8 files changed, 45 insertions(+), 17 deletions(-)
[PATCH v3] sched: Restart fair hrtick after same-task repicks
Posted by Shubhang Kaushik (Ampere) 1 week, 2 days ago
Fair hrticks are one-shot timers. A same-task repick after hrtick expiry
skips set_next_task_fair() and leaves no hrtick armed for the next fair
preemption point:

    hrtick()
      -> task_tick_fair(..., queued=1)
         -> entity_tick()
            -> resched_curr()
      -> schedule()
         -> pick_task_fair() selects current
         -> put_prev_set_next_task(rq, prev, next)
            -> next == prev
            -> return

Introduce SNT_REPICK for the next == prev path:

    put_prev_set_next_task(rq, prev, next)
      -> next == prev
      -> next->sched_class->set_next_task(rq, next, SNT_REPICK)
         -> set_next_task_fair()
            -> hrtick_start_fair()

set_next_task_fair() skips task-transition work for SNT_REPICK and
restarts the fair hrtick. hrtick_start() records the delay during
schedule(), and hrtick_schedule_exit() rearms the timer.

Do not restart the DL hrtick for SNT_REPICK. put_prev_task_dl() is
skipped for next == prev, so dl_se->runtime may be stale.

Signed-off-by: Shubhang Kaushik (Ampere) <sh@gentwo.org>
---
Testing:
    - HRTICK and DELAY_DEQUEUE enabled, base_slice_ns=3000000:
        p90:  3.998ms baseline -> 3.053ms v3
        p99:  5.144ms baseline -> 4.533ms v3
        >4ms: 7.89% baseline -> 2.26% v3
        >6ms: 0.309% baseline -> 0.013% v3
    - HRTICK_DL stress-ng smoke test: baseline and v3 completed 2691
      bogo ops with no new dmesg warnings.
    - Also built with CONFIG_HIGH_RES_TIMERS=n, which disables
      CONFIG_SCHED_HRTICK and CONFIG_HRTIMER_REARM_DEFERRED. The kernel
      booted successfully; a 30-second stress-ng --cpu 2 run completed
      without new dmesg warnings.
---
Changes in v3:
  - Drop the SCHED_DEADLINE SNT_REPICK hrtick rearm from v2.

Link to v2: https://lore.kernel.org/r/20260911-sched-fair-hrtick-restart-v2-1-0d34db26ecd1@gentwo.org

Changes in v2:

 - Replace the fair-specific hrtick rearm state with SNT_REPICK.
 - Restart hrticks for both fair and SCHED_DEADLINE same-task repicks.
 - Remove the delayed-dequeue runnable-count condition.

Link to v1: https://lore.kernel.org/r/20260813-sched-fair-hrtick-restart-v1-1-4230d1e18fbb@gentwo.org
---
 kernel/sched/core.c      |  2 +-
 kernel/sched/deadline.c  |  7 +++++--
 kernel/sched/ext/ext.c   |  5 ++++-
 kernel/sched/fair.c      | 15 ++++++++++-----
 kernel/sched/idle.c      |  5 ++++-
 kernel/sched/rt.c        |  7 +++++--
 kernel/sched/sched.h     | 16 ++++++++++++----
 kernel/sched/stop_task.c |  5 ++++-
 8 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 7885ff76e69f28e1c81d72d87e5ceef0562ccea9..4f7d2139f29d5e6a9241e0bb87369bc20359955c 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -7222,7 +7222,7 @@ static void __sched notrace __schedule(int sched_mode)
 			 * on_cpu.
 			 */
 			donor->sched_class->put_prev_task(rq, donor, donor);
-			donor->sched_class->set_next_task(rq, donor, true);
+			donor->sched_class->set_next_task(rq, donor, SNT_PICK);
 		}
 	} else {
 		rq_set_donor(rq, next);
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 0663c00c41c04213fda2ebaf16a0bdf34e0849a8..1502a45d76aaf44079d618d8ae193c9b213fc401 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -2773,11 +2773,14 @@ static void start_hrtick_dl(struct rq *rq, struct sched_dl_entity *dl_se)
  * DL keeps current in tree, because ->deadline is not typically changed while
  * a task is runnable.
  */
-static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first)
+static void set_next_task_dl(struct rq *rq, struct task_struct *p, enum snt_e type)
 {
 	struct sched_dl_entity *dl_se = &p->dl;
 	struct dl_rq *dl_rq = &rq->dl;
 
+	if (type == SNT_REPICK)
+		return;
+
 	p->se.exec_start = rq_clock_task(rq);
 	if (on_dl_rq(&p->dl))
 		update_stats_wait_end_dl(dl_rq, dl_se);
@@ -2788,7 +2791,7 @@ static void set_next_task_dl(struct rq *rq, struct task_struct *p, bool first)
 	WARN_ON_ONCE(dl_rq->curr);
 	dl_rq->curr = dl_se;
 
-	if (!first)
+	if (type != SNT_PICK)
 		return;
 
 	if (rq->donor->sched_class != &dl_sched_class)
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 51de1d8b72a1ede6d16c07b58defb1284285b8a6..31a300f2d3b202d588ca666d6b8f6d4705ee6c79 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -3003,10 +3003,13 @@ static enum scx_dsp_verdict dispatch_one(struct rq *rq, struct task_struct *prev
 	return verdict;
 }
 
-static void set_next_task_scx(struct rq *rq, struct task_struct *p, bool first)
+static void set_next_task_scx(struct rq *rq, struct task_struct *p, enum snt_e type)
 {
 	struct scx_sched *sch = scx_task_sched(p);
 
+	if (type == SNT_REPICK)
+		return;
+
 	if (p->scx.flags & SCX_TASK_QUEUED) {
 		/*
 		 * Core-sched might decide to execute @p before it is
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 7455a83a6a9907b4fd18c3a99df707438818709b..b46b180d363cf8a0e8fd1ad330c1cd36b71b313b 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -15253,14 +15253,18 @@ static void switched_to_fair(struct rq *rq, struct task_struct *p)
 	}
 }
 
-static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
+static void set_next_task_fair(struct rq *rq, struct task_struct *p, enum snt_e type)
 {
 	struct sched_entity *se = &p->se;
-	bool throttled = false;
 	struct cfs_rq *cfs_rq = &rq->cfs;
 	unsigned long weight = NICE_0_LOAD;
+	bool first = type == SNT_PICK;
+	bool throttled = false;
 	bool on_rq = se->on_rq;
 
+	if (type == SNT_REPICK)
+		goto repick;
+
 	clear_buddies(cfs_rq, se);
 
 	if (on_rq)
@@ -15304,11 +15308,12 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
 
 	WARN_ON_ONCE(se->sched_delayed);
 
-	if (hrtick_enabled_fair(rq))
-		hrtick_start_fair(rq, p);
-
 	update_misfit_status(p, rq);
 	sched_fair_update_stop_tick(rq, p);
+
+repick:
+	if (hrtick_enabled_fair(rq))
+		hrtick_start_fair(rq, p);
 }
 
 void init_cfs_rq(struct cfs_rq *cfs_rq)
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index eb73b65ce6c4714db1e07066c86a8cf39adbc328..76f3c84ca6846afc30609fdc512553c6797e9557 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -487,8 +487,11 @@ static void put_prev_task_idle(struct rq *rq, struct task_struct *prev, struct t
 	update_rq_avg_idle(rq);
 }
 
-static void set_next_task_idle(struct rq *rq, struct task_struct *next, bool first)
+static void set_next_task_idle(struct rq *rq, struct task_struct *next, enum snt_e type)
 {
+	if (type == SNT_REPICK)
+		return;
+
 	update_idle_core(rq);
 	scx_update_idle(rq, true, true);
 	schedstat_inc(rq->sched_goidle);
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 85303add726df4c2da846c427aab2d627fca9589..1535046a23ffa0705d90dce301fa4c5b015703a1 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -1654,11 +1654,14 @@ static void wakeup_preempt_rt(struct rq *rq, struct task_struct *p, int flags)
 		check_preempt_equal_prio(rq, p);
 }
 
-static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, bool first)
+static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, enum snt_e type)
 {
 	struct sched_rt_entity *rt_se = &p->rt;
 	struct rt_rq *rt_rq = &rq->rt;
 
+	if (type == SNT_REPICK)
+		return;
+
 	p->se.exec_start = rq_clock_task(rq);
 	if (on_rt_rq(&p->rt))
 		update_stats_wait_end_rt(rt_rq, rt_se);
@@ -1666,7 +1669,7 @@ static inline void set_next_task_rt(struct rq *rq, struct task_struct *p, bool f
 	/* The running task is never eligible for pushing */
 	dequeue_pushable_task(rq, p);
 
-	if (!first)
+	if (type != SNT_PICK)
 		return;
 
 	/*
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf864d1ed91d4ec3d4624850aded7e0..defcf7fd023fa5bb3c34c3c29f99afe3c42a87de 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2621,6 +2621,12 @@ struct affinity_context {
 
 extern s64 update_curr_common(struct rq *rq);
 
+enum snt_e {
+	SNT_NORMAL,
+	SNT_PICK,
+	SNT_REPICK,
+};
+
 struct sched_class {
 
 #ifdef CONFIG_UCLAMP_TASK
@@ -2678,7 +2684,7 @@ struct sched_class {
 	 * __schedule: rq->lock
 	 */
 	void (*put_prev_task)(struct rq *rq, struct task_struct *p, struct task_struct *next);
-	void (*set_next_task)(struct rq *rq, struct task_struct *p, bool first);
+	void (*set_next_task)(struct rq *rq, struct task_struct *p, enum snt_e type);
 
 	/*
 	 * select_task_rq: p->pi_lock
@@ -2781,7 +2787,7 @@ static inline void put_prev_task(struct rq *rq, struct task_struct *prev)
 
 static inline void set_next_task(struct rq *rq, struct task_struct *next)
 {
-	next->sched_class->set_next_task(rq, next, false);
+	next->sched_class->set_next_task(rq, next, SNT_NORMAL);
 }
 
 static inline void
@@ -2802,11 +2808,13 @@ static inline void put_prev_set_next_task(struct rq *rq,
 
 	__put_prev_set_next_dl_server(rq, prev, next);
 
-	if (next == prev)
+	if (next == prev) {
+		next->sched_class->set_next_task(rq, next, SNT_REPICK);
 		return;
+	}
 
 	prev->sched_class->put_prev_task(rq, prev, next);
-	next->sched_class->set_next_task(rq, next, true);
+	next->sched_class->set_next_task(rq, next, SNT_PICK);
 }
 
 /*
diff --git a/kernel/sched/stop_task.c b/kernel/sched/stop_task.c
index c909ca0d8c87c1055ad5ffe58ae9f044194ee358..1e0109ec36b3f9b84f6f7290e584e38257db701c 100644
--- a/kernel/sched/stop_task.c
+++ b/kernel/sched/stop_task.c
@@ -27,8 +27,11 @@ wakeup_preempt_stop(struct rq *rq, struct task_struct *p, int flags)
 	/* we're never preempted */
 }
 
-static void set_next_task_stop(struct rq *rq, struct task_struct *stop, bool first)
+static void set_next_task_stop(struct rq *rq, struct task_struct *stop, enum snt_e type)
 {
+	if (type == SNT_REPICK)
+		return;
+
 	stop->se.exec_start = rq_clock_task(rq);
 }
 

---
base-commit: f6e7b42bf05b2427fb8a7a1d1c387a86638bb413
change-id: 20260813-sched-fair-hrtick-restart-ab9d3d47ef78

Best regards,
-- 
Shubhang Kaushik (Ampere) <sh@gentwo.org>
Re: [PATCH v3] sched: Restart fair hrtick after same-task repicks
Posted by Zhan Xusheng 1 week, 2 days ago
On Tue, Sep 15, 2026 at 06:13:18PM -0700, Shubhang Kaushik (Ampere) wrote:
> Do not restart the DL hrtick for SNT_REPICK. put_prev_task_dl() is
> skipped for next == prev, so dl_se->runtime may be stale.

put_prev_task_fair() is skipped for next == prev too, so as written this
reason applies to fair as well.

What separates them is that rq->cfs.curr holds the task entity rather
than the root-level one, so pick_task_fair()'s update_curr_eevdf()
refreshes exactly the entity hrtick_start_fair() reads, group scheduling
or not.  pick_task_dl() has no counterpart.  update_deadline() then exits
with deadline > vruntime, which is what keeps the repick out of

	if ((s64)vdelta < 0) {
		if (task_current_donor(rq, p))
			resched_curr(rq);
		return;
	}

That branch arms nothing, and __schedule() clears TIF_NEED_RESCHED just
after pick_next_task(), so a repick reaching it would come out with no
preemption point at all, which is the state you are fixing.  Two lines in
the changelog, or a comment at the repick label, would stop that
invariant from being broken silently from three functions away.

> +enum snt_e {
> + SNT_NORMAL,
> + SNT_PICK,
> + SNT_REPICK,
> +};

Three values, six classes reading them, no comment.  SNT_REPICK explains
itself; NORMAL versus PICK does not.  At the call sites they mean
"through set_next_task()" and "the running task changed".

I checked the conversion with the compiler rather than by grepping:
reverting any one class to bool fails on the incompatible function
pointer, and rt, stop, fair, idle and deadline build W=1 clean.
ext/ext.c I could not compile, SCHED_CLASS_EXT wants DEBUG_INFO_BTF and
pahole is broken here.  first is unused in set_next_task_scx()'s body so
the rename is safe, but someone with BTF should build it.

Booted 4 CPUs, HRTICK on, four spinners under panic_on_warn=1: clean.  I
did not reproduce the latency numbers.

Reviewed-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
Re: [PATCH v3] sched: Restart fair hrtick after same-task repicks
Posted by Shubhang 1 week, 1 day ago
Hi Zhan,

Thanks for the review and testing. Yes, skipping 
put_prev_task_*() does not distinguish fair from DL.

For fair class, pick_task_fair() calls update_curr_eevdf() before 
selecting the current task again. This refreshes the task entity used 
by hrtick_start_fair(), including with group scheduling. When that update
observes an expired slice, it advances the entity's virtual deadline 
before hrtick_start_fair() computes the next expiry.

pick_task_dl() has no corresponding picker-side runtime update, so
p->dl.runtime can be stale on SNT_REPICK.

I will clarify this in the changelog, add a comment on the fair repick 
path, and document the SNT_NORMAL and SNT_PICK meanings.

I also rebuilt with CONFIG_SCHED_CLASS_EXT=y and it compiled
cleanly on my side.

Thanks,
Shubhang Kaushik

On Wed, 16 Sep 2026, Zhan Xusheng wrote:

> On Tue, Sep 15, 2026 at 06:13:18PM -0700, Shubhang Kaushik (Ampere) wrote:
>> Do not restart the DL hrtick for SNT_REPICK. put_prev_task_dl() is
>> skipped for next == prev, so dl_se->runtime may be stale.
>
> put_prev_task_fair() is skipped for next == prev too, so as written this
> reason applies to fair as well.
>
> What separates them is that rq->cfs.curr holds the task entity rather
> than the root-level one, so pick_task_fair()'s update_curr_eevdf()
> refreshes exactly the entity hrtick_start_fair() reads, group scheduling
> or not.  pick_task_dl() has no counterpart.  update_deadline() then exits
> with deadline > vruntime, which is what keeps the repick out of
>
> 	if ((s64)vdelta < 0) {
> 		if (task_current_donor(rq, p))
> 			resched_curr(rq);
> 		return;
> 	}
>
> That branch arms nothing, and __schedule() clears TIF_NEED_RESCHED just
> after pick_next_task(), so a repick reaching it would come out with no
> preemption point at all, which is the state you are fixing.  Two lines in
> the changelog, or a comment at the repick label, would stop that
> invariant from being broken silently from three functions away.
>
>> +enum snt_e {
>> + SNT_NORMAL,
>> + SNT_PICK,
>> + SNT_REPICK,
>> +};
>
> Three values, six classes reading them, no comment.  SNT_REPICK explains
> itself; NORMAL versus PICK does not.  At the call sites they mean
> "through set_next_task()" and "the running task changed".
>
> I checked the conversion with the compiler rather than by grepping:
> reverting any one class to bool fails on the incompatible function
> pointer, and rt, stop, fair, idle and deadline build W=1 clean.
> ext/ext.c I could not compile, SCHED_CLASS_EXT wants DEBUG_INFO_BTF and
> pahole is broken here.  first is unused in set_next_task_scx()'s body so
> the rename is safe, but someone with BTF should build it.
>
> Booted 4 CPUs, HRTICK on, four spinners under panic_on_warn=1: clean.  I
> did not reproduce the latency numbers.
>
> Reviewed-by: Zhan Xusheng <zhanxusheng@xiaomi.com>
>