[PATCH v2 02/21] sched: Refuse SCHED_FLAG_KEEP_PARAMS that would change the policy
From: Mathieu Desnoyers
Date: Wed Nov 04 2026 - 21:42:49 EST
SCHED_FLAG_KEEP_PARAMS keeps the parameters of a task: it skips
__setscheduler_params(), which is what sets p->policy. A request that
asks for another policy is then carried out by half, and succeeds:
SCHED_OTHER to SCHED_BATCH with SCHED_FLAG_KEEP_PARAMS returns 0 and
leaves the task where it was.
For SCHED_DEADLINE it costs bandwidth. A task that priority inheritance
boosted into the deadline class is of that class already, so
prev_class == next_class and the class check of
commit 8a70415351ba ("sched: Reject SCHED_FLAG_KEEP_PARAMS when it would
change scheduling class") lets the request through: sched_dl_overflow()
admits it and adds
its bandwidth, which the skipped __setscheduler_params() never gives to
the task. It keeps SCHED_NORMAL and a dl_bw of 0, so nothing releases
that bandwidth, and total_bw of the root domain holds it for good.
Refuse a policy change as well, where the class change was refused
alone.
A SCHED_OTHER owner of a PTHREAD_PRIO_INHERIT mutex with a slice of
40ms, boosted by a deadline waiter blocked on it, leaves 0.4 of a CPU
behind at every such call: total_bw 419424 -> 838854 and there to stay,
in 8-CPU VMs of v7.4-rc1 with the four deadline fixes of sched/urgent,
and of this series up to here. With this change the call is refused and
total_bw is as it was. The selftest added later in this series has the
case, params_keep_boosted.
Found by the Sashiko AI review of the first posting of this series.
Fixes: a509a7cd7974 ("sched/uclamp: Extend sched_setattr() to support utilization clamping")
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/syscalls.c | 29 +++++++++++++++++------------
1 file changed, 17 insertions(+), 12 deletions(-)
diff --git a/kernel/sched/syscalls.c b/kernel/sched/syscalls.c
index f0b7b2d52614..ab0a573e9b37 100644
--- a/kernel/sched/syscalls.c
+++ b/kernel/sched/syscalls.c
@@ -674,20 +674,25 @@ int __sched_setscheduler(struct task_struct *p,
prev_class = p->sched_class;
next_class = __setscheduler_class(policy, newprio);
- if (prev_class != next_class) {
- /*
- * SCHED_FLAG_KEEP_PARAMS prevents the class change from
- * actually happening (it skips __setscheduler_params and
- * the sched_class assignment). Reject contradictory
- * requests that ask for a class change with KEEP_PARAMS.
- */
- if (attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) {
- retval = -EINVAL;
- goto unlock;
- }
- queue_flags |= DEQUEUE_CLASS;
+ /*
+ * SCHED_FLAG_KEEP_PARAMS prevents the policy and the class change
+ * from actually happening: it skips __setscheduler_params() and the
+ * sched_class assignment. Reject the contradictory requests that
+ * ask for either, rather than carry out half of what they ask.
+ *
+ * A class change with the policy kept is one of them: the class of a
+ * policy depends on the priority that priority inheritance leaves,
+ * and on whether a BPF scheduler is in charge.
+ */
+ if ((attr->sched_flags & SCHED_FLAG_KEEP_PARAMS) &&
+ (policy != p->policy || prev_class != next_class)) {
+ retval = -EINVAL;
+ goto unlock;
}
+ if (prev_class != next_class)
+ queue_flags |= DEQUEUE_CLASS;
+
/*
* If setscheduling to SCHED_DEADLINE (or changing the parameters
* of a SCHED_DEADLINE task) we need to check if enough bandwidth
--
2.47.3