[PATCH v4 05/26] sched/deadline: Do not divide by zero active CPUs of a root domain
From: Mathieu Desnoyers
Date: Fri Nov 06 2026 - 20:48:30 EST
__dl_sub() and __dl_add() spread a change of a root domain's total_bw
over the extra_bw of its active CPUs, dividing it by their number,
dl_bw_cpus(). That number is 0 for a root domain whose CPUs all went
offline, such as the default root domain, which holds the offline CPUs:
the division traps.
A debugfs write of a fair server's runtime or period divides there while
its CPU goes offline or comes online: the CPU sits in the default root
domain from the rebuild in sched_cpu_deactivate() until it is offline,
and from its online until sched_cpu_activate(), and
commit 4043f5498416 ("sched/deadline: Reject debugfs dl_server writes
for offline CPUs") refuses the write only once the CPU is offline. A
write that leaves the server's bandwidth as it is passes admission
there. Writing a fair server's own runtime back in a loop while its CPU
goes offline and online oopses on the first round, in 8-CPU VMs of
v7.4-rc1:
Oops: divide error: 0000 [#1] SMP NOPTI
RIP: 0010:dl_server_apply_params+0x335/0x380
Call Trace:
sched_server_write_common.isra.0+0x1c0/0x260
full_proxy_write+0x58/0x90
vfs_write+0xcb/0x550
inactive_task_timer() can divide too, found by reading: it releases the
bandwidth of a task that left SCHED_DEADLINE, or died, from the root
domain of its CPU, which may have gone offline since. With admission
control off, so can sched_setattr(SCHED_DEADLINE) of a task asleep on an
offline CPU.
Refuse the write while the runqueue is offline, as once the CPU is: the
server's bandwidth is not counted then, and the rebuild at the CPU's
activation counts what the server has. rq->online is set under the
runqueue lock by the attach to a root domain that counts the CPU, and
cleared before the rebuild that drops it, where cpu_active() is set
before the attach and would be read without cpus_read_lock(). With no
active CPU there is no extra_bw to update either: change total_bw alone,
for the paths that have no write to refuse. 20 rounds of the reproducer
leave no warning then, its writes refused with EBUSY in the window.
Fixes: daec57983670 ("sched/deadline: Reclaim bandwidth not used by dl tasks")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 11 +++++++++--
kernel/sched/debug.c | 9 ++++++++-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index dfae08625342..00983487bff3 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -189,18 +189,25 @@ void __dl_update(struct dl_bw *dl_b, s64 bw)
}
}
+/*
+ * @cpus is the number of active CPUs of the root domain, whose extra_bw
+ * shares the change: none once they all went offline, and total_bw alone
+ * changes then.
+ */
static inline
void __dl_sub(struct dl_bw *dl_b, u64 tsk_bw, int cpus)
{
dl_b->total_bw -= tsk_bw;
- __dl_update(dl_b, (s32)tsk_bw / cpus);
+ if (cpus)
+ __dl_update(dl_b, (s32)tsk_bw / cpus);
}
static inline
void __dl_add(struct dl_bw *dl_b, u64 tsk_bw, int cpus)
{
dl_b->total_bw += tsk_bw;
- __dl_update(dl_b, -((s32)tsk_bw / cpus));
+ if (cpus)
+ __dl_update(dl_b, -((s32)tsk_bw / cpus));
}
static inline bool
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index e6a3b516c703..5f8303630296 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -410,7 +410,14 @@ static ssize_t sched_server_write_common(struct file *filp, const char __user *u
return -EINVAL;
}
- if (!cpu_online(cpu_of(rq)))
+ /*
+ * Nor while its runqueue is offline, the CPU going offline or
+ * coming online: the server's bandwidth is not counted in a
+ * root domain then, which has no active CPU to spread a
+ * change over. rq->online follows the attach to a root domain
+ * under this lock; cpu_active() is set before it.
+ */
+ if (!rq->online)
return -EBUSY;
update_rq_clock(rq);
--
2.47.3