[PATCH v5 25/29] sched/deadline: Count the bandwidth a boosted task is given
From: Mathieu Desnoyers
Date: Sun Nov 08 2026 - 13:58:58 EST
sched_setattr(SCHED_DEADLINE) on a sleeping task that priority
inheritance boosted into the class leaves the new bandwidth out of
this_bw. switched_to_dl() does not run, since the task is in the class
already. sched_dl_overflow() changes this_bw only for a task of the
deadline policy. When the task wakes up, __add_running_bw() warns:
WARN_ON_ONCE(dl_rq->running_bw > dl_rq->this_bw);
When the task leaves the runqueue, this_bw would stay short by that
bandwidth, and skew GRUB reclaiming on that CPU.
Fix this by changing this_bw in __setparam_dl(), which every task that
gains deadline parameters goes through. If no runqueue counts the
bandwidth of the task, dl_change_utilization() now adds the new one. It
now also runs when the bandwidth does not change. It then returns early,
so that a sleeping task keeps its running_bw and its inactive timer.
A sugov kthread given a reservation of its own now has its bandwidth
counted in this_bw. It reaches dl_change_utilization(), so the
WARN_ON_ONCE() for SCHED_FLAG_SUGOV there goes. sched_dl_overflow()
returned early for an unchanged bandwidth, even if no root domain counts
it. That is the case for such a kthread, and for a task that a rebuild
left out. Admit such a bandwidth as new. Without room, that fails with
EBUSY. The Sashiko AI review of the third posting found this early
return.
A reproducer shows the warning, and this_bw unchanged by the change.
With this patch, this_bw takes the new bandwidth, and nothing warns.
Fixes: 8fd27231c330 ("sched/deadline: Track the "total rq utilization" too")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 54 ++++++++++++++++++++++++++++++++---------
1 file changed, 43 insertions(+), 11 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 47adff100392..bc76203ab506 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -507,22 +507,37 @@ void cancel_inactive_timer(struct sched_dl_entity *dl_se)
cancel_dl_timer(dl_se, &dl_se->inactive_timer);
}
+/*
+ * Make the runqueue of a sleeping task count @new_bw as the bandwidth of
+ * the task. Do nothing for a queued task. The dequeue and the enqueue
+ * around the change of its parameters update this_bw.
+ */
static void dl_change_utilization(struct task_struct *p, u64 new_bw)
{
- WARN_ON_ONCE(p->dl.flags & SCHED_FLAG_SUGOV);
+ struct sched_dl_entity *dl_se = &p->dl;
+ struct rq *rq = task_rq(p);
if (task_on_rq_queued(p))
return;
/*
- * The task is waking up on another CPU, and no runqueue counts its
- * bandwidth. The enqueue adds it, with the new parameters that
- * __setparam_dl() sets right after this.
+ * No runqueue counts the bandwidth of the task. Add the new one to
+ * this runqueue, so that add_rq_bw() adds nothing later. A task that
+ * enters the class asleep comes here, and so does a special entity
+ * given a reservation of its own. So does a task waking up on
+ * another CPU: task_cpu() already points at the CPU it wakes on.
*/
- if (!p->dl.dl_bw_oncpu)
+ if (!dl_se->dl_bw_oncpu) {
+ __add_rq_bw(new_bw, &rq->dl);
+ dl_se->dl_bw_oncpu = 1;
+ return;
+ }
+
+ /* Same bandwidth: leave running_bw and the inactive timer alone. */
+ if (new_bw == dl_se->dl_bw)
return;
- dl_rq_change_utilization(task_rq(p), &p->dl, new_bw);
+ dl_rq_change_utilization(rq, dl_se, new_bw);
}
static void __dl_clear_params(struct sched_dl_entity *dl_se);
@@ -4407,10 +4422,6 @@ int sched_dl_overflow(struct task_struct *p, int policy,
if (attr->sched_flags & SCHED_FLAG_SUGOV)
return 0;
- /* !deadline task may carry old deadline bandwidth */
- if (new_bw == p->dl.dl_bw && task_has_dl_policy(p))
- return 0;
-
/*
* Change the bandwidth of a deadline task where it is counted: in
* the root domain of the CPU recorded with it. A task moved asleep
@@ -4425,6 +4436,16 @@ int sched_dl_overflow(struct task_struct *p, int policy,
*/
counted = task_has_dl_policy(p) && p->dl.dl_bw_cpu >= 0 &&
p->dl.dl_bw_gen == dl_bw_gen;
+
+ /*
+ * !deadline task may carry old deadline bandwidth. The same
+ * bandwidth needs no admission if it is counted. If it is not, admit
+ * it as new below. A special entity given a reservation of its own
+ * has no bandwidth counted, nor has a task that a rebuild left out.
+ */
+ if (new_bw == p->dl.dl_bw && counted)
+ return 0;
+
cpu = counted && p->dl.dl_bw ? p->dl.dl_bw_cpu : task_cpu(p);
dl_b = dl_bw_of(cpu);
@@ -4454,7 +4475,6 @@ int sched_dl_overflow(struct task_struct *p, int policy,
if (counted)
__dl_sub(dl_b, p->dl.dl_bw, cpus);
__dl_add(dl_b, new_bw, cpus);
- dl_change_utilization(p, new_bw);
err = 0;
} else if (!dl_policy(policy) && task_has_dl_policy(p)) {
/*
@@ -4504,6 +4524,18 @@ int sched_dl_overflow(struct task_struct *p, int policy,
void __setparam_dl(struct task_struct *p, const struct sched_attr *attr)
{
struct sched_dl_entity *dl_se = &p->dl;
+ u64 period = attr->sched_period ?: attr->sched_deadline;
+
+ /*
+ * Change the bandwidth that the runqueue counts here, with the
+ * parameters. A task that priority inheritance boosted into the
+ * class is in it already, so switched_to_dl() does not run for it.
+ *
+ * A special entity has no bandwidth on a runqueue. Test the flags
+ * of the request, since those of the entity are still the old ones.
+ */
+ if (!(attr->sched_flags & SCHED_FLAG_SUGOV))
+ dl_change_utilization(p, to_ratio(period, attr->sched_runtime));
dl_se->dl_runtime = attr->sched_runtime;
dl_se->dl_deadline = attr->sched_deadline;
--
2.47.3