* [PATCH v2 0/2] sched: Fix execution-context tick handling under proxy execution
@ 2026-09-03 4:11 Hui Su
2026-09-03 4:11 ` [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context Hui Su
2026-09-03 4:11 ` [PATCH v2 2/2] sched/cache: Drive cache " Hui Su
0 siblings, 2 replies; 15+ messages in thread
From: Hui Su @ 2026-09-03 4:11 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Juri Lelli, Vincent Guittot, Dietmar Eggemann, K Prateek Nayak,
Valentin Schneider, John Stultz, Chen Yu, Tim Chen, linux-kernel,
Hui Su
Proxy execution separates the scheduling context in rq->donor from the
execution context in rq->curr. sched_tick() dispatches the scheduling-class
task tick through rq->donor, while NUMA and cache task ticks consume state
belonging to the task actually executing.
Move both execution-context ticks to the scheduler core and run them when
rq->curr is a fair task. Keep the corresponding handling in
sched_tick_remote() for full-dynticks CPUs.
Changes in v2:
- Move NUMA and cache execution-context tick handling from task_tick_fair()
to sched_tick().
- Invoke the hooks when rq->curr is a fair task, allowing them to run when
the donor belongs to another scheduling class.
- Add the corresponding calls to sched_tick_remote() to preserve
full-dynticks behavior.
Tested with a QEMU topology providing two NUMA nodes and two LLCs. With
an RT donor and a fair mutex owner, task_tick_numa() and
task_tick_cache() were observed with p == rq->curr while rq->donor was
a different RT task. The proxy test completed three episodes without
warnings or errors.
Also runtime-tested a QEMU guest with nohz_full=1. sched_tick_remote()
ran approximately once per second on the full-dynticks CPU and entered
the fair execution-context tick path. The relevant configurations,
including NUMA/cache feature combinations and CONFIG_NO_HZ_FULL=y, were
build-tested; the affected objects also passed a W=1 build.
Link: https://lore.kernel.org/r/20260902163336.1552840-1-sh_def@163.com
Hui Su (2):
sched/numa: Drive NUMA task tick from execution context
sched/cache: Drive cache task tick from execution context
kernel/sched/core.c | 13 +++++++++++++
kernel/sched/fair.c | 13 ++++---------
kernel/sched/sched.h | 2 ++
3 files changed, 19 insertions(+), 9 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 4:11 [PATCH v2 0/2] sched: Fix execution-context tick handling under proxy execution Hui Su
@ 2026-09-03 4:11 ` Hui Su
2026-09-03 12:41 ` Chen, Yu C
2026-09-03 4:11 ` [PATCH v2 2/2] sched/cache: Drive cache " Hui Su
1 sibling, 1 reply; 15+ messages in thread
From: Hui Su @ 2026-09-03 4:11 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Juri Lelli, Vincent Guittot, Dietmar Eggemann, K Prateek Nayak,
Valentin Schneider, John Stultz, Chen Yu, Tim Chen, linux-kernel,
Hui Su
Proxy execution separates the scheduling context in rq->donor from the
execution context in rq->curr. sched_tick() invokes task_tick() for the
donor's scheduling class.
task_tick_numa() is currently called from task_tick_fair(). This works
when the donor is a fair task, but not when a fair task executes on
behalf of an RT or deadline donor. In that case the donor's task_tick()
still updates the execution task's sum_exec_runtime through
update_curr_common(), but task_tick_fair() is not invoked and NUMA scan
work for the execution task is not driven.
Move the NUMA tick handling one level up into sched_tick(), and invoke it
when the execution context is a fair task.
Do the same in sched_tick_remote() so full-dynticks CPUs continue to
receive NUMA tick handling after it is removed from task_tick_fair().
Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()")
Suggested-by: Tim Chen <tim.c.chen@linux.intel.com>
Signed-off-by: Hui Su <sh_def@163.com>
---
kernel/sched/core.c | 9 +++++++++
kernel/sched/fair.c | 7 ++-----
kernel/sched/sched.h | 1 +
3 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index f78275192036..15fcc218d2fe 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5794,6 +5794,11 @@ void sched_tick(void)
resched_curr(rq);
donor->sched_class->task_tick(rq, donor, 0);
+
+ if (rq->curr->sched_class == &fair_sched_class &&
+ static_branch_unlikely(&sched_numa_balancing))
+ task_tick_numa(rq, rq->curr);
+
if (sched_feat(LATENCY_WARN))
resched_latency = cpu_resched_latency(rq);
calc_global_load_tick(rq);
@@ -5891,6 +5896,10 @@ static void sched_tick_remote(struct work_struct *work)
}
curr->sched_class->task_tick(rq, curr, 0);
+ if (curr->sched_class == &fair_sched_class &&
+ static_branch_unlikely(&sched_numa_balancing))
+ task_tick_numa(rq, curr);
+
calc_load_nohz_remote(rq);
}
}
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..55f0460e4ae3 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -4425,7 +4425,7 @@ void init_numa_balancing(u64 clone_flags, struct task_struct *p)
/*
* Drive the periodic memory faults..
*/
-static void task_tick_numa(struct rq *rq, struct task_struct *curr)
+void task_tick_numa(struct rq *rq, struct task_struct *curr)
{
struct callback_head *work = &curr->numa_work;
u64 period, now;
@@ -4491,7 +4491,7 @@ static void update_scan_period(struct task_struct *p, int new_cpu)
#else /* !CONFIG_NUMA_BALANCING: */
-static void task_tick_numa(struct rq *rq, struct task_struct *curr)
+void task_tick_numa(struct rq *rq, struct task_struct *curr)
{
}
@@ -15042,9 +15042,6 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
if (queued)
return;
- if (static_branch_unlikely(&sched_numa_balancing))
- task_tick_numa(rq, curr);
-
task_tick_cache(rq, curr);
update_misfit_status(curr, rq);
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e656c7059bf8..4d619f272b15 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4152,6 +4152,7 @@ extern void sched_cache_active_set(void);
void sched_domains_free_llc_id(int cpu);
extern void init_sched_mm(struct task_struct *p);
+void task_tick_numa(struct rq *rq, struct task_struct *p);
extern u64 avg_vruntime(struct cfs_rq *cfs_rq);
extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se);
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v2 2/2] sched/cache: Drive cache task tick from execution context
2026-09-03 4:11 [PATCH v2 0/2] sched: Fix execution-context tick handling under proxy execution Hui Su
2026-09-03 4:11 ` [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context Hui Su
@ 2026-09-03 4:11 ` Hui Su
2026-09-03 4:37 ` K Prateek Nayak
2026-09-03 17:23 ` Tim Chen
1 sibling, 2 replies; 15+ messages in thread
From: Hui Su @ 2026-09-03 4:11 UTC (permalink / raw)
To: Peter Zijlstra, Ingo Molnar
Cc: Juri Lelli, Vincent Guittot, Dietmar Eggemann, K Prateek Nayak,
Valentin Schneider, John Stultz, Chen Yu, Tim Chen, linux-kernel,
Hui Su
Cache-aware scheduling accounts CPU runtime to the mm of the task
actually executing. update_se() passes the execution task to
account_mm_sched() for this purpose.
With proxy execution, however, sched_tick() invokes task_tick() for the
scheduling context in rq->donor. task_tick_cache() is currently called
from task_tick_fair(), so it is skipped when a fair task executes on
behalf of an RT or deadline donor.
In that case account_mm_sched() continues to advance runtime accounting
for rq->curr, while task_tick_cache() does not advance the corresponding
mm scan epoch. Once the epoch becomes stale, account_mm_sched() can
invalidate the mm's preferred LLC.
Move cache tick handling one level up into sched_tick(), alongside NUMA
tick handling, and run it when the execution context is a fair task.
Do the same in sched_tick_remote() so full-dynticks CPUs retain cache
tick handling after it is removed from task_tick_fair().
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/core.c | 16 ++++++++++------
kernel/sched/fair.c | 6 ++----
kernel/sched/sched.h | 1 +
3 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 15fcc218d2fe..dfef97ef8217 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -5795,9 +5795,11 @@ void sched_tick(void)
donor->sched_class->task_tick(rq, donor, 0);
- if (rq->curr->sched_class == &fair_sched_class &&
- static_branch_unlikely(&sched_numa_balancing))
- task_tick_numa(rq, rq->curr);
+ if (rq->curr->sched_class == &fair_sched_class) {
+ if (static_branch_unlikely(&sched_numa_balancing))
+ task_tick_numa(rq, rq->curr);
+ task_tick_cache(rq, rq->curr);
+ }
if (sched_feat(LATENCY_WARN))
resched_latency = cpu_resched_latency(rq);
@@ -5896,9 +5898,11 @@ static void sched_tick_remote(struct work_struct *work)
}
curr->sched_class->task_tick(rq, curr, 0);
- 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);
+ task_tick_cache(rq, curr);
+ }
calc_load_nohz_remote(rq);
}
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 55f0460e4ae3..f335479ba562 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -1774,7 +1774,7 @@ void account_mm_sched(struct rq *rq, struct task_struct *p, s64 delta_exec)
}
}
-static void task_tick_cache(struct rq *rq, struct task_struct *p)
+void task_tick_cache(struct rq *rq, struct task_struct *p)
{
struct callback_head *work = &p->cache_work;
struct mm_struct *mm = p->mm;
@@ -1996,7 +1996,7 @@ static inline void account_mm_sched(struct rq *rq, struct task_struct *p,
void init_sched_mm(struct task_struct *p) { }
-static void task_tick_cache(struct rq *rq, struct task_struct *p) { }
+void task_tick_cache(struct rq *rq, struct task_struct *p) { }
static inline int get_pref_llc(struct task_struct *p,
struct mm_struct *mm)
@@ -15042,8 +15042,6 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
if (queued)
return;
- task_tick_cache(rq, curr);
-
update_misfit_status(curr, rq);
check_update_overutilized_status(task_rq(curr));
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index 4d619f272b15..5d1f5ee47bf1 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -4153,6 +4153,7 @@ void sched_domains_free_llc_id(int cpu);
extern void init_sched_mm(struct task_struct *p);
void task_tick_numa(struct rq *rq, struct task_struct *p);
+void task_tick_cache(struct rq *rq, struct task_struct *p);
extern u64 avg_vruntime(struct cfs_rq *cfs_rq);
extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se);
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/2] sched/cache: Drive cache task tick from execution context
2026-09-03 4:11 ` [PATCH v2 2/2] sched/cache: Drive cache " Hui Su
@ 2026-09-03 4:37 ` K Prateek Nayak
2026-09-03 4:51 ` Hui Su
2026-09-03 17:23 ` Tim Chen
1 sibling, 1 reply; 15+ messages in thread
From: K Prateek Nayak @ 2026-09-03 4:37 UTC (permalink / raw)
To: Hui Su, Peter Zijlstra, Ingo Molnar
Cc: Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Valentin Schneider, John Stultz, Chen Yu, Tim Chen, linux-kernel
Hello Hui,
On 9/3/2026 9:41 AM, Hui Su wrote:
> curr->sched_class->task_tick(rq, curr, 0);
>
> - 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);
> + task_tick_cache(rq, curr);
> + }
nit.
Might be worthwhile to extract the above into a task_tick(rq, curr, donor)
helper instead of duplicating in two places.
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/2] sched/cache: Drive cache task tick from execution context
2026-09-03 4:37 ` K Prateek Nayak
@ 2026-09-03 4:51 ` Hui Su
0 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-03 4:51 UTC (permalink / raw)
To: K Prateek Nayak
Cc: Hui Su, Peter Zijlstra, Ingo Molnar, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Chen Yu,
Tim Chen, linux-kernel
Hello Prateek,
On 9/3/2026, K Prateek Nayak wrote:
> nit.
>
> Might be worthwhile to extract the above into a task_tick(rq, curr, donor)
> helper instead of duplicating in two places.
Agreed. A helper would also make the scheduling-context versus
execution-context split explicit and keep sched_tick() and
sched_tick_remote() in sync.
I'll fold this into the next revision, keeping the donor scheduling-class
tick before the fair execution-context NUMA/cache hooks.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 4:11 ` [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context Hui Su
@ 2026-09-03 12:41 ` Chen, Yu C
2026-09-03 21:30 ` Tim Chen
0 siblings, 1 reply; 15+ messages in thread
From: Chen, Yu C @ 2026-09-03 12:41 UTC (permalink / raw)
To: Hui Su
Cc: K Prateek Nayak, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Valentin Schneider, John Stultz, Ingo Molnar, Tim Chen,
Peter Zijlstra, linux-kernel, chen.yu
Hi Su,
On 9/3/2026 12:11 PM, Hui Su wrote:
> Proxy execution separates the scheduling context in rq->donor from the
> execution context in rq->curr. sched_tick() invokes task_tick() for the
> donor's scheduling class.
>
> task_tick_numa() is currently called from task_tick_fair(). This works
> when the donor is a fair task, but not when a fair task executes on
> behalf of an RT or deadline donor. In that case the donor's task_tick()
> still updates the execution task's sum_exec_runtime through
> update_curr_common(), but task_tick_fair() is not invoked and NUMA scan
> work for the execution task is not driven.
Thanks for bringing this up. Previously Prateek has suggested to fix the
rq->donor
issue [1] and unfortunately I missed the task_tick_cache() part.
Regarding above line in the commit log, although I agree that
task_tick_numa()
should be moved one level up, I did not quite get the reason why
sum_exec_runtime
is mentioned here, could you please elaborate a little more?
I guess what you mean is that, in task_tick_numa(), the
curr->se.sum_exec_runtime
is used to check if there is a timeout to launch the task_numa_work(), so
curr->se.sum_exec_runtime has to be up-to-date. With proxy execution, the
se.sum_exec_runtime is only accumulated in rq->curr rather than rq->donor,
so passing a "paused" rq->donor.sum_exec_runtime to task_tick_numa() is
inaccurate?
But I also see that in task_tick_core(), the sum_exec_runtime is also
leveraged
to calculate the delta "wall time" via __entity_slice_used():
se->sum_exec_runtime - se->prev_sum_exec_runtime
does it mean task_tick_core() also needs to be bring one level up to
sched_tick()
and passed with rq->curr?
On the other hand, as Prateek mentioned in [1], it seems that
sum_exec_runtime
might not the reason for passing rq->curr, but it could be:
"with "rq->curr->mm" being the one that is being used on CPU",
both sched_cache and NUMA balance fit Prateek's conclusion.
thanks,
Chenyu
[1]
https://lore.kernel.org/lkml/78c81f74-7b27-4f28-9ca2-0d1e27ed9c56@amd.com/
>
> Move the NUMA tick handling one level up into sched_tick(), and invoke it
> when the execution context is a fair task.
>
> Do the same in sched_tick_remote() so full-dynticks CPUs continue to
> receive NUMA tick handling after it is removed from task_tick_fair().
>
> Fixes: 7de9d4f94638 ("sched: Start blocked_on chain processing in find_proxy_task()")
> Suggested-by: Tim Chen <tim.c.chen@linux.intel.com>
> Signed-off-by: Hui Su <sh_def@163.com>
> ---
> kernel/sched/core.c | 9 +++++++++
> kernel/sched/fair.c | 7 ++-----
> kernel/sched/sched.h | 1 +
> 3 files changed, 12 insertions(+), 5 deletions(-)
>
> diff --git a/kernel/sched/core.c b/kernel/sched/core.c
> index f78275192036..15fcc218d2fe 100644
> --- a/kernel/sched/core.c
> +++ b/kernel/sched/core.c
> @@ -5794,6 +5794,11 @@ void sched_tick(void)
> resched_curr(rq);
>
> donor->sched_class->task_tick(rq, donor, 0);
> +
> + if (rq->curr->sched_class == &fair_sched_class &&
> + static_branch_unlikely(&sched_numa_balancing))
> + task_tick_numa(rq, rq->curr);
> +
> if (sched_feat(LATENCY_WARN))
> resched_latency = cpu_resched_latency(rq);
> calc_global_load_tick(rq);
> @@ -5891,6 +5896,10 @@ static void sched_tick_remote(struct work_struct *work)
> }
> curr->sched_class->task_tick(rq, curr, 0);
>
> + if (curr->sched_class == &fair_sched_class &&
> + static_branch_unlikely(&sched_numa_balancing))
> + task_tick_numa(rq, curr);
> +
> calc_load_nohz_remote(rq);
> }
> }
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 8dff37059faf..55f0460e4ae3 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -4425,7 +4425,7 @@ void init_numa_balancing(u64 clone_flags, struct task_struct *p)
> /*
> * Drive the periodic memory faults..
> */
> -static void task_tick_numa(struct rq *rq, struct task_struct *curr)
> +void task_tick_numa(struct rq *rq, struct task_struct *curr)
> {
> struct callback_head *work = &curr->numa_work;
> u64 period, now;
> @@ -4491,7 +4491,7 @@ static void update_scan_period(struct task_struct *p, int new_cpu)
>
> #else /* !CONFIG_NUMA_BALANCING: */
>
> -static void task_tick_numa(struct rq *rq, struct task_struct *curr)
> +void task_tick_numa(struct rq *rq, struct task_struct *curr)
> {
> }
>
> @@ -15042,9 +15042,6 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
> if (queued)
> return;
>
> - if (static_branch_unlikely(&sched_numa_balancing))
> - task_tick_numa(rq, curr);
> -
> task_tick_cache(rq, curr);
>
> update_misfit_status(curr, rq);
> diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
> index e656c7059bf8..4d619f272b15 100644
> --- a/kernel/sched/sched.h
> +++ b/kernel/sched/sched.h
> @@ -4152,6 +4152,7 @@ extern void sched_cache_active_set(void);
> void sched_domains_free_llc_id(int cpu);
>
> extern void init_sched_mm(struct task_struct *p);
> +void task_tick_numa(struct rq *rq, struct task_struct *p);
>
> extern u64 avg_vruntime(struct cfs_rq *cfs_rq);
> extern int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se);
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/2] sched/cache: Drive cache task tick from execution context
2026-09-03 4:11 ` [PATCH v2 2/2] sched/cache: Drive cache " Hui Su
2026-09-03 4:37 ` K Prateek Nayak
@ 2026-09-03 17:23 ` Tim Chen
2026-09-04 4:03 ` Hui Su
1 sibling, 1 reply; 15+ messages in thread
From: Tim Chen @ 2026-09-03 17:23 UTC (permalink / raw)
To: Hui Su, Peter Zijlstra, Ingo Molnar
Cc: Juri Lelli, Vincent Guittot, Dietmar Eggemann, K Prateek Nayak,
Valentin Schneider, John Stultz, Chen Yu, linux-kernel
On Thu, 2026-09-03 at 12:11 +0800, Hui Su wrote:
>
[snip]
> void init_sched_mm(struct task_struct *p) { }
>
> -static void task_tick_cache(struct rq *rq, struct task_struct *p) { }
> +void task_tick_cache(struct rq *rq, struct task_struct *p) { }
>
> static inline int get_pref_llc(struct task_struct *p,
> struct mm_struct *mm)
> @@ -15042,8 +15042,6 @@ static void task_tick_fair(struct rq *rq, struct task_struct *curr, int queued)
> if (queued)
> return;
>
> - task_tick_cache(rq, curr);
> -
May be worth adding a comment here. Say you consolidate the
exec context stuff to a new sched_tick_exec_ctx() helper.
You can say something like the following here to help future
reader of this code.
+ /*
+ * Note: misfit, overutilized and core scheduling state describe the
+ * entity the load balancer would move, i.e. the scheduling context,
+ * and therefore stay with @curr rather than with rq->curr. See
+ * sched_tick_exec_ctx() for the execution context counterpart.
+ */
> update_misfit_status(curr, rq);
> check_update_overutilized_status(task_rq(curr));
Tim
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 12:41 ` Chen, Yu C
@ 2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
` (2 more replies)
0 siblings, 3 replies; 15+ messages in thread
From: Tim Chen @ 2026-09-03 21:30 UTC (permalink / raw)
To: Chen, Yu C, Hui Su
Cc: K Prateek Nayak, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Valentin Schneider, John Stultz, Ingo Molnar, Peter Zijlstra,
linux-kernel, chen.yu
On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
> Hi Su,
>
> On 9/3/2026 12:11 PM, Hui Su wrote:
> > Proxy execution separates the scheduling context in rq->donor from the
> > execution context in rq->curr. sched_tick() invokes task_tick() for the
> > donor's scheduling class.
> >
> > task_tick_numa() is currently called from task_tick_fair(). This works
> > when the donor is a fair task, but not when a fair task executes on
> > behalf of an RT or deadline donor. In that case the donor's task_tick()
> > still updates the execution task's sum_exec_runtime through
> > update_curr_common(), but task_tick_fair() is not invoked and NUMA scan
> > work for the execution task is not driven.
>
> Thanks for bringing this up. Previously Prateek has suggested to fix the
> rq->donor
> issue [1] and unfortunately I missed the task_tick_cache() part.
>
> Regarding above line in the commit log, although I agree that
> task_tick_numa()
> should be moved one level up, I did not quite get the reason why
> sum_exec_runtime
> is mentioned here, could you please elaborate a little more?
> I guess what you mean is that, in task_tick_numa(), the
> curr->se.sum_exec_runtime
> is used to check if there is a timeout to launch the task_numa_work(), so
> curr->se.sum_exec_runtime has to be up-to-date. With proxy execution, the
> se.sum_exec_runtime is only accumulated in rq->curr rather than rq->donor,
> so passing a "paused" rq->donor.sum_exec_runtime to task_tick_numa() is
> inaccurate?
>
> But I also see that in task_tick_core(), the sum_exec_runtime is also
> leveraged
> to calculate the delta "wall time" via __entity_slice_used():
> se->sum_exec_runtime - se->prev_sum_exec_runtime
> does it mean task_tick_core() also needs to be bring one level up to
> sched_tick()
> and passed with rq->curr?
I think task_tick_core() needs to stay with the donor's context
as it is the scheduling context.
task_tick_core() is not about the execution context --
it decides whether the current scheduling context has used
up enough of its slice to let a force-idled SMT sibling run. That
slice belongs to the donor, so the donor is the right task to pass.
There is a separate issue lurking here, task_tick_core() measures consumed slice as
se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
donor's sum_exec_runtime does not advance (update_se() charges the
runtime to rq->curr instead), so that delta stays near zero and the
force-idle resched may never trigger.
Passing rq->curr does not fix it either. __entity_slice_used() takes
the runtime and the slice from the same entity, so passing rq->curr
just compares the running task against its own slice. But this check
is about the donor: it asks whether the scheduling context that owns
the CPU has used up its slice. The running task is only borrowing the
CPU through proxy, so its slice is not the one we care about here.
Maybe something like below (only compile tested) to fix the issue.
That said, this is somewhat orthogonal to the issue that the execution context
series is trying to solve. It should be fixed separately.
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index 8dff37059faf..cd240bf52d03 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -14748,10 +14748,29 @@ 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 vslice, vused;
- return (rtime * min_nr_tasks > slice);
+ /*
+ * @se is the scheduling context (rq->donor). Under proxy execution
+ * it need not be the task executing on the CPU, so its
+ * sum_exec_runtime is not advanced and cannot be used to tell how
+ * much of its slice it has consumed. Its vruntime, however, is
+ * advanced by update_curr() with the proxy runtime, and its EEVDF
+ * deadline reflects the granted slice, so measure the consumed
+ * fraction in virtual time instead.
+ *
+ * This is equivalent to the previous real-time comparison in the
+ * non-proxy case: both @vused and @vslice are scaled by the same
+ * weight factor, so the ratio (and thus the min_nr_tasks test) is
+ * unchanged.
+ */
+ if (vruntime_cmp(se->vruntime, ">=", se->deadline))
+ return true;
+
+ vslice = calc_delta_fair(se->slice, se);
+ vused = vslice - (se->deadline - se->vruntime);
+
+ return (vused * min_nr_tasks > vslice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
>
> On the other hand, as Prateek mentioned in [1], it seems that
> sum_exec_runtime
> might not the reason for passing rq->curr, but it could be:
> "with "rq->curr->mm" being the one that is being used on CPU",
> both sched_cache and NUMA balance fit Prateek's conclusion.
I agree with you on this.
Thanks.
Tim
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 2/2] sched/cache: Drive cache task tick from execution context
2026-09-03 17:23 ` Tim Chen
@ 2026-09-04 4:03 ` Hui Su
0 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-04 4:03 UTC (permalink / raw)
To: Tim Chen, Peter Zijlstra, Ingo Molnar
Cc: Hui Su, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
K Prateek Nayak, Valentin Schneider, John Stultz, Chen Yu,
linux-kernel
On Thu, Sep 3, 2026 at 10:23 AM, Tim Chen wrote:
> May be worth adding a comment here. Say you consolidate the
> exec context stuff to a new sched_tick_exec_ctx() helper.
> You can say something like the following here to help future
> reader of this code.
>
> + /*
> + * Note: misfit, overutilized and core scheduling state describe the
> + * entity the load balancer would move, i.e. the scheduling context,
> + * and therefore stay with @curr rather than with rq->curr. See
> + * sched_tick_exec_ctx() for the execution context counterpart.
> + */
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> There is a separate issue lurking here, task_tick_core() measures
> consumed slice as se->sum_exec_runtime - se->prev_sum_exec_runtime.
> Under proxy the donor's sum_exec_runtime does not advance, so that
> delta stays near zero and the force-idle resched may never trigger.
> Passing rq->curr does not fix it either. This is somewhat orthogonal
> to the issue that the execution context series is trying to solve.
> It should be fixed separately.
Hi Tim,
Thanks. I'll consolidate the NUMA/cache execution-context handling into
a sched_tick_exec_ctx() helper and use it from both sched_tick() and
sched_tick_remote().
I'll also add a comment in task_tick_fair() explaining that misfit,
overutilized and core-scheduling state remain associated with the
scheduling context.
I'll fold these changes into v3.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
@ 2026-09-04 5:16 ` Hui Su
2026-09-04 14:10 ` Hui Su
2026-09-04 16:10 ` Chen Yu
2 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-04 5:16 UTC (permalink / raw)
To: Tim Chen, Chen Yu
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Thu, Sep 3, 2026 at 2:30 PM, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
> > Thanks for bringing this up. Previously Prateek has suggested to fix the
> > rq->donor issue [1] and unfortunately I missed the task_tick_cache() part.
> >
> > Regarding above line in the commit log, although I agree that
> > task_tick_numa() should be moved one level up, I did not quite get the
> > reason why sum_exec_runtime is mentioned here?
> > I guess what you mean is that, in task_tick_numa(), the
> > curr->se.sum_exec_runtime is used to check if there is a timeout to launch
> > the task_numa_work(), so curr->se.sum_exec_runtime has to be up-to-date.
> > With proxy execution, the se.sum_exec_runtime is only accumulated in
> > rq->curr rather than rq->donor, so passing a "paused"
> > rq->donor.sum_exec_runtime to task_tick_numa() is inaccurate?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context -- it decides
> whether the current scheduling context has used up enough of its slice
> to let a force-idled SMT sibling run. That slice belongs to the donor,
> so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures
> consumed slice as se->sum_exec_runtime - se->prev_sum_exec_runtime.
> Under proxy the donor's sum_exec_runtime does not advance, so that
> delta stays near zero and the force-idle resched may never trigger.
> Passing rq->curr does not fix it either. This is somewhat orthogonal
> to the issue that the execution context series is trying to solve.
> It should be fixed separately.
>
> I agree with you on this.
Hi Tim, Chenyu,
Thanks for the clarification.
The sum_exec_runtime paragraph was intended to point out that
task_tick_numa() uses execution runtime to drive NUMA scanning, but I
agree that this is too narrow. The more general reason is that
task_tick_numa() operates on execution-context state, including the task
and mm associated with the execution context. I'll reword the changelog
accordingly in v3.
For task_tick_core(), I agree that it should remain associated with the
donor, since the slice being evaluated belongs to the scheduling
context. The fact that the donor's sum_exec_runtime does not advance
under proxy execution is a separate issue, so I won't fold that into
this series.
I'll test the virtual-time prototype separately and follow up on that
issue independently if the approach holds up.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
@ 2026-09-04 14:10 ` Hui Su
2026-09-04 20:15 ` Tim Chen
2026-09-04 16:10 ` Chen Yu
2 siblings, 1 reply; 15+ messages in thread
From: Hui Su @ 2026-09-04 14:10 UTC (permalink / raw)
To: Tim Chen, Chen, Yu C
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel
On Thu, 2026-09-03 at 14:30 -0700, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
> > Hi Su,
> >
> > On 9/3/2026 12:11 PM, Hui Su wrote:
> > > Proxy execution separates the scheduling context in rq->donor from the
> > > execution context in rq->curr. sched_tick() invokes task_tick() for the
> > > donor's scheduling class.
> > >
> > > task_tick_numa() is currently called from task_tick_fair(). This works
> > > when the donor is a fair task, but not when a fair task executes on
> > > behalf of an RT or deadline donor. In that case the donor's task_tick()
> > > still updates the execution task's sum_exec_runtime through
> > > update_curr_common(), but task_tick_fair() is not invoked and NUMA scan
> > > work for the execution task is not driven.
> >
> > Thanks for bringing this up. Previously Prateek has suggested to fix the
> > rq->donor issue [1] and unfortunately I missed the task_tick_cache() part.
> >
> > Regarding above line in the commit log, although I agree that
> > task_tick_numa() should be moved one level up, I did not quite get the
> > reason why sum_exec_runtime is mentioned here, could you please elaborate
> > a little more?
> > I guess what you mean is that, in task_tick_numa(), the
> > curr->se.sum_exec_runtime is used to check if there is a timeout to launch
> > the task_numa_work(), so curr->se.sum_exec_runtime has to be up-to-date.
> > With proxy execution, the se.sum_exec_runtime is only accumulated in
> > rq->curr rather than rq->donor, so passing a "paused" rq->donor.sum_exec_runtime
> > to task_tick_numa() is inaccurate?
> >
> > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > leveraged to calculate the delta "wall time" via __entity_slice_used():
> > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > does it mean task_tick_core() also needs to be bring one level up to
> > sched_tick() and passed with rq->curr?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context --
> it decides whether the current scheduling context has used
> up enough of its slice to let a force-idled SMT sibling run. That
> slice belongs to the donor, so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures consumed slice as
> se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> donor's sum_exec_runtime does not advance (update_se() charges the
> runtime to rq->curr instead), so that delta stays near zero and the
> force-idle resched may never trigger.
>
> Passing rq->curr does not fix it either. __entity_slice_used() takes
> the runtime and the slice from the same entity, so passing rq->curr
> just compares the running task against its own slice. But this check
> is about the donor: it asks whether the scheduling context that owns
> the CPU has used up its slice. The running task is only borrowing the
> CPU through proxy, so its slice is not the one we care about here.
>
> Maybe something like below (only compile tested) to fix the issue.
> That said, this is somewhat orthogonal to the issue that the execution
> context series is trying to solve. It should be fixed separately.
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 8dff37059faf..cd240bf52d03 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -14748,10 +14748,29 @@ 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 vslice, vused;
>
> - return (rtime * min_nr_tasks > slice);
> + /*
> + * @se is the scheduling context (rq->donor). Under proxy execution
> + * it need not be the task executing on the CPU, so its
> + * sum_exec_runtime is not advanced and cannot be used to tell how
> + * much of its slice it has consumed. Its vruntime, however, is
> + * advanced by update_curr() with the proxy runtime, and its EEVDF
> + * deadline reflects the granted slice, so measure the consumed
> + * fraction in virtual time instead.
> + *
> + * This is equivalent to the previous real-time comparison in the
> + * non-proxy case: both @vused and @vslice are scaled by the same
> + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> + * unchanged.
> + */
> + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> + return true;
> +
> + vslice = calc_delta_fair(se->slice, se);
> + vused = vslice - (se->deadline - se->vruntime);
> +
> + return (vused * min_nr_tasks > vslice);
> }
>
> Thanks.
> Tim
Hi Tim, Chen Yu,
Thanks for pointing out this separate issue and for the prototype.
I tested the deadline-based calculation from the prototype with the
same proxy-execution and core-scheduling reproducer. The reproducer has
a FAIR donor proxy-executing a task on an SMT CPU while the sibling is
force-idled.
The relevant ordering in the current code is:
update_curr()
-> vruntime += delta
-> update_deadline()
...
task_tick_core()
When update_deadline() advances the deadline before
__entity_slice_used() is called, the prototype observes the newly
advanced deadline. Since the new deadline is based on the current
vruntime plus a new virtual slice, deadline - vruntime is reset to
approximately vslice. Consequently, the reconstructed vused becomes
small even though the donor has consumed scheduling service since it was
selected.
In the CONFIG_HZ=1000, nice-0 run, instrumentation showed:
existing runtime delta == 0
vslice == 2100000
reconstructed vused in the tens or hundreds of thousands
used == 0
For comparison, I tested a selection-time vruntime snapshot:
set_next_entity():
core_prev_vruntime = se->vruntime;
__entity_slice_used():
vused = se->vruntime - se->core_prev_vruntime;
I then repeated the test with CONFIG_HZ=1000, 250 and 100, and also
with a nice -10 FAIR donor at HZ=250 and HZ=100.
For the counts below I only included ticks where rq->donor != rq->curr,
core force-idle was active, rq->cfs.h_nr_queued == 1, and the donor's
existing runtime delta == 0.
The observed behavior was consistent across these runs:
configuration deadline prototype vruntime snapshot
HZ=1000, nice 0 10/10 used=0 8/8 used=1
HZ=250, nice 0 10/10 used=0 6/6 used=1
HZ=100, nice 0 14/14 used=0 16/16 used=1
HZ=250, nice -10 30/30 used=0 6/6 used=1
HZ=100, nice -10 15/15 used=0 17/17 used=1
The number of ticks in each window is timing-dependent and can change
when a successful slice check triggers rescheduling. The comparison
above is based on the per-tick result, rather than on equal window
lengths.
I also compared the existing runtime-based predicate with the snapshot
predicate on the non-proxy path. In a CONFIG_HZ=1000 run, the decisions
matched for all 83 observed force-idle ticks with rq->donor == rq->curr.
For all 177 matching proxy ticks in the same run, the existing predicate
was false while the snapshot predicate was true.
In these runs, the snapshot version tracked the donor's vruntime
progress across deadline rollovers and triggered the force-idle
reschedule. It returned used == 1 for every matching tick observed in
the proxy windows listed above. This also matches the previous
sum_exec_runtime - prev_sum_exec_runtime semantics more closely: the
measurement starts when the scheduling context is selected and is not
tied to the current EEVDF request after a deadline rollover.
These tests suggest that reconstructing the consumed service from the
current deadline may lose the original selection-time semantics across
a deadline rollover. The virtual-time direction still looks
appropriate, while the consumed service appears to need a
selection-time baseline rather than being reconstructed from a
deadline that update_deadline() may already have advanced.
I am keeping this as a separate patch from the execution-context tick
series. I will continue validating the snapshot approach with
fair-group scheduling before posting it.
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
2026-09-04 14:10 ` Hui Su
@ 2026-09-04 16:10 ` Chen Yu
2026-09-04 20:24 ` Tim Chen
2 siblings, 1 reply; 15+ messages in thread
From: Chen Yu @ 2026-09-04 16:10 UTC (permalink / raw)
To: Tim Chen
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Thu, Sep 03, 2026 at 02:30:25PM -0700, Tim Chen wrote:
> On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
[ ... ]
> > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > leveraged
> > to calculate the delta "wall time" via __entity_slice_used():
> > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > does it mean task_tick_core() also needs to be bring one level up to
> > sched_tick()
> > and passed with rq->curr?
>
> I think task_tick_core() needs to stay with the donor's context
> as it is the scheduling context.
>
> task_tick_core() is not about the execution context --
> it decides whether the current scheduling context has used
> up enough of its slice to let a force-idled SMT sibling run. That
> slice belongs to the donor, so the donor is the right task to pass.
>
> There is a separate issue lurking here, task_tick_core() measures consumed slice as
> se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> donor's sum_exec_runtime does not advance (update_se() charges the
> runtime to rq->curr instead), so that delta stays near zero and the
> force-idle resched may never trigger.
>
> Passing rq->curr does not fix it either. __entity_slice_used() takes
> the runtime and the slice from the same entity, so passing rq->curr
> just compares the running task against its own slice. But this check
> is about the donor: it asks whether the scheduling context that owns
> the CPU has used up its slice. The running task is only borrowing the
> CPU through proxy, so its slice is not the one we care about here.
>
Got it, I see.
> Maybe something like below (only compile tested) to fix the issue.
> That said, this is somewhat orthogonal to the issue that the execution context
> series is trying to solve. It should be fixed separately.
>
> diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> index 8dff37059faf..cd240bf52d03 100644
> --- a/kernel/sched/fair.c
> +++ b/kernel/sched/fair.c
> @@ -14748,10 +14748,29 @@ 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 vslice, vused;
>
> - return (rtime * min_nr_tasks > slice);
> + /*
> + * @se is the scheduling context (rq->donor). Under proxy execution
> + * it need not be the task executing on the CPU, so its
> + * sum_exec_runtime is not advanced and cannot be used to tell how
> + * much of its slice it has consumed. Its vruntime, however, is
> + * advanced by update_curr() with the proxy runtime, and its EEVDF
> + * deadline reflects the granted slice, so measure the consumed
> + * fraction in virtual time instead.
> + *
> + * This is equivalent to the previous real-time comparison in the
> + * non-proxy case: both @vused and @vslice are scaled by the same
> + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> + * unchanged.
> + */
> + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> + return true;
> +
> + vslice = calc_delta_fair(se->slice, se);
> + vused = vslice - (se->deadline - se->vruntime);
> +
> + return (vused * min_nr_tasks > vslice);
> }
This fix looks good to me. And just one minor question that I'm
trying to figure out:
Consider that there is only one running task p on one of the SMT siblings.
The original comparison is between:
se->sum_exec_runtime - se->prev_sum_exec_runtime vs slice,
and since there is only one runnable task, p continues to run
without any preemption, so se->prev_sum_exec_runtime remains
unchanged, while se->sum_exec_runtime moves forward. Therefore,
the duration delta of se->sum_exec_runtime - se->prev_sum_exec_runtime
could expand to many slices in theory.
After switching to the vruntime-based comparison, even
if p has not been preempted, se->deadline together with se->vruntime
will move forward by update_dealine(). That is to say, we now only
consider the delta within one slice. This seems to tighten the
restriction for task_tick_core() to trigger a force reschedule.
But Overall I think it should not be a good deal to check
within a slice period.
thanks,
Chenyu
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 14:10 ` Hui Su
@ 2026-09-04 20:15 ` Tim Chen
2026-09-05 13:58 ` Hui Su
0 siblings, 1 reply; 15+ messages in thread
From: Tim Chen @ 2026-09-04 20:15 UTC (permalink / raw)
To: Hui Su, Chen, Yu C
Cc: K Prateek Nayak, Juri Lelli, Vincent Guittot, Dietmar Eggemann,
Valentin Schneider, John Stultz, Ingo Molnar, Peter Zijlstra,
linux-kernel
On Fri, 2026-09-04 at 22:10 +0800, Hui Su wrote:
[...]
>
> Hi Tim, Chen Yu,
>
> Thanks for pointing out this separate issue and for the prototype.
>
> I tested the deadline-based calculation from the prototype with the
> same proxy-execution and core-scheduling reproducer. The reproducer has
> a FAIR donor proxy-executing a task on an SMT CPU while the sibling is
> force-idled.
>
> The relevant ordering in the current code is:
>
> update_curr()
> -> vruntime += delta
> -> update_deadline()
> ...
> task_tick_core()
>
> When update_deadline() advances the deadline before
> __entity_slice_used() is called, the prototype observes the newly
> advanced deadline. Since the new deadline is based on the current
> vruntime plus a new virtual slice, deadline - vruntime is reset to
> approximately vslice. Consequently, the reconstructed vused becomes
> small even though the donor has consumed scheduling service since it was
> selected.
Yes, you have a good point. The code I proposed just look at whether
we have consumed our allotment in the current slice.
What we should have looked at is whether the donor's total run time has
exceeded its quota when doing core scheduling. And we may happen to
hit __entity_slice_used() at the front of the slice after advancing
the deadline and __entity_slice_used()
returns false instead of true, even though I have consumed more than
my fair share when looking at longer time period across multiple slices.
This breaks the lone task case that Chen Yu has raised as it could
just keep running as __entity_slice_used() returns false.
>
> In the CONFIG_HZ=1000, nice-0 run, instrumentation showed:
>
> existing runtime delta == 0
> vslice == 2100000
> reconstructed vused in the tens or hundreds of thousands
> used == 0
>
> For comparison, I tested a selection-time vruntime snapshot:
>
> set_next_entity():
> core_prev_vruntime = se->vruntime;
>
> __entity_slice_used():
> vused = se->vruntime - se->core_prev_vruntime;
>
> I then repeated the test with CONFIG_HZ=1000, 250 and 100, and also
> with a nice -10 FAIR donor at HZ=250 and HZ=100.
>
> For the counts below I only included ticks where rq->donor != rq->curr,
> core force-idle was active, rq->cfs.h_nr_queued == 1, and the donor's
> existing runtime delta == 0.
>
> The observed behavior was consistent across these runs:
>
> configuration deadline prototype vruntime snapshot
> HZ=1000, nice 0 10/10 used=0 8/8 used=1
> HZ=250, nice 0 10/10 used=0 6/6 used=1
> HZ=100, nice 0 14/14 used=0 16/16 used=1
> HZ=250, nice -10 30/30 used=0 6/6 used=1
> HZ=100, nice -10 15/15 used=0 17/17 used=1
>
> The number of ticks in each window is timing-dependent and can change
> when a successful slice check triggers rescheduling. The comparison
> above is based on the per-tick result, rather than on equal window
> lengths.
>
> I also compared the existing runtime-based predicate with the snapshot
> predicate on the non-proxy path. In a CONFIG_HZ=1000 run, the decisions
> matched for all 83 observed force-idle ticks with rq->donor == rq->curr.
> For all 177 matching proxy ticks in the same run, the existing predicate
> was false while the snapshot predicate was true.
>
> In these runs, the snapshot version tracked the donor's vruntime
> progress across deadline rollovers and triggered the force-idle
> reschedule. It returned used == 1 for every matching tick observed in
> the proxy windows listed above. This also matches the previous
> sum_exec_runtime - prev_sum_exec_runtime semantics more closely: the
> measurement starts when the scheduling context is selected and is not
> tied to the current EEVDF request after a deadline rollover.
>
> These tests suggest that reconstructing the consumed service from the
> current deadline may lose the original selection-time semantics across
> a deadline rollover. The virtual-time direction still looks
> appropriate, while the consumed service appears to need a
> selection-time baseline rather than being reconstructed from a
> deadline that update_deadline() may already have advanced.
The accumulated run time of the donor since it was picked for running
should be used for selection time baseline.
So maybe a patch like the following instead.
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 8b3d47a325cc..bf105f436808 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -590,6 +590,10 @@ struct sched_entity {
u64 sum_exec_runtime;
u64 prev_sum_exec_runtime;
u64 vruntime;
+#ifdef CONFIG_SCHED_CORE
+ /* vruntime at the last pick, for the force-idle slice check: */
+ u64 core_slice_vruntime;
+#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 f78275192036..dde1f45051e3 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4580,6 +4580,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_slice_vruntime = 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 8dff37059faf..ee06ee62e8dc 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,9 @@ 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
+ se->core_slice_vruntime = se->vruntime;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14748,10 +14751,21 @@ 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 vused, vslice;
+
+ /*
+ * @se is the scheduling context (rq->donor), which under proxy
+ * execution may not be the running task; its sum_exec_runtime is then
+ * not advanced. Use vruntime instead -- update_curr() advances it with
+ * the proxy runtime -- measured from a baseline taken at pick time in
+ * set_next_entity(). Being pick-based rather than per-slice, it stays
+ * correct when the tick period exceeds the slice, and matches the old
+ * rtime/slice test in the non-proxy case (same weight scaling).
+ */
+ vused = se->vruntime - se->core_slice_vruntime;
+ vslice = calc_delta_fair(se->slice, se);
- return (rtime * min_nr_tasks > slice);
+ return (vused * min_nr_tasks > vslice);
}
#define MIN_NR_TASKS_DURING_FORCEIDLE 2
base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
>
> I am keeping this as a separate patch from the execution-context tick
> series. I will continue validating the snapshot approach with
> fair-group scheduling before posting it.
Tim
>
> Thanks,
> Hui
>
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 16:10 ` Chen Yu
@ 2026-09-04 20:24 ` Tim Chen
0 siblings, 0 replies; 15+ messages in thread
From: Tim Chen @ 2026-09-04 20:24 UTC (permalink / raw)
To: Chen Yu
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel, chen.yu
On Sat, 2026-09-05 at 00:10 +0800, Chen Yu wrote:
> On Thu, Sep 03, 2026 at 02:30:25PM -0700, Tim Chen wrote:
> > On Thu, 2026-09-03 at 20:41 +0800, Chen, Yu C wrote:
>
> [ ... ]
>
> > > But I also see that in task_tick_core(), the sum_exec_runtime is also
> > > leveraged
> > > to calculate the delta "wall time" via __entity_slice_used():
> > > se->sum_exec_runtime - se->prev_sum_exec_runtime
> > > does it mean task_tick_core() also needs to be bring one level up to
> > > sched_tick()
> > > and passed with rq->curr?
> >
> > I think task_tick_core() needs to stay with the donor's context
> > as it is the scheduling context.
> >
> > task_tick_core() is not about the execution context --
> > it decides whether the current scheduling context has used
> > up enough of its slice to let a force-idled SMT sibling run. That
> > slice belongs to the donor, so the donor is the right task to pass.
> >
> > There is a separate issue lurking here, task_tick_core() measures consumed slice as
> > se->sum_exec_runtime - se->prev_sum_exec_runtime. Under proxy the
> > donor's sum_exec_runtime does not advance (update_se() charges the
> > runtime to rq->curr instead), so that delta stays near zero and the
> > force-idle resched may never trigger.
> >
> > Passing rq->curr does not fix it either. __entity_slice_used() takes
> > the runtime and the slice from the same entity, so passing rq->curr
> > just compares the running task against its own slice. But this check
> > is about the donor: it asks whether the scheduling context that owns
> > the CPU has used up its slice. The running task is only borrowing the
> > CPU through proxy, so its slice is not the one we care about here.
> >
>
> Got it, I see.
>
> > Maybe something like below (only compile tested) to fix the issue.
> > That said, this is somewhat orthogonal to the issue that the execution context
> > series is trying to solve. It should be fixed separately.
> >
> > diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
> > index 8dff37059faf..cd240bf52d03 100644
> > --- a/kernel/sched/fair.c
> > +++ b/kernel/sched/fair.c
> > @@ -14748,10 +14748,29 @@ 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 vslice, vused;
> >
> > - return (rtime * min_nr_tasks > slice);
> > + /*
> > + * @se is the scheduling context (rq->donor). Under proxy execution
> > + * it need not be the task executing on the CPU, so its
> > + * sum_exec_runtime is not advanced and cannot be used to tell how
> > + * much of its slice it has consumed. Its vruntime, however, is
> > + * advanced by update_curr() with the proxy runtime, and its EEVDF
> > + * deadline reflects the granted slice, so measure the consumed
> > + * fraction in virtual time instead.
> > + *
> > + * This is equivalent to the previous real-time comparison in the
> > + * non-proxy case: both @vused and @vslice are scaled by the same
> > + * weight factor, so the ratio (and thus the min_nr_tasks test) is
> > + * unchanged.
> > + */
> > + if (vruntime_cmp(se->vruntime, ">=", se->deadline))
> > + return true;
> > +
> > + vslice = calc_delta_fair(se->slice, se);
> > + vused = vslice - (se->deadline - se->vruntime);
> > +
> > + return (vused * min_nr_tasks > vslice);
> > }
>
> This fix looks good to me. And just one minor question that I'm
> trying to figure out:
>
> Consider that there is only one running task p on one of the SMT siblings.
> The original comparison is between:
> se->sum_exec_runtime - se->prev_sum_exec_runtime vs slice,
> and since there is only one runnable task, p continues to run
> without any preemption, so se->prev_sum_exec_runtime remains
> unchanged, while se->sum_exec_runtime moves forward. Therefore,
> the duration delta of se->sum_exec_runtime - se->prev_sum_exec_runtime
> could expand to many slices in theory.
> After switching to the vruntime-based comparison, even
> if p has not been preempted, se->deadline together with se->vruntime
> will move forward by update_dealine(). That is to say, we now only
> consider the delta within one slice.
>
You raised a good point. And coupled with Hui's comment make me realize
that the deadline advancement in each slice means that I am only checking
whether I am using up my quota in the current slice.
I should check the run time since the task was picked to run to
see if the donor has exceeded its allotment if it keeps running
across slices in the lone task case.
I replied to Hui's email with a new proposal.
Tim
> This seems to tighten the
> restriction for task_tick_core() to trigger a force reschedule.
> But Overall I think it should not be a good deal to check
> within a slice period.
>
> thanks,
> Chenyu
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context
2026-09-04 20:15 ` Tim Chen
@ 2026-09-05 13:58 ` Hui Su
0 siblings, 0 replies; 15+ messages in thread
From: Hui Su @ 2026-09-05 13:58 UTC (permalink / raw)
To: Tim Chen, Chen, Yu C
Cc: Hui Su, K Prateek Nayak, Juri Lelli, Vincent Guittot,
Dietmar Eggemann, Valentin Schneider, John Stultz, Ingo Molnar,
Peter Zijlstra, linux-kernel
On Fri, Sep 4, 2026 at 1:15 PM, Tim Chen wrote:
> Yes, you have a good point. The code I proposed just look at whether
> we have consumed our allotment in the current slice.
>
> What we should have looked at is whether the donor's total run time has
> exceeded its quota when doing core scheduling. And we may happen to
> hit __entity_slice_used() at the front of the slice after advancing
> the deadline and __entity_slice_used()
> returns false instead of true, even though I have consumed more than
> my fair share when looking at longer time period across multiple slices.
>
> The accumulated run time of the donor since it was picked for running
> should be used for selection time baseline.
>
> So maybe a patch like the following instead.
Thanks for the updated prototype.
I tested the pick-time vruntime snapshot further. With a fixed weight,
it behaves as expected: it fixes the proxy case and continues to match
the existing runtime-based predicate in the non-proxy tests I ran.
I then tested it with CONFIG_FAIR_GROUP_SCHED, using nested cgroups and
changing the hierarchical weights in an observation-only setup. The
kernel computes the existing predicate, the pick-time vruntime predicate,
and a pick-time task-clock predicate on the same tick, while the existing
predicate remains the actual return value.
I think the "same weight scaling" condition in the non-proxy equivalence
is the interesting part here. If the hierarchical weight changes after
core_slice_vruntime is captured, vused represents service accumulated
since the pick-time baseline, while vslice is converted using the current
weight.
Across 11 instrumented runs I observed 5985 non-proxy samples. 29 of
those were explicitly identified as cases where the same task's
sched_entity had been reweighted after the current selection-time
baseline was taken. The existing and task-clock predicates matched in all
5985 non-proxy samples, while the vruntime predicate differed in four of
the 29 reweight-after-pick samples.
I also reproduced the divergence with actual runnable load in the
hierarchy. In one causal trace:
selection=5
pick_hweight=1048576
current_hweight=15138
reweight_vruntime=768327601->768327601
existing:
rtime=7980540
used=1
pick-time vruntime:
vused=7980540
vslice=145462386
used=0
task-clock:
rtime=7980540
used=1
This was the same selection: the pick-time baseline had already been
established, the hierarchical weight then changed, and there was no
new pick before the predicate was evaluated.
In this example reweight_eevdf() did not change se->vruntime, so the
divergence does not depend on a vruntime coordinate adjustment. The
weight change alone is enough for the accumulated vused and the
current-weight vslice to no longer necessarily use the same scale.
I also observed the reverse predicate direction in the observation-only
runs, although I have not yet reduced that case to the same detailed
causal trace.
One way to keep the vruntime approach would be to rebase the accumulated
virtual service whenever the relevant weight changes, preserving the
vused / vslice ratio across the reweight. Simply resetting
core_slice_vruntime at reweight time would lose service already consumed
before the reweight.
A simpler alternative I have been testing is to keep the check in the
same real-time domain as the existing predicate:
set_next_entity():
core_sched_start = se->exec_start;
__entity_slice_used():
rtime = se->exec_start - se->core_sched_start;
return rtime * min_nr_tasks > se->slice;
update_se() advances the donor's exec_start from rq_clock_task() while
it is the scheduling context, including during proxy execution, even
though the task-level sum_exec_runtime is charged to rq->curr. This
keeps the force-idle slice check in the same real-time domain as the
existing sum_exec_runtime - prev_sum_exec_runtime comparison and avoids
converting accumulated service across weight changes.
For reference, the local task-clock variant I am testing is below. It
is not intended as a formal posting yet:
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 f78275192036..22ae5dc57337 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -4580,6 +4580,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 8dff37059faf..1bd05c906d3a 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -6502,6 +6502,9 @@ 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
+ se->core_sched_start = se->exec_start;
+#endif
}
static bool __dequeue_task(struct rq *rq, struct task_struct *p, int flags);
@@ -14748,10 +14751,9 @@ 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
The task-clock version has matched the existing predicate in the
non-proxy tests so far and fixes the proxy reproducer as well. I am
still validating reselection, migration, and the remaining proxy
boundary cases, so I have not posted either implementation.
Do you think keeping the check in the original real-time/task-clock
domain is a reasonable direction here, or would you prefer preserving
the vruntime approach by carrying the accumulated service across
reweights?
Thanks,
Hui
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-09-05 14:00 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-03 4:11 [PATCH v2 0/2] sched: Fix execution-context tick handling under proxy execution Hui Su
2026-09-03 4:11 ` [PATCH v2 1/2] sched/numa: Drive NUMA task tick from execution context Hui Su
2026-09-03 12:41 ` Chen, Yu C
2026-09-03 21:30 ` Tim Chen
2026-09-04 5:16 ` Hui Su
2026-09-04 14:10 ` Hui Su
2026-09-04 20:15 ` Tim Chen
2026-09-05 13:58 ` Hui Su
2026-09-04 16:10 ` Chen Yu
2026-09-04 20:24 ` Tim Chen
2026-09-03 4:11 ` [PATCH v2 2/2] sched/cache: Drive cache " Hui Su
2026-09-03 4:37 ` K Prateek Nayak
2026-09-03 4:51 ` Hui Su
2026-09-03 17:23 ` Tim Chen
2026-09-04 4:03 ` 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®