mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: K Prateek Nayak <kprateek.nayak@amd.com>
To: John Stultz <jstultz@google.com>,
	Suleiman Souhlal <suleiman@google.com>,
	Ingo Molnar <mingo@redhat.com>,
	Peter Zijlstra <peterz@infradead.org>,
	"Juri Lelli" <juri.lelli@redhat.com>,
	Vincent Guittot <vincent.guittot@linaro.org>,
	Will Deacon <will@kernel.org>, Boqun Feng <boqun@kernel.org>,
	Andrea Righi <arighi@nvidia.com>, <linux-kernel@vger.kernel.org>
Cc: Dietmar Eggemann <dietmar.eggemann@arm.com>,
	Steven Rostedt <rostedt@goodmis.org>,
	Ben Segall <bsegall@google.com>, Mel Gorman <mgorman@suse.de>,
	Valentin Schneider <vschneid@redhat.com>,
	Waiman Long <longman@redhat.com>,
	K Prateek Nayak <kprateek.nayak@amd.com>
Subject: [RFC PATCH 14/16] sched/core: Introduce chain-wakeup to activate blocked donors
Date: Wed, 26 Aug 2026 06:28:58 +0000	[thread overview]
Message-ID: <20260826062901.2137-15-kprateek.nayak@amd.com> (raw)
In-Reply-To: <20260826062901.2137-1-kprateek.nayak@amd.com>

Activating blocked task with sched_proxy_exec() needed grabbing
p->blocked_lock for all wakeups to prevent missing additions to
blocked_head during wakeup.

With proxy_enqueue_on_owner() converted to plug the race between enqueue
on owner and owner's wakeup, simplify the wakeup path with the following
three step process:

activate_blocked_task(owner)
  /* Part 1: Enqueue race */		  enqueue_on_owner(owner, p)
  owner->on_rq = TASK_ON_RQ_MIGRATING;	    list_add(&p->blocked_node, &owner->blocked_head)

  smp_mb();				    smp_mb();

  if (list_empty(&owner->blocked_head))	    if (owner->on_rq) /* Raced */
    /*					      list_del_init(&p->blocked_node)
     * Fast-path: Future additions will	      return;
     * see owner->on_rq != 0 and dequeue    /*
     * themself.			     * Owner has to observe !list_empty() after
     */					     * this point so it is safe to block.
     __activate_task(p)			     */
					    block_task(p);

  /* END: Part 1 */

enqueue_on_owner() holds mutex->wait_lock and "owner" needs to grab the
same wait_lock during unlock so "owner" cannot disappear during
enqueue_on_owner().

  /* Part 2: Prepare migration chain */

  rq_unlock(rq);

  LIST_HEAD(migration_list)
  LIST_HEAD(wakeup_list)
							try_to_wake_up()
 							  /* Sees proxy_task_is_linked(p) */
							  ttwu_runnable(donor)
  rq_lock(rq_of(owner->blocked_cpu))			    task_rq_lock(donor) /* Same rq */

  do {

    list_for_each_entry(donor, &owner->blocked_head)	  if (task_is_linked(donor)
      donor->on_rq = TASK_ON_RQ_MIGRATING /* Part 1 */	    list_del_init(&p->blocked_node)

      if (list_empty(&owner->blocked_head)		activate_blocked_task(donor)
        list_add(&doner, &migration_list)		  /* Same as left */
      else
        list_add(&donor, &wakeup_list)

      list_add(&owner, &wakeup_list)
      owner = list_next_entry(&migration_list)

  } while (!list_empty(&migration_list))

  rq_unlock(rq_of(owner->blocked_cpu))

  /* END: Part 2 */

Since owner->blocked_cpu matches with task_cpu() of the entire chain
(including tasks queued on the blocked donors, and task queued on them,
and so on), grabbing "rq_of(owner->blocked_cpu)" is enough to stall any
concurrent wakeups for any of the linked tasks.

If try_to_wake_up() wins, donor will remove itself from the chain and
continue its wakeup as if it is a blocked task waking up.

If activate_blocked_task() wins, it will set all the donors to
TASK_ON_RQ_MIGRATING, repeating Part 1, to prevent other tasks from
queueing on it form then on and iterates its "blocked_head". This is
done in a breadth-first fashion until full chain is set to
TASK_ON_RQ_MIGRATING.

"owner->blocked_head" traversal and manipulations are still guarded by
"owner->blocked_lock" which has not been depicted here.

  /* Part 3: Wakeup chain */

  rq_lock(rq);

  __activate_task(p); /* Activate top owner first */

  /* Activate the entire chain next */
  list_for_each_entry(donor, &owner->blocked_head)
    list_del_init(&donor->blocked_node)
    __activate_task(donor)

  /* END: Part 3 */

  The wakeup steps skipped for donors is performormed during the chain
  wakeup.

Advantages of this method:

o Does not involve juggling p->pi_lock, rq_lock, owner->blocked_lock
  in addition to  checking task state. Task on chain is fully blocked
  and only one rq_lock + onwer_blocked lock can hold off
  ttwu_runnable() from waking them.

o No need to borrow reference to "owner" before queuing. The mechanism
  ensures p->blocked_owner remains valid as long as is task is on chain.

Disadvantages:

o Since chain wakeups funnel through a single CPU, it is possible for
  increased lock contention however large chain wakeups and concurrent
  wakeups of donors for reasons other than an unlock is rare.

Co-developed-by: John Stultz <jstultz@google.com>
Signed-off-by: John Stultz <jstultz@google.com>
Signed-off-by: K Prateek Nayak <kprateek.nayak@amd.com>
---
 kernel/sched/core.c | 183 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 181 insertions(+), 2 deletions(-)

diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index e6abcea97683..3153ad007447 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2247,6 +2247,8 @@ void activate_task(struct rq *rq, struct task_struct *p, int en_flags)
 
 #ifdef CONFIG_SCHED_PROXY_EXEC
 
+static inline void proxy_set_task_cpu(struct task_struct *p, int cpu);
+
 static void __proxy_dequeue_from_owner(struct task_struct *p)
 {
 	list_del_init(&p->blocked_node);
@@ -2337,6 +2339,183 @@ static bool proxy_try_dequeue_from_owner(struct task_struct *p)
 	return true;
 }
 
+static void
+proxy_activate_blocked_task(struct rq *rq, struct task_struct *p, int en_flags)
+{
+	int iowait_count = 0, load_contrib_count = 0;
+	struct task_struct *donor, *owner, *tmp;
+	int blocked_cpu, this_cpu = cpu_of(rq);
+	LIST_HEAD(migration_head);
+	LIST_HEAD(wakeup_head);
+	struct rq *blocked_rq;
+	bool needs_migration;
+
+	WRITE_ONCE(p->on_rq, TASK_ON_RQ_MIGRATING);
+	ASSERT_EXCLUSIVE_WRITER(p->on_rq);
+
+	/*
+	 * Pairs against smp_mb() in proxy_enqueue_on_owner() which
+	 * orders ownwer->on_rq state against the blocked_head addition.
+	 */
+	smp_mb();
+
+	/*
+	 * Fast-path: Tasks blocking on us will see p->on_rq updated and
+	 * will bail out natturally in proxy_enqueue_on_owner().
+	 *
+	 * proxy_enqueue_on_owner() holds the wait_lock to prevent owner
+	 * from running and disappearing before the transient task can
+	 * observe p->on_rq change and dequeue itself.
+	 */
+	if (list_empty(&p->blocked_head)) {
+		__activate_task(rq, p, en_flags);
+		return;
+	}
+
+	/*
+	 * Slow-path: Since we need to wakeup tasks (and task queued on
+	 * them, and task queued on them, and ... you get the gist) we
+	 * need to grab some locks.
+	 *
+	 * Lucky for us, we have simplified this via p->blocked_cpu
+	 * which is the task_cpu() for the entire chain. Wakeup is
+	 * broken into two parts.
+	 *
+	 * Part1: Under the rq_lock + blocked lock, mark all tasks on
+	 * the chain as TASK_ON_RQ_MIGRATING in breadth-first manner.
+	 *
+	 * Part2: Active all the tasks in bulk.
+	 */
+
+	/* Part 1: Prepare the blocked donor chain. */
+	blocked_cpu = p->blocked_cpu;
+	blocked_rq = cpu_rq(blocked_cpu);
+
+	/*
+	 * Blocked chain is linked to a different rq. Drop the rq_lock
+	 * and migrate the chain over before wkaing it up here.
+	 */
+	needs_migration = blocked_rq != rq;
+	if (needs_migration) {
+		raw_spin_rq_unlock(rq);
+		raw_spin_rq_lock(blocked_rq);
+	}
+
+	owner = p;
+
+	do {
+		guard(raw_spinlock)(&owner->blocked_lock);
+
+		/* Iterate the tasks blocked on this owner. */
+		list_for_each_entry_safe(donor, tmp, &owner->blocked_head, blocked_node) {
+			/* Everything hinges on this assumption. */
+			WARN_ON_ONCE(task_cpu(donor) != blocked_cpu);
+			WARN_ON_ONCE(donor->se.sched_delayed);
+
+			WRITE_ONCE(donor->on_rq, TASK_ON_RQ_MIGRATING);
+			ASSERT_EXCLUSIVE_WRITER(donor->on_rq);
+
+			if (donor->in_iowait) {
+				delayacct_blkio_end(donor);
+				iowait_count++;
+			}
+
+			if (donor->sched_contributes_to_load)
+				load_contrib_count++;
+
+			/*
+			 * Pairs against the smp_mb() in proxy_enqueue_on_owner().
+			 * See the comment at the beginning of the function.
+			 */
+			smp_mb();
+
+			/*
+			 * If the donor has more task queued on it,
+			 * add it to the migration list else directly
+			 * queue it onto the wakeup list.
+			 */
+			if (!list_empty(&donor->blocked_head)) {
+				list_move_tail(&donor->blocked_node, &migration_head);
+			} else {
+				list_move_tail(&donor->blocked_node, &wakeup_head);
+			}
+		}
+
+		owner = list_first_entry_or_null(&migration_head,
+						 typeof(*owner),
+						 blocked_node);
+
+		/*
+		 * Move the owner to the wakeup list before preparing
+		 * the sub-chain queued on it.
+		 */
+		if (owner)
+			list_move_tail(&owner->blocked_node, &wakeup_head);
+
+	} while (owner);
+
+	/* Part 2: Bulk wakeup */
+
+	atomic_sub(iowait_count, &blocked_rq->nr_iowait);
+
+	if (needs_migration) {
+		raw_spin_rq_unlock(blocked_rq);
+		raw_spin_rq_lock(rq);
+
+		update_rq_clock(rq);
+		en_flags |= ENQUEUE_NOCLOCK;
+	}
+
+	rq->nr_uninterruptible -= load_contrib_count;
+
+	if (!(en_flags & ENQUEUE_NOCLOCK)) {
+		update_rq_clock(rq);
+		en_flags |= ENQUEUE_NOCLOCK;
+	}
+
+	/* Activate the top owner first. */
+	__activate_task(rq, p, en_flags);
+
+	/*
+	 * Add ENQUEUE_MIGRATED for all tasks on the chain
+	 * since they are all sched_migrated_on_blocking
+	 * and have skipped ->migrate_task_rq() callback.
+	 */
+	en_flags |= ENQUEUE_MIGRATING;
+
+	list_for_each_entry_safe(donor, tmp, &wakeup_head, blocked_node) {
+		__proxy_dequeue_from_owner(donor);
+
+		/*
+		 * Correct the task_cpu() before activating.
+		 *
+		 * Since chain preparation already adds enough barrier
+		 * after storing p->on_rq = MIGRATING, no additional
+		 * barriers are required after modifying p->is_linked
+		 * via __proxy_dequeue_from_owner() above.
+		 */
+		if (needs_migration)
+			proxy_set_task_cpu(donor, this_cpu);
+
+		__activate_task(rq, donor, en_flags);
+		wakeup_preempt(rq, donor, en_flags);
+		/*
+		 * Blocked tasks do not add push callbacks and it is
+		 * safe to skip calling ->task_woken() here.
+		 */
+	}
+}
+
+static void activate_blocked_task(struct rq *rq, struct task_struct *p, int en_flags)
+{
+	if (!sched_proxy_exec()) {
+		__activate_task(rq, p, en_flags);
+		return;
+	}
+
+	proxy_activate_blocked_task(rq, p, en_flags);
+}
+
 #else /* !CONFIG_SCHED_PROXY_EXEC */
 
 static bool proxy_try_dequeue_from_owner(struct task_struct *p)
@@ -2344,13 +2523,13 @@ static bool proxy_try_dequeue_from_owner(struct task_struct *p)
 	return false;
 }
 
-#endif /* CONFIG_SCHED_PROXY_EXEC */
-
 static void activate_blocked_task(struct rq *rq, struct task_struct *p, int en_flags)
 {
 	__activate_task(rq, p, en_flags);
 }
 
+#endif /* CONFIG_SCHED_PROXY_EXEC */
+
 void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
 {
 	WARN_ON_ONCE(flags & DEQUEUE_SLEEP);
-- 
2.34.1


  parent reply	other threads:[~2026-08-26  6:34 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-26  6:28 [RFC PATCH 00/16][PoC] sched/core: Alternate approach to sleeping-owner handling in PROXY_EXEC K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 01/16] sched/core: Break activation of blocked task into a separate helper K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 02/16] sched/core: Use enqueue/dequeue flags instead of task_on_rq_migrating() K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 03/16] sched/fair: Use enqueue flags for DO_ATTACH in update_load_avg() K Prateek Nayak
2026-08-26 15:36   ` Andrea Righi
2026-08-26  6:28 ` [RFC PATCH 04/16] sched/core: Activate blocked donor when no owner is found K Prateek Nayak
2026-08-26 15:56   ` Andrea Righi
2026-08-26 17:20     ` K Prateek Nayak
2026-08-28  6:04       ` K Prateek Nayak
2026-08-31 15:07         ` Andrea Righi
2026-09-16  4:16           ` K Prateek Nayak
2026-09-16  3:29   ` John Stultz
2026-09-16  4:16     ` K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 05/16] sched/core: Do not queue blocked donor on a delayed owner K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 06/16] sched/core: Queue blocked donor onto sleeping owner for chain-wakeup K Prateek Nayak
2026-08-26 16:20   ` Andrea Righi
2026-08-27  3:51     ` K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 07/16] sched/core: Avoid delaying blocked donors queued on sleeping owner K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 08/16] sched/deadline: Prepare for blocking and proxy activation with MIGRATING flag K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 09/16] sched/core: Track CPU where the task was blocked on K Prateek Nayak
2026-09-16  5:52   ` John Stultz
2026-09-16  6:29     ` K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 10/16] sched/core: Introduce p->is_linked to track if task is queued on sleeping owner K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 11/16] sched/core: Prepare to inspect ->is_linked alongside ->on_rq during wakeup K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 12/16] sched:core: Add MIGRATING flags when blocking and activating linked donors K Prateek Nayak
2026-08-26  6:28 ` [RFC PATCH 13/16] sched/core: Use p->is_linked state to unlink from sleeping owner early K Prateek Nayak
2026-08-26  6:28 ` K Prateek Nayak [this message]
2026-09-15  5:48   ` [RFC PATCH 14/16] sched/core: Introduce chain-wakeup to activate blocked donors John Stultz
2026-08-26  6:28 ` [RFC PATCH 15/16] locking/mutex: Track locks owned by a task in a per-task counter K Prateek Nayak
2026-08-26  6:29 ` [RFC PATCH 16/16] sched/core: Set activation of non lock-holders to fast-path K Prateek Nayak
2026-09-16  5:22 ` [RFC PATCH 00/16][PoC] sched/core: Alternate approach to sleeping-owner handling in PROXY_EXEC John Stultz
2026-09-16  6:10   ` K Prateek Nayak
2026-09-16  6:23     ` John Stultz
2026-09-16  6:59       ` K Prateek Nayak
2026-09-16 17:57   ` John Stultz
2026-09-17  5:22     ` K Prateek Nayak

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=20260826062901.2137-15-kprateek.nayak@amd.com \
    --to=kprateek.nayak@amd.com \
    --cc=arighi@nvidia.com \
    --cc=boqun@kernel.org \
    --cc=bsegall@google.com \
    --cc=dietmar.eggemann@arm.com \
    --cc=jstultz@google.com \
    --cc=juri.lelli@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=longman@redhat.com \
    --cc=mgorman@suse.de \
    --cc=mingo@redhat.com \
    --cc=peterz@infradead.org \
    --cc=rostedt@goodmis.org \
    --cc=suleiman@google.com \
    --cc=vincent.guittot@linaro.org \
    --cc=vschneid@redhat.com \
    --cc=will@kernel.org \
    /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®