* [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; 16+ 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] 16+ 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 9:46 ` sashiko-bot 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, 2 replies; 16+ 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 related [flat|nested] 16+ 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 9:46 ` sashiko-bot 2026-09-09 10:42 ` Hui Su 1 sibling, 0 replies; 16+ messages in thread From: sashiko-bot @ 2026-09-09 9:46 UTC (permalink / raw) To: Hui Su; +Cc: sched-ext 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> sched: Dispatch task ticks for donor and execution classes Proxy execution can run a task from one scheduling class on behalf of a donor from another. This patch updates sched_class::task_tick() to drop the task argument, using a common dispatcher to call the donor class first and, if needed, the execution class second. Link: https://lore.kernel.org/r/20260908104407.GD687043@noisy.programming.kicks-ass.net > diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c > index 51de1d8b72a1e..70815bef52960 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); [Severity: High] 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 · https://sashiko.dev/#/patchset/20260909092901.2989564-1-sh_def@163.com?part=1 ^ permalink raw reply [flat|nested] 16+ 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 9:46 ` sashiko-bot @ 2026-09-09 10:42 ` Hui Su 2026-09-09 17:43 ` Andrea Righi 1 sibling, 1 reply; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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 related [flat|nested] 16+ 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; 16+ 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 related [flat|nested] 16+ 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; 16+ 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] 16+ 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; 16+ 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] 16+ 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; 16+ 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 related [flat|nested] 16+ 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 2026-09-12 17:30 ` Hui Su 0 siblings, 2 replies; 16+ 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] 16+ 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 2026-09-12 17:30 ` Hui Su 1 sibling, 0 replies; 16+ 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 related [flat|nested] 16+ 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 @ 2026-09-12 17:30 ` Hui Su 1 sibling, 0 replies; 16+ messages in thread From: Hui Su @ 2026-09-12 17:30 UTC (permalink / raw) To: Peter Zijlstra, Zhidao Su, Andrea Righi Cc: 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 Hi Peter, > 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(). I took another look at this after your comment above, as well as at the lifecycle-based WIP I posted afterwards. I agree that putting RT-specific reset handling directly in __schedule() is the wrong abstraction. The current WIP instead exposes generic proxy relationship lifecycle events from the scheduler core and lets the RT class consume those events. The original attribution issue is relatively small: the RT scheduling context belongs to rq->donor, while the watchdog state needs to follow the execution context in rq->curr. The harder part is defining the watchdog interval reset boundaries under proxy execution. The current version therefore has generic START/STOP/BLOCK notifications and propagates proxy-context changes through find_proxy_task(). Compared with the previous WIP, this version no longer invokes the lifecycle callback directly from the mutex handoff path. The handoff only changes the blocked_donor relation, and the resulting STOP is reported when the execution task next enters __schedule(), with the rq lock held. It also preserves the previous effective root when an intermediate retained donor is promoted, so that root changes can be propagated to downstream execution owners. While working through this, I noticed that this is also the same area touched by Zhidao Su's pending proxy-walk cycle handling: https://lore.kernel.org/r/20260722120346.93000-1-soolaugust@gmail.com/ In particular, the new retained blocked_donor walks in this WIP assume an acyclic chain. Zhidao's work adds cycle detection in find_proxy_task(), while the new walks here rely on the retained blocked_donor chain being acyclic. The lifecycle handling therefore needs to be reconciled with that work before I consider the RT patch ready. There is a separate integration issue around patch 1 of this series: its task_tick() donor/curr ownership changes overlap with Andrea's pending sched_ext/proxy-execution work: https://lore.kernel.org/r/20260831134338.1531664-1-arighi@nvidia.com/ Rather than coupling that integration work with the independent RT/find_proxy_task() issue, I am inclined to make the next revision a four-patch series containing the current patches 1, 2, 3 and the core-slice patch (patch 5 in v4, renumbered as 4/4), while continuing the RT watchdog work separately. The task_tick()/sched_ext overlap can then be handled on its own without being tied to the RT lifecycle changes. In the meantime, I would appreciate any thoughts on the current RT lifecycle approach. I have included the current WIP diff below. It is not intended for merging as-is; in particular, I still need to reconcile the find_proxy_task() changes with the pending proxy-walk cycle handling. The WIP diff below is based on patches 1-3 of this series, so task_tick() already uses the updated callback interface. The main idea is: * watchdog accounting follows rq->curr while RT scheduling remains donor-owned; * blocking terminates the current watchdog interval; * START/STOP mark changes to the effective proxy scheduling context; * native RT-policy tasks keep their existing timeout semantics; * non-RT execution owners reset their timeout when entering or leaving an RT proxy context; * root changes in a retained proxy chain are propagated to downstream execution owners. If this direction looks reasonable, I will keep the RT work separate, reconcile it with the cycle handling, and let the remaining four-patch series continue without being tied to the RT lifecycle changes. Thanks, Hui --- kernel/sched/core.c | 138 ++++++++++++++++++++++++++++++++++++++++++++++++--- kernel/sched/rt.c | 35 ++++++++++++- kernel/sched/sched.h | 15 ++++++ 3 files changed, 180 insertions(+), 8 deletions(-) diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 05e599665fdd..57d24cbd8eea 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -4647,6 +4647,93 @@ static void __sched_fork(u64 clone_flags, struct task_struct *p) init_sched_mm(p); } +#ifdef CONFIG_SCHED_PROXY_EXEC +/* + * Follow the blocked_donor stack built by the current donor-pick cycle. + * The stack is stable while rq->lock is held. + */ +static inline struct task_struct * +proxy_root_donor(struct rq *rq, struct task_struct *exec) +{ + struct task_struct *donor = exec; + + lockdep_assert_rq_held(rq); + + while (donor->blocked_donor) + donor = donor->blocked_donor; + + return donor; +} + +/* Notify the classes owning the proxy scheduling and execution contexts. */ +static inline void proxy_event(struct rq *rq, struct task_struct *donor, + struct task_struct *exec, enum sched_proxy_event event) +{ + const struct sched_class *donor_class, *exec_class; + + lockdep_assert_rq_held(rq); + + donor_class = donor->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(rq, donor, exec, event); +} + +/* Promote @p from a retained proxy owner to the new scheduling root. */ +static inline struct task_struct * +proxy_promote_root(struct rq *rq, struct task_struct *p) +{ + struct task_struct *old_root = NULL; + + if (READ_ONCE(p->blocked_donor)) { + old_root = proxy_root_donor(rq, p); + + if (old_root != p) + proxy_event(rq, old_root, p, SCHED_PROXY_STOP); + else + old_root = NULL; + } + + p->blocked_donor = NULL; + + return old_root; +} + +#else +static inline struct task_struct * +proxy_root_donor(struct rq *rq, struct task_struct *exec) +{ + return exec; +} + +static inline void proxy_event(struct rq *rq, 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) {} +static inline struct task_struct * +proxy_promote_root(struct rq *rq, struct task_struct *p) +{ + return NULL; +} +#endif + DEFINE_STATIC_KEY_FALSE(sched_numa_balancing); #ifdef CONFIG_NUMA_BALANCING @@ -6768,6 +6855,9 @@ static bool try_to_block_task(struct rq *rq, struct task_struct *p, return false; } + if (sched_proxy_exec()) + proxy_event(rq, rq->donor, p, SCHED_PROXY_BLOCK); + p->is_blocked = 1; /* @@ -6925,11 +7015,12 @@ static void proxy_migrate_task(struct rq *rq, struct rq_flags *rf, * Returns the task that is going to be used as execution context (the one * that is actually going to be run on cpu_of(rq)). */ -static struct task_struct * -find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) +static struct task_struct *find_proxy_task(struct rq *rq, struct task_struct *donor, + struct task_struct *old_root, struct rq_flags *rf) __must_hold(__rq_lockp(rq)) { struct task_struct *owner = NULL; + bool context_changed = old_root != NULL; bool curr_in_chain = false; int this_cpu = cpu_of(rq); struct task_struct *p; @@ -7056,7 +7147,24 @@ 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) { + old_root = proxy_root_donor(rq, owner); + + if (old_root != owner) + proxy_event(rq, old_root, owner, SCHED_PROXY_STOP); + + owner->blocked_donor = p; + context_changed = true; + } else if (context_changed && old_root != owner) { + proxy_event(rq, old_root, owner, SCHED_PROXY_STOP); + } + if (context_changed) + proxy_event(rq, donor, owner, SCHED_PROXY_START); } WARN_ON_ONCE(owner && !owner->on_rq); return owner; @@ -7069,8 +7177,8 @@ find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) return NULL; } #else /* SCHED_PROXY_EXEC */ -static struct task_struct * -find_proxy_task(struct rq *rq, struct task_struct *donor, struct rq_flags *rf) +static struct task_struct *find_proxy_task(struct rq *rq, struct task_struct *donor, + struct task_struct *old_root, struct rq_flags *rf) { WARN_ONCE(1, "This should never be called in the !SCHED_PROXY_EXEC case\n"); return donor; @@ -7208,11 +7316,19 @@ static void __sched notrace __schedule(int sched_mode) rq->next_class = next->sched_class; if (sched_proxy_exec()) { struct task_struct *prev_donor = rq->donor; + struct task_struct *old_root; + + /* + * A mutex handoff may clear the backlink outside rq->lock. + * Report the resulting proxy STOP once serialized here. + */ + if (prev != prev_donor && !READ_ONCE(prev->blocked_donor)) + proxy_event(rq, prev_donor, prev, SCHED_PROXY_STOP); rq_set_donor(rq, next); - next->blocked_donor = NULL; + old_root = proxy_promote_root(rq, next); if (unlikely(next->is_blocked)) { - next = find_proxy_task(rq, next, &rf); + next = find_proxy_task(rq, next, old_root, &rf); if (!next) { zap_balance_callbacks(rq); goto pick_again; @@ -11277,6 +11393,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 +11455,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..14595f632b17 100644 --- a/kernel/sched/rt.c +++ b/kernel/sched/rt.c @@ -2530,6 +2530,36 @@ 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 +2580,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 +2655,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 f2f1e3642831..426cc466e813 100644 --- a/kernel/sched/sched.h +++ b/kernel/sched/sched.h @@ -2623,6 +2623,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 @@ -2721,6 +2727,15 @@ struct sched_class { * sched_tick_remote: rq->lock */ void (*task_tick)(struct rq *rq, int queued); +#ifdef CONFIG_SCHED_PROXY_EXEC + /* + * Proxy execution transitions. Callbacks run with the rq lock held and + * must not sleep. Donor and execution task lifetime is protected by the + * caller. + */ + 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 related [flat|nested] 16+ 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; 16+ 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 related [flat|nested] 16+ 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; 16+ 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 related [flat|nested] 16+ messages in thread
end of thread, other threads:[~2026-09-12 18:05 UTC | newest] Thread overview: 16+ 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 9:46 ` sashiko-bot 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-12 17:30 ` 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