* [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks
@ 2026-09-09 9:28 Hui Su
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
` (4 more replies)
0 siblings, 5 replies; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:28 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Proxy execution allows a task to execute on behalf of a different
scheduling context. Scheduler tick users do not all operate on the same
context: scheduling-class state and core-scheduling slice decisions belong
to the donor, while NUMA/cache work and RT watchdog state follow the task
that is actually executing.
The sched_class::task_tick() interface currently receives a single task
argument. Under cross-class proxy execution this is insufficient because
both the donor class and the execution class can have tick work to perform.
v3 handled the NUMA and cache cases with a common execution-context helper.
Following Peter Zijlstra's suggestion, v4 instead removes the task argument
from sched_class::task_tick() and lets each class derive the state it owns
from the runqueue. The common dispatcher invokes the donor class first and,
when the execution class differs, invokes that class afterwards.
The series is organized as follows:
- Patch 1 introduces the split-context task_tick() dispatch mechanism while
keeping the existing class-specific behavior donor-gated.
- Patches 2 and 3 move NUMA and cache tick work to the FAIR execution
context.
- Patch 4 keeps RT scheduling-class state and RR time-slice management with
the donor while associating the RT watchdog with the execution task. It
also handles an RT execution context whose donor belongs to another
scheduling class.
- Patch 5 fixes the complementary core-scheduling issue found during
review. task_tick_core() correctly remains donor-based, but the donor's
sum_exec_runtime does not advance while another task executes on its
behalf. Measure the donor's consumed slice in the task-clock domain
instead.
The RT watchdog follows the task whose execution runtime advances, while
RT scheduling-class state remains associated with the donor.
Changes in v4 (since v3):
- Rework the series around per-class task_tick() dispatch as suggested by
Peter Zijlstra, replacing the v3 common execution-context helper.
- Call the donor class before a different execution class so runtime
accounting is updated before execution-context consumers run.
- Keep task_tick() outside CONFIG_SCHED_HRTICK and preserve queued/hrtick
behavior.
- Keep task_tick_numa() and task_tick_cache() local to fair.c and preserve
the ordinary FAIR tick ordering.
- Fold in the RT watchdog proxy-execution fix, rebased on the new
task_tick() interface, including execution-only RT callbacks with another
donor class.
- Preserve RT-policy watchdog intervals across temporary DL PI boosts while
still clearing stale intervals when non-RT owners leave RT proxy service.
- Add the separate core-scheduling donor slice-accounting fix identified
during review, using a task-clock selection baseline.
- Extend the proxy-execution reproducers to cover FAIR/RT/DL cross-class
and same-class dispatch, NUMA/cache work, RT watchdog behavior, and
core-slice accounting.
v3: https://lore.kernel.org/r/20260904085244.799276-1-sh_def@163.com
Testing:
- Built kernel/sched/ at each patch boundary and built the final x86_64
bzImage. Also tested CONFIG_SCHED_HRTICK=n, CONFIG_POSIX_TIMERS=n,
CONFIG_SCHED_CLASS_EXT=y with proxy execution disabled,
CONFIG_SCHED_CORE=n, CONFIG_NUMA_BALANCING=n, CONFIG_SCHED_CACHE=n,
and CONFIG_FAIR_GROUP_SCHED=n.
- Verified NUMA and cache execution-context ticks and actual work callbacks
in a two-node, two-SMT-pair QEMU guest with FAIR, RT, and DL donors.
NO_HZ_FULL remote ticks were also exercised.
- Exercised RT-to-FAIR, DL-to-FAIR, FAIR-to-FAIR, RT-to-RT, DL-to-RT,
and DL-to-RR split-context dispatch. hrtick callbacks with queued=1 were
also exercised.
- Verified FIFO/RR watchdog attribution, blocking and nested proxy
execution, SIGXCPU, hard RLIMIT_RTTIME handling, and DL-donor/RT-owner
execution. For DL-to-RR execution-only callbacks, the execution owner's
watchdog advanced while its rt.time_slice remained unchanged.
- Reproduced an incorrect 123-to-0 timeout reset when a SCHED_FIFO owner
was PI-boosted into the DL class. With the policy guard, the timeout
remained 123 during the boost and after deboosting; a FAIR owner leaving
RT proxy service still reset its timeout from 123 to zero.
- Compared the existing and task-clock core-slice predicates over 89,900
non-proxy samples across HZ=100/250/1000 and nice -10/0/+10, with zero
mismatches. Under proxy execution the donor's sum_exec_runtime delta
remained zero while the task-clock delta advanced and reached the
force-idle reschedule condition.
- Exercised dynamic nice and cgroup-weight changes, migration, and
execution-owner handoff.
- Ran a 31-minute KASAN/LOCKDEP soak covering 33 cycles x 13 modes
(429 cases), with no failures or kernel fault reports.
Hui Su (5):
sched: Dispatch task ticks for donor and execution classes
sched/numa: Drive NUMA task tick from execution context
sched/cache: Drive cache task tick from execution context
sched/rt: Fix RT watchdog accounting for proxy execution
sched/core: Fix donor slice accounting under proxy execution
include/linux/sched.h | 3 ++
kernel/sched/core.c | 43 +++++++++++++++++++++++++--
kernel/sched/deadline.c | 10 +++++--
kernel/sched/ext/ext.c | 10 +++++--
kernel/sched/fair.c | 64 ++++++++++++++++++++++++----------------
kernel/sched/idle.c | 8 ++---
kernel/sched/rt.c | 23 +++++++++++----
kernel/sched/sched.h | 2 +-
kernel/sched/stop_task.c | 5 ++--
9 files changed, 121 insertions(+), 47 deletions(-)
base-commit: df2908090cda368b01ff43709f51890076c56157
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
@ 2026-09-09 9:28 ` Hui Su
2026-09-09 10:42 ` Hui Su
2026-09-09 9:28 ` [PATCH v4 2/5] sched/numa: Drive NUMA task tick from execution context Hui Su
` (3 subsequent siblings)
4 siblings, 1 reply; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:28 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Proxy execution can run a task from one scheduling class on behalf of a
donor from another. Scheduler ticks therefore need to reach both the class
which owns the scheduling context and the class which owns the execution
context.
Remove the task argument from sched_class::task_tick() and let each class
select the state it owns through the runqueue. Add a common task_tick()
dispatcher which calls the donor class first and, when proxy execution
splits the classes, the execution class afterwards.
Calling the donor first preserves the existing runtime-accounting order for
execution-context consumers. Keep the existing class-specific tick behavior
donor-gated in this patch, so the change only introduces the new interface
and dispatch mechanism.
Suggested-by: Peter Zijlstra <peterz@infradead.org>
Link: https://lore.kernel.org/r/20260908104407.GD687043@noisy.programming.kicks-ass.net
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/sched/core.c | 22 +++++++++++++++++++---
kernel/sched/deadline.c | 10 +++++++---
kernel/sched/ext/ext.c | 10 ++++++++--
kernel/sched/fair.c | 25 +++++++++++++++----------
kernel/sched/idle.c | 8 ++++----
kernel/sched/rt.c | 13 +++++++++----
kernel/sched/sched.h | 2 +-
kernel/sched/stop_task.c | 5 ++---
8 files changed, 65 insertions(+), 30 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index b998ef6b87af..05e599665fdd 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -892,6 +892,22 @@ void update_rq_clock(struct rq *rq)
update_rq_clock_task(rq, delta);
}
+/*
+ * Run the scheduling-context class first so its runtime update precedes
+ * execution-context tick work. A different execution class runs second.
+ * Same-class proxy execution gets one callback; ownership-specific work
+ * can select rq->donor or rq->curr as appropriate.
+ */
+static inline void task_tick(struct rq *rq, int queued)
+{
+ const struct sched_class *curr_class = rq->curr->sched_class;
+ const struct sched_class *donor_class = rq->donor->sched_class;
+
+ donor_class->task_tick(rq, queued);
+ if (sched_proxy_exec() && curr_class != donor_class)
+ curr_class->task_tick(rq, queued);
+}
+
#ifdef CONFIG_SCHED_HRTICK
/*
* Use HR-timers to deliver accurate preemption points.
@@ -923,7 +939,7 @@ static enum hrtimer_restart hrtick(struct hrtimer *timer)
rq_lock(rq, &rf);
update_rq_clock(rq);
- rq->donor->sched_class->task_tick(rq, rq->donor, 1);
+ task_tick(rq, 1);
rq_unlock(rq, &rf);
return HRTIMER_NORESTART;
@@ -5799,7 +5815,7 @@ void sched_tick(void)
if (dynamic_preempt_lazy() && tif_test_bit(TIF_NEED_RESCHED_LAZY))
resched_curr(rq);
- donor->sched_class->task_tick(rq, donor, 0);
+ task_tick(rq, 0);
if (sched_feat(LATENCY_WARN))
resched_latency = cpu_resched_latency(rq);
calc_global_load_tick(rq);
@@ -5895,7 +5911,7 @@ static void sched_tick_remote(struct work_struct *work)
u64 delta = rq_clock_task(rq) - curr->se.exec_start;
WARN_ON_ONCE(delta > (u64)NSEC_PER_SEC * 30);
}
- curr->sched_class->task_tick(rq, curr, 0);
+ task_tick(rq, 0);
calc_load_nohz_remote(rq);
}
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 0663c00c41c0..da7613acab18 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -2873,11 +2873,15 @@ static void put_prev_task_dl(struct rq *rq, struct task_struct *p, struct task_s
*
* NOTE: This function can be called remotely by the tick offload that
* goes along full dynticks. Therefore no local assumption can be made
- * and everything must be accessed through the @rq and @curr passed in
- * parameters.
+ * and all state must be accessed through @rq.
*/
-static void task_tick_dl(struct rq *rq, struct task_struct *p, int queued)
+static void task_tick_dl(struct rq *rq, int queued)
{
+ struct task_struct *p = rq->donor;
+
+ if (p->sched_class != &dl_sched_class)
+ return;
+
update_curr_dl(rq);
update_dl_rq_load_avg(rq_clock_pelt(rq), rq, 1);
diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
index 51de1d8b72a1..70815bef5296 100644
--- a/kernel/sched/ext/ext.c
+++ b/kernel/sched/ext/ext.c
@@ -3789,9 +3789,15 @@ void scx_tick(struct rq *rq)
update_other_load_avgs(rq);
}
-static void task_tick_scx(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_scx(struct rq *rq, int queued)
{
- struct scx_sched *sch = scx_task_sched(curr);
+ struct task_struct *curr = rq->donor;
+ struct scx_sched *sch;
+
+ if (curr->sched_class != &ext_sched_class)
+ return;
+
+ sch = scx_task_sched(curr);
update_curr_scx(rq);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ade1eceb39b8..6f1777799371 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -15057,12 +15057,17 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
*
* NOTE: This function can be called remotely by the tick offload that
* goes along full dynticks. Therefore no local assumption can be made
- * and everything must be accessed through the @rq and @curr passed in
- * parameters.
+ * and all state must be accessed through @rq.
*/
-static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_fair(struct rq *rq, int queued)
{
- struct sched_entity *se = &curr->se;
+ struct task_struct *donor = rq->donor;
+ struct sched_entity *se;
+
+ if (donor->sched_class != &fair_sched_class)
+ return;
+
+ se = &donor->se;
if (se->on_rq) {
unsigned long weight = NICE_0_LOAD;
@@ -15075,7 +15080,7 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
weight = __calc_prop_weight(cfs_rq, se, weight);
}
- se = &curr->se;
+ se = &donor->se;
reweight_eevdf(cfs_rq, se, weight, se->on_rq);
}
@@ -15083,14 +15088,14 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
return;
if (static_branch_unlikely(&sched_numa_balancing))
- task_tick_numa(rq, curr);
+ task_tick_numa(rq, donor);
- task_tick_cache(rq, curr);
+ task_tick_cache(rq, donor);
- update_misfit_status(curr, rq);
- check_update_overutilized_status(task_rq(curr));
+ update_misfit_status(donor, rq);
+ check_update_overutilized_status(task_rq(donor));
- task_tick_core(rq, curr);
+ task_tick_core(rq, donor);
}
/*
diff --git a/kernel/sched/idle.c b/kernel/sched/idle.c
index eb73b65ce6c4..c1e597b0912a 100644
--- a/kernel/sched/idle.c
+++ b/kernel/sched/idle.c
@@ -532,12 +532,12 @@ dequeue_task_idle(struct rq *rq, struct task_struct *p, int flags)
*
* NOTE: This function can be called remotely by the tick offload that
* goes along full dynticks. Therefore no local assumption can be made
- * and everything must be accessed through the @rq and @curr passed in
- * parameters.
+ * and all state must be accessed through @rq.
*/
-static void task_tick_idle(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_idle(struct rq *rq, int queued)
{
- update_curr_idle(rq);
+ if (rq->donor->sched_class == &idle_sched_class)
+ update_curr_idle(rq);
}
static void switching_to_idle(struct rq *rq, struct task_struct *p)
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index 85303add726d..dd058a6ca06b 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -2535,12 +2535,17 @@ static inline void watchdog(struct rq *rq, struct task_struct *p) { }
*
* NOTE: This function can be called remotely by the tick offload that
* goes along full dynticks. Therefore no local assumption can be made
- * and everything must be accessed through the @rq and @curr passed in
- * parameters.
+ * and all state must be accessed through @rq.
*/
-static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
+static void task_tick_rt(struct rq *rq, int queued)
{
- struct sched_rt_entity *rt_se = &p->rt;
+ struct task_struct *p = rq->donor;
+ struct sched_rt_entity *rt_se;
+
+ if (p->sched_class != &rt_sched_class)
+ return;
+
+ rt_se = &p->rt;
update_curr_rt(rq);
update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..6a8deddc725b 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2718,7 +2718,7 @@ struct sched_class {
* sched_tick: rq->lock
* sched_tick_remote: rq->lock
*/
- void (*task_tick)(struct rq *rq, struct task_struct *p, int queued);
+ void (*task_tick)(struct rq *rq, int queued);
/*
* sched_cgroup_fork: p->pi_lock
*/
diff --git a/kernel/sched/stop_task.c b/kernel/sched/stop_task.c
index c909ca0d8c87..87b46fc74f81 100644
--- a/kernel/sched/stop_task.c
+++ b/kernel/sched/stop_task.c
@@ -68,10 +68,9 @@ static void put_prev_task_stop(struct rq *rq, struct task_struct *prev, struct t
*
* NOTE: This function can be called remotely by the tick offload that
* goes along full dynticks. Therefore no local assumption can be made
- * and everything must be accessed through the @rq and @curr passed in
- * parameters.
+ * and all state must be accessed through @rq.
*/
-static void task_tick_stop(struct rq *rq, struct task_struct *curr, int queued)
+static void task_tick_stop(struct rq *rq, int queued)
{
}
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 2/5] sched/numa: Drive NUMA task tick from execution context
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
@ 2026-09-09 9:28 ` Hui Su
2026-09-09 9:28 ` [PATCH v4 3/5] sched/cache: Drive cache " Hui Su
` (2 subsequent siblings)
4 siblings, 0 replies; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:28 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Proxy execution separates the scheduling context in rq->donor from the
execution context in rq->curr. The task tick dispatcher can therefore
invoke task_tick_fair() for either side of a cross-class proxy
relationship.
task_tick_numa() operates on state associated with the task actually
executing, including its mm and NUMA work state. Task-level execution
runtime is likewise accounted to rq->curr, and task_tick_numa() uses that
runtime to drive periodic NUMA scanning.
Split task_tick_fair() into donor and execution-context sections and run
the NUMA tick only when rq->curr belongs to the fair scheduling class. The
donor class tick runs first, so execution runtime is accounted before the
NUMA tick consumes it.
Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()")
Suggested-by: K Prateek Nayak <kprateek.nayak@amd.com>
Suggested-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/sched/fair.c | 44 +++++++++++++++++++++++---------------------
1 file changed, 23 insertions(+), 21 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 6f1777799371..a8c7a9a29ace 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -15061,41 +15061,43 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
*/
static void task_tick_fair(struct rq *rq, int queued)
{
- struct task_struct *donor = rq->donor;
- struct sched_entity *se;
+ struct task_struct *curr = rq->curr, *donor = rq->donor;
- if (donor->sched_class != &fair_sched_class)
- return;
+ if (donor->sched_class == &fair_sched_class) {
+ struct sched_entity *se = &donor->se;
- se = &donor->se;
+ if (se->on_rq) {
+ unsigned long weight = NICE_0_LOAD;
+ struct cfs_rq *cfs_rq;
- if (se->on_rq) {
- unsigned long weight = NICE_0_LOAD;
- struct cfs_rq *cfs_rq;
+ for_each_sched_entity(se) {
+ cfs_rq = cfs_rq_of(se);
+ entity_tick(cfs_rq, se, queued);
- for_each_sched_entity(se) {
- cfs_rq = cfs_rq_of(se);
- entity_tick(cfs_rq, se, queued);
+ weight = __calc_prop_weight(cfs_rq, se, weight);
+ }
- weight = __calc_prop_weight(cfs_rq, se, weight);
+ se = &donor->se;
+ reweight_eevdf(cfs_rq, se, weight, se->on_rq);
}
-
- se = &donor->se;
- reweight_eevdf(cfs_rq, se, weight, se->on_rq);
}
if (queued)
return;
- if (static_branch_unlikely(&sched_numa_balancing))
- task_tick_numa(rq, donor);
+ /* Update state owned by the execution context. */
+ if (curr->sched_class == &fair_sched_class &&
+ static_branch_unlikely(&sched_numa_balancing))
+ task_tick_numa(rq, curr);
- task_tick_cache(rq, donor);
+ if (donor->sched_class == &fair_sched_class) {
+ task_tick_cache(rq, donor);
- update_misfit_status(donor, rq);
- check_update_overutilized_status(task_rq(donor));
+ update_misfit_status(donor, rq);
+ check_update_overutilized_status(task_rq(donor));
- task_tick_core(rq, donor);
+ task_tick_core(rq, donor);
+ }
}
/*
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 3/5] sched/cache: Drive cache task tick from execution context
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
2026-09-09 9:28 ` [PATCH v4 2/5] sched/numa: Drive NUMA task tick from execution context Hui Su
@ 2026-09-09 9:28 ` Hui Su
2026-09-09 11:03 ` Peter Zijlstra
2026-09-09 9:29 ` [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution Hui Su
2026-09-09 9:29 ` [PATCH v4 5/5] sched/core: Fix donor slice accounting under " Hui Su
4 siblings, 1 reply; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:28 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Cache-aware scheduling accounts CPU runtime to the mm of the task actually
executing. update_se() passes rq->curr to account_mm_sched() for this
purpose.
With proxy execution, task_tick_cache() still runs for the scheduling
context in rq->donor. When a fair task executes on behalf of an RT or
deadline donor, its mm runtime advances but its cache scan epoch is not
driven. This can cause account_mm_sched() to invalidate the mm's preferred
LLC.
Run task_tick_cache() from the fair execution-context section of
task_tick_fair(), alongside NUMA tick handling, so cache work and runtime
accounting refer to the same task and mm.
Fixes: df0d98475954 ("sched/cache: Introduce infrastructure for cache-aware load balancing")
Suggested-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/sched/fair.c | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index a8c7a9a29ace..43d558289856 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -15086,13 +15086,15 @@ static void task_tick_fair(struct rq *rq, int queued)
return;
/* Update state owned by the execution context. */
- if (curr->sched_class == &fair_sched_class &&
- static_branch_unlikely(&sched_numa_balancing))
- task_tick_numa(rq, curr);
+ if (curr->sched_class == &fair_sched_class) {
+ if (static_branch_unlikely(&sched_numa_balancing))
+ task_tick_numa(rq, curr);
- if (donor->sched_class == &fair_sched_class) {
- task_tick_cache(rq, donor);
+ task_tick_cache(rq, curr);
+ }
+ /* Update state owned by the scheduling context. */
+ if (donor->sched_class == &fair_sched_class) {
update_misfit_status(donor, rq);
check_update_overutilized_status(task_rq(donor));
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
` (2 preceding siblings ...)
2026-09-09 9:28 ` [PATCH v4 3/5] sched/cache: Drive cache " Hui Su
@ 2026-09-09 9:29 ` Hui Su
2026-09-09 11:04 ` Peter Zijlstra
2026-09-09 9:29 ` [PATCH v4 5/5] sched/core: Fix donor slice accounting under " Hui Su
4 siblings, 1 reply; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:29 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Proxy execution separates the scheduling context in rq->donor from the
execution context in rq->curr.
When rq->donor belongs to the RT class, task_tick_rt() keeps RT
scheduling-class state, load tracking, and RR time-slice management
associated with rq->donor.
The RT watchdog is different. It looks up RLIMIT_RTTIME through its task
argument and updates that task's rt.timeout and posix_cputimers state.
update_curr_rt(), however, accounts elapsed task runtime to rq->curr, and
run_posix_cpu_timers() checks the execution task after the scheduler tick.
Keep the RT scheduling work on rq->donor, but run watchdog() for rq->curr.
The watchdog state then follows the task whose execution runtime advances.
The callback can also be dispatched for an RT execution context whose donor
belongs to another scheduling class. In that case, run only the watchdog
for rq->curr, without applying RT donor accounting or RR time-slice
management.
Reset rt.timeout when a task blocks under proxy execution. A mutex-blocked
task can remain on the runqueue and bypass ENQUEUE_WAKEUP, which normally
resets the RT watchdog interval. Also reset it when a task without an RT
policy is subsequently selected with neither the execution nor scheduling
context in the RT class. Preserve the timeout across ordinary scheduler
preemption and when an RT-policy task is temporarily PI-boosted into the
deadline class.
The donor/curr attribution was checked with both the in-kernel mutex
reproducer and a userspace owner in a two-node QEMU guest. FIFO and RR
donors produced donor-targeted watchdog traces on the baseline kernel and
execution-owner-targeted traces with this change; both proxy runs
completed successfully. A DL donor with an RT execution owner was also
exercised; the execution owner's timeout advanced and SIGXCPU was delivered
to it, while an RR execution owner's rt.time_slice remained unchanged in
execution-only callbacks.
A targeted rtmutex test set rt.timeout to 123 on a SCHED_FIFO owner. A DL
waiter then PI-boosted it into the deadline class. Without the policy
guard, scheduling the boosted owner reset the timeout to zero. With the
guard, it remained 123 during the boost and after deboosting. A FAIR owner
leaving an RT proxy interval still reset its timeout from 123 to zero.
Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()")
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/sched/core.c | 18 ++++++++++++++++++
kernel/sched/rt.c | 12 ++++++++++--
2 files changed, 28 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 05e599665fdd..d8a785bec639 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -6768,6 +6768,14 @@ static bool try_to_block_task(struct rq *rq, struct task_struct *p,
return false;
}
+ /*
+ * Proxy execution can keep a mutex-blocked task on the runqueue, so it
+ * may not pass through ENQUEUE_WAKEUP, which normally resets the RT
+ * watchdog interval.
+ */
+ if (sched_proxy_exec() && p->rt.timeout)
+ p->rt.timeout = 0;
+
p->is_blocked = 1;
/*
@@ -7243,6 +7251,16 @@ static void __sched notrace __schedule(int sched_mode)
rq_set_donor(rq, next);
}
+ /*
+ * End a previous RT proxy watchdog interval once neither context is
+ * in the RT class. Preserve the interval for an RT-policy task that is
+ * temporarily PI-boosted into the DL class.
+ */
+ if (sched_proxy_exec() && !task_has_rt_policy(next) &&
+ !rt_prio(next->prio) &&
+ !rt_prio(rq->donor->prio) && next->rt.timeout)
+ next->rt.timeout = 0;
+
picked:
clear_tsk_need_resched(prev);
clear_preempt_need_resched();
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index dd058a6ca06b..0fdb8edb7528 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -2542,15 +2542,23 @@ static void task_tick_rt(struct rq *rq, int queued)
struct task_struct *p = rq->donor;
struct sched_rt_entity *rt_se;
- if (p->sched_class != &rt_sched_class)
+ if (p->sched_class != &rt_sched_class) {
+ /*
+ * The RT callback can also be dispatched for an RT execution
+ * context whose scheduling context belongs to another class.
+ * Keep the watchdog tied to the task whose runtime is advancing.
+ */
+ if (rq->curr->sched_class == &rt_sched_class)
+ watchdog(rq, rq->curr);
return;
+ }
rt_se = &p->rt;
update_curr_rt(rq);
update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);
- watchdog(rq, p);
+ watchdog(rq, rq->curr);
/*
* RR tasks need a special form of time-slice management.
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 5/5] sched/core: Fix donor slice accounting under proxy execution
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
` (3 preceding siblings ...)
2026-09-09 9:29 ` [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution Hui Su
@ 2026-09-09 9:29 ` Hui Su
2026-09-10 10:55 ` Hui Su
4 siblings, 1 reply; 14+ messages in thread
From: Hui Su @ 2026-09-09 9:29 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
Core scheduling uses __entity_slice_used() to decide whether the current
scheduling context has consumed enough of its slice to let a force-idled
SMT sibling run.
The check is correctly made against rq->donor, since the slice belongs
to the scheduling context. With proxy execution, however, task runtime
is accounted to rq->curr. The donor's sum_exec_runtime therefore does
not advance while another task executes on its behalf, causing
se->sum_exec_runtime - se->prev_sum_exec_runtime
to remain near zero and preventing the force-idle reschedule from
triggering.
Using rq->curr is not correct either, as that would compare the execution
task's runtime against its own slice rather than the donor's slice.
Track the donor's task-clock timestamp when it is selected and measure
the elapsed service using se->exec_start. update_se() advances the donor's
exec_start from rq_clock_task() even under proxy execution, while the
accumulated task runtime itself is charged to rq->curr.
Keeping the comparison in the task-clock domain also avoids depending on
the entity's weight. A vruntime delta accumulated across different
weights cannot reliably be compared against a slice converted using only
the current weight.
Only snapshot task entities, as task_tick_core() performs the consumed
slice check on the donor task.
In non-proxy testing, the task-clock predicate matched the existing
sum_exec_runtime predicate across HZ=100/250/1000 and nice -10/0/+10.
Under proxy execution, the donor's sum_exec_runtime delta remained zero
while the task-clock delta advanced and triggered the force-idle
reschedule.
Fixes: aa4f74dfd42b ("sched: Fix runtime accounting w/ split exec & sched contexts")
Suggested-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Hui Su <sh_def@163.com>
---
include/linux/sched.h | 3 +++
kernel/sched/core.c | 3 +++
kernel/sched/fair.c | 15 +++++++++------
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..c32d9931129f 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -590,6 +590,9 @@ struct sched_entity {
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 vruntime;
+#ifdef CONFIG_SCHED_CORE
+ u64 core_sched_start;
+#endif
/* Approximated virtual lag: */
s64 vlag;
/* 'Protected' deadline, to give out minimum quantums: */
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index d8a785bec639..87178ec08392 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4602,6 +4602,9 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
p->se.prev_sum_exec_runtime = 0;
p->se.nr_migrations = 0;
p->se.vruntime = 0;
+#ifdef CONFIG_SCHED_CORE
+ p->se.core_sched_start = 0;
+#endif
p->se.vlag = 0;
p->se.rel_deadline = 0;
INIT_LIST_HEAD(&p->se.group_node);
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 43d558289856..0528dc4846f3 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,10 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
}
se->prev_sum_exec_runtime = se->sum_exec_runtime;
+#ifdef CONFIG_SCHED_CORE
+ if (entity_is_task(se))
+ se->core_sched_start = se->exec_start;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14788,14 +14792,13 @@ static void rq_offline_fair(struct rq *rq)
static inline bool
__entity_slice_used(struct sched_entity *se, int min_nr_tasks)
{
- u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
- u64 slice = se->slice;
+ u64 rtime = se->exec_start - se->core_sched_start;
- return (rtime * min_nr_tasks > slice);
+ return (rtime * min_nr_tasks > se->slice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
-static inline void task_tick_core(struct rq *rq, struct task_struct *curr)
+static inline void task_tick_core(struct rq *rq, struct task_struct *donor)
{
if (!sched_core_enabled(rq))
return;
@@ -14815,7 +14818,7 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr)
* if we need to give up the CPU.
*/
if (rq->core->core_forceidle_count && rq->cfs.h_nr_queued == 1 &&
- __entity_slice_used(&curr->se, MIN_NR_TASKS_DURING_FORCEIDLE))
+ __entity_slice_used(&donor->se, MIN_NR_TASKS_DURING_FORCEIDLE))
resched_curr(rq);
}
@@ -15049,7 +15052,7 @@ static int task_is_throttled_fair(struct task_struct *p, int cpu)
return throttled_hierarchy(cfs_rq);
}
#else /* !CONFIG_SCHED_CORE: */
-static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
+static inline void task_tick_core(struct rq *rq, struct task_struct *donor) {}
#endif /* !CONFIG_SCHED_CORE */
/*
--
2.55.0
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
@ 2026-09-09 10:42 ` Hui Su
2026-09-09 17:43 ` Andrea Righi
0 siblings, 1 reply; 14+ messages in thread
From: Hui Su @ 2026-09-09 10:42 UTC (permalink / raw)
To: sh_def
Cc: arighi, bsegall, changwoo, connoro, dietmar.eggemann, jstultz,
juri.lelli, kprateek.nayak, linux-kernel, mgorman, mingo, peterz,
rostedt, sched-ext, tim.c.chen, tj, vincent.guittot, void,
vschneid, yu.c.chen
On Tue, Sep 09, 2026, Sashiko AI review wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider:
>
> - ## [High] Infinite timeslice and preemption bypass during SCX proxy
> execution due to a mismatch between slice decrement (execution context)
> and slice checking (scheduling context).
>
> commit ff57ff4a11692a1b0d98a6cd57257eb58a1bd864
> Author: Hui Su <sh_def@163.com>
>
> Does this cause an infinite timeslice for SCX tasks during proxy execution?
>
> When an SCX task acts as a donor in proxy execution, task_tick_scx() sets
> curr to rq->donor. However, update_curr_scx() applies the slice decrement
> to the execution context (rq->curr):
>
> static void update_curr_scx(struct rq *rq)
> {
> struct task_struct *curr = rq->curr;
> ...
> if (curr->scx.slice != SCX_SLICE_INF)
> curr->scx.slice -= min_t(u64, curr->scx.slice, delta_exec);
> ...
> }
>
> Later in task_tick_scx(), the slice check is performed against the donor
> task which was never decremented:
>
> if (!curr->scx.slice)
> resched_curr(rq);
>
> Since the donor's slice doesn't decrease, it will never hit zero, meaning
> resched_curr(rq) is bypassed indefinitely.
>
> Additionally, if the proxy task is SCX but the donor is not,
> task_tick_scx() returns early here because the donor is not an SCX task,
> skipping the SCX tick processing completely. Should the slice management
> be explicitly tied to the execution context rather than the donor?
>
> --
> Sashiko AI review
Thanks for the report.
This configuration is currently unreachable. CONFIG_SCHED_PROXY_EXEC
depends on !SCHED_CLASS_EXT, so sched_ext and proxy execution cannot be
enabled together.
When CONFIG_SCHED_CLASS_EXT=y, proxy execution is disabled and rq->donor
and rq->curr alias the same task. Therefore task_tick_scx() using
rq->donor and update_curr_scx() using rq->curr still operate on the same
task, and the slice decrement and check cannot diverge.
The sched_ext change in this patch only adapts it to the new task_tick()
interface for the currently supported configuration.
If sched_ext and proxy execution are made compatible in the future, the
SCX donor/execution-context ownership will need to be handled as part of
that integration.
Thanks,
Hui
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 3/5] sched/cache: Drive cache task tick from execution context
2026-09-09 9:28 ` [PATCH v4 3/5] sched/cache: Drive cache " Hui Su
@ 2026-09-09 11:03 ` Peter Zijlstra
2026-09-10 10:53 ` Hui Su
0 siblings, 1 reply; 14+ messages in thread
From: Peter Zijlstra @ 2026-09-09 11:03 UTC (permalink / raw)
To: Hui Su
Cc: mingo, tim.c.chen, yu.c.chen, kprateek.nayak, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
The result at this point in the series is:
~ static void task_tick_fair(struct rq *rq, int queued)
{
~ struct task_struct *curr = rq->curr, *donor = rq->donor;
~ if (donor->sched_class == &fair_sched_class) {
~ struct sched_entity *se = &donor->se;
~ if (se->on_rq) {
~ unsigned long weight = NICE_0_LOAD;
~ struct cfs_rq *cfs_rq;
+ for_each_sched_entity(se) {
+ cfs_rq = cfs_rq_of(se);
~ entity_tick(cfs_rq, se, queued);
+ weight = __calc_prop_weight(cfs_rq, se, weight);
+ }
+
~ se = &donor->se;
~ reweight_eevdf(cfs_rq, se, weight, se->on_rq);
+ }
}
if (queued)
return;
+ /* Update state owned by the execution context. */
+ if (curr->sched_class == &fair_sched_class) {
~ if (static_branch_unlikely(&sched_numa_balancing))
~ task_tick_numa(rq, curr);
~ task_tick_cache(rq, curr);
+ }
+ /* Update state owned by the scheduling context. */
+ if (donor->sched_class == &fair_sched_class) {
~ update_misfit_status(donor, rq);
~ check_update_overutilized_status(task_rq(donor));
~ task_tick_core(rq, donor);
+ }
}
And that is rather weird given how task_tick() works. Please order
things in a single donor_class and a single curr_class block. A second
donor_class block makes no sense.
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution
2026-09-09 9:29 ` [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution Hui Su
@ 2026-09-09 11:04 ` Peter Zijlstra
2026-09-10 10:54 ` Hui Su
0 siblings, 1 reply; 14+ messages in thread
From: Peter Zijlstra @ 2026-09-09 11:04 UTC (permalink / raw)
To: Hui Su
Cc: mingo, tim.c.chen, yu.c.chen, kprateek.nayak, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
On Wed, Sep 09, 2026 at 06:29:00PM +0900, Hui Su wrote:
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index 05e599665fdd..d8a785bec639 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -6768,6 +6768,14 @@ static bool try_to_block_task(struct rq *rq, struct task_struct *p,
> return false;
> }
>
> + /*
> + * Proxy execution can keep a mutex-blocked task on the runqueue, so it
> + * may not pass through ENQUEUE_WAKEUP, which normally resets the RT
> + * watchdog interval.
> + */
> + if (sched_proxy_exec() && p->rt.timeout)
> + p->rt.timeout = 0;
> +
> p->is_blocked = 1;
>
> /*
> @@ -7243,6 +7251,16 @@ static void __sched notrace __schedule(int sched_mode)
> rq_set_donor(rq, next);
> }
>
> + /*
> + * End a previous RT proxy watchdog interval once neither context is
> + * in the RT class. Preserve the interval for an RT-policy task that is
> + * temporarily PI-boosted into the DL class.
> + */
> + if (sched_proxy_exec() && !task_has_rt_policy(next) &&
> + !rt_prio(next->prio) &&
> + !rt_prio(rq->donor->prio) && next->rt.timeout)
> + next->rt.timeout = 0;
> +
> picked:
> clear_tsk_need_resched(prev);
> clear_preempt_need_resched();
It might come as no surprise that this isn't going to fly. I've not
though about the problem yet, but we're not going to be sprinkling rt
bits like this in the middle of __schedule().
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes
2026-09-09 10:42 ` Hui Su
@ 2026-09-09 17:43 ` Andrea Righi
2026-09-10 10:49 ` Hui Su
0 siblings, 1 reply; 14+ messages in thread
From: Andrea Righi @ 2026-09-09 17:43 UTC (permalink / raw)
To: Hui Su
Cc: bsegall, changwoo, connoro, dietmar.eggemann, jstultz,
juri.lelli, kprateek.nayak, linux-kernel, mgorman, mingo, peterz,
rostedt, sched-ext, tim.c.chen, tj, vincent.guittot, void,
vschneid, yu.c.chen
Hi Hui,
On Wed, Sep 09, 2026 at 07:42:51PM +0900, Hui Su wrote:
> On Tue, Sep 09, 2026, Sashiko AI review wrote:
> > Thank you for your contribution! Sashiko AI review found 1 potential
> > issue(s) to consider:
> >
> > - ## [High] Infinite timeslice and preemption bypass during SCX proxy
> > execution due to a mismatch between slice decrement (execution context)
> > and slice checking (scheduling context).
> >
> > commit ff57ff4a11692a1b0d98a6cd57257eb58a1bd864
> > Author: Hui Su <sh_def@163.com>
> >
> > Does this cause an infinite timeslice for SCX tasks during proxy execution?
> >
> > When an SCX task acts as a donor in proxy execution, task_tick_scx() sets
> > curr to rq->donor. However, update_curr_scx() applies the slice decrement
> > to the execution context (rq->curr):
> >
> > static void update_curr_scx(struct rq *rq)
> > {
> > struct task_struct *curr = rq->curr;
> > ...
> > if (curr->scx.slice != SCX_SLICE_INF)
> > curr->scx.slice -= min_t(u64, curr->scx.slice, delta_exec);
> > ...
> > }
> >
> > Later in task_tick_scx(), the slice check is performed against the donor
> > task which was never decremented:
> >
> > if (!curr->scx.slice)
> > resched_curr(rq);
> >
> > Since the donor's slice doesn't decrease, it will never hit zero, meaning
> > resched_curr(rq) is bypassed indefinitely.
> >
> > Additionally, if the proxy task is SCX but the donor is not,
> > task_tick_scx() returns early here because the donor is not an SCX task,
> > skipping the SCX tick processing completely. Should the slice management
> > be explicitly tied to the execution context rather than the donor?
> >
> > --
> > Sashiko AI review
>
> Thanks for the report.
>
> This configuration is currently unreachable. CONFIG_SCHED_PROXY_EXEC
> depends on !SCHED_CLASS_EXT, so sched_ext and proxy execution cannot be
> enabled together.
>
> When CONFIG_SCHED_CLASS_EXT=y, proxy execution is disabled and rq->donor
> and rq->curr alias the same task. Therefore task_tick_scx() using
> rq->donor and update_curr_scx() using rq->curr still operate on the same
> task, and the slice decrement and check cannot diverge.
>
> The sched_ext change in this patch only adapts it to the new task_tick()
> interface for the currently supported configuration.
>
> If sched_ext and proxy execution are made compatible in the future, the
> SCX donor/execution-context ownership will need to be handled as part of
> that integration.
They should be compatible soon:
https://lore.kernel.org/r/20260831134338.1531664-1-arighi@nvidia.com/
It'd be good to handle SCX donor/execution context ownership in this serie as
well, or we can coordinate to figure out the required changes.
I haven't looked at the whole patch series yet (will do soon), this is JFYI. :)
Thanks,
-Andrea
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes
2026-09-09 17:43 ` Andrea Righi
@ 2026-09-10 10:49 ` Hui Su
0 siblings, 0 replies; 14+ messages in thread
From: Hui Su @ 2026-09-10 10:49 UTC (permalink / raw)
To: Andrea Righi
Cc: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, connoro, jstultz, tj, void, changwoo, linux-kernel,
sched-ext
On Wed, Sep 09, 2026 at 07:43:44PM +0200, Andrea Righi wrote:
> They should be compatible soon:
> https://lore.kernel.org/r/20260831134338.1531664-1-arighi@nvidia.com/
>
> It'd be good to handle SCX donor/execution context ownership in this serie as
> well, or we can coordinate to figure out the required changes.
>
> I haven't looked at the whole patch series yet (will do soon), this is JFYI. :)
Thanks for the heads-up.
I kept task_tick_scx() explicitly donor-gated in the current local draft and
did not duplicate the donor-accounting changes from your v13 series. The common
task_tick() dispatcher invokes the donor class first and, when the execution
class differs, invokes the execution class second. If sched_ext is reached
only as the execution class for a non-EXT donor, task_tick_scx() returns
without SCX policy or slice work.
I previously applied your v13 series and an integration tree containing an
earlier revision of this five-patch series on the same base. In that
integration, the task_tick_scx() hunk was the only textual conflict I
encountered.
The current revision additionally handles retained-donor scheduling-class
transitions around sched_change_begin() and sched_change_end(). Your v13
series also changes that transition path, so I have not revalidated the
combined tree after this latest P4 change. The expected resolution still
uses task_tick(rq, queued), derives the donor from rq->donor, keeps the
explicit donor-class guard, and preserves your donor-based update_curr_scx()
and SCX proxy accounting.
That earlier combined integration tree built and booted with
CONFIG_SCHED_CLASS_EXT=y and CONFIG_SCHED_PROXY_EXEC=y. It did not provide a
stable full mixed EXT->FAIR, FAIR->EXT, EXT->EXT, and RT/DL->EXT runtime
matrix, and it predates the current P4 transition changes. I am therefore
not claiming complete runtime compatibility for the current revision.
The resolved task-tick path in the integration tree is:
struct task_struct *donor = rq->donor;
if (donor->sched_class != &ext_sched_class)
return;
update_curr_scx(rq); /* donor-based in your series */
SCX_CALL_OP_TASK(sch, tick, rq, donor);
if (!donor->scx.slice)
resched_curr(rq);
The complete conflict resolution is carried by the integration tree; this
excerpt records the donor guard retained from this series and the donor
accounting retained from yours.
Could you take a look at whether this donor-gated integration is the right
direction? If so, I will revalidate the current combined tree, carry the
result into the series, and post it as v5 after the pending runtime checks.
Thanks,
Hui
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 3/5] sched/cache: Drive cache task tick from execution context
2026-09-09 11:03 ` Peter Zijlstra
@ 2026-09-10 10:53 ` Hui Su
0 siblings, 0 replies; 14+ messages in thread
From: Hui Su @ 2026-09-10 10:53 UTC (permalink / raw)
To: Peter Zijlstra
Cc: mingo, tim.c.chen, yu.c.chen, kprateek.nayak, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
On Wed, Sep 09, 2026 at 01:03:48PM +0200, Peter Zijlstra wrote:
> The result at this point in the series is:
>
> ~ static void task_tick_fair(struct rq *rq, int queued)
> {
> ~ struct task_struct *curr = rq->curr, *donor = rq->donor;
>
> ~ if (donor->sched_class == &fair_sched_class) {
> ~ struct sched_entity *se = &donor->se;
>
> ~ if (se->on_rq) {
> ~ unsigned long weight = NICE_0_LOAD;
> ~ struct cfs_rq *cfs_rq;
>
> + for_each_sched_entity(se) {
> + cfs_rq = cfs_rq_of(se);
> ~ entity_tick(cfs_rq, se, queued);
>
> + weight = __calc_prop_weight(cfs_rq, se, weight);
> + }
> +
> ~ se = &donor->se;
> ~ reweight_eevdf(cfs_rq, se, weight, se->on_rq);
> + }
> }
>
> if (queued)
> return;
>
> + /* Update state owned by the execution context. */
> + if (curr->sched_class == &fair_sched_class) {
> ~ if (static_branch_unlikely(&sched_numa_balancing))
> ~ task_tick_numa(rq, curr);
>
> ~ task_tick_cache(rq, curr);
> + }
>
> + /* Update state owned by the scheduling context. */
> + if (donor->sched_class == &fair_sched_class) {
> ~ update_misfit_status(donor, rq);
> ~ check_update_overutilized_status(task_rq(donor));
>
> ~ task_tick_core(rq, donor);
> + }
> }
>
>
> And that is rather weird given how task_tick() works. Please order
> things in a single donor_class and a single curr_class block. A second
> donor_class block makes no sense.
Agreed. I reordered task_tick_fair() so the final form has a single
scheduling-context block followed by a single execution-context block.
Patch 2 moves NUMA handling into the execution block, and patch 3 moves
cache handling into that same block. Misfit, overutilized, and core
scheduling work remain in the donor block.
The resulting layout is:
if (donor->sched_class == &fair_sched_class) {
entity_tick();
reweight_eevdf();
misfit/overutilized/core(donor);
}
if (queued)
return;
if (curr->sched_class == &fair_sched_class) {
task_tick_numa(rq, curr);
task_tick_cache(rq, curr);
}
This excerpt shows that there is no second donor block.
Could you take a look at whether this layout addresses your concern? If so,
I will carry it into v5 and post the updated series.
Thanks,
Hui
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution
2026-09-09 11:04 ` Peter Zijlstra
@ 2026-09-10 10:54 ` Hui Su
0 siblings, 0 replies; 14+ messages in thread
From: Hui Su @ 2026-09-10 10:54 UTC (permalink / raw)
To: Peter Zijlstra
Cc: mingo, tim.c.chen, yu.c.chen, kprateek.nayak, juri.lelli,
vincent.guittot, dietmar.eggemann, rostedt, bsegall, mgorman,
vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
On Wed, Sep 09, 2026 at 01:04:38PM +0200, Peter Zijlstra wrote:
> It might come as no surprise that this isn't going to fly. I've not
> though about the problem yet, but we're not going to be sprinkling rt
> bits like this in the middle of __schedule().
Thanks for the review.
I removed the RT-specific handling from __schedule(). The updated design
reports generic proxy lifecycle transitions through a sched_class callback;
the RT class consumes those events to preserve RLIMIT_RTTIME interval
semantics.
The relevant ownership rule is:
RT service applicability follows the effective donor scheduling class;
watchdog state is charged to rq->curr, whose execution runtime advances.
The callback does not acquire locks or sleep. Scheduler-core callers invoke
it with rq->lock held. The mutex handoff path invokes it without rq->lock but
with preemption disabled; the existing handoff locking keeps the proxy donor
stack stable for that transition. The current RT consumer only resets the
execution task's watchdog interval.
The implementation now handles proxy-chain relationship transitions rather
than only immediate blocked_donor changes. When a relation is replaced, the
old effective donor is reported with STOP before the new relation is
installed; an upstream change propagates STOP/START to every downstream
owner in a nested chain. Teardown also resolves the effective root donor
before sending STOP, including the mutex handoff path.
This lifecycle form avoids the per-task generation state I initially tried,
which increased task_struct by one 64-byte allocation unit on both x86-64
and i386. The task-clock baseline used by patch 5 is likewise stored in the
runqueue rather than every sched_entity; the old per-entity field is removed.
The focused checks cover RT-policy PI boost to DL (timeout preserved), RT
donor to FAIR execution (watchdog charged to the execution task), blocking,
nested chains, donor replacement, DL donor to RT/RR execution (no watchdog),
and proxy-disabled builds. The current tree also passes the proxy-on,
proxy-off, POSIX_TIMERS-off, SCHED_CORE-on, x86_64 full-image, i386 object,
module, and regenerated checkpatch checks.
Retained root donors that change scheduling class are also handled around
sched_change_begin()/sched_change_end(): the current proxy chain receives
STOP before the class change and START after it. This keeps an RT-to-FAIR-to-RT
transition from carrying a stale execution-owner interval. The dedicated
in-kernel POC builds successfully; runtime execution is reserved for the new
QEMU image.
The class-transition handling walks the current proxy chain, so a retained
nested chain is stopped and restarted for every downstream execution owner,
even when only the root donor changes scheduling class. Ordinary preemption
and resumption leave the chain intact and do not generate another transition.
Could you take a look at whether this lifecycle-based version is a reasonable
direction? If so, I will complete the pending runtime check and carry it into
v5 before posting the updated series.
For reference, the complete current RT watchdog patch follows.
Thanks,
Hui
---
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..c36b772ce19f 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1691,11 +1691,13 @@ struct task_struct {
#ifdef CONFIG_SCHED_PROXY_EXEC
DECLARE_STATIC_KEY_TRUE(__sched_proxy_exec);
+void sched_proxy_stop(struct task_struct *exec);
static inline bool sched_proxy_exec(void)
{
return static_branch_likely(&__sched_proxy_exec);
}
#else
+static inline void sched_proxy_stop(struct task_struct *exec) {}
static inline bool sched_proxy_exec(void)
{
return false;
diff --git a/kernel/locking/mutex.c b/kernel/locking/mutex.c
index 942a939cee95..c1be99391076 100644
--- a/kernel/locking/mutex.c
+++ b/kernel/locking/mutex.c
@@ -1044,6 +1044,7 @@ static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigne
next_lock = __get_task_blocked_on(donor);
if (next_lock == lock) {
next = get_task_struct(donor);
+ sched_proxy_stop(current);
__clear_task_blocked_on(next, lock);
current->blocked_donor = NULL;
}
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 05e599665fdd..0e6ef9418604 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4647,6 +4647,69 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p)
init_sched_mm(p);
}
+#ifdef CONFIG_SCHED_PROXY_EXEC
+/*
+ * Return the effective scheduling donor for a proxy execution context.
+ * blocked_donor links are stable while preemption is disabled or the rq lock
+ * is held, which are the contexts in which this helper is used.
+ */
+static inline struct task_struct *proxy_root_donor(struct task_struct *exec)
+{
+ struct task_struct *donor = exec;
+
+ while (READ_ONCE(donor->blocked_donor))
+ donor = READ_ONCE(donor->blocked_donor);
+
+ return donor;
+}
+
+/* Notify the classes owning the proxy scheduling and execution contexts. */
+static inline void proxy_event(struct task_struct *donor,
+ struct task_struct *exec,
+ enum sched_proxy_event event)
+{
+ const struct sched_class *donor_class = donor->sched_class;
+ const struct sched_class *exec_class = exec->sched_class;
+
+ if (donor_class->proxy_event)
+ donor_class->proxy_event(donor, exec, event);
+ if (exec_class != donor_class && exec_class->proxy_event)
+ exec_class->proxy_event(donor, exec, event);
+}
+
+/* Notify every execution context in the current proxy chain. */
+static inline void proxy_event_chain(struct rq *rq,
+ struct task_struct *donor,
+ enum sched_proxy_event event)
+{
+ struct task_struct *exec = rq->curr;
+
+ if (exec == donor)
+ return;
+
+ for (; exec && exec != donor;
+ exec = READ_ONCE(exec->blocked_donor))
+ proxy_event(donor, exec, event);
+}
+
+void sched_proxy_stop(struct task_struct *exec)
+{
+ proxy_event(proxy_root_donor(exec), exec, SCHED_PROXY_STOP);
+}
+#else
+static inline struct task_struct *proxy_root_donor(struct task_struct *exec)
+{
+ return exec;
+}
+
+static inline void proxy_event(struct task_struct *donor,
+ struct task_struct *exec,
+ enum sched_proxy_event event) {}
+static inline void proxy_event_chain(struct rq *rq,
+ struct task_struct *donor,
+ enum sched_proxy_event event) {}
+#endif
+
DEFINE_STATIC_KEY_FALSE(sched_numa_balancing);
#ifdef CONFIG_NUMA_BALANCING
@@ -6768,6 +6831,9 @@ static bool try_to_block_task(struct rq *rq, struct task_struct *p,
return false;
}
+ if (sched_proxy_exec())
+ proxy_event(rq->donor, p, SCHED_PROXY_BLOCK);
+
p->is_blocked = 1;
/*
@@ -6930,6 +6996,8 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
__must_hold(__rq_lockp(rq))
{
struct task_struct *owner = NULL;
+ struct task_struct *old_donor = NULL;
+ bool context_changed = false;
bool curr_in_chain = false;
int this_cpu = cpu_of(rq);
struct task_struct *p;
@@ -7056,7 +7124,25 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf)
* rq, therefore holding @rq->lock is sufficient to
* guarantee its existence, as per ttwu_remote().
*/
- owner->blocked_donor = p;
+ /*
+ * The relation survives ordinary preemption and resumption. Once
+ * an upstream relation changes, however, all downstream owners
+ * inherit a new proxy scheduling context as well.
+ */
+ if (owner->blocked_donor != p) {
+ if (!context_changed)
+ old_donor = proxy_root_donor(owner);
+
+ if (old_donor != owner)
+ proxy_event(old_donor, owner, SCHED_PROXY_STOP);
+
+ owner->blocked_donor = p;
+ context_changed = true;
+ } else if (context_changed && old_donor != owner) {
+ proxy_event(old_donor, owner, SCHED_PROXY_STOP);
+ }
+ if (context_changed)
+ proxy_event(donor, owner, SCHED_PROXY_START);
}
WARN_ON_ONCE(owner && !owner->on_rq);
return owner;
@@ -7210,6 +7296,8 @@ static void __sched notrace __schedule(int sched_mode)
struct task_struct *prev_donor = rq->donor;
rq_set_donor(rq, next);
+ if (!next->is_blocked && next->blocked_donor)
+ proxy_event(proxy_root_donor(next), next, SCHED_PROXY_STOP);
next->blocked_donor = NULL;
if (unlikely(next->is_blocked)) {
next = find_proxy_task(rq, next, &rf);
@@ -11277,6 +11365,10 @@ struct sched_change_ctx *sched_change_begin(struct task_struct *p, unsigned int
lockdep_assert_rq_held(rq);
+ /* End proxy service before changing the donor's scheduling class. */
+ if ((flags & DEQUEUE_CLASS) && task_current_donor(rq, p))
+ proxy_event_chain(rq, p, SCHED_PROXY_STOP);
+
if (!(flags & DEQUEUE_NOCLOCK)) {
update_rq_clock(rq);
flags |= DEQUEUE_NOCLOCK;
@@ -11335,6 +11427,10 @@ void sched_change_end(struct sched_change_ctx *ctx)
if (p->sched_class->switched_to)
p->sched_class->switched_to(rq, p);
+ /* Restart proxy service with the donor's new scheduling class. */
+ if (ctx->running)
+ proxy_event_chain(rq, p, SCHED_PROXY_START);
+
if (ctx->running) {
/*
* If this was a class promotion; let the old class
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index dd058a6ca06b..9e16d06baf48 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -2530,6 +2530,37 @@ static void watchdog(struct rq *rq, struct task_struct *p)
static inline void watchdog(struct rq *rq, struct task_struct *p) { }
#endif /* !CONFIG_POSIX_TIMERS */
+#ifdef CONFIG_SCHED_PROXY_EXEC
+static void proxy_event_rt(struct task_struct *donor,
+ struct task_struct *exec,
+ enum sched_proxy_event event)
+{
+ /* A new relation or a blocking edge terminates the prior interval. */
+ switch (event) {
+ case SCHED_PROXY_START:
+ /*
+ * A proxy START begins an RT watchdog interval only when
+ * the scheduling context itself is RT. This callback may also
+ * be reached through an RT execution context while another
+ * class supplies the donor.
+ */
+ if (donor->sched_class == &rt_sched_class &&
+ !task_has_rt_policy(exec))
+ exec->rt.timeout = 0;
+ break;
+ case SCHED_PROXY_BLOCK:
+ exec->rt.timeout = 0;
+ break;
+ case SCHED_PROXY_STOP:
+ /* Stop the interval when RT proxy service ends for this task. */
+ if (donor->sched_class == &rt_sched_class &&
+ !task_has_rt_policy(exec))
+ exec->rt.timeout = 0;
+ break;
+ }
+}
+#endif
+
/*
* scheduler tick hitting a task of our scheduling class.
*
@@ -2550,7 +2581,7 @@ static void task_tick_rt(struct rq *rq, int queued)
update_curr_rt(rq);
update_rt_rq_load_avg(rq_clock_pelt(rq), rq, 1);
- watchdog(rq, p);
+ watchdog(rq, rq->curr);
/*
* RR tasks need a special form of time-slice management.
@@ -2625,6 +2656,9 @@ DEFINE_SCHED_CLASS(rt) = {
.find_lock_rq = find_lock_lowest_rq,
.task_tick = task_tick_rt,
+#ifdef CONFIG_SCHED_PROXY_EXEC
+ .proxy_event = proxy_event_rt,
+#endif
.get_rr_interval = get_rr_interval_rt,
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 6a8deddc725b..bb6f87552220 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 sched_proxy_event {
+ SCHED_PROXY_START,
+ SCHED_PROXY_BLOCK,
+ SCHED_PROXY_STOP,
+};
+
struct sched_class {
#ifdef CONFIG_UCLAMP_TASK
@@ -2719,6 +2725,16 @@ struct sched_class {
* sched_tick_remote: rq->lock
*/
void (*task_tick)(struct rq *rq, int queued);
+ /*
+ * Proxy execution transitions. Callbacks must not sleep. Scheduler-core
+ * events run with the rq lock held; mutex handoff may invoke the callback
+ * without the rq lock, with preemption disabled and blocked relation locks
+ * held. Donor and execution task lifetime is protected by callers.
+ */
+#ifdef CONFIG_SCHED_PROXY_EXEC
+ void (*proxy_event)(struct task_struct *donor, struct task_struct *exec,
+ enum sched_proxy_event event);
+#endif
/*
* sched_cgroup_fork: p->pi_lock
*/
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH v4 5/5] sched/core: Fix donor slice accounting under proxy execution
2026-09-09 9:29 ` [PATCH v4 5/5] sched/core: Fix donor slice accounting under " Hui Su
@ 2026-09-10 10:55 ` Hui Su
0 siblings, 0 replies; 14+ messages in thread
From: Hui Su @ 2026-09-10 10:55 UTC (permalink / raw)
To: peterz, mingo, tim.c.chen, yu.c.chen, kprateek.nayak
Cc: juri.lelli, vincent.guittot, dietmar.eggemann, rostedt, bsegall,
mgorman, vschneid, connoro, jstultz, arighi, tj, void, changwoo,
linux-kernel, sched-ext
On Wed, Sep 09, 2026 at 06:29:01PM +0900, Hui Su wrote:
> Core scheduling uses __entity_slice_used() to decide whether the current
> scheduling context has consumed enough of its slice to let a force-idled
> SMT sibling run.
>
> The check is correctly made against rq->donor, since the slice belongs
> to the scheduling context. With proxy execution, however, task runtime
> is accounted to rq->curr. The donor's sum_exec_runtime therefore does
> not advance while another task executes on its behalf, causing
>
> se->sum_exec_runtime - se->prev_sum_exec_runtime
>
> to remain near zero and preventing the force-idle reschedule from
> triggering.
>
> Using rq->curr is not correct either, as that would compare the execution
> task's runtime against its own slice rather than the donor's slice.
>
> Track the donor's task-clock timestamp when it is selected and measure
> the elapsed service using se->exec_start. update_se() advances the donor's
> exec_start from rq_clock_task() even under proxy execution, while the
> accumulated task runtime itself is charged to rq->curr.
>
> Keeping the comparison in the task-clock domain also avoids depending on
> the entity's weight. A vruntime delta accumulated across different
> weights cannot reliably be compared against a slice converted using only
> the current weight.
>
> Only snapshot task entities, as task_tick_core() performs the consumed
> slice check on the donor task.
>
> In non-proxy testing, the task-clock predicate matched the existing
> sum_exec_runtime predicate across HZ=100/250/1000 and nice -10/0/+10.
> Under proxy execution, the donor's sum_exec_runtime delta remained zero
> while the task-clock delta advanced and triggered the force-idle
> reschedule.
Following up on the task_tick_core() discussion with Tim and Chen Yu, I
reworked the implementation while keeping the slice check associated with
rq->donor, because the slice belongs to the scheduling context.
The new predicate measures consumed service in the task-clock domain rather
than using the donor's sum_exec_runtime:
rtime = se->exec_start - rq->core_sched_start;
update_se() advances the donor's exec_start from rq_clock_task() during
proxy execution even though task-level sum_exec_runtime is charged to
rq->curr. This keeps both sides of the comparison in the task-clock domain.
The deadline/virtual-time prototype did not work reliably in my reproducer:
update_deadline() can advance the deadline before task_tick_core() evaluates
the predicate, making the reconstructed virtual service small again after
the donor has consumed service.
The baseline is stored in struct rq rather than every sched_entity. There is
one active donor per runqueue, so per-entity storage was unnecessary and
increased sched_entity from 224 to 256 bytes on i386 with
CONFIG_SCHED_CORE=y.
One proxy-specific detail is that an execution-owner handoff can retain the
same donor. The scheduler then uses a synthetic put_prev_task()/
set_next_task() pair for balance handling. That reselect must not reset the
donor's core_sched_start, or service before the handoff would be discarded.
The implementation refreshes the baseline only when selecting a different
donor or when reactivating a task after a scheduling-class transition.
In non-proxy testing, the new predicate matched the existing
sum_exec_runtime predicate in 89,900 samples across HZ=100/250/1000 and
nice -10/0/+10 with zero mismatches. The proxy-enabled
CONFIG_SCHED_CORE=y scheduler objects and full bzImage also build with this
change. A focused force-idle test which splits one donor's service across two
execution owners is still pending; I will not claim owner-handoff runtime
coverage until that case runs.
Could you take a look at whether this task-clock and per-rq baseline approach
is reasonable? If so, I will complete the focused owner-handoff test and carry
the result into v5 before posting the updated series.
For reference, the complete current core-slice patch follows.
Thanks,
Hui
---
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index ed08b287016a..0fea899d61c7 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6469,7 +6469,8 @@ dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
}
static void
-set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
+set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
+ bool reset_core_slice)
{
/* 'current' is not kept within the tree. */
if (se->on_rq) {
@@ -6502,6 +6503,10 @@ set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
}
se->prev_sum_exec_runtime = se->sum_exec_runtime;
+#ifdef CONFIG_SCHED_CORE
+ if (reset_core_slice && entity_is_task(se))
+ rq_of(cfs_rq)->core_sched_start = se->exec_start;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14786,16 +14791,15 @@ static void rq_offline_fair(struct rq *rq)
#ifdef CONFIG_SCHED_CORE
static inline bool
-__entity_slice_used(struct sched_entity *se, int min_nr_tasks)
+__entity_slice_used(struct rq *rq, struct sched_entity *se, int min_nr_tasks)
{
- u64 rtime = se->sum_exec_runtime - se->prev_sum_exec_runtime;
- u64 slice = se->slice;
+ u64 rtime = se->exec_start - rq->core_sched_start;
- return (rtime * min_nr_tasks > slice);
+ return (rtime * min_nr_tasks > se->slice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
-static inline void task_tick_core(struct rq *rq, struct task_struct *curr)
+static inline void task_tick_core(struct rq *rq, struct task_struct *donor)
{
if (!sched_core_enabled(rq))
return;
@@ -14815,7 +14819,8 @@ static inline void task_tick_core(struct rq *rq, struct task_struct *curr)
* if we need to give up the CPU.
*/
if (rq->core->core_forceidle_count && rq->cfs.h_nr_queued == 1 &&
- __entity_slice_used(&curr->se, MIN_NR_TASKS_DURING_FORCEIDLE))
+ __entity_slice_used(rq, &donor->se,
+ MIN_NR_TASKS_DURING_FORCEIDLE))
resched_curr(rq);
}
@@ -15049,7 +15054,7 @@ static int task_is_throttled_fair(struct task_struct *p, int cpu)
return throttled_hierarchy(cfs_rq);
}
#else /* !CONFIG_SCHED_CORE: */
-static inline void task_tick_core(struct rq *rq, struct task_struct *curr) {}
+static inline void task_tick_core(struct rq *rq, struct task_struct *donor) {}
#endif /* !CONFIG_SCHED_CORE */
/*
@@ -15257,6 +15262,8 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
{
struct sched_entity *se = &p->se;
bool throttled = false;
+ /* Keep service accumulated across a same-donor proxy reselect. */
+ bool reset_core_slice = !first || rq->donor != p;
struct cfs_rq *cfs_rq = &rq->cfs;
unsigned long weight = NICE_0_LOAD;
bool on_rq = se->on_rq;
@@ -15271,7 +15278,7 @@ static void set_next_task_fair(struct rq *rq, struct task_struct *p, bool first)
if (!IS_ENABLED(CONFIG_FAIR_GROUP_SCHED) ||
!first || !cfs_rq->h_curr)
- set_next_entity(cfs_rq, se);
+ set_next_entity(cfs_rq, se, reset_core_slice);
/* ensure bandwidth has been allocated on our new cfs_rq */
throttled |= account_cfs_rq_runtime(cfs_rq, 0);
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index bb6f87552220..b430092fc9ad 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -1382,6 +1382,7 @@ struct rq {
unsigned int core_forceidle_seq;
unsigned int core_forceidle_occupation;
u64 core_forceidle_start;
+ u64 core_sched_start;
unsigned int core_pick_in_flight;
#endif /* CONFIG_SCHED_CORE */
^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-09-10 10:57 UTC | newest]
Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
2026-09-09 10:42 ` Hui Su
2026-09-09 17:43 ` Andrea Righi
2026-09-10 10:49 ` Hui Su
2026-09-09 9:28 ` [PATCH v4 2/5] sched/numa: Drive NUMA task tick from execution context Hui Su
2026-09-09 9:28 ` [PATCH v4 3/5] sched/cache: Drive cache " Hui Su
2026-09-09 11:03 ` Peter Zijlstra
2026-09-10 10:53 ` Hui Su
2026-09-09 9:29 ` [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution Hui Su
2026-09-09 11:04 ` Peter Zijlstra
2026-09-10 10:54 ` Hui Su
2026-09-09 9:29 ` [PATCH v4 5/5] sched/core: Fix donor slice accounting under " Hui Su
2026-09-10 10:55 ` Hui Su
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®