Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Operating Systems  Module 4 – CPU scheduling

Real-time scheduling and priority inversion

Hard, firm and soft deadlines, utilisation tests for rate-monotonic and EDF scheduling, priority inversion and inheritance, and the real-time policies of Linux.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Clang 21.0.0 and GCC 16.2.0
  • By MySmartCoPilot
Free core, Pro depth: 7 of 20 minutes free
Free: Hard, firm and soft: what a missed deadline costs · Rate-monotonic priorities need headroom; EDF can fill the CPU · Priority inversion: a lock that reverses priorities · Practise: test a task set. With a pass: 3 in-depth parts and 3 interview answers, about 13 minutes.

What you will learn

  • Distinguish hard, firm and soft real-time requirements
  • Test schedulability with rate-monotonic and earliest-deadline-first analysis
  • Explain priority inversion and how priority inheritance bounds it
  • Use Linux's SCHED_FIFO, SCHED_RR and SCHED_DEADLINE policies safely

Before you start

A flight controller that computes the right correction too late has still failed. In a real-time system a result must be right and on time, and the ROS 2 design notes define such a system by a deterministic schedule, a guarantee that each task finishes by a certain time, rather than by low latency.

Real-time scheduling proves that guarantee before the system runs. This lesson tests task sets by their utilisation, runs rate-monotonic and EDF scheduling side by side, and shows priority inversion, a lock making the most urgent task wait behind a less urgent one.

Hard, firm and soft: what a missed deadline costs

Real-time requirements differ in what a missed deadline costs, from a failed system to a glitch. The ROS 2 design article sorts them three ways:

Requirement A missed deadline Its examples
hard real-time: A real-time requirement under which a missed deadline counts as a failure of the system, as in an autopilot, so every deadline must be met and proved in advance. is a failure of the system autopilots, spacecraft, planetary rovers
firm real-time: A real-time requirement under which a result that comes after its deadline is worthless, though the system carries on, as on a robotic assembly line. makes the late result worthless, but the system carries on financial forecasts, robotic assembly lines
soft real-time: A real-time requirement under which a missed deadline lowers the quality of service without failing the system, as in audio and video playback. lowers the quality of service audio and video playback

Real-time scheduling theory serves hard requirements: it proves in advance that no deadline will be missed. It needs each task’s worst-case execution time: The longest a job of a task can take on the CPU when it runs without interruption. Real-time analysis uses it rather than the average, because one slow job can miss a deadline., never its average, because one slow job is enough to miss.

Checkpoint

Check: Hard, firm and soft real-time requirements

  1. Question 1 of 2 A car's emergency-braking controller must decide within 20 ms of detecting an obstacle, and a late decision counts as a failure of the car. Its dashboard map redraws every 50 ms, and a late redraw only looks jerky. Which requirements are these?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Hard for the braking controller, soft for the map

    The kind of requirement follows from the cost of a miss: a failure of the system makes it hard, a worthless late result with the system carrying on makes it firm, and lower quality makes it soft.

  2. Question 2 of 2 A task's jobs need 2 ms of CPU on average, at most 3 ms in 99 runs of 100, and never more than 5 ms. Which time, in ms, goes into a real-time analysis of the task?

    Type a number.

    A hint

    The analysis must hold for every job, the slowest included.

    Show the answer: Question 2 of 2

    Answer: 5 ms

    The analysis uses the worst-case execution time, 5 ms here, never the average of 2 ms or the 3 ms that one job in a hundred exceeds, because one slow job is enough to miss a deadline.

Rate-monotonic priorities need headroom; EDF can fill the CPU

The theory models each task as periodic: a job arrives every T ms, needs at most C ms of CPU and must finish before the next one arrives, so its deadline is the end of its period. RTEMS’s documentation lists these assumptions, with tasks independent and preemptible. A task’s utilisation: The share of one CPU a periodic task needs: its worst-case execution time divided by its period. A task set's utilisation is the sum over its tasks. is C ÷ T, the share of one CPU it needs, and a set’s utilisation U is the sum.

rate-monotonic scheduling: Fixed-priority scheduling of periodic tasks in which the shorter a task's period, the higher its priority. fixes priorities by period: the shorter the period, the higher the priority. It is the optimal fixed-priority rule for such tasks on one CPU: if any fixed priorities meet every deadline, rate-monotonic ones do. earliest deadline first (EDF): Scheduling that always runs the ready job whose deadline is nearest, so priorities change as jobs arrive. On one CPU it meets every deadline whenever the utilisation is at most 1., earliest deadline first, runs the ready job whose deadline is nearest. The program runs both rules on two tasks for 35 ms, after which both periods line up again: T1 needs 2 ms every 5 ms and T2 4 ms every 7 ms. Its 14 versions printed the same lines:

One program · 14 languages Rate-monotonic and EDF on one CPU, millisecond by millisecond

Python 3.14 · main.py

def simulate(tasks: list[tuple[int, int]], horizon: int, rule: str) -> tuple[str, str]:
    """One CPU in 1 ms steps: the timeline and the first missed deadline."""
    left = [0] * len(tasks)  # work left in each current job
    deadline = [0] * len(tasks)

    def key(i: int) -> tuple[int, int]:
        # RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
        return (tasks[i][1] if rule == "RM" else deadline[i], i)

    timeline = ""
    for t in range(horizon + 1):
        for i, (work, period) in enumerate(tasks):
            if t % period == 0:
                if left[i]:
                    return (
                        timeline,
                        f"T{i + 1} misses its deadline at {t} ms, {left[i]} ms short",
                    )
                left[i], deadline[i] = work, t + period
        if t == horizon:
            break
        pick = min((i for i in range(len(tasks)) if left[i]), key=key, default=-1)
        if pick >= 0:
            left[pick] -= 1
        timeline += str(pick + 1) if pick >= 0 else "."
    return timeline, f"no deadline missed in {horizon} ms"


tasks, horizon = [(2, 5), (4, 7)], 35  # (work, period) in ms, and the hyperperiod
print("tasks (work/period):", " ".join(f"{w}/{p}" for w, p in tasks))
for rule in ["RM", "EDF"]:
    timeline, verdict = simulate(tasks, horizon, rule)
    print(f"  {rule:<3} {timeline}\n      {verdict}")

In Python: min over the tasks with work left, keyed on (period or deadline, index), gives a tie to T1, and default=-1 marks a millisecond with nothing to run.

Recorded with CPython 3.14.8 on macOS 26 arm64. It runs in your browser with Pyodide 314.0.7 (CPython 3.14.2). To run it yourself: python3 main.py

JavaScript Node 24 · main.mjs

// One CPU in 1 ms steps: the timeline and the first missed deadline.
function simulate(tasks, horizon, rule) {
  const left = tasks.map(() => 0); // work left in each current job
  const deadline = tasks.map(() => 0);
  let timeline = "";
  for (let t = 0; t <= horizon; t++) {
    for (const [i, [work, period]] of tasks.entries()) {
      if (t % period !== 0) continue;
      if (left[i] > 0)
        return [timeline, `T${i + 1} misses its deadline at ${t} ms, ${left[i]} ms short`];
      [left[i], deadline[i]] = [work, t + period];
    }
    if (t === horizon) break;
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    const key = (i) => (rule === "RM" ? tasks[i][1] : deadline[i]);
    let pick = -1;
    for (let i = 0; i < tasks.length; i++) {
      if (left[i] > 0 && (pick < 0 || key(i) < key(pick))) pick = i;
    }
    if (pick >= 0) left[pick]--;
    timeline += pick >= 0 ? String(pick + 1) : ".";
  }
  return [timeline, `no deadline missed in ${horizon} ms`];
}

const tasks = [
  [2, 5],
  [4, 7],
]; // (work, period) in ms
const horizon = 35; // the hyperperiod, when both periods end together
console.log(`tasks (work/period): ${tasks.map(([work, period]) => `${work}/${period}`).join(" ")}`);
for (const rule of ["RM", "EDF"]) {
  const [timeline, verdict] = simulate(tasks, horizon, rule);
  console.log(`  ${rule.padEnd(3)} ${timeline}\n      ${verdict}`);
}

In JavaScript: The loop keeps a task only when its key is strictly smaller than the best so far, so a tie goes to T1; a destructuring assignment releases a job in one statement.

Recorded with Node.js 24.21.0 LTS (V8 13.6) on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0). To run it yourself: node main.mjs

Java 25 LTS · Main.java

import java.util.ArrayList;
import java.util.List;
import java.util.function.IntUnaryOperator;

public class Main {
  record Task(int work, int period) {} // in ms

  // One CPU in 1 ms steps: the timeline and the first missed deadline.
  static String[] simulate(List<Task> tasks, int horizon, String rule) {
    int[] left = new int[tasks.size()]; // work left in each current job
    int[] deadline = new int[tasks.size()];
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    IntUnaryOperator key = i -> rule.equals("RM") ? tasks.get(i).period() : deadline[i];
    StringBuilder timeline = new StringBuilder();
    for (int t = 0; t <= horizon; t++) {
      for (int i = 0; i < tasks.size(); i++) {
        if (t % tasks.get(i).period() != 0) {
          continue;
        }
        if (left[i] > 0) {
          String miss = "T" + (i + 1) + " misses its deadline at " + t + " ms, " + left[i];
          return new String[] {timeline.toString(), miss + " ms short"};
        }
        left[i] = tasks.get(i).work();
        deadline[i] = t + tasks.get(i).period();
      }
      if (t == horizon) {
        break;
      }
      int pick = -1;
      for (int i = 0; i < tasks.size(); i++) {
        if (left[i] > 0 && (pick < 0 || key.applyAsInt(i) < key.applyAsInt(pick))) {
          pick = i;
        }
      }
      if (pick >= 0) {
        left[pick]--;
      }
      timeline.append(pick >= 0 ? String.valueOf(pick + 1) : ".");
    }
    return new String[] {timeline.toString(), "no deadline missed in " + horizon + " ms"};
  }

  public static void main(String[] args) {
    List<Task> tasks = List.of(new Task(2, 5), new Task(4, 7));
    int horizon = 35; // the hyperperiod, when both periods end together
    List<String> shown = new ArrayList<>();
    tasks.forEach(t -> shown.add(t.work() + "/" + t.period()));
    System.out.println("tasks (work/period): " + String.join(" ", shown));
    for (String rule : List.of("RM", "EDF")) {
      String[] run = simulate(tasks, horizon, rule);
      System.out.println("  " + rule + (rule.length() < 3 ? " " : "") + " " + run[0]);
      System.out.println("      " + run[1]);
    }
  }
}

In Java: A record holds each task and an IntUnaryOperator the key of the rule; simulate returns the timeline and the verdict in a two-element array.

Recorded with OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. It runs in your browser with a TeaVM 0.16.0 build in QuickJS (quickjs-emscripten 0.32.0). To run it yourself: java Main.java

C++23 · main.cpp

#include <format>
#include <print>
#include <string>
#include <utility>
#include <vector>

struct Task {
  int work, period; // in ms
};

// One CPU in 1 ms steps: the timeline and the first missed deadline.
std::pair<std::string, std::string>
simulate(const std::vector<Task> &tasks, int horizon, const std::string &rule) {
  std::vector<int> left(tasks.size()); // work left in each current job
  std::vector<int> deadline(tasks.size());
  // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
  auto key = [&](std::size_t i) {
    return rule == "RM" ? tasks[i].period : deadline[i];
  };
  std::string timeline;
  for (int t = 0; t <= horizon; ++t) {
    for (std::size_t i = 0; i < tasks.size(); ++i) {
      if (t % tasks[i].period != 0) {
        continue;
      }
      if (left[i] > 0) {
        return {timeline,
                std::format("T{} misses its deadline at {} ms, {} ms short",
                            i + 1, t, left[i])};
      }
      left[i] = tasks[i].work;
      deadline[i] = t + tasks[i].period;
    }
    if (t == horizon) {
      break;
    }
    std::size_t pick = tasks.size();
    for (std::size_t i = 0; i < tasks.size(); ++i) {
      if (left[i] > 0 && (pick == tasks.size() || key(i) < key(pick))) {
        pick = i;
      }
    }
    if (pick < tasks.size()) {
      --left[pick];
    }
    timeline += pick < tasks.size() ? std::to_string(pick + 1) : ".";
  }
  return {timeline, std::format("no deadline missed in {} ms", horizon)};
}

int main() {
  const std::vector<Task> tasks = {{2, 5}, {4, 7}};
  const int horizon = 35; // the hyperperiod, when both periods end together
  std::string shown;
  for (const Task &task : tasks) {
    shown += std::format(" {}/{}", task.work, task.period);
  }
  std::println("tasks (work/period):{}", shown);
  for (const std::string rule : {"RM", "EDF"}) {
    const auto [timeline, verdict] = simulate(tasks, horizon, rule);
    std::println("  {:<3} {}\n      {}", rule, timeline, verdict);
  }
}

In C++: simulate returns a std::pair that main takes apart with a structured binding, and std::format builds the verdict that std::println prints.

Recorded with Apple clang 21.0.0 (C++23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang++ -std=c++23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.cpp -o main && ./main

C# 14 · .NET 10 · app.cs

Task[] tasks = [new(2, 5), new(4, 7)];
int horizon = 35; // the hyperperiod, when both periods end together
var shown = tasks.Select(t => $"{t.Work}/{t.Period}");
Console.WriteLine($"tasks (work/period): {string.Join(" ", shown)}");
foreach (string rule in new[] { "RM", "EDF" })
{
    var (timeline, verdict) = Simulate(tasks, horizon, rule);
    Console.WriteLine($"  {rule,-3} {timeline}\n      {verdict}");
}

// One CPU in 1 ms steps: the timeline and the first missed deadline.
static (string, string) Simulate(Task[] tasks, int horizon, string rule)
{
    int[] left = new int[tasks.Length]; // work left in each current job
    int[] deadline = new int[tasks.Length];
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    int Key(int i) => rule == "RM" ? tasks[i].Period : deadline[i];
    string timeline = "";
    for (int t = 0; t <= horizon; t++)
    {
        for (int i = 0; i < tasks.Length; i++)
        {
            if (t % tasks[i].Period != 0)
            {
                continue;
            }
            if (left[i] > 0)
            {
                return (timeline, $"T{i + 1} misses its deadline at {t} ms, {left[i]} ms short");
            }
            (left[i], deadline[i]) = (tasks[i].Work, t + tasks[i].Period);
        }
        if (t == horizon)
        {
            break;
        }
        int pick = -1;
        for (int i = 0; i < tasks.Length; i++)
        {
            if (left[i] > 0 && (pick < 0 || Key(i) < Key(pick)))
            {
                pick = i;
            }
        }
        if (pick >= 0)
        {
            left[pick]--;
        }
        timeline += pick >= 0 ? $"{pick + 1}" : ".";
    }
    return (timeline, $"no deadline missed in {horizon} ms");
}

record Task(int Work, int Period); // in ms

In C#: The top-level statements come first and the local function Simulate after them; a tuple assignment releases a job in one statement.

Recorded with C# 14 on .NET 10.0.12 (SDK 10.0.401) on macOS 26 arm64. It runs in your browser with the .NET runtime for WebAssembly 10.0.12. To run it yourself: dotnet run app.cs

C23 · main.c

#include <stdio.h>

enum { MAX_TASKS = 8 };

struct task {
  int work, period; // in ms
};

// One CPU in 1 ms steps: prints the timeline and the first missed deadline.
static void simulate(const struct task tasks[], int n, int horizon, bool rm) {
  int left[MAX_TASKS] = {0}; // work left in each current job
  int deadline[MAX_TASKS] = {0};
  printf("  %-3s ", rm ? "RM" : "EDF");
  for (int t = 0; t <= horizon; t++) {
    for (int i = 0; i < n; i++) {
      if (t % tasks[i].period != 0) {
        continue;
      }
      if (left[i] > 0) {
        printf("\n      T%d misses its deadline at %d ms, %d ms short\n", i + 1,
               t, left[i]);
        return;
      }
      left[i] = tasks[i].work;
      deadline[i] = t + tasks[i].period;
    }
    if (t == horizon) {
      break;
    }
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    int pick = -1;
    for (int i = 0; i < n; i++) {
      const int key = rm ? tasks[i].period : deadline[i];
      if (left[i] > 0 &&
          (pick < 0 || key < (rm ? tasks[pick].period : deadline[pick]))) {
        pick = i;
      }
    }
    if (pick >= 0) {
      left[pick]--;
    }
    putchar(pick >= 0 ? '1' + pick : '.');
  }
  printf("\n      no deadline missed in %d ms\n", horizon);
}

int main(void) {
  static const struct task tasks[] = {{2, 5}, {4, 7}};
  const int n = (int)(sizeof tasks / sizeof tasks[0]);
  const int horizon = 35; // the hyperperiod, when both periods end together
  printf("tasks (work/period):");
  for (int i = 0; i < n; i++) {
    printf(" %d/%d", tasks[i].work, tasks[i].period);
  }
  printf("\n");
  simulate(tasks, n, horizon, true);
  simulate(tasks, n, horizon, false);
  return 0;
}

In C: simulate prints the timeline as it goes instead of building a string, so a missed deadline ends the line early; putchar prints the task number as a character.

Recorded with Apple clang 21.0.0 (C23) and GCC 16.2.0 on macOS 26 arm64. It runs in your browser with Clang in WebAssembly (YoWASP) 22.0.0-git20542-10. To run it yourself: clang -std=c23 -O2 -Wall -Wextra -Werror -pedantic -fsanitize=address,undefined main.c -o main && ./main

PHP 8.5 · main.php

<?php

declare(strict_types=1);

/**
 * One CPU in 1 ms steps: the timeline and the first missed deadline.
 * @param list<array{int, int}> $tasks [work, period] in ms
 * @return array{string, string}
 */
function simulate(array $tasks, int $horizon, string $rule): array
{
    $left = array_fill(0, count($tasks), 0); // work left in each current job
    $deadline = $left;
    $timeline = '';
    for ($t = 0; $t <= $horizon; $t++) {
        foreach ($tasks as $i => [$work, $period]) {
            if ($t % $period !== 0) {
                continue;
            }
            if ($left[$i] > 0) {
                $miss = 'T' . ($i + 1) . " misses its deadline at {$t} ms, {$left[$i]} ms short";
                return [$timeline, $miss];
            }
            [$left[$i], $deadline[$i]] = [$work, $t + $period];
        }
        if ($t === $horizon) {
            break;
        }
        // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
        $key = fn(int $i): int => $rule === 'RM' ? $tasks[$i][1] : $deadline[$i];
        $pick = -1;
        foreach (array_keys($tasks) as $i) {
            if ($left[$i] > 0 && ($pick < 0 || $key($i) < $key($pick))) {
                $pick = $i;
            }
        }
        if ($pick >= 0) {
            $left[$pick]--;
        }
        $timeline .= $pick >= 0 ? (string) ($pick + 1) : '.';
    }
    return [$timeline, "no deadline missed in {$horizon} ms"];
}

$tasks = [[2, 5], [4, 7]];
$horizon = 35; // the hyperperiod, when both periods end together
$shown = array_map(fn(array $task): string => "{$task[0]}/{$task[1]}", $tasks);
echo 'tasks (work/period): ' . implode(' ', $shown) . "\n";
foreach (['RM', 'EDF'] as $rule) {
    [$timeline, $verdict] = simulate($tasks, $horizon, $rule);
    printf("  %-3s %s\n      %s\n", $rule, $timeline, $verdict);
}

In PHP: An arrow function (fn) copies the variables it uses when it is created, so $key is created again every millisecond to see the current deadlines.

Recorded with PHP 8.5.11 (CLI) on macOS 26 arm64. It runs in your browser with php-wasm 0.2.0 (PHP 8.5.2). To run it yourself: php main.php

Rust 1.99 · main.rs

// One CPU in 1 ms steps: the timeline and the first missed deadline.
fn simulate(tasks: &[(u32, u32)], horizon: u32, rule: &str) -> (String, String) {
    let mut left = vec![0; tasks.len()]; // work left in each current job
    let mut deadline = vec![0; tasks.len()];
    let mut timeline = String::new();
    for t in 0..=horizon {
        for (i, &(work, period)) in tasks.iter().enumerate() {
            if t % period != 0 {
                continue;
            }
            if left[i] > 0 {
                let miss = format!(
                    "T{} misses its deadline at {t} ms, {} ms short",
                    i + 1,
                    left[i]
                );
                return (timeline, miss);
            }
            (left[i], deadline[i]) = (work, t + period);
        }
        if t == horizon {
            break;
        }
        // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
        let key = |i: usize| {
            if rule == "RM" {
                tasks[i].1
            } else {
                deadline[i]
            }
        };
        match (0..tasks.len())
            .filter(|&i| left[i] > 0)
            .min_by_key(|&i| (key(i), i))
        {
            Some(pick) => {
                left[pick] -= 1;
                timeline += &(pick + 1).to_string();
            }
            None => timeline.push('.'),
        }
    }
    (timeline, format!("no deadline missed in {horizon} ms"))
}

fn main() {
    let tasks = [(2, 5), (4, 7)]; // (work, period) in ms
    let horizon = 35; // the hyperperiod, when both periods end together
    let shown: Vec<String> = tasks.iter().map(|(w, p)| format!("{w}/{p}")).collect();
    println!("tasks (work/period): {}", shown.join(" "));
    for rule in ["RM", "EDF"] {
        let (timeline, verdict) = simulate(&tasks, horizon, rule);
        println!("  {rule:<3} {timeline}\n      {verdict}");
    }
}

In Rust: min_by_key on (key, index) returns the first minimum, which gives a tie to T1, and the match handles the idle millisecond as None.

Recorded with rustc 1.99.0 (edition 2024) on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: rustc --edition 2024 -O main.rs && ./main

TypeScript 6.0 · main.ts

type Task = [work: number, period: number]; // in ms

// One CPU in 1 ms steps: the timeline and the first missed deadline.
function simulate(tasks: Task[], horizon: number, rule: string): [string, string] {
  const left = tasks.map(() => 0); // work left in each current job
  const deadline = tasks.map(() => 0);
  let timeline = "";
  for (let t = 0; t <= horizon; t++) {
    for (const [i, [work, period]] of tasks.entries()) {
      if (t % period !== 0) continue;
      if (left[i] > 0)
        return [timeline, `T${i + 1} misses its deadline at ${t} ms, ${left[i]} ms short`];
      [left[i], deadline[i]] = [work, t + period];
    }
    if (t === horizon) break;
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    const key = (i: number): number => (rule === "RM" ? tasks[i][1] : deadline[i]);
    let pick = -1;
    for (let i = 0; i < tasks.length; i++) {
      if (left[i] > 0 && (pick < 0 || key(i) < key(pick))) pick = i;
    }
    if (pick >= 0) left[pick]--;
    timeline += pick >= 0 ? String(pick + 1) : ".";
  }
  return [timeline, `no deadline missed in ${horizon} ms`];
}

const tasks: Task[] = [
  [2, 5],
  [4, 7],
];
const horizon = 35; // the hyperperiod, when both periods end together
console.log(`tasks (work/period): ${tasks.map(([work, period]) => `${work}/${period}`).join(" ")}`);
for (const rule of ["RM", "EDF"]) {
  const [timeline, verdict] = simulate(tasks, horizon, rule);
  console.log(`  ${rule.padEnd(3)} ${timeline}\n      ${verdict}`);
}

In TypeScript: A labelled tuple type, [work: number, period: number], names the two numbers of each task, and simulate returns a pair of strings.

Recorded with TypeScript 6.0.3 (strict), run by Node.js 24.21.0 on macOS 26 arm64. It runs in your browser with QuickJS (quickjs-emscripten 0.32.0) after Sucrase 3.35.1. To run it yourself: npx -p typescript@6.0.3 tsc --noEmit --strict main.ts && node main.ts

Go 1.27 · main.go

package main

import (
	"fmt"
	"strconv"
)

type task struct{ work, period int } // in ms

// simulate runs one CPU in 1 ms steps and returns the timeline and the first missed deadline.
func simulate(tasks []task, horizon int, rule string) (string, string) {
	left := make([]int, len(tasks)) // work left in each current job
	deadline := make([]int, len(tasks))
	// RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
	key := func(i int) int {
		if rule == "RM" {
			return tasks[i].period
		}
		return deadline[i]
	}
	timeline := ""
	for t := 0; t <= horizon; t++ {
		for i, tk := range tasks {
			if t%tk.period != 0 {
				continue
			}
			if left[i] > 0 {
				miss := fmt.Sprintf("T%d misses its deadline at %d ms, %d ms short",
					i+1, t, left[i])
				return timeline, miss
			}
			left[i], deadline[i] = tk.work, t+tk.period
		}
		if t == horizon {
			break
		}
		pick := -1
		for i := range tasks {
			if left[i] > 0 && (pick < 0 || key(i) < key(pick)) {
				pick = i
			}
		}
		if pick < 0 {
			timeline += "."
			continue
		}
		left[pick]--
		timeline += strconv.Itoa(pick + 1)
	}
	return timeline, fmt.Sprintf("no deadline missed in %d ms", horizon)
}

func main() {
	tasks := []task{{2, 5}, {4, 7}}
	horizon := 35 // the hyperperiod, when both periods end together
	fmt.Print("tasks (work/period):")
	for _, tk := range tasks {
		fmt.Printf(" %d/%d", tk.work, tk.period)
	}
	fmt.Println()
	for _, rule := range []string{"RM", "EDF"} {
		timeline, verdict := simulate(tasks, horizon, rule)
		fmt.Printf("  %-3s %s\n      %s\n", rule, timeline, verdict)
	}
}

In Go: key is a closure over rule and deadline, and simulate returns two strings, the Go way to return a pair.

Recorded with Go 1.27.1 on macOS 26 arm64. It runs in your browser with the Go compiler in WebAssembly 1.27.1. To run it yourself: go run main.go

Swift 6.4 · main.swift

/// One CPU in 1 ms steps: the timeline and the first missed deadline.
func simulate(_ tasks: [(work: Int, period: Int)], _ horizon: Int, _ rule: String) -> (
  String, String
) {
  var left = [Int](repeating: 0, count: tasks.count)  // work left in each current job
  var deadline = left
  // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
  let key = { (i: Int) in rule == "RM" ? tasks[i].period : deadline[i] }
  var timeline = ""
  for t in 0...horizon {
    for (i, task) in tasks.enumerated() where t % task.period == 0 {
      if left[i] > 0 {
        return (timeline, "T\(i + 1) misses its deadline at \(t) ms, \(left[i]) ms short")
      }
      (left[i], deadline[i]) = (task.work, t + task.period)
    }
    if t == horizon { break }
    guard let pick = tasks.indices.filter({ left[$0] > 0 }).min(by: { key($0) < key($1) }) else {
      timeline += "."
      continue
    }
    left[pick] -= 1
    timeline += String(pick + 1)
  }
  return (timeline, "no deadline missed in \(horizon) ms")
}

let tasks = [(work: 2, period: 5), (work: 4, period: 7)]
let horizon = 35  // the hyperperiod, when both periods end together
print("tasks (work/period): \(tasks.map { "\($0.work)/\($0.period)" }.joined(separator: " "))")
for rule in ["RM", "EDF"] {
  let (timeline, verdict) = simulate(tasks, horizon, rule)
  print("  \(rule)\(rule.count < 3 ? " " : "") \(timeline)\n      \(verdict)")
}

In Swift: The where clause of the inner loop releases jobs only at multiples of the period, and guard let handles the idle millisecond.

Recorded with Apple Swift 6.4 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: swiftc -O main.swift -o main && ./main

Ruby 4.0 · main.rb

# frozen_string_literal: true

# One CPU in 1 ms steps: the timeline and the first missed deadline.
class Simulation
  def initialize(tasks, rule)
    @tasks = tasks # [work, period] in ms
    @rule = rule
    @left = [0] * tasks.size # work left in each current job
    @deadline = [0] * tasks.size
  end

  def call(horizon)
    timeline = +''
    (0..horizon).each do |t|
      missed = release(t)
      return [timeline, missed] if missed
      break if t == horizon

      timeline << step
    end
    [timeline, "no deadline missed in #{horizon} ms"]
  end

  private

  def release(time)
    @tasks.each_with_index do |(work, period), i|
      next unless (time % period).zero?

      miss = "T#{i + 1} misses its deadline at #{time} ms, #{@left[i]} ms short"
      return miss if @left[i].positive?

      @left[i] = work
      @deadline[i] = time + period
    end
    nil
  end

  # RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
  def step
    pick = @tasks.each_index.select { |i| @left[i].positive? }.min_by { |i| [key(i), i] }
    return '.' unless pick

    @left[pick] -= 1
    (pick + 1).to_s
  end

  def key(index) = @rule == 'RM' ? @tasks[index][1] : @deadline[index]
end

tasks = [[2, 5], [4, 7]] # [work, period] in ms
horizon = 35 # the hyperperiod, when both periods end together
puts "tasks (work/period): #{tasks.map { |work, period| "#{work}/#{period}" }.join(' ')}"
%w[RM EDF].each do |rule|
  timeline, verdict = Simulation.new(tasks, rule).call(horizon)
  puts "  #{rule.ljust(3)} #{timeline}", "      #{verdict}"
end

In Ruby: A small class with one method per step keeps each method short, as RuboCop asks, and min_by with [key, index] gives a tie to T1.

Recorded with CRuby 4.0.7 on macOS 26 arm64. It runs in your browser with ruby.wasm 2.10.1 (Ruby 4.0.0). To run it yourself: ruby main.rb

Kotlin 2.4 · Main.kt

data class Task(
    val work: Int,
    val period: Int,
) // in ms

// One CPU in 1 ms steps: the timeline and the first missed deadline.
fun simulate(
    tasks: List<Task>,
    horizon: Int,
    rule: String,
): Pair<String, String> {
    val left = IntArray(tasks.size) // work left in each current job
    val deadline = IntArray(tasks.size)
    // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
    val key = { i: Int -> if (rule == "RM") tasks[i].period else deadline[i] }
    val timeline = StringBuilder()
    for (t in 0..horizon) {
        for ((i, task) in tasks.withIndex()) {
            if (t % task.period != 0) continue
            val miss = "T${i + 1} misses its deadline at $t ms, ${left[i]} ms short"
            if (left[i] > 0) return "$timeline" to miss
            left[i] = task.work
            deadline[i] = t + task.period
        }
        if (t == horizon) break
        val pick = tasks.indices.filter { left[it] > 0 }.minByOrNull(key)
        if (pick != null) left[pick]--
        timeline.append(if (pick != null) "${pick + 1}" else ".")
    }
    return "$timeline" to "no deadline missed in $horizon ms"
}

fun main() {
    val tasks = listOf(Task(2, 5), Task(4, 7))
    val horizon = 35 // the hyperperiod, when both periods end together
    println("tasks (work/period): ${tasks.joinToString(" ") { "${it.work}/${it.period}" }}")
    for (rule in listOf("RM", "EDF")) {
        val (timeline, verdict) = simulate(tasks, horizon, rule)
        println("  ${rule.padEnd(3)} $timeline\n      $verdict")
    }
}

In Kotlin: minByOrNull returns the first task with the smallest key, so a tie goes to T1, and null marks an idle millisecond.

Recorded with Kotlin 2.4.20 on Java 25.0.4.1 on macOS 26 arm64. It runs in your browser with a WebAssembly build on the WASI runner (browser_wasi_shim 0.4.2). To run it yourself: kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar

Dart 3.13 · main.dart

typedef Task = ({int work, int period}); // in ms

/// One CPU in 1 ms steps: the timeline and the first missed deadline.
(String, String) simulate(List<Task> tasks, int horizon, String rule) {
  final left = List.filled(tasks.length, 0); // work left in each current job
  final deadline = List.filled(tasks.length, 0);
  // RM: the shorter period first; EDF: the earlier deadline. Ties: T1 first.
  int key(int i) => rule == 'RM' ? tasks[i].period : deadline[i];
  var timeline = '';
  for (var t = 0; t <= horizon; t++) {
    for (final (i, task) in tasks.indexed) {
      if (t % task.period != 0) continue;
      if (left[i] > 0) {
        return (
          timeline,
          'T${i + 1} misses its deadline at $t ms, ${left[i]} ms short',
        );
      }
      left[i] = task.work;
      deadline[i] = t + task.period;
    }
    if (t == horizon) break;
    var pick = -1;
    for (var i = 0; i < tasks.length; i++) {
      if (left[i] > 0 && (pick < 0 || key(i) < key(pick))) pick = i;
    }
    if (pick >= 0) left[pick]--;
    timeline += pick >= 0 ? '${pick + 1}' : '.';
  }
  return (timeline, 'no deadline missed in $horizon ms');
}

void main() {
  final List<Task> tasks = [(work: 2, period: 5), (work: 4, period: 7)];
  const horizon = 35; // the hyperperiod, when both periods end together
  print(
    'tasks (work/period): ${tasks.map((t) => '${t.work}/${t.period}').join(' ')}',
  );
  for (final rule in ['RM', 'EDF']) {
    final (timeline, verdict) = simulate(tasks, horizon, rule);
    print('  ${rule.padRight(3)} $timeline\n      $verdict');
  }
}

In Dart: A record typedef names the work and period of each task, and simulate returns a record of two strings that main takes apart with a pattern.

Recorded with Dart 3.13.5 on macOS 26 arm64. It runs in your browser with a WebAssembly build by dart2wasm 3.13.5. To run it yourself: dart run main.dart

What every language printed

tasks (work/period): 2/5 4/7
  RM  1122211
      T2 misses its deadline at 7 ms, 1 ms short
  EDF 1122221122221121122211222211221122.
      no deadline missed in 35 ms

Under rate-monotonic priorities, T1’s second job preempts T2 at 5 ms, and T2 reaches its deadline at 7 ms with 1 ms of work left. EDF lets T2 run on at 5 ms, because its deadline of 7 ms is nearer than T1’s new one of 10 ms, and meets every deadline.

Liu and Layland proved two tests for this model in 1973. Rate-monotonic priorities meet every deadline of n tasks if U ≤ n(2^(1/n) − 1): 1 for one task, 0.828 for two and 0.780 for three, falling towards ln 2, about 0.693. EDF on one CPU meets every deadline if and only if U ≤ 1, as the kernel’s deadline documentation also states. The program’s tasks have U = 2/5 + 4/7 = 0.971, above 0.828 and below 1, so only EDF had a guarantee. The bound is sufficient, not necessary: RTEMS notes that, on average, random task sets stay schedulable up to a utilisation of about 0.88. For fixed priorities, response-time analysis decides the sets that the bound leaves open.

Checkpoint

Check: Rate-monotonic and EDF scheduling and their utilisation tests

  1. Question 1 of 2 Three periodic tasks need 1 ms every 4 ms, 1 ms every 5 ms and 2 ms every 10 ms. What is their utilisation? Give two decimal places.

    Type a number.

    A hint

    Divide each task's work by its period and add the three shares.

    Show the answer: Question 1 of 2

    Answer: 0.65 (anything from 0.645 to 0.655 counts)

    1/4 + 1/5 + 2/10 = 0.25 + 0.2 + 0.2 = 0.65, below the rate-monotonic bound of 0.780 for three tasks, so rate-monotonic priorities meet every deadline of this set.

  2. Question 2 of 2 Two periodic tasks on one CPU have a utilisation of 0.85. What do the two utilisation tests say?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Rate-monotonic priorities have no guarantee, since 0.85 is above 0.828, but EDF meets every deadline

    Above the rate-monotonic bound the test proves nothing either way, while EDF on one CPU meets every deadline whenever U ≤ 1. The lesson's two tasks, at 0.971, missed under rate-monotonic priorities and not under EDF.

Priority inversion: a lock that reverses priorities

A high-priority task H that needs a lock held by a low-priority task L must wait for L to release it, a short wait as long as L runs. If a medium-priority task M becomes ready, it preempts L, and H waits for M as well, though M never touches the lock. This is priority inversion: A high-priority task waiting while lower-priority tasks run, typically because it needs a lock that a low-priority task holds while a medium-priority task keeps that task off the CPU., and the kernel’s rt-mutex design document calls it unbounded when M can keep L off the CPU for an undetermined time:

Interactive · step by step Priority inversion on one CPU Step through the run. Each task shows what it is doing, and the lock shows which task holds it.
Priority inversion on one CPU, step 7 of 7. Showing H (done at 13 ms), The lock (free), M (done), L (1 ms left, waits). In focus: H.Hdone at 13 msThe lockfreeMdoneL1 ms left, waits

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. 0 ms: L runs. Only L has arrived, so it runs its first millisecond of work, outside the lock.
  2. 1 ms: L takes the lock. L takes the lock and starts its critical section, 3 ms of work inside it.
  3. 2 ms: H preempts L. H arrives and preempts L at once, because its priority is higher. L keeps the lock while it waits.
  4. 3 ms: H blocks, and M runs. H needs the lock, which L holds, so H blocks. M arrives at the same moment and runs, because its priority is higher than L's. H is now waiting for M, which never touches the lock.
  5. 9 ms: M finishes. M's 6 ms are done, so L runs again, the rest of its critical section.
  6. 11 ms: L releases the lock. L leaves its critical section and releases the lock, and H takes it and runs at once.
  7. 13 ms: H finishes. H is done at 13 ms, 11 ms after it arrived, although it needed only 3 ms of work. L runs its last millisecond after it.

Priority inversion reset the Mars Pathfinder lander’s computer during its mission in 1997. Glenn Reeves, who led its flight software team, traced the resets to a low-priority weather task holding a lock that a high-priority bus task needed while medium-priority tasks ran. The fix was priority inheritance: A locking rule under which a task holding a lock runs at the priority of the highest-priority task waiting for that lock, until it releases it.: a task holding a lock that a higher-priority task waits for runs at the waiter’s priority, so H waits only for the rest of L’s critical section. POSIX mutexes offer it as PTHREAD_PRIO_INHERIT. A priority ceiling, the other fix, raises a task to the highest priority among the lock’s users as soon as it takes the lock.

On Linux, SCHED_FIFO and SCHED_RR give fixed priorities from 1 to 99 that run before every normal task. SCHED_DEADLINE runs EDF, holding each task to the CPU time it reserved.

Checkpoint

Check: Priority inversion and priority inheritance

  1. Question 1 of 2 In the step-through, H blocks on the lock at 3 ms. Which task runs from 3 to 9 ms, and why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: M, because its priority is higher than L's and it needs no lock

    M runs its whole 6 ms while H waits, although M never touches the lock: H's wait now includes M's work, which is the inversion.

  2. Question 2 of 2 With priority inheritance, what does H's wait for the lock depend on?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Only on the rest of L's critical section

    Inheritance raises the holder to the waiter's priority, so the wait is bounded by the time L still needs inside the lock.

Practise: test a task set

Write rm_guaranteed(tasks) and edf_schedulable(tasks), the two utilisation tests. Their sample tests include the lesson’s two tasks and a set above the rate-monotonic bound that EDF still schedules:

Exercise · Easy · Python

Test a task set with the rate-monotonic bound and with EDF's limit

Write two functions in schedulable.py. Each takes tasks, a list of (work, period) pairs in milliseconds for periodic tasks on one CPU, each of which must finish its work before its next period starts.

  • rm_guaranteed(tasks) returns True when the Liu and Layland bound guarantees the set under rate-monotonic priorities: its utilisation is at most n(2^(1/n) − 1) for n tasks.
  • edf_schedulable(tasks) returns True when EDF meets every deadline: the utilisation is at most 1.

The lesson's two tasks have a utilisation of 0.971, so rm_guaranteed([(2, 5), (4, 7)]) returns False and edf_schedulable([(2, 5), (4, 7)]) returns True.

Starter code · schedulable.py

def rm_guaranteed(tasks):
    """True when the Liu and Layland bound guarantees the (work, period) tasks under RM."""
    # Replace this line with your code.
    return False


def edf_schedulable(tasks):
    """True when EDF meets every deadline of the (work, period) tasks on one CPU."""
    # Replace this line with your code.
    return False
The sample tests · test_schedulable.py
from schedulable import edf_schedulable, rm_guaranteed


def test_the_lessons_two_tasks():
    """gives no rate-monotonic guarantee at 0.971, though EDF meets every deadline"""
    assert not rm_guaranteed([(2, 5), (4, 7)])
    assert edf_schedulable([(2, 5), (4, 7)])


def test_a_light_set():
    """guarantees three tasks at a utilisation of 0.65, under the bound of 0.780"""
    assert rm_guaranteed([(1, 4), (1, 5), (2, 10)])
    assert edf_schedulable([(1, 4), (1, 5), (2, 10)])


def test_above_the_bound_and_below_one():
    """gives no guarantee to three tasks at 0.833, which EDF still schedules"""
    assert not rm_guaranteed([(1, 4), (2, 6), (3, 12)])
    assert edf_schedulable([(1, 4), (2, 6), (3, 12)])


def test_an_overloaded_cpu():
    """refuses a set that needs 110 % of the CPU under both rules"""
    assert not rm_guaranteed([(3, 5), (3, 6)])
    assert not edf_schedulable([(3, 5), (3, 6)])
A hint

A task's utilisation is its work divided by its period, and a set's utilisation is the sum over its tasks. Compare it with n * (2 ** (1 / n) - 1) for rate-monotonic priorities and with 1 for EDF, with <=: a set exactly at the limit passes.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

The self-check below covers all three ideas, weakest first.

Practice that adapts to you

Practise this lesson

11 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 11 By the ROS 2 design article's definitions, which of these is a hard real-time requirement?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: An autopilot's control loop, where a missed deadline is a failure of the system

    The article's hard examples are airplane sensor and autopilot systems, spacecraft and planetary rovers; its soft ones are audio and video delivery.

  2. Question 2 of 11 A robotic assembly line computes where to place each part. An answer that comes late is thrown away, and the line carries on. Which kind of requirement is that?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: Firm

    Firm requirements sit between the two: like soft ones they do not fail the system, but a late result is invalid.

  3. Question 3 of 11 Why does real-time scheduling theory use each task's worst-case execution time rather than its average?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: Because one job that runs long is enough to miss a deadline

    A guarantee for every deadline must hold for every job, so the analysis assumes each job takes as long as any job of that task can.

  4. Question 4 of 11 Under rate-monotonic scheduling, which of two periodic tasks gets the higher priority?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: The one with the shorter period

    In the lesson's program T1, with a period of 5 ms, always has priority over T2, with 7 ms.

  5. Question 5 of 11 In the program's EDF run, why does T2 keep the CPU at 5 ms, when T1's second job arrives?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: T2's deadline of 7 ms is nearer than T1's new deadline of 10 ms

    Rate-monotonic priorities would let T1 preempt T2 at 5 ms, and T2 would then reach its deadline at 7 ms with 1 ms of work left.

  6. Question 6 of 11 What is the utilisation of T1 = 2 ms every 5 ms and T2 = 4 ms every 7 ms? Give three decimal places.

    Type a number.

    A hint

    2/5 + 4/7.

    Show the answer: Question 6 of 11

    Answer: 0.971 (anything from 0.97 to 0.972 counts)

    0.4 + 0.571 = 0.971: above the bound of 0.828 for two tasks and below EDF's limit of 1.

  7. Question 7 of 11 What is the rate-monotonic bound n(2^(1/n) − 1) for three tasks? Give three decimal places.

    Type a number.

    A hint

    The cube root of 2 is about 1.26.

    Show the answer: Question 7 of 11

    Answer: 0.78 (anything from 0.779 to 0.781 counts)

    3 × (2^(1/3) − 1) = 3 × 0.2599 = 0.780. The bound falls towards ln 2, about 0.693, as the number of tasks grows.

  8. Question 8 of 11 Three tasks have a utilisation of 0.833, above the rate-monotonic bound of 0.780. What can you conclude about rate-monotonic priorities?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: Nothing yet, because the bound is sufficient but not necessary

    The bound only says yes. A set above it needs a test that looks at the actual periods and work before anyone can say whether it is safe.

  9. Question 9 of 11 What makes priority inversion unbounded?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: A medium-priority task keeps the lock holder off the CPU for as long as it runs

    The kernel's rt-mutex design document calls an inversion unbounded when the holder can be kept off the CPU for an undetermined time.

  10. Question 10 of 11 Under priority inheritance, at which priority does a lock holder run while a higher-priority task waits for its lock?

    Choose one answer.

    Show the answer: Question 10 of 11

    Answer: At the waiting task's priority, until it releases the lock

    Running at the waiter's priority, the holder finishes its critical section without medium-priority tasks in the way, and drops back when it releases the lock.

  11. Question 11 of 11 What fixed the Mars Pathfinder resets, according to Glenn Reeves' account?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: Turning on priority inheritance for the mutex the tasks shared

    A low-priority weather task held a mutex that a high-priority bus task needed, medium-priority tasks ran, and a scheduler that found the bus task unfinished reset the system; inheritance removed the medium tasks from the wait.

Response-time analysis: the exact test for fixed priorities Pro: opens with a Pro, Premium or Ultimate pass

The utilisation bound answers only yes or not proven. This part computes each task’s worst-case response time, which decides every set exactly, and works through a set that the bound cannot decide.

In this part:

  • The worst case starts with every task released at once
  • Worked: three tasks the bound cannot decide
  • Deadlines shorter than periods

1 runnable example (Python) · about 4 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Inheritance and priority ceilings, worked through Pro: opens with a Pro, Premium or Ultimate pass

Priority inheritance bounds a wait once it starts, and the priority ceiling protocol raises the holder before anyone waits. This part runs the inversion with and without inheritance in 14 languages, works the ceiling protocol by hand and maps both onto POSIX mutexes and Linux.

In this part:

  • The inversion, run with and without inheritance
  • The ceiling protocol, by hand
  • Inheritance in POSIX, in Linux and on Pathfinder

1 runnable example · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Linux's real-time policies, used safely Pro: opens with a Pro, Premium or Ultimate pass

Linux gives time-critical work three policies and a safety margin. This part sets each policy up, shows what stops a runaway real-time thread from locking up the machine and chooses a policy for a job.

In this part:

  • SCHED_FIFO and SCHED_RR: fixed priorities from 1 to 99
  • What stops a runaway real-time thread
  • SCHED_DEADLINE: EDF with a budget

about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

  1. Fresher to mid · Warm-up

    What do the utilisation tests of rate-monotonic and EDF scheduling say, and why can a task set above the rate-monotonic bound still meet every deadline?

    For periodic tasks on one CPU, each due by the end of its period, a set's utilisation is the sum of each task's worst-case work divided by its period. Liu and Layland showed that rate-monotonic priorities, the shorter period first, meet every deadline of n tasks if the utilisation is at most n(2^(1/n) − 1): 0.828 for two tasks and 0.780 for three, falling towards about 0.693. EDF, which runs the nearest deadline first, meets every deadline if and only if the utilisation is at most 1. The rate-monotonic test is only sufficient: it looks at nothing but the utilisation, so it must hold for the worst periods there are, and a particular set above it can still fit. The lesson's tasks of 2 ms every 5 ms and 4 ms every 7 ms, at 0.971, missed a deadline under rate-monotonic priorities and none under EDF.

  2. Mid to senior

    A drone's flight controller runs its control loop at the highest priority, telemetry at a medium priority and a logger at a low one. The logger and the control loop share a mutex. In flight tests the control loop sometimes misses its 5 ms deadline by tens of milliseconds. What is happening, and how do you fix it?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Three periodic tasks share one core: 1 ms of work every 4 ms, 2 ms every 6 ms and 3 ms every 12 ms, each due by the end of its period. With fixed priorities, is the set schedulable? Show how you decide, and say what changes under EDF.

    Model answer, Pro. First step and follow-ups

  4. Senior to staff

    A team runs the audio mixing thread of a small single-core Linux device under SCHED_FIFO at priority 80. A bug makes it loop forever: the device becomes almost unresponsive but never quite freezes. Explain why, and how you would run real-time threads on such devices safely.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • A real-time system is judged by deadlines: a hard requirement fails on one missed deadline, a firm one wastes the late result and a soft one loses quality.
  • Rate-monotonic priorities are guaranteed up to U ≤ n(2^(1/n) − 1), and EDF on one CPU up to U ≤ 1; the program’s two tasks at 0.971 missed a deadline under the first and none under the second.
  • Priority inversion lets medium-priority work delay a high-priority task that waits for a lock; priority inheritance bounds the wait to the holder’s critical section.

Terms in this lesson

earliest deadline first (EDF)
Scheduling that always runs the ready job whose deadline is nearest, so priorities change as jobs arrive. On one CPU it meets every deadline whenever the utilisation is at most 1.
firm real-time
A real-time requirement under which a result that comes after its deadline is worthless, though the system carries on, as on a robotic assembly line.
hard real-time
A real-time requirement under which a missed deadline counts as a failure of the system, as in an autopilot, so every deadline must be met and proved in advance.
priority inheritance
A locking rule under which a task holding a lock runs at the priority of the highest-priority task waiting for that lock, until it releases it.
priority inversion
A high-priority task waiting while lower-priority tasks run, typically because it needs a lock that a low-priority task holds while a medium-priority task keeps that task off the CPU.
rate-monotonic scheduling
Fixed-priority scheduling of periodic tasks in which the shorter a task's period, the higher its priority.
soft real-time
A real-time requirement under which a missed deadline lowers the quality of service without failing the system, as in audio and video playback.
utilisation
The share of one CPU a periodic task needs: its worst-case execution time divided by its period. A task set's utilisation is the sum over its tasks.
worst-case execution time
The longest a job of a task can take on the CPU when it runs without interruption. Real-time analysis uses it rather than the average, because one slow job can miss a deadline.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.