[PATCH v5 18/29] sched/deadline: Move a task's bandwidth only where room was made
From: Mathieu Desnoyers
Date: Sun Nov 08 2026 - 13:58:02 EST
Commit 7f51412a415d ("sched/deadline: Fix bandwidth check/update when
migrating tasks between exclusive cpusets") moves a deadline task's
bandwidth in two halves. cpuset_can_attach() reserves it in the
destination root domain. set_cpus_allowed_dl() then frees it in the
source, when the task's affinity changes. Its comment relies on the
first half: "We already made space for us in the destination domain".
But set_cpus_allowed_dl() runs on every change of affinity. It frees
the bandwidth whenever the new mask leaves the task's root domain, with
or without a reservation. sched_setaffinity() gets there too, if the
task's cpuset spans two root domains. With admission control on,
__sched_setaffinity() refuses such a mask. With sched_rt_runtime_us
at -1, nothing refuses it. The bandwidth then moves with no room made,
and no root domain counts it any more. The destination would underflow
when the task leaves.
Fix this by flagging the affinity changes that come with a reservation,
as SCA_DL_BW_RESERVED. cpuset_attach_task() sets it, through the new
set_cpus_allowed_bw_reserved(). set_cpus_allowed_dl() moves the
bandwidth only with the flag. Without it, the bandwidth stays where it
is counted, until the next rebuild of the accounting counts it anew.
The attach still flags a task that became a deadline task after
cpuset_can_attach(), with nothing reserved for it. "cgroup/cpuset: Give
back the bandwidth an attach did not move" fixes that.
Fixes: 7f51412a415d ("sched/deadline: Fix bandwidth check/update when migrating tasks between exclusive cpusets")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
include/linux/sched.h | 2 ++
kernel/cgroup/cpuset.c | 13 ++++++++++++-
kernel/sched/core.c | 16 ++++++++++++++++
kernel/sched/deadline.c | 14 +++++++++-----
kernel/sched/sched.h | 2 ++
5 files changed, 41 insertions(+), 6 deletions(-)
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 942d9acff203..5f14005d737e 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -1979,6 +1979,8 @@ extern void set_cpus_allowed_force(struct task_struct *p, const struct cpumask *
* Return: zero if successful, or a negative error code
*/
extern int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask);
+extern int set_cpus_allowed_bw_reserved(struct task_struct *p,
+ const struct cpumask *new_mask);
extern int dup_user_cpus_ptr(struct task_struct *dst, struct task_struct *src, int node);
extern void release_user_cpus_ptr(struct task_struct *p);
extern int dl_task_check_affinity(struct task_struct *p, const struct cpumask *mask);
diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index 7628cf77bdfd..194a438e25c3 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -3261,6 +3261,7 @@ static cpumask_var_t cpus_attach;
static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
{
+ bool reserved = false;
struct mm_struct *mm;
lockdep_assert_cpuset_lock_held();
@@ -3270,11 +3271,21 @@ static void cpuset_attach_task(struct cpuset *cs, struct task_struct *task)
else
cpumask_andnot(cpus_attach, task_cpu_possible_mask(task),
subpartitions_cpus);
+ /*
+ * cpuset_reserve_dl_bw() made room in @cs for the bandwidth of the
+ * deadline tasks that cpuset_can_attach() counted. Flag the move, so
+ * that the bandwidth of this task moves with it.
+ */
+ if (dl_task(task) && dl_task_needs_bw_move(task, cs->effective_cpus))
+ reserved = true;
/*
* can_attach beforehand should guarantee that this doesn't
* fail. TODO: have a better way to handle failure here
*/
- WARN_ON_ONCE(set_cpus_allowed_ptr(task, cpus_attach));
+ if (reserved)
+ WARN_ON_ONCE(set_cpus_allowed_bw_reserved(task, cpus_attach));
+ else
+ WARN_ON_ONCE(set_cpus_allowed_ptr(task, cpus_attach));
if (cpuset_v2() && !attach_ctx.mems_updated)
return;
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index bf722f5ff97e..030106b3d97f 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3272,6 +3272,22 @@ int set_cpus_allowed_ptr(struct task_struct *p, const struct cpumask *new_mask)
}
EXPORT_SYMBOL_GPL(set_cpus_allowed_ptr);
+/*
+ * Like set_cpus_allowed_ptr(), for a caller that has made room for the
+ * deadline bandwidth of @p in the root domain of @new_mask. Move that
+ * bandwidth there with @p. Return 0 on success, or a negative error
+ * code.
+ */
+int set_cpus_allowed_bw_reserved(struct task_struct *p, const struct cpumask *new_mask)
+{
+ struct affinity_context ac = {
+ .new_mask = new_mask,
+ .flags = SCA_DL_BW_RESERVED,
+ };
+
+ return __set_cpus_allowed_ptr(p, &ac);
+}
+
/*
* Change a given task's CPU affinity to the intersection of its current
* affinity mask and @subset_mask, writing the resulting mask to @new_mask.
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 84102368822c..b365401e7148 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -3564,12 +3564,16 @@ static void set_cpus_allowed_dl(struct task_struct *p,
WARN_ON_ONCE(!dl_task(p));
/*
- * Migrating a SCHED_DEADLINE task between exclusive
- * cpusets (different root_domains) entails a bandwidth
- * update. We already made space for us in the destination
- * domain (see cpuset_can_attach()).
+ * Migrating a SCHED_DEADLINE task between exclusive cpusets
+ * (different root_domains) entails a bandwidth update. Move the
+ * bandwidth only if the caller made room for it in the destination,
+ * as SCA_DL_BW_RESERVED says. Only cpuset_attach_task() sets it.
+ * Otherwise, the bandwidth stays where it is counted, until the next
+ * rebuild of the accounting. Moving it without room would leave no
+ * root domain counting it.
*/
- if (dl_task_needs_bw_move(p, ctx->new_mask)) {
+ if ((ctx->flags & SCA_DL_BW_RESERVED) &&
+ dl_task_needs_bw_move(p, ctx->new_mask)) {
unsigned int src_cpu = dl_task_root_domain_cpu(p);
struct dl_bw *src_dl_b = &cpu_rq(src_cpu)->rd->dl_bw;
unsigned int cpu;
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index d0f5ed98ef48..d7b4cad28262 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -2957,6 +2957,8 @@ extern struct task_struct *pick_task_idle(struct rq *rq, struct rq_flags *rf);
#define SCA_MIGRATE_DISABLE 0x02
#define SCA_MIGRATE_ENABLE 0x04
#define SCA_USER 0x08
+/* The caller made room for the deadline bandwidth in the new root domain. */
+#define SCA_DL_BW_RESERVED 0x20
extern void update_group_capacity(struct sched_domain *sd, int cpu);
--
2.47.3