Earliest Deadline First Scheduling
**EDF scheduling** — earliest deadline first — always runs the task whose deadline is nearest. Priorities are recomputed as time passes rather than fixed in advance, which is what lets EDF use 100% of the CPU where fixed-priority scheduling gives up around 69%.
Deadlines as Dynamic Priority
Earliest deadline first scheduling assigns priority by absolute deadline: at any instant the running task is the ready task whose deadline is soonest. Nothing about a task's identity determines its rank — a task that was lowest priority a moment ago becomes highest as its deadline approaches. This is the defining contrast with rate-monotonic scheduling, where a task's period fixes its priority permanently.
The scheduler acts at two moments: when a task arrives, and when one completes. On arrival, the new task's deadline is compared with the running task's; if it is sooner, the running task is preempted immediately. On completion, the ready queue's minimum deadline is selected. Implemented properly the ready queue is a priority queue keyed on absolute deadline, giving O(log n) per operation.
Note that absolute deadline, not relative, is the key. A task with relative deadline 10 released at time 30 has absolute deadline 40, and it is that number which is compared. Two instances of the same periodic task therefore have different priorities.
- Priority is the absolute deadline, recomputed as time passes
- The scheduler acts on every arrival and completion
- A ready queue keyed on deadline gives O(log n)
- Two jobs of one task can hold different priorities
The 100% Utilisation Result
The central theorem, from Liu and Layland's 1973 paper, is that for independent preemptible periodic tasks with deadlines equal to periods, EDF is optimal: a task set is schedulable by EDF if and only if total utilisation U = Σ(Cᵢ/Tᵢ) ≤ 1. If any algorithm can schedule the set, EDF can.
Compare rate-monotonic, whose sufficient bound is U ≤ n(2^(1/n) − 1), converging to ln 2 ≈ 0.693 as the number of tasks grows. A task set at 80% utilisation may be unschedulable under RM and is guaranteed schedulable under EDF. On a system where CPU cost is real, that difference is 30% of a processor.
The classic worked example: tasks (C=1,T=3), (C=2,T=5), (C=1,T=6) give U = 0.333 + 0.4 + 0.167 = 0.9. That exceeds the three-task RM bound of 0.779, so rate-monotonic offers no guarantee, while EDF schedules it with certainty because 0.9 ≤ 1. This is the example worth working through step by step, which is what the tracer on this page does.
- Schedulable if and only if U ≤ 1
- Rate-monotonic converges to a 0.693 bound
- A set at 0.9 utilisation is feasible for EDF only
- That gap is roughly 30% of a processor
Why Industry Often Chooses RM Anyway
Given that EDF is provably optimal, its relative rarity in production needs explaining, and there are three real reasons.
Overload behaviour is the serious one. If utilisation exceeds 1, EDF does not degrade gracefully — it degrades catastrophically. Tasks that miss their deadlines keep running and keep the highest priority, since a passed deadline is the earliest deadline of all. The scheduler devotes the CPU to work that is already worthless, causing a domino effect where nearly everything misses. Rate-monotonic under overload predictably sacrifices the low-priority tasks and keeps the high-priority ones on time, which for a safety system is the behaviour you want.
Runtime cost: RM priorities are computed once offline and the dispatcher is a fixed-priority lookup. EDF needs a deadline-ordered queue maintained at runtime, and typically causes more preemptions and more context switches. Analysis and certification: fixed-priority response-time analysis is mature, well tooled, and familiar to certification authorities in avionics and automotive.
So the accurate summary is that EDF wins on utilisation and RM wins on predictability under stress. EDF appears where load is well characterised and CPU is scarce; RM appears where the consequence of overload is measured in lives.
- Under overload EDF degrades catastrophically
- A missed deadline is still the earliest deadline
- Fixed priority sheds low-priority tasks predictably
- Certification tooling favours fixed-priority analysis
Three periodic tasks at 90% utilisation — RM cannot promise this, EDF can
# Earliest Deadline First: at every instant, run the ready job whose
# ABSOLUTE deadline is nearest. Priority is recomputed as time passes --
# that is what separates EDF from rate-monotonic's fixed priorities.
import heapq
def edf(tasks, horizon):
ready, timeline, nxt = [], [], {n: 0 for n, _, _ in tasks}
for t in range(horizon):
for name, c, p in tasks: # release anything due at t
if nxt[name] == t:
# key on ABSOLUTE deadline, so the heap pops the urgent job
heapq.heappush(ready, (t + p, name, c))
nxt[name] = t + p
if not ready:
timeline.append("idle")
continue
dl, name, left = heapq.heappop(ready) # nearest deadline wins
timeline.append(name)
if left - 1: # preemptible: requeue
heapq.heappush(ready, (dl, name, left - 1))
elif t + 1 > dl:
raise RuntimeError(name + " missed deadline")
return timeline
def utilisation(tasks):
return sum(c / p for _, c, p in tasks)
TASKS = [("T1", 1, 3), ("T2", 2, 5), ("T3", 1, 6)]
run = edf(TASKS, 15)
print("schedule:", " ".join(run))
print("U = %.2f -> schedulable by EDF (bound is 1.0)" % utilisation(TASKS))
// Earliest Deadline First. The ready set is a min-heap keyed on the
// ABSOLUTE deadline, so the pop is always the most urgent job.
#include <iostream>
#include <queue>
#include <string>
#include <vector>
#include <map>
struct Job {
int deadline, left;
std::string name;
};
struct Later {
bool operator()(const Job& a, const Job& b) const {
return a.deadline > b.deadline; // min-heap on deadline
}
};
struct Task {
std::string name;
int c, p;
};
std::vector<std::string> edf(const std::vector<Task>& tasks, int horizon) {
std::priority_queue<Job, std::vector<Job>, Later> ready;
std::map<std::string, int> next;
for (const auto& t : tasks) next[t.name] = 0;
std::vector<std::string> timeline;
for (int t = 0; t < horizon; ++t) {
for (const auto& task : tasks) // releases at this tick
if (next[task.name] == t) {
ready.push({t + task.p, task.c, task.name});
next[task.name] = t + task.p;
}
if (ready.empty()) {
timeline.push_back("idle");
continue;
}
Job j = ready.top();
ready.pop(); // nearest deadline wins
timeline.push_back(j.name);
if (--j.left) ready.push(j); // preemptible: requeue
}
return timeline;
}
int main() {
for (const auto& s : edf({{"T1",1,3},{"T2",2,5},{"T3",1,6}}, 15))
std::cout << s << ' ';
std::cout << '\n';
}// Earliest Deadline First -- dynamic priority by absolute deadline.
import java.util.*;
class EDF {
record Job(int deadline, int left, String name) {
}
static List<String> run(int[][] tasks, String[] names, int horizon) {
// ordered by absolute deadline: the soonest deadline is highest priority
PriorityQueue<Job> ready = new PriorityQueue<>(
Comparator.comparingInt(Job::deadline));
int[] next = new int[tasks.length];
List<String> timeline = new ArrayList<>();
for (int t = 0; t < horizon; t++) {
for (int i = 0; i < tasks.length; i++)
if (next[i] == t) { // release
ready.add(new Job(t + tasks[i][1], tasks[i][0], names[i]));
next[i] = t + tasks[i][1];
}
if (ready.isEmpty()) {
timeline.add("idle");
continue;
}
Job j = ready.poll(); // most urgent
timeline.add(j.name());
if (j.left() - 1 > 0) // preempt back onto the heap
ready.add(new Job(j.deadline(), j.left() - 1, j.name()));
else if (t + 1 > j.deadline())
throw new IllegalStateException("missed " + j.name());
}
return timeline;
}
}Step through it
Running on T1(C=1,T=3) T2(C=2,T=5) T3(C=1,T=6) U = 0.90
Read all 14 Steps
- t=0 — all three release together, T1 has the nearest deadline Every task releases at time 0. Their absolute deadlines are T1 at 3, T2 at 5, T3 at 6. EDF compares those three numbers and nothing else — not period, not arrival order, not how long each needs. T1's deadline of 3 is soonest, so T1 runs.
- t=1 — T1 done with 2 ticks to spare, T2 is now nearest T1 needed only 1 tick and finished at t=1, well inside its deadline of 3. Of what remains, T2's deadline is 5 and T3's is 6, so T2 takes the CPU. This is the first place a fixed-priority scheduler could differ — under rate-monotonic, priority would be locked to period forever.
- t=2 — T2 continues, it needs 2 ticks in total T2 requires 2 ticks of compute and has used 1. No new task has been released, and no ready task has a deadline sooner than T2's 5, so there is nothing to preempt it. It keeps the CPU.
- t=3 — T1 releases again, and a tie appears T2 completed at t=3. T1 releases its second job with absolute deadline 3+3=6 — the same deadline as T3, which is still waiting. EDF's rule does not resolve exact ties, so any consistent tie-break is valid; here T1 goes first. Note the priority order has now changed from what it was at t=0, which fixed-priority scheduling cannot do.
- t=4 — T3 finally runs, one tick before its deadline T1's second job finished in one tick. T3 has been ready since t=0 and only now reaches the CPU, with absolute deadline 6 and one tick of work. It will finish exactly at t=5, one tick early. Waiting four ticks is fine — EDF guarantees the deadline, not promptness.
- t=5 — T2's second job releases, deadline 10 T3 completed at t=5, its first job done inside deadline 6. T2 releases again with absolute deadline 5+5=10. It is the only ready job, so it runs. The CPU has been busy every tick so far: at 90% utilisation there is very little slack.
- t=6 — T1 releases and preempts T2 mid-execution This is the preemption that makes EDF work. T2 is running with one tick left and deadline 10. T1 releases with deadline 6+3=9, which is sooner, so EDF immediately preempts T2 and runs T1. A non-preemptive scheduler could not do this, and with tight deadlines it would eventually miss one.
- t=7 — T1 done, preempted T2 resumes exactly where it stopped T1's third job finished in its single tick, comfortably inside deadline 9. T2 resumes with its remaining tick. Preemption cost it nothing but a delay: its state was preserved and its deadline of 10 is still four ticks away.
- t=8 — T2 completes, T3's second job takes over T2's second job is done at t=8, inside its deadline of 10. T3 released at t=6 with deadline 6+6=12 and is the only ready job. Every deadline so far has been met, on a task set rate-monotonic analysis could not certify.
- t=9 — T1 releases for the fourth time T3's second job completed at t=9. T1 releases with absolute deadline 9+3=12. It ties with nothing currently ready, so it runs immediately. T1 has now run four times in nine ticks — the highest frequency of the three, which is what a period of 3 means.
- t=10 — T2's third job, deadline 15 T1 finished immediately as always. T2 releases with deadline 10+5=15 and needs 2 ticks. Nothing else is ready. The pattern is settling into a repeating cycle whose length is the hyperperiod — lcm(3,5,6) = 30.
- t=11 — T2 continues uninterrupted T2 uses its second tick. No release happens at t=11 for any task: T1 next releases at 12, T3 at 12. With no competitor there is nothing for EDF to decide.
- t=12 — T1 and T3 release together, T1's deadline is nearer T2 finished at t=12, inside deadline 15. Both T1 (deadline 15) and T3 (deadline 18) release now. T1's deadline is sooner so it runs first, and T3 waits. Every one of the twelve jobs dispatched so far has met its deadline.
- why this schedule was guaranteed before it ran Total utilisation is 1/3 + 2/5 + 1/6 = 0.90. EDF's schedulability test is exactly U ≤ 1, so this set was provably feasible before a single tick executed. The rate-monotonic sufficient bound for three tasks is 3(2^(1/3) − 1) = 0.779, which 0.90 exceeds — so RM analysis could not certify it. That 12% of CPU is the practical value of dynamic priorities. The warning attached to it: push utilisation past 1.0 and EDF does not degrade gracefully, because a job that has already missed its deadline holds the earliest deadline of all and keeps the CPU while everything else piles up behind it.