[PATCH v2 4/7] sched: Move DL's admission control to sysfs

From: Sebastian Andrzej Siewior

Date: Tue Nov 10 2026 - 12:43:44 EST


SCHED_DL uses the sysctl sched_rt_{period|runtime}_us to configure its
admission control. It is used to verify that it does not allocate more
time gobally to SCHED_DL tasks than it can theoretically handle.

This sysctl used to control RT throttling. With the introduction of the
DL server this knob controls RT throttling within a RT-cgroup if
CONFIG_RT_GROUP_SCHED is enabled. In this case the knob is also stuck
and can not be changed. So it can not be used for SCHED_DL control at
all. Unless to disable it by writing -1.

Split the DL bits into its knob at sysfs' location. Once the DL logic
is out, sched_domains_mutex.*() and rebuild_sched_domains() can be
removed from sched_rt_handler().

Move SCHED_DL usage of this sysctl to /sys/kernel/sched/ as
dl_runtime_us + dl_period_us. Replace global_rt_period()/
global_rt_runtime() with sysfs_dl_period/ sysfs_dl_runtime. Store the
new attribues in ns. Move global_rt_period()/ global_rt_runtime() to
CONFIG_RT_GROUP_SCHED.

Signed-off-by: Sebastian Andrzej Siewior <bigeasy@xxxxxxxxxxxxx>
---
Documentation/arch/arm64/asymmetric-32bit.rst | 2 +-
Documentation/scheduler/sched-deadline.rst | 15 +--
kernel/sched/deadline.c | 163 ++++++++++++++++++++++++--
kernel/sched/rt.c | 25 +---
kernel/sched/sched.h | 28 ++---
5 files changed, 174 insertions(+), 59 deletions(-)

diff --git a/Documentation/arch/arm64/asymmetric-32bit.rst b/Documentation/arch/arm64/asymmetric-32bit.rst
index fc0c350c5e001..375958f8971e5 100644
--- a/Documentation/arch/arm64/asymmetric-32bit.rst
+++ b/Documentation/arch/arm64/asymmetric-32bit.rst
@@ -105,7 +105,7 @@ affinity for the task is updated and any saved mask from a prior
Explicit admission of a 32-bit deadline task to the default root domain
(e.g. by calling ``sched_setattr(2)``) is rejected on an asymmetric
32-bit system unless admission control is disabled by writing -1 to
-``/proc/sys/kernel/sched_rt_runtime_us``.
+``/sys/kernel/sched/dl_runtime_us``.

``execve(2)`` of a 32-bit program from a 64-bit deadline task will
return ``-ENOEXEC`` if the root domain for the task contains any
diff --git a/Documentation/scheduler/sched-deadline.rst b/Documentation/scheduler/sched-deadline.rst
index 9019b66f6a5ba..23ea88b2ed7dc 100644
--- a/Documentation/scheduler/sched-deadline.rst
+++ b/Documentation/scheduler/sched-deadline.rst
@@ -590,24 +590,19 @@ Deadline Task Scheduling
4.1 System wide settings
------------------------

- The system wide settings are configured under the /proc virtual file system.
+ The system wide settings are configured under /sys/kernel/sched.

- For now the -rt knobs are used for -deadline admission control and with
- CONFIG_RT_GROUP_SCHED the -deadline runtime is accounted against the (root)
- -rt runtime. With !CONFIG_RT_GROUP_SCHED the knob only serves for the -dl
- admission control. We realize that this isn't entirely desirable; however, it
- is better to have a small interface for now, and be able to change it easily
- later. The ideal situation (see 5.) is to run -rt tasks from a -deadline
- server; in which case the -rt bandwidth is a direct subset of dl_bw.
+ The ideal situation (see 5.) is to run -rt tasks from a -deadline server; in
+ which case the -rt bandwidth is a direct subset of dl_bw.

This means that, for a root_domain comprising M CPUs, -deadline tasks
can be created while the sum of their bandwidths stays below:

- M * (sched_rt_runtime_us / sched_rt_period_us)
+ M * (dl_runtime_us / dl_period_us)

It is also possible to disable this bandwidth management logic, and
be thus free of oversubscribing the system up to any arbitrary level.
- This is done by writing -1 in /proc/sys/kernel/sched_rt_runtime_us.
+ This is done by writing -1 in /sys/kernel/sched/dl_runtime_us.


4.2 Task interface
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index 24633af261be6..c5a2499a1450a 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -23,6 +23,14 @@
#include "sched.h"
#include "pelt.h"

+/*
+ * Admission control for DL tasks. For a root_domain comprising M CPUs,
+ * -deadline tasks can be created while the sum of their bandwidths stays
+ * below: M * (dl_period / dl_runtime)
+ */
+static u64 sysfs_dl_period = 1000000 * NSEC_PER_USEC;
+static u64 sysfs_dl_runtime = 1000000 * NSEC_PER_USEC;
+
/*
* Default limits for DL period; on the top end we guard against small util
* tasks still getting ridiculously long effective runtimes, on the bottom end we
@@ -509,10 +517,10 @@ static void init_dl_rq_bw_ratio(struct dl_rq *dl_rq);
void init_dl_bw(struct dl_bw *dl_b)
{
raw_spin_lock_init(&dl_b->lock);
- if (global_rt_runtime() == RUNTIME_INF)
+ if (sysfs_dl_runtime == RUNTIME_INF)
dl_b->bw = -1;
else
- dl_b->bw = to_ratio(global_rt_period(), global_rt_runtime());
+ dl_b->bw = to_ratio(sysfs_dl_period, sysfs_dl_runtime);
dl_b->total_bw = 0;
}

@@ -3712,10 +3720,8 @@ DEFINE_SCHED_CLASS(dl) = {
*/
u64 dl_cookie;

-int sched_dl_global_validate(void)
+static int sched_dl_global_validate(u64 runtime, u64 period)
{
- u64 runtime = global_rt_runtime();
- u64 period = global_rt_period();
u64 new_bw = to_ratio(period, runtime);
u64 cookie = ++dl_cookie;
struct dl_bw *dl_b;
@@ -3753,18 +3759,18 @@ int sched_dl_global_validate(void)

static void init_dl_rq_bw_ratio(struct dl_rq *dl_rq)
{
- if (global_rt_runtime() == RUNTIME_INF) {
+ if (sysfs_dl_runtime == RUNTIME_INF) {
dl_rq->bw_ratio = 1 << RATIO_SHIFT;
dl_rq->max_bw = dl_rq->extra_bw = 1 << BW_SHIFT;
} else {
- dl_rq->bw_ratio = to_ratio(global_rt_runtime(),
- global_rt_period()) >> (BW_SHIFT - RATIO_SHIFT);
+ dl_rq->bw_ratio = to_ratio(sysfs_dl_runtime,
+ sysfs_dl_period) >> (BW_SHIFT - RATIO_SHIFT);
dl_rq->max_bw = dl_rq->extra_bw =
- to_ratio(global_rt_period(), global_rt_runtime());
+ to_ratio(sysfs_dl_period, sysfs_dl_runtime);
}
}

-void sched_dl_do_global(void)
+static void sched_dl_do_global(void)
{
u64 new_bw = -1;
u64 cookie = ++dl_cookie;
@@ -3772,8 +3778,8 @@ void sched_dl_do_global(void)
int cpu;
unsigned long flags;

- if (global_rt_runtime() != RUNTIME_INF)
- new_bw = to_ratio(global_rt_period(), global_rt_runtime());
+ if (sysfs_dl_runtime != RUNTIME_INF)
+ new_bw = to_ratio(sysfs_dl_period, sysfs_dl_runtime);

for_each_possible_cpu(cpu)
init_dl_rq_bw_ratio(&cpu_rq(cpu)->dl);
@@ -4124,3 +4130,136 @@ void print_dl_stats(struct seq_file *m, int cpu)
{
print_dl_rq(m, cpu, &cpu_rq(cpu)->dl);
}
+
+#define SCHED_ATTR(_name) \
+ static struct kobj_attribute sched_attr_## _name = \
+ __ATTR(_name, 0644, sched_attr_##_name##_show, sched_attr_##_name##_store)
+
+static int sched_dl_try_boundary(int runtime, int period)
+{
+ int ret;
+ u64 runtime_ns;
+ u64 period_ns;
+
+ if (runtime) {
+ if (runtime < 0)
+ runtime_ns = RUNTIME_INF;
+ else
+ runtime_ns = runtime * NSEC_PER_USEC;
+ } else {
+ runtime_ns = sysfs_dl_runtime;
+ }
+
+ if (period)
+ period_ns = period * NSEC_PER_USEC;
+ else
+ period_ns = sysfs_dl_period;
+
+ if (runtime_ns != RUNTIME_INF) {
+ if (runtime_ns > period_ns || runtime_ns > MAX_BW)
+ return -EINVAL;
+ }
+
+ sched_domains_mutex_lock();
+ ret = sched_dl_global_validate(runtime_ns, period_ns);
+ if (ret) {
+ sched_domains_mutex_unlock();
+ return ret;
+ }
+
+ sysfs_dl_period = period_ns;
+ sysfs_dl_runtime = runtime_ns;
+
+ sched_dl_do_global();
+ sched_domains_mutex_unlock();
+ /*
+ * After changing maximum available bandwidth for DEADLINE, we need to
+ * recompute per root domain and per cpus variables accordingly.
+ */
+ rebuild_sched_domains();
+ return 0;
+}
+
+static ssize_t sched_attr_dl_runtime_us_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u64 val = sysfs_dl_runtime;
+
+ if (val == RUNTIME_INF)
+ val = -1;
+ else
+ do_div(val, NSEC_PER_USEC);
+
+ return sysfs_emit(buf, "%lld\n", val);
+}
+
+static ssize_t sched_attr_dl_runtime_us_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+
+ if (!(val == -1 || val > 0))
+ return -EINVAL;
+
+ ret = sched_dl_try_boundary(val, 0);
+ if (ret)
+ return ret;
+ return count;
+}
+SCHED_ATTR(dl_runtime_us);
+
+static ssize_t sched_attr_dl_period_us_show(struct kobject *kobj,
+ struct kobj_attribute *attr, char *buf)
+{
+ u64 val = sysfs_dl_period;
+
+ do_div(val, NSEC_PER_USEC);
+ return sysfs_emit(buf, "%lld\n", val);
+}
+
+static ssize_t sched_attr_dl_period_us_store(struct kobject *kobj,
+ struct kobj_attribute *attr,
+ const char *buf, size_t count)
+{
+ int val;
+ int ret;
+
+ ret = kstrtoint(buf, 0, &val);
+ if (ret)
+ return ret;
+ if (val <= 0)
+ return -EINVAL;
+
+ ret = sched_dl_try_boundary(0, val);
+ if (ret)
+ return ret;
+ return count;
+}
+SCHED_ATTR(dl_period_us);
+
+static struct attribute *sched_global_attrs[] = {
+ &sched_attr_dl_runtime_us.attr,
+ &sched_attr_dl_period_us.attr,
+ NULL,
+};
+
+static const struct attribute_group sched_global_attrs_group = {
+ .attrs = sched_global_attrs,
+};
+
+static int __init sched_dl_init(void)
+{
+ struct kobject *sched_kobj;
+
+ sched_kobj = kobject_create_and_add("sched", kernel_kobj);
+ if (!sched_kobj)
+ return -ENOMEM;
+ return sysfs_create_group(sched_kobj, &sched_global_attrs_group);
+}
+late_initcall(sched_dl_init);
diff --git a/kernel/sched/rt.c b/kernel/sched/rt.c
index cdeddd3fc9af2..1a6450f91d567 100644
--- a/kernel/sched/rt.c
+++ b/kernel/sched/rt.c
@@ -2871,7 +2871,6 @@ static int sched_rt_handler(const struct ctl_table *table, int write, void *buff
int ret;

mutex_lock(&mutex);
- sched_domains_mutex_lock();
old_period = sysctl_sched_rt_period;
old_runtime = sysctl_sched_rt_runtime;

@@ -2879,29 +2878,13 @@ static int sched_rt_handler(const struct ctl_table *table, int write, void *buff

if (!ret && write) {
ret = sched_rt_global_validate();
- if (ret)
- goto undo;
-
- ret = sched_dl_global_validate();
- if (ret)
- goto undo;
-
- sched_dl_do_global();
+ if (ret) {
+ sysctl_sched_rt_period = old_period;
+ sysctl_sched_rt_runtime = old_runtime;
+ }
}
- if (0) {
-undo:
- sysctl_sched_rt_period = old_period;
- sysctl_sched_rt_runtime = old_runtime;
- }
- sched_domains_mutex_unlock();
mutex_unlock(&mutex);

- /*
- * After changing maximum available bandwidth for DEADLINE, we need to
- * recompute per root domain and per cpus variables accordingly.
- */
- rebuild_sched_domains();
-
return ret;
}

diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index a7964537bac75..5f99f5ef415e7 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -354,8 +354,6 @@ struct dl_bw {
};

extern void init_dl_bw(struct dl_bw *dl_b);
-extern int sched_dl_global_validate(void);
-extern void sched_dl_do_global(void);
extern int sched_dl_overflow(struct task_struct *p, int policy, const struct sched_attr *attr);
extern void __setparam_dl(struct task_struct *p, const struct sched_attr *attr);
extern void __getparam_dl(struct task_struct *p, struct sched_attr *attr, unsigned int flags);
@@ -599,6 +597,19 @@ static inline int rt_bandwidth_enabled(void)
{
return sysctl_sched_rt_runtime >= 0;
}
+
+static inline u64 global_rt_period(void)
+{
+ return (u64)sysctl_sched_rt_period * NSEC_PER_USEC;
+}
+
+static inline u64 global_rt_runtime(void)
+{
+ if (sysctl_sched_rt_runtime < 0)
+ return RUNTIME_INF;
+
+ return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;
+}
#endif

extern struct task_group *sched_create_group(struct task_group *parent);
@@ -2505,19 +2516,6 @@ extern union sched_feat_key sched_feat_keys[__SCHED_FEAT_NR];
extern struct static_key_false sched_numa_balancing;
extern struct static_key_false sched_schedstats;

-static inline u64 global_rt_period(void)
-{
- return (u64)sysctl_sched_rt_period * NSEC_PER_USEC;
-}
-
-static inline u64 global_rt_runtime(void)
-{
- if (sysctl_sched_rt_runtime < 0)
- return RUNTIME_INF;
-
- return (u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;
-}
-
/*
* Is p the current execution context?
*/

--
2.55.0