SCHED_DEADLINE and the Constant Bandwidth Server: Resource Reservations in Linux

Raspberry Pi 4 Model B board
Image: Raspberry Pi Ltd — Raspberry Pi Documentation, CC BY-SA 4.0
Table of contents

    For most of Linux’s history, a developer who needed timing guarantees had two tools: the fixed-priority policies SCHED_FIFO and SCHED_RR. They work, but they describe importance rather than timing. A priority says “run me before that other thread”; it says nothing about how much CPU time a thread needs, how often, or by when. And a single misbehaving high-priority thread can starve everything beneath it.

    SCHED_DEADLINE, merged into mainline Linux in version 3.14 (2014), takes a different approach. Each task declares a runtime, a deadline and a period. The kernel schedules tasks by Earliest Deadline First (EDF) and enforces every declaration with the Constant Bandwidth Server (CBS) algorithm. What you get is a resource reservation: a promised slice of CPU bandwidth that other tasks cannot steal, and that the task itself cannot overrun. This article explains the theory behind it, shows how to use the API, and covers the practical pitfalls engineers run into.

    Key takeaways

    • SCHED_DEADLINE combines EDF scheduling with CBS budget enforcement, so each task gets a reserved share of CPU time, isolated from the others.
    • Each task is described by runtime ≤ deadline ≤ period, all in nanoseconds. The kernel rejects parameters that break this ordering.
    • Admission control caps total deadline bandwidth at sched_rt_runtime_us / sched_rt_period_us (95% by default) per CPU in a root domain. A request that would exceed the cap fails with EBUSY.
    • On multicore machines the default is global EDF. Passing admission control then guarantees bounded tardiness, not zero deadline misses. For hard guarantees, partition the CPUs with exclusive cpusets.
    • Deadline tasks cannot fork unless they set reset-on-fork, cannot use arbitrary CPU affinity masks, and get only approximate priority inheritance.

    From CBS theory to a Linux scheduling class

    The Constant Bandwidth Server was introduced by Luca Abeni and Giorgio Buttazzo in 1998, at the ReTiS Lab of Scuola Superiore Sant’Anna in Pisa. They wanted to let soft real-time multimedia workloads share a processor with hard real-time tasks without being able to disturb them. The central idea is the reservation: each server is guaranteed a budget Q every period T, so it receives a bandwidth of Q/T. It is scheduled by EDF on a deadline the algorithm manages itself, and it can never use more than its share.

    Reservation-based scheduling was a central theme of European real-time research in the 2000s. The FRESCOR project (2006–2009), whose archive this site preserves, built its contract-based scheduling framework on top of this kind of mechanism. An application negotiated a contract for a minimum budget and period, and the platform enforced it with a reservation server. Scuola Superiore Sant’Anna and Evidence Srl were both FRESCOR partners. Researchers from that same Pisa ecosystem developed the Linux patch set first posted in 2009 as SCHED_EDF, later renamed SCHED_DEADLINE. Its development was supported in part by follow-up EU research such as the ACTORS project. After several years of review and rework, the scheduling class was merged in Linux 3.14.

    The deadline class sits above the real-time class, so a runnable deadline task with budget preempts any SCHED_FIFO or SCHED_RR task.

    How the Constant Bandwidth Server works

    Each deadline task keeps two pieces of dynamic state: its remaining runtime (the budget left in the current period) and its absolute scheduling deadline. The scheduler always picks the runnable, non-throttled task with the earliest absolute deadline. That is plain EDF, which is optimal on a single processor: if any algorithm can meet all the deadlines, EDF can. See our comparison of rate monotonic and EDF.

    Budget consumption and throttling

    While a task runs, its remaining runtime goes down. When it reaches zero the task is throttled: it is removed from the run queue until its replenishment time, even if it still has work to do. At that point the runtime is refilled and the absolute deadline moves forward by one period. Linux uses this “hard reservation” behavior by default. It is the property that delivers temporal isolation: a buggy or overloaded task can only hurt itself.

    The wake-up rule

    The subtle part of CBS is what happens when a task wakes up after sleeping. If it simply kept its old deadline and leftover runtime, it could consume budget at a higher rate than it reserved and break the guarantees given to everyone else. In the original CBS formulation, a waking task keeps its current deadline and runtime only if doing so would not exceed its reserved bandwidth, that is, if remaining_runtime / (deadline − now) ≤ runtime / period. Otherwise the task gets a fresh deadline now + relative_deadline and a full budget. Linux implements this rule and adds a revised version for constrained-deadline tasks, where the relative deadline is shorter than the period.

    Why “constant bandwidth”? However a task behaves (sleeping, waking early, spinning in a loop), CBS ensures that over any interval it is never served at more than its reserved bandwidth runtime/period. On a uniprocessor, if the reserved bandwidths sum to at most 1, every server meets its scheduling deadlines no matter how the others behave.

    The SCHED_DEADLINE API

    The three parameters

    Parameter Meaning Typical choice
    sched_runtime CPU budget granted per period (ns) Measured or analyzed worst-case execution time plus margin
    sched_deadline Relative deadline from each activation by which the runtime must be delivered (ns) Equal to the period, or shorter for tighter latency
    sched_period Replenishment interval (ns); if 0, it is set equal to the deadline Activation rate of the workload (e.g. 1 ms control loop, 16.67 ms video frame)
    sched_flags Optional behavior modifiers SCHED_FLAG_RESET_ON_FORK, SCHED_FLAG_RECLAIM, SCHED_FLAG_DL_OVERRUN

    The kernel requires runtime ≤ deadline ≤ period, a non-zero deadline, and a runtime of at least about a microsecond. Admission control charges the task a bandwidth of runtime/period.

    Trying it from the shell with chrt

    Recent versions of chrt from util-linux support the deadline policy. All values are in nanoseconds, and the priority argument must be 0:

    # 2 ms of CPU every 10 ms, to be delivered within 8 ms of each activation
    sudo chrt -d --sched-runtime 2000000 
                 --sched-deadline 8000000 
                 --sched-period 10000000 0 ./control_loop
    
    # Inspect the policy and parameters of a running task
    chrt -p $(pidof control_loop)

    Using sched_setattr from C

    The deadline parameters don’t fit in the old sched_param structure, so Linux added the sched_setattr() and sched_getattr() system calls. Recent glibc releases ship wrappers for them; on older toolchains you call them through syscall(). A minimal periodic task looks like this:

    #define _GNU_SOURCE
    #include <stdint.h>
    #include <stdio.h>
    #include <sched.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    
    #ifndef SCHED_DEADLINE
    #define SCHED_DEADLINE 6
    #endif
    
    struct dl_attr {              /* layout of the kernel's struct sched_attr (v0) */
        uint32_t size;
        uint32_t sched_policy;
        uint64_t sched_flags;
        int32_t  sched_nice;
        uint32_t sched_priority;
        uint64_t sched_runtime;   /* nanoseconds */
        uint64_t sched_deadline;
        uint64_t sched_period;
    };
    
    static void do_one_job(void) { /* control-loop body */ }
    
    int main(void)
    {
        struct dl_attr attr = {
            .size           = sizeof(attr),
            .sched_policy   = SCHED_DEADLINE,
            .sched_runtime  =  2 * 1000 * 1000,   /*  2 ms */
            .sched_deadline =  8 * 1000 * 1000,   /*  8 ms */
            .sched_period   = 10 * 1000 * 1000,   /* 10 ms */
        };
    
        if (syscall(SYS_sched_setattr, 0, &attr, 0) != 0) {
            perror("sched_setattr");   /* EBUSY: rejected by admission control */
            return 1;
        }
        for (;;) {
            do_one_job();
            sched_yield();   /* job finished: sleep until the next period */
        }
    }

    For a deadline task, calling sched_yield() gives up the rest of the current budget and suspends the task until its next replenishment. That makes it a convenient way to say “this job is done.”

    Useful flags

    • SCHED_FLAG_RESET_ON_FORK: children start under SCHED_OTHER. Without this flag, fork() from a deadline task fails.
    • SCHED_FLAG_RECLAIM (Linux 4.13+): enables GRUB (Greedy Reclamation of Unused Bandwidth), so the task may use bandwidth that other reservations leave idle, within the admission cap.
    • SCHED_FLAG_DL_OVERRUN (Linux 4.16+): sends SIGXCPU to the task when it exhausts its runtime, which is useful for logging budget overruns in the field.

    Admission control and what it actually guarantees

    Before accepting new parameters, the kernel checks that the total reserved bandwidth still fits. The limit comes from two sysctls that also govern real-time throttling:

    $ cat /proc/sys/kernel/sched_rt_period_us
    1000000
    $ cat /proc/sys/kernel/sched_rt_runtime_us
    950000

    With these defaults, deadline tasks in a root domain may reserve at most 95% of each CPU’s capacity: Σ runtime_i/period_i ≤ 0.95 × M, where M is the number of CPUs in the root domain. The remaining 5% is left for ordinary tasks. Setting sched_rt_runtime_us to -1 disables the check, which is rarely a good idea in production.

    Admission is not a hard guarantee on multicore. On a single CPU, total bandwidth ≤ 1 is necessary and sufficient for EDF to meet every deadline (given correct runtimes and implicit deadlines). On M CPUs, Linux schedules deadline tasks with global EDF. The ≤ M test is necessary, but it is not sufficient for zero misses. What it guarantees is bounded tardiness: deadlines may be missed, but only by a bounded amount. The kernel documentation says so explicitly. Our article on multicore real-time scheduling explains the underlying Dhall effect.

    If you need hard guarantees on a multicore machine, partition it. Create exclusive cpusets with load balancing disabled (or, with cgroup v2, set cpuset.cpus.partition to root). Each partition becomes its own root domain with its own admission control and its own EDF run queue. A single-CPU partition gives you uniprocessor EDF and the exact utilization test. For a single core you can check a candidate task set with our schedulability calculator before deploying it.

    Choosing runtime, deadline and period

    A reservation only protects you if the runtime covers the work. If the runtime is too small, the task is throttled in the middle of a job and misses its own deadline. If it is too large, you waste reserved bandwidth and other tasks fail admission. A pragmatic workflow:

    1. Set the period from the application. Use the control-loop rate, sensor sampling interval or frame rate. For sporadic, event-driven tasks, use the minimum inter-arrival time.
    2. Establish the execution-time budget. Measure the job on the target hardware under realistic interference (cache pressure, interrupts, memory traffic), take a high-water mark, and add margin. Our guide to WCET analysis methods explains why measurement alone can underestimate the true worst case and when static or hybrid analysis is worth it.
    3. Tighten the deadline only when latency matters. A deadline shorter than the period bounds how long after activation the work completes, at the cost of a stricter schedulability condition (density runtime/deadline instead of utilization).
    4. Monitor overruns. Use SCHED_FLAG_DL_OVERRUN or tracing (trace-cmd, the sched_switch tracepoint) to confirm the budget is rarely or never exhausted.

    Runtime is consumed by everything in the task’s context, including system calls and page faults, so call mlockall(MCL_CURRENT | MCL_FUTURE) and pre-fault buffers before entering the loop.

    Practical pitfalls and limitations

    CPU affinity

    A deadline task’s affinity mask must cover its whole root domain. Calling sched_setaffinity() to pin a deadline task to one CPU inside a larger domain fails, because it would invalidate the global admission test. Use cpusets to restrict deadline tasks to particular cores.

    Locking and priority inheritance

    When a deadline task blocks on a PI-futex held by another task, the kernel boosts the lock holder using the waiter’s deadline parameters. This is a pragmatic approximation, not full bandwidth inheritance, and the boosted holder is not throttled the way a normal reservation is. Keep critical sections short and bounded. The classic background is in our article on priority inversion and the priority ceiling protocol.

    Interaction with interrupts and the kernel

    EDF and CBS can only guarantee CPU time that the scheduler actually controls. Non-preemptible kernel sections, long interrupt handlers and firmware events (SMIs on x86) all delay deadline tasks. A PREEMPT_RT kernel, which is configurable in mainline since Linux 6.12, greatly reduces non-preemptible regions and runs most interrupt handlers in threads. That makes SCHED_DEADLINE far more predictable. Our RTOS comparison discusses when PREEMPT_RT Linux is the right platform.

    Frequency scaling and energy

    With the schedutil cpufreq governor, the reserved deadline bandwidth sets a floor on the CPU frequency. The kernel scales runtime accounting with the current frequency and CPU capacity, so the reservation is consistent across operating points. Still, validate timing at the lowest frequency you allow. The trade-offs are covered in energy-aware real-time scheduling.

    SCHED_DEADLINE in the bigger picture

    Reservations have become part of the kernel’s own infrastructure. Starting with Linux 6.12, the kernel uses an internal deadline server to give normal (fair-class) tasks a minimum share of CPU time. It is gradually replacing the older real-time throttling approach.

    Aspect SCHED_FIFO / SCHED_RR SCHED_DEADLINE
    Task model Static priority 1–99 Runtime, deadline, period
    Scheduling algorithm Fixed priority, preemptive EDF with CBS enforcement
    Temporal isolation None per task (only global RT throttling) Per-task budget enforcement
    Admission control None Total bandwidth check, EBUSY on failure
    Uniprocessor utilization bound Up to ~69% guaranteed with RM (higher with exact analysis) 100% (implicit deadlines)
    Best suited for Simple, well-analyzed task sets, legacy code Composing independent components, multimedia, control loops with known budgets

    The deeper value of SCHED_DEADLINE is composability. Independently developed components can each declare a reservation, be admitted or rejected up front, and run without timing interference from one another. This is the property that contract-based frameworks were designed around. If you are tuning a production system, our Real-Time Linux Tuning service covers PREEMPT_RT configuration and deadline reservations end to end, and the glossary defines the terms used here.

    Frequently asked questions

    Do I need a PREEMPT_RT kernel to use SCHED_DEADLINE?

    No. SCHED_DEADLINE is available in standard mainline kernels since 3.14. However, the guarantees assume the scheduler can preempt promptly. On a non-RT kernel, long non-preemptible sections can delay deadline tasks by hundreds of microseconds or more. For latency-sensitive work, combine it with CONFIG_PREEMPT_RT.

    Why does sched_setattr return EBUSY?

    Admission control rejected the request because the new total bandwidth in the task’s root domain would exceed the limit set by sched_rt_runtime_us/sched_rt_period_us. Reduce the runtime, lengthen the period, move tasks to another cpuset partition, or review existing reservations.

    What happens if my task needs more than its runtime?

    It is throttled until its next replenishment. The current job continues in the next period, which usually means that job misses its deadline, while all other tasks remain unaffected. With SCHED_FLAG_RECLAIM, the task can also use bandwidth that other reservations leave unused.

    Can I pin a SCHED_DEADLINE task to a single core?

    Not with sched_setaffinity() inside a multi-CPU root domain. Instead, create an exclusive cpuset (or a cgroup v2 partition root) containing that core and move the task into it. That core then has its own root domain, admission control and uniprocessor EDF scheduling.

    Are runtime, deadline and period in microseconds?

    No. In struct sched_attr and on the chrt command line they are in nanoseconds. Only the admission-control sysctls (sched_rt_runtime_us, sched_rt_period_us) are expressed in microseconds.

    References and further reading

    1. Deadline Task Scheduling — Linux kernel documentation
    2. sched(7) — overview of CPU scheduling — Linux man-pages project
    3. SCHED_DEADLINE — Wikipedia
    4. L. Abeni and G. Buttazzo, “Integrating Multimedia Applications in Hard Real-Time Systems,” Proc. IEEE Real-Time Systems Symposium (RTSS), 1998.
    5. G. Lipari and S. Baruah, “Greedy Reclamation of Unused Bandwidth in Constant-Bandwidth Servers,” Proc. Euromicro Conference on Real-Time Systems (ECRTS), 2000.
    6. Control Group v2 (cpuset partitions) — Linux kernel documentation
    7. FRESCOR project record — CORDIS, European Commission

    FRESCOR editorial team
    Independent writers and reviewers covering real-time and embedded systems. Read our editorial policy or report a correction.