From: Hui Su <sh_def@163.com>
To: Peter Zijlstra <peterz@infradead.org>,
Zhidao Su <soolaugust@gmail.com>,
Andrea Righi <arighi@nvidia.com>
Cc: mingo@redhat.com, tim.c.chen@linux.intel.com,
yu.c.chen@intel.com, kprateek.nayak@amd.com,
juri.lelli@redhat.com, vincent.guittot@linaro.org,
dietmar.eggemann@arm.com, rostedt@goodmis.org,
bsegall@google.com, mgorman@suse.de, vschneid@redhat.com,
connoro@google.com, jstultz@google.com, tj@kernel.org,
void@manifault.com, changwoo@igalia.com,
linux-kernel@vger.kernel.org, sched-ext@lists.linux.dev
Subject: Re: [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution
Date: Sun, 13 Sep 2026 02:30:00 +0900 [thread overview]
Message-ID: <c8b457d91942d655cea763b6094c9df4.sh_def@163.com> (raw)
In-Reply-To: <20260909110438.GA4120091@noisy.programming.kicks-ass.net>
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
*/
next prev parent reply other threads:[~2026-09-12 18:06 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-09 9:28 [PATCH v4 0/5] sched: Handle split scheduling and execution contexts in task ticks Hui Su
2026-09-09 9:28 ` [PATCH v4 1/5] sched: Dispatch task ticks for donor and execution classes Hui Su
2026-09-09 10:42 ` Hui Su
2026-09-09 17:43 ` Andrea Righi
2026-09-10 10:49 ` Hui Su
2026-09-09 9:28 ` [PATCH v4 2/5] sched/numa: Drive NUMA task tick from execution context Hui Su
2026-09-09 9:28 ` [PATCH v4 3/5] sched/cache: Drive cache " Hui Su
2026-09-09 11:03 ` Peter Zijlstra
2026-09-10 10:53 ` Hui Su
2026-09-09 9:29 ` [PATCH v4 4/5] sched/rt: Fix RT watchdog accounting for proxy execution Hui Su
2026-09-09 11:04 ` Peter Zijlstra
2026-09-10 10:54 ` Hui Su
2026-09-12 17:30 ` Hui Su [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=c8b457d91942d655cea763b6094c9df4.sh_def@163.com \
--to=sh_def@163.com \
--cc=arighi@nvidia.com \
--cc=bsegall@google.com \
--cc=changwoo@igalia.com \
--cc=connoro@google.com \
--cc=dietmar.eggemann@arm.com \
--cc=jstultz@google.com \
--cc=juri.lelli@redhat.com \
--cc=kprateek.nayak@amd.com \
--cc=linux-kernel@vger.kernel.org \
--cc=mgorman@suse.de \
--cc=mingo@redhat.com \
--cc=peterz@infradead.org \
--cc=rostedt@goodmis.org \
--cc=sched-ext@lists.linux.dev \
--cc=soolaugust@gmail.com \
--cc=tim.c.chen@linux.intel.com \
--cc=tj@kernel.org \
--cc=vincent.guittot@linaro.org \
--cc=void@manifault.com \
--cc=vschneid@redhat.com \
--cc=yu.c.chen@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®